configs: Parser type
configs.Parser is the entry-point for this package, providing functions to load and parse HCL-based configuration files. We use the library "afero" to decouple the parser from the physical OS filesystem, which here allows us to easily use an in-memory filesystem for testing and will, in future, allow us to read files from more unusual places, such as configuration embedded in a plan file.
This commit is contained in:
parent
7987a2fdb2
commit
a0f4a313ef
|
@ -0,0 +1,85 @@
|
||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
|
"github.com/hashicorp/hcl2/hclparse"
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parser is the main interface to read configuration files and other related
|
||||||
|
// files from disk.
|
||||||
|
//
|
||||||
|
// It retains a cache of all files that are loaded so that they can be used
|
||||||
|
// to create source code snippets in diagnostics, etc.
|
||||||
|
type Parser struct {
|
||||||
|
fs afero.Afero
|
||||||
|
p *hclparse.Parser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates and returns a new Parser that reads files from the given
|
||||||
|
// filesystem. If a nil filesystem is passed then the system's "real" filesystem
|
||||||
|
// will be used, via afero.OsFs.
|
||||||
|
func NewParser(fs afero.Fs) *Parser {
|
||||||
|
if fs == nil {
|
||||||
|
fs = afero.OsFs{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Parser{
|
||||||
|
fs: afero.Afero{Fs: fs},
|
||||||
|
p: hclparse.NewParser(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadHCLFile is a low-level method that reads the file at the given path,
|
||||||
|
// parses it, and returns the hcl.Body representing its root. In many cases
|
||||||
|
// it is better to use one of the other Load*File methods on this type,
|
||||||
|
// which additionally decode the root body in some way and return a higher-level
|
||||||
|
// construct.
|
||||||
|
//
|
||||||
|
// If the file cannot be read at all -- e.g. because it does not exist -- then
|
||||||
|
// this method will return a nil body and error diagnostics. In this case
|
||||||
|
// callers may wish to ignore the provided error diagnostics and produce
|
||||||
|
// a more context-sensitive error instead.
|
||||||
|
//
|
||||||
|
// The file will be parsed using the HCL native syntax unless the filename
|
||||||
|
// ends with ".json", in which case the HCL JSON syntax will be used.
|
||||||
|
func (p *Parser) LoadHCLFile(path string) (hcl.Body, hcl.Diagnostics) {
|
||||||
|
src, err := p.fs.ReadFile(path)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, hcl.Diagnostics{
|
||||||
|
{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Failed to read file",
|
||||||
|
Detail: fmt.Sprintf("The file %q could not be read.", path),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var file *hcl.File
|
||||||
|
var diags hcl.Diagnostics
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(path, ".json"):
|
||||||
|
file, diags = p.p.ParseJSON(src, path)
|
||||||
|
default:
|
||||||
|
file, diags = p.p.ParseHCL(src, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the returned file or body is nil, then we'll return a non-nil empty
|
||||||
|
// body so we'll meet our contract that nil means an error reading the file.
|
||||||
|
if file == nil || file.Body == nil {
|
||||||
|
return hcl.EmptyBody(), diags
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Body, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sources returns a map of the cached source buffers for all files that
|
||||||
|
// have been loaded through this parser, with source filenames (as requested
|
||||||
|
// when each file was opened) as the keys.
|
||||||
|
func (p *Parser) Sources() map[string][]byte {
|
||||||
|
return p.p.Sources()
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/spf13/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testParser returns a parser that reads files from the given map, which
|
||||||
|
// is from paths to file contents.
|
||||||
|
//
|
||||||
|
// Since this function uses only in-memory objects, it should never fail.
|
||||||
|
// If any errors are encountered in practice, this function will panic.
|
||||||
|
func testParser(files map[string]string) *Parser {
|
||||||
|
fs := afero.Afero{Fs: afero.NewMemMapFs()}
|
||||||
|
|
||||||
|
for filePath, contents := range files {
|
||||||
|
dirPath := path.Dir(filePath)
|
||||||
|
err := fs.MkdirAll(dirPath, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = fs.WriteFile(filePath, []byte(contents), os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewParser(fs)
|
||||||
|
}
|
Loading…
Reference in New Issue