6c4344623b
The main HCL package is more visible this way, and so it's easier than having to pick it out from dozens of other package directories.
294 lines
7.9 KiB
Go
294 lines
7.9 KiB
Go
package hcl
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// A Traversal is a description of traversing through a value through a
|
|
// series of operations such as attribute lookup, index lookup, etc.
|
|
//
|
|
// It is used to look up values in scopes, for example.
|
|
//
|
|
// The traversal operations are implementations of interface Traverser.
|
|
// This is a closed set of implementations, so the interface cannot be
|
|
// implemented from outside this package.
|
|
//
|
|
// A traversal can be absolute (its first value is a symbol name) or relative
|
|
// (starts from an existing value).
|
|
type Traversal []Traverser
|
|
|
|
// TraversalJoin appends a relative traversal to an absolute traversal to
|
|
// produce a new absolute traversal.
|
|
func TraversalJoin(abs Traversal, rel Traversal) Traversal {
|
|
if abs.IsRelative() {
|
|
panic("first argument to TraversalJoin must be absolute")
|
|
}
|
|
if !rel.IsRelative() {
|
|
panic("second argument to TraversalJoin must be relative")
|
|
}
|
|
|
|
ret := make(Traversal, len(abs)+len(rel))
|
|
copy(ret, abs)
|
|
copy(ret[len(abs):], rel)
|
|
return ret
|
|
}
|
|
|
|
// TraverseRel applies the receiving traversal to the given value, returning
|
|
// the resulting value. This is supported only for relative traversals,
|
|
// and will panic if applied to an absolute traversal.
|
|
func (t Traversal) TraverseRel(val cty.Value) (cty.Value, Diagnostics) {
|
|
if !t.IsRelative() {
|
|
panic("can't use TraverseRel on an absolute traversal")
|
|
}
|
|
|
|
current := val
|
|
var diags Diagnostics
|
|
for _, tr := range t {
|
|
var newDiags Diagnostics
|
|
current, newDiags = tr.TraversalStep(current)
|
|
diags = append(diags, newDiags...)
|
|
if newDiags.HasErrors() {
|
|
return cty.DynamicVal, diags
|
|
}
|
|
}
|
|
return current, diags
|
|
}
|
|
|
|
// TraverseAbs applies the receiving traversal to the given eval context,
|
|
// returning the resulting value. This is supported only for absolute
|
|
// traversals, and will panic if applied to a relative traversal.
|
|
func (t Traversal) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) {
|
|
if t.IsRelative() {
|
|
panic("can't use TraverseAbs on a relative traversal")
|
|
}
|
|
|
|
split := t.SimpleSplit()
|
|
root := split.Abs[0].(TraverseRoot)
|
|
name := root.Name
|
|
|
|
thisCtx := ctx
|
|
hasNonNil := false
|
|
for thisCtx != nil {
|
|
if thisCtx.Variables == nil {
|
|
thisCtx = thisCtx.parent
|
|
continue
|
|
}
|
|
hasNonNil = true
|
|
val, exists := thisCtx.Variables[name]
|
|
if exists {
|
|
return split.Rel.TraverseRel(val)
|
|
}
|
|
thisCtx = thisCtx.parent
|
|
}
|
|
|
|
if !hasNonNil {
|
|
return cty.DynamicVal, Diagnostics{
|
|
{
|
|
Severity: DiagError,
|
|
Summary: "Variables not allowed",
|
|
Detail: "Variables may not be used here.",
|
|
Subject: &root.SrcRange,
|
|
},
|
|
}
|
|
}
|
|
|
|
suggestions := make([]string, 0, len(ctx.Variables))
|
|
thisCtx = ctx
|
|
for thisCtx != nil {
|
|
for k := range thisCtx.Variables {
|
|
suggestions = append(suggestions, k)
|
|
}
|
|
thisCtx = thisCtx.parent
|
|
}
|
|
suggestion := nameSuggestion(name, suggestions)
|
|
if suggestion != "" {
|
|
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
|
}
|
|
|
|
return cty.DynamicVal, Diagnostics{
|
|
{
|
|
Severity: DiagError,
|
|
Summary: "Unknown variable",
|
|
Detail: fmt.Sprintf("There is no variable named %q.%s", name, suggestion),
|
|
Subject: &root.SrcRange,
|
|
},
|
|
}
|
|
}
|
|
|
|
// IsRelative returns true if the receiver is a relative traversal, or false
|
|
// otherwise.
|
|
func (t Traversal) IsRelative() bool {
|
|
if len(t) == 0 {
|
|
return true
|
|
}
|
|
if _, firstIsRoot := t[0].(TraverseRoot); firstIsRoot {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// SimpleSplit returns a TraversalSplit where the name lookup is the absolute
|
|
// part and the remainder is the relative part. Supported only for
|
|
// absolute traversals, and will panic if applied to a relative traversal.
|
|
//
|
|
// This can be used by applications that have a relatively-simple variable
|
|
// namespace where only the top-level is directly populated in the scope, with
|
|
// everything else handled by relative lookups from those initial values.
|
|
func (t Traversal) SimpleSplit() TraversalSplit {
|
|
if t.IsRelative() {
|
|
panic("can't use SimpleSplit on a relative traversal")
|
|
}
|
|
return TraversalSplit{
|
|
Abs: t[0:1],
|
|
Rel: t[1:],
|
|
}
|
|
}
|
|
|
|
// RootName returns the root name for a absolute traversal. Will panic if
|
|
// called on a relative traversal.
|
|
func (t Traversal) RootName() string {
|
|
if t.IsRelative() {
|
|
panic("can't use RootName on a relative traversal")
|
|
|
|
}
|
|
return t[0].(TraverseRoot).Name
|
|
}
|
|
|
|
// SourceRange returns the source range for the traversal.
|
|
func (t Traversal) SourceRange() Range {
|
|
if len(t) == 0 {
|
|
// Nothing useful to return here, but we'll return something
|
|
// that's correctly-typed at least.
|
|
return Range{}
|
|
}
|
|
|
|
return RangeBetween(t[0].SourceRange(), t[len(t)-1].SourceRange())
|
|
}
|
|
|
|
// TraversalSplit represents a pair of traversals, the first of which is
|
|
// an absolute traversal and the second of which is relative to the first.
|
|
//
|
|
// This is used by calling applications that only populate prefixes of the
|
|
// traversals in the scope, with Abs representing the part coming from the
|
|
// scope and Rel representing the remaining steps once that part is
|
|
// retrieved.
|
|
type TraversalSplit struct {
|
|
Abs Traversal
|
|
Rel Traversal
|
|
}
|
|
|
|
// TraverseAbs traverses from a scope to the value resulting from the
|
|
// absolute traversal.
|
|
func (t TraversalSplit) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics) {
|
|
return t.Abs.TraverseAbs(ctx)
|
|
}
|
|
|
|
// TraverseRel traverses from a given value, assumed to be the result of
|
|
// TraverseAbs on some scope, to a final result for the entire split traversal.
|
|
func (t TraversalSplit) TraverseRel(val cty.Value) (cty.Value, Diagnostics) {
|
|
return t.Rel.TraverseRel(val)
|
|
}
|
|
|
|
// Traverse is a convenience function to apply TraverseAbs followed by
|
|
// TraverseRel.
|
|
func (t TraversalSplit) Traverse(ctx *EvalContext) (cty.Value, Diagnostics) {
|
|
v1, diags := t.TraverseAbs(ctx)
|
|
if diags.HasErrors() {
|
|
return cty.DynamicVal, diags
|
|
}
|
|
v2, newDiags := t.TraverseRel(v1)
|
|
diags = append(diags, newDiags...)
|
|
return v2, diags
|
|
}
|
|
|
|
// Join concatenates together the Abs and Rel parts to produce a single
|
|
// absolute traversal.
|
|
func (t TraversalSplit) Join() Traversal {
|
|
return TraversalJoin(t.Abs, t.Rel)
|
|
}
|
|
|
|
// RootName returns the root name for the absolute part of the split.
|
|
func (t TraversalSplit) RootName() string {
|
|
return t.Abs.RootName()
|
|
}
|
|
|
|
// A Traverser is a step within a Traversal.
|
|
type Traverser interface {
|
|
TraversalStep(cty.Value) (cty.Value, Diagnostics)
|
|
SourceRange() Range
|
|
isTraverserSigil() isTraverser
|
|
}
|
|
|
|
// Embed this in a struct to declare it as a Traverser
|
|
type isTraverser struct {
|
|
}
|
|
|
|
func (tr isTraverser) isTraverserSigil() isTraverser {
|
|
return isTraverser{}
|
|
}
|
|
|
|
// TraverseRoot looks up a root name in a scope. It is used as the first step
|
|
// of an absolute Traversal, and cannot itself be traversed directly.
|
|
type TraverseRoot struct {
|
|
isTraverser
|
|
Name string
|
|
SrcRange Range
|
|
}
|
|
|
|
// TraversalStep on a TraverseName immediately panics, because absolute
|
|
// traversals cannot be directly traversed.
|
|
func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics) {
|
|
panic("Cannot traverse an absolute traversal")
|
|
}
|
|
|
|
func (tn TraverseRoot) SourceRange() Range {
|
|
return tn.SrcRange
|
|
}
|
|
|
|
// TraverseAttr looks up an attribute in its initial value.
|
|
type TraverseAttr struct {
|
|
isTraverser
|
|
Name string
|
|
SrcRange Range
|
|
}
|
|
|
|
func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
|
|
return GetAttr(val, tn.Name, &tn.SrcRange)
|
|
}
|
|
|
|
func (tn TraverseAttr) SourceRange() Range {
|
|
return tn.SrcRange
|
|
}
|
|
|
|
// TraverseIndex applies the index operation to its initial value.
|
|
type TraverseIndex struct {
|
|
isTraverser
|
|
Key cty.Value
|
|
SrcRange Range
|
|
}
|
|
|
|
func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
|
|
return Index(val, tn.Key, &tn.SrcRange)
|
|
}
|
|
|
|
func (tn TraverseIndex) SourceRange() Range {
|
|
return tn.SrcRange
|
|
}
|
|
|
|
// TraverseSplat applies the splat operation to its initial value.
|
|
type TraverseSplat struct {
|
|
isTraverser
|
|
Each Traversal
|
|
SrcRange Range
|
|
}
|
|
|
|
func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
|
|
panic("TraverseSplat not yet implemented")
|
|
}
|
|
|
|
func (tn TraverseSplat) SourceRange() Range {
|
|
return tn.SrcRange
|
|
}
|