opentofu/internal/configs/parser.go
Martin Atkins 31349a9c3a Move configs/ to internal/configs/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

101 lines
3.0 KiB
Go

package configs
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/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()
}
// 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,
})
}