2018-02-01 21:07:02 -06:00
|
|
|
package configs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2019-09-09 17:58:44 -05:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclparse"
|
2018-02-01 21:07:02 -06:00
|
|
|
"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()
|
|
|
|
}
|
2018-06-20 21:27:14 -05:00
|
|
|
|
|
|
|
// ForceFileSource artificially adds source code to the cache of file sources,
|
|
|
|
// as if it had been loaded from the given filename.
|
|
|
|
//
|
|
|
|
// This should be used only in special situations where configuration is loaded
|
|
|
|
// some other way. Most callers should load configuration via methods of
|
|
|
|
// Parser, which will update the sources cache automatically.
|
|
|
|
func (p *Parser) ForceFileSource(filename string, src []byte) {
|
|
|
|
// We'll make a synthetic hcl.File here just so we can reuse the
|
|
|
|
// existing cache.
|
|
|
|
p.p.AddFile(filename, &hcl.File{
|
|
|
|
Body: hcl.EmptyBody(),
|
|
|
|
Bytes: src,
|
|
|
|
})
|
|
|
|
}
|