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