zcl/hclhil: remove HCL/HIL support
This package was a prototype of wrapping the HCL/HIL API in the new zcl API as a compatibility strategy. This avenue wasn't chosen in the end, so we'll remove this to avoid confusion as we rename everything else in this repository to be called "hcl" now.
This commit is contained in:
parent
c3ca111fff
commit
386f7134f1
@ -1,8 +0,0 @@
|
||||
// Package hclhil adapts Hashicorp Configuration Language and Hashicorp
|
||||
// Interpolation Language (HCL and HIL, respectively) to work within the
|
||||
// ZCL API.
|
||||
//
|
||||
// This is intended to help applications that previously used HCL/HIL to
|
||||
// gradually adopt zcl while remaining generally compatible with their
|
||||
// previous configuration formats.
|
||||
package hclhil
|
@ -1,29 +0,0 @@
|
||||
package hclhil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl"
|
||||
hclast "github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
)
|
||||
|
||||
func parse(src []byte, filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
hclFile, err := hcl.ParseBytes(src)
|
||||
if err != nil {
|
||||
return nil, zcl.Diagnostics{
|
||||
&zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Syntax error in configuration",
|
||||
Detail: fmt.Sprintf("The file %q could not be parsed: %s", filename, err),
|
||||
Subject: errorRange(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &zcl.File{
|
||||
Body: &body{
|
||||
oli: hclFile.Node.(*hclast.ObjectList),
|
||||
},
|
||||
}, nil
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package hclhil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
)
|
||||
|
||||
// Parse attempts to parse the given buffer as HCL with HIL expressions and,
|
||||
// if successful, returns a zcl.File for the zcl configuration represented by
|
||||
// it.
|
||||
//
|
||||
// The returned file is valid only if the returned diagnostics returns false
|
||||
// from its HasErrors method. If HasErrors returns true, the file represents
|
||||
// the subset of data that was able to be parsed, which may be none.
|
||||
func Parse(src []byte, filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
return parse(src, filename)
|
||||
}
|
||||
|
||||
// ParseFile is a convenience wrapper around Parse that first attempts to load
|
||||
// data from the given filename, passing the result to Parse if successful.
|
||||
//
|
||||
// If the file cannot be read, an error diagnostic with nil context is returned.
|
||||
func ParseFile(filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, zcl.Diagnostics{
|
||||
{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Failed to open file",
|
||||
Detail: fmt.Sprintf("The file %q could not be opened.", filename),
|
||||
},
|
||||
}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
src, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, zcl.Diagnostics{
|
||||
{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Failed to read file",
|
||||
Detail: fmt.Sprintf("The file %q was opened, but an error occured while reading it.", filename),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return Parse(src, filename)
|
||||
}
|
||||
|
||||
// ParseTemplate attempts to parse the given buffer as a HIL template and,
|
||||
// if successful, returns a zcl.Expression for the value represented by it.
|
||||
//
|
||||
// The returned file is valid only if the returned diagnostics returns false
|
||||
// from its HasErrors method. If HasErrors returns true, the file represents
|
||||
// the subset of data that was able to be parsed, which may be none.
|
||||
func ParseTemplate(src []byte, filename string) (zcl.Expression, zcl.Diagnostics) {
|
||||
return parseTemplate(src, filename, zcl.Pos{Line: 1, Column: 1})
|
||||
}
|
||||
|
||||
// ParseTemplateEmbedded is like ParseTemplate but is for templates that are
|
||||
// embedded in a file in another language. Practically-speaking this just
|
||||
// offsets the source positions returned in diagnostics, etc to be relative
|
||||
// to the given position.
|
||||
func ParseTemplateEmbedded(src []byte, filename string, startPos zcl.Pos) (zcl.Expression, zcl.Diagnostics) {
|
||||
return parseTemplate(src, filename, startPos)
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package hclhil
|
||||
|
||||
import (
|
||||
hclparser "github.com/hashicorp/hcl/hcl/parser"
|
||||
hcltoken "github.com/hashicorp/hcl/hcl/token"
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
hilast "github.com/hashicorp/hil/ast"
|
||||
hilparser "github.com/hashicorp/hil/parser"
|
||||
)
|
||||
|
||||
// errorRange attempts to extract a source range from the given error,
|
||||
// returning a pointer to the range if possible or nil if not.
|
||||
//
|
||||
// errorRange understands HCL's "PosError" type, which wraps an error
|
||||
// with a source position.
|
||||
func errorRange(err error) *zcl.Range {
|
||||
switch terr := err.(type) {
|
||||
case *hclparser.PosError:
|
||||
rng := rangeFromHCLPos(terr.Pos)
|
||||
return &rng
|
||||
case *hilparser.ParseError:
|
||||
rng := rangeFromHILPos(terr.Pos)
|
||||
return &rng
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func rangeFromHCLPos(pos hcltoken.Pos) zcl.Range {
|
||||
// HCL only marks single positions rather than ranges, so we adapt this
|
||||
// by creating a single-character range at the given position.
|
||||
return zcl.Range{
|
||||
Filename: pos.Filename,
|
||||
Start: zcl.Pos{
|
||||
Byte: pos.Offset,
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
},
|
||||
End: zcl.Pos{
|
||||
Byte: pos.Offset + 1,
|
||||
Line: pos.Line,
|
||||
Column: pos.Column + 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func rangeFromHILPos(pos hilast.Pos) zcl.Range {
|
||||
// HIL only marks single positions rather than ranges, so we adapt this
|
||||
// by creating a single-character range at the given position.
|
||||
// HIL also doesn't track byte offsets, so we will hard-code these to
|
||||
// zero so that no position can be considered to be "inside" these
|
||||
// from a byte offset perspective.
|
||||
return zcl.Range{
|
||||
Filename: pos.Filename,
|
||||
Start: zcl.Pos{
|
||||
Byte: 0,
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
},
|
||||
End: zcl.Pos{
|
||||
Byte: 0,
|
||||
Line: pos.Line,
|
||||
Column: pos.Column + 1,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
@ -1,416 +0,0 @@
|
||||
package hclhil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
hclast "github.com/hashicorp/hcl/hcl/ast"
|
||||
hcltoken "github.com/hashicorp/hcl/hcl/token"
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// body is our implementation of zcl.Body in terms of an HCL ObjectList
|
||||
type body struct {
|
||||
oli *hclast.ObjectList
|
||||
hiddenNames map[string]struct{}
|
||||
}
|
||||
|
||||
func (b *body) Content(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Diagnostics) {
|
||||
content, _, diags := b.content(schema, false)
|
||||
return content, diags
|
||||
}
|
||||
|
||||
func (b *body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Body, zcl.Diagnostics) {
|
||||
return b.content(schema, true)
|
||||
}
|
||||
|
||||
func (b *body) content(schema *zcl.BodySchema, partial bool) (*zcl.BodyContent, zcl.Body, zcl.Diagnostics) {
|
||||
attrSchemas := make(map[string]zcl.AttributeSchema)
|
||||
blockSchemas := make(map[string]zcl.BlockHeaderSchema)
|
||||
for _, attrS := range schema.Attributes {
|
||||
attrSchemas[attrS.Name] = attrS
|
||||
}
|
||||
for _, blockS := range schema.Blocks {
|
||||
blockSchemas[blockS.Type] = blockS
|
||||
}
|
||||
|
||||
attrs := make(zcl.Attributes)
|
||||
var blocks zcl.Blocks
|
||||
var diags zcl.Diagnostics
|
||||
|
||||
namesUsed := make(map[string]struct{})
|
||||
|
||||
for _, item := range b.oli.Items {
|
||||
if len(item.Keys) == 0 {
|
||||
// Should never happen, since we don't use b.oli.Filter
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Invalid item",
|
||||
Detail: "Somehow we have an HCL item with no keys. This should never happen.",
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
name := item.Keys[0].Token.Value().(string)
|
||||
if _, hidden := b.hiddenNames[name]; hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, isAttr := attrSchemas[name]; isAttr {
|
||||
if len(item.Keys) > 1 {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Unsupported block type",
|
||||
Detail: fmt.Sprintf("Blocks of type %q are not expected here. Did you mean to define an attribute named %q?", name, name),
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
diags = append(diags, insertAttr(attrs, item)...)
|
||||
namesUsed[name] = struct{}{}
|
||||
} else if blockS, isBlock := blockSchemas[name]; isBlock {
|
||||
obj, isBlock := item.Val.(*hclast.ObjectType)
|
||||
if !isBlock {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Unsupported attribute",
|
||||
Detail: fmt.Sprintf("An attribute named %q is not expected here. Did you mean to define a block of type %q?", name, name),
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if item.Assign.Line != 0 {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagWarning,
|
||||
Summary: "Attribute syntax used for block",
|
||||
Detail: fmt.Sprintf("Block %q is defined using attribute syntax, which is deprecated. The equals sign is not used to define a block.", name),
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
labelKeys := item.Keys[1:]
|
||||
|
||||
if len(labelKeys) > len(blockS.LabelNames) {
|
||||
if len(blockS.LabelNames) == 0 {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: fmt.Sprintf("Extraneous label for %s", name),
|
||||
Detail: fmt.Sprintf(
|
||||
"No labels are expected for %s blocks.", name,
|
||||
),
|
||||
Subject: rangeFromHCLPos(labelKeys[len(blockS.LabelNames)].Pos()).Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: fmt.Sprintf("Extraneous label for %s", name),
|
||||
Detail: fmt.Sprintf(
|
||||
"Only %d labels (%s) are expected for %s blocks.",
|
||||
len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "), name,
|
||||
),
|
||||
Subject: rangeFromHCLPos(labelKeys[len(blockS.LabelNames)].Pos()).Ptr(),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(labelKeys) < len(blockS.LabelNames) {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: fmt.Sprintf("Missing %s for %s", blockS.LabelNames[len(labelKeys)], name),
|
||||
Detail: fmt.Sprintf(
|
||||
"All %s blocks must have %d labels (%s).",
|
||||
name, len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "),
|
||||
),
|
||||
Subject: rangeFromHCLPos(obj.Pos()).Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
var labels []string
|
||||
var labelRanges []zcl.Range
|
||||
if len(labelKeys) > 0 {
|
||||
labels = make([]string, len(labelKeys))
|
||||
labelRanges = make([]zcl.Range, len(labelKeys))
|
||||
for i, objKey := range labelKeys {
|
||||
labels[i] = objKey.Token.Value().(string)
|
||||
labelRanges[i] = rangeFromHCLPos(objKey.Pos())
|
||||
}
|
||||
}
|
||||
|
||||
blocks = append(blocks, &zcl.Block{
|
||||
Type: name,
|
||||
Labels: labels,
|
||||
Body: &body{
|
||||
oli: obj.List,
|
||||
},
|
||||
|
||||
DefRange: rangeFromHCLPos(obj.Pos()),
|
||||
TypeRange: rangeFromHCLPos(item.Keys[0].Pos()),
|
||||
LabelRanges: labelRanges,
|
||||
})
|
||||
namesUsed[name] = struct{}{}
|
||||
|
||||
} else {
|
||||
if !partial {
|
||||
if item.Assign.Line == 0 {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Unsupported block type",
|
||||
Detail: fmt.Sprintf("Blocks of type %q are not expected here.", name),
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Unsupported attribute",
|
||||
Detail: fmt.Sprintf("An attribute named %q is not expected here.", name),
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, attrS := range schema.Attributes {
|
||||
if !attrS.Required {
|
||||
continue
|
||||
}
|
||||
|
||||
if attrs[attrS.Name] == nil {
|
||||
// HCL has a bug where it panics if you ask for the position of an
|
||||
// empty object list. This means we can't specify a subject for
|
||||
// this diagnostic in that case.
|
||||
var subject *zcl.Range
|
||||
if len(b.oli.Items) > 0 {
|
||||
subject = rangeFromHCLPos(b.oli.Pos()).Ptr()
|
||||
}
|
||||
diags = diags.Append(&zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Missing required attribute",
|
||||
Detail: fmt.Sprintf("The attribute %q is required, but no definition was found.", attrS.Name),
|
||||
Subject: subject,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var leftovers zcl.Body
|
||||
if partial {
|
||||
for name := range b.hiddenNames {
|
||||
namesUsed[name] = struct{}{}
|
||||
}
|
||||
leftovers = &body{
|
||||
oli: b.oli,
|
||||
hiddenNames: namesUsed,
|
||||
}
|
||||
}
|
||||
|
||||
return &zcl.BodyContent{
|
||||
Attributes: attrs,
|
||||
Blocks: blocks,
|
||||
|
||||
MissingItemRange: b.MissingItemRange(),
|
||||
}, leftovers, diags
|
||||
}
|
||||
|
||||
func (b *body) JustAttributes() (zcl.Attributes, zcl.Diagnostics) {
|
||||
items := b.oli.Items
|
||||
attrs := make(zcl.Attributes)
|
||||
var diags zcl.Diagnostics
|
||||
|
||||
for _, item := range items {
|
||||
if len(item.Keys) == 0 {
|
||||
// Should never happen, since we don't use b.oli.Filter
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Invalid item",
|
||||
Detail: "Somehow we have an HCL item with no keys. This should never happen.",
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
name := item.Keys[0].Token.Value().(string)
|
||||
if _, hidden := b.hiddenNames[name]; hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(item.Keys) > 1 {
|
||||
name := item.Keys[0].Token.Value().(string)
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unexpected %s block", name),
|
||||
Detail: "Blocks are not allowed here.",
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
diags = append(diags, insertAttr(attrs, item)...)
|
||||
}
|
||||
|
||||
return attrs, diags
|
||||
}
|
||||
|
||||
func insertAttr(attrs zcl.Attributes, item *hclast.ObjectItem) zcl.Diagnostics {
|
||||
name := item.Keys[0].Token.Value().(string)
|
||||
var diags zcl.Diagnostics
|
||||
|
||||
if item.Assign.Line == 0 {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagWarning,
|
||||
Summary: "Block syntax used for attribute",
|
||||
Detail: fmt.Sprintf("Attribute %q is defined using block syntax, which is deprecated. Use an equals sign after the attribute name instead.", name),
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
if attrs[name] != nil {
|
||||
diags = append(diags, &zcl.Diagnostic{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Duplicate attribute definition",
|
||||
Detail: fmt.Sprintf(
|
||||
"Attribute %q was previously defined at %s",
|
||||
name, attrs[name].NameRange.String(),
|
||||
),
|
||||
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
attrs[name] = &zcl.Attribute{
|
||||
Name: name,
|
||||
Expr: &expression{src: item.Val},
|
||||
Range: rangeFromHCLPos(item.Pos()),
|
||||
NameRange: rangeFromHCLPos(item.Keys[0].Pos()),
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func (b *body) MissingItemRange() zcl.Range {
|
||||
if len(b.oli.Items) == 0 {
|
||||
// Can't return a sensible range in this case, because HCL panics if
|
||||
// you ask for the position of an empty list.
|
||||
return zcl.Range{
|
||||
Filename: "<unknown>",
|
||||
}
|
||||
}
|
||||
return rangeFromHCLPos(b.oli.Pos())
|
||||
}
|
||||
|
||||
// body is our implementation of zcl.Body in terms of an HCL node, which may
|
||||
// internally have strings to be interpreted as HIL templates.
|
||||
type expression struct {
|
||||
src hclast.Node
|
||||
}
|
||||
|
||||
func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||||
return ctyValueFromHCLNode(e.src, ctx)
|
||||
}
|
||||
|
||||
func (e *expression) Variables() []zcl.Traversal {
|
||||
node := e.src
|
||||
var vars []zcl.Traversal
|
||||
|
||||
switch tn := node.(type) {
|
||||
case *hclast.LiteralType:
|
||||
tok := tn.Token
|
||||
switch tok.Type {
|
||||
case hcltoken.STRING, hcltoken.HEREDOC:
|
||||
// TODO: HIL parsing and evaluation, if ctx is non-nil.
|
||||
}
|
||||
case *hclast.ObjectType:
|
||||
list := tn.List
|
||||
attrs, _ := (&body{oli: list}).JustAttributes()
|
||||
if attrs != nil {
|
||||
for _, attr := range attrs {
|
||||
vars = append(vars, attr.Expr.Variables()...)
|
||||
}
|
||||
}
|
||||
case *hclast.ListType:
|
||||
nodes := tn.List
|
||||
for _, node := range nodes {
|
||||
vars = append(vars, (&expression{src: node}).Variables()...)
|
||||
}
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
func (e *expression) Range() zcl.Range {
|
||||
return rangeFromHCLPos(e.src.Pos())
|
||||
}
|
||||
func (e *expression) StartRange() zcl.Range {
|
||||
return rangeFromHCLPos(e.src.Pos())
|
||||
}
|
||||
|
||||
func ctyValueFromHCLNode(node hclast.Node, ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||||
|
||||
switch tn := node.(type) {
|
||||
case *hclast.LiteralType:
|
||||
tok := tn.Token
|
||||
switch tok.Type {
|
||||
case hcltoken.NUMBER: // means integer, in HCL land
|
||||
val := tok.Value().(int64)
|
||||
return cty.NumberIntVal(val), nil
|
||||
case hcltoken.FLOAT:
|
||||
val := tok.Value().(float64)
|
||||
return cty.NumberFloatVal(val), nil
|
||||
case hcltoken.STRING, hcltoken.HEREDOC:
|
||||
val := tok.Value().(string)
|
||||
// TODO: HIL parsing and evaluation, if ctx is non-nil.
|
||||
return cty.StringVal(val), nil
|
||||
case hcltoken.BOOL:
|
||||
val := tok.Value().(bool)
|
||||
return cty.BoolVal(val), nil
|
||||
default:
|
||||
// should never happen
|
||||
panic(fmt.Sprintf("unsupported HCL literal type %s", tok.Type))
|
||||
}
|
||||
case *hclast.ObjectType:
|
||||
list := tn.List
|
||||
attrs, diags := (&body{oli: list}).JustAttributes()
|
||||
if attrs == nil {
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
vals := map[string]cty.Value{}
|
||||
for name, attr := range attrs {
|
||||
val, valDiags := attr.Expr.Value(ctx)
|
||||
if len(valDiags) > 0 {
|
||||
diags = append(diags, valDiags...)
|
||||
}
|
||||
if val == cty.NilVal {
|
||||
// If we skip one attribute then our return type will be
|
||||
// inconsistent, so we'll prefer to return dynamic to prevent
|
||||
// any weird downstream type errors.
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
vals[name] = val
|
||||
}
|
||||
return cty.ObjectVal(vals), diags
|
||||
case *hclast.ListType:
|
||||
nodes := tn.List
|
||||
vals := make([]cty.Value, len(nodes))
|
||||
var diags zcl.Diagnostics
|
||||
for i, node := range nodes {
|
||||
val, valDiags := ctyValueFromHCLNode(node, ctx)
|
||||
if len(valDiags) > 0 {
|
||||
diags = append(diags, valDiags...)
|
||||
}
|
||||
if val == cty.NilVal {
|
||||
// If we skip one element then our return type will be
|
||||
// inconsistent, so we'll prefer to return dynamic to prevent
|
||||
// any weird downstream type errors.
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
vals[i] = val
|
||||
}
|
||||
return cty.TupleVal(vals), diags
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported HCL value type %T", tn))
|
||||
}
|
||||
|
||||
}
|
@ -1,529 +0,0 @@
|
||||
package hclhil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
hclast "github.com/hashicorp/hcl/hcl/ast"
|
||||
hcltoken "github.com/hashicorp/hcl/hcl/token"
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestBodyPartialContent(t *testing.T) {
|
||||
tests := []struct {
|
||||
Source string
|
||||
Schema *zcl.BodySchema
|
||||
Want *zcl.BodyContent
|
||||
DiagCount int
|
||||
}{
|
||||
{
|
||||
``,
|
||||
&zcl.BodySchema{},
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
MissingItemRange: zcl.Range{
|
||||
Filename: "<unknown>",
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`foo = 1`,
|
||||
&zcl.BodySchema{},
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
MissingItemRange: zcl.Range{
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`foo = 1`,
|
||||
&zcl.BodySchema{
|
||||
Attributes: []zcl.AttributeSchema{
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{
|
||||
"foo": {
|
||||
Name: "foo",
|
||||
Expr: &expression{
|
||||
src: &hclast.LiteralType{
|
||||
Token: hcltoken.Token{
|
||||
Type: hcltoken.NUMBER,
|
||||
Text: `1`,
|
||||
Pos: hcltoken.Pos{
|
||||
Offset: 6,
|
||||
Line: 1,
|
||||
Column: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Range: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
NameRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
MissingItemRange: zcl.Range{
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
``,
|
||||
&zcl.BodySchema{
|
||||
Attributes: []zcl.AttributeSchema{
|
||||
{
|
||||
Name: "foo",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
MissingItemRange: zcl.Range{
|
||||
Filename: "<unknown>",
|
||||
},
|
||||
},
|
||||
1, // missing required attribute
|
||||
},
|
||||
{
|
||||
`foo {}`,
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
Blocks: zcl.Blocks{
|
||||
{
|
||||
Type: "foo",
|
||||
Body: &body{
|
||||
oli: &hclast.ObjectList{},
|
||||
},
|
||||
DefRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 4, Line: 1, Column: 5},
|
||||
End: zcl.Pos{Byte: 5, Line: 1, Column: 6},
|
||||
},
|
||||
TypeRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
MissingItemRange: zcl.Range{
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`foo "unwanted" {}`,
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
MissingItemRange: zcl.Range{
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
},
|
||||
1, // no labels are expected
|
||||
},
|
||||
{
|
||||
`foo {}`,
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
LabelNames: []string{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
MissingItemRange: zcl.Range{
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
},
|
||||
1, // missing name
|
||||
},
|
||||
{
|
||||
`foo "wanted" {}`,
|
||||
&zcl.BodySchema{
|
||||
Blocks: []zcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "foo",
|
||||
LabelNames: []string{"name"},
|
||||
},
|
||||
},
|
||||
},
|
||||
&zcl.BodyContent{
|
||||
Attributes: zcl.Attributes{},
|
||||
Blocks: zcl.Blocks{
|
||||
{
|
||||
Type: "foo",
|
||||
Labels: []string{"wanted"},
|
||||
Body: &body{
|
||||
oli: &hclast.ObjectList{},
|
||||
},
|
||||
DefRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 13, Line: 1, Column: 14},
|
||||
End: zcl.Pos{Byte: 14, Line: 1, Column: 15},
|
||||
},
|
||||
TypeRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
LabelRanges: []zcl.Range{
|
||||
{
|
||||
Start: zcl.Pos{Byte: 4, Line: 1, Column: 5},
|
||||
End: zcl.Pos{Byte: 5, Line: 1, Column: 6},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MissingItemRange: zcl.Range{
|
||||
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
||||
file, diags := Parse([]byte(test.Source), "test.hcl")
|
||||
if len(diags) != 0 {
|
||||
t.Fatalf("diagnostics from parse: %s", diags.Error())
|
||||
}
|
||||
|
||||
got, _, diags := file.Body.PartialContent(test.Schema)
|
||||
if len(diags) != test.DiagCount {
|
||||
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
|
||||
for _, diag := range diags {
|
||||
t.Logf(" - %s", diag.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.Want) {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestBodyJustAttributes(t *testing.T) {
|
||||
tests := []struct {
|
||||
Source string
|
||||
Want zcl.Attributes
|
||||
DiagCount int
|
||||
}{
|
||||
{
|
||||
``,
|
||||
zcl.Attributes{},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`foo = "a"`,
|
||||
zcl.Attributes{
|
||||
"foo": &zcl.Attribute{
|
||||
Name: "foo",
|
||||
Expr: &expression{
|
||||
src: &hclast.LiteralType{
|
||||
Token: hcltoken.Token{
|
||||
Type: hcltoken.STRING,
|
||||
Pos: hcltoken.Pos{
|
||||
Offset: 6,
|
||||
Line: 1,
|
||||
Column: 7,
|
||||
},
|
||||
Text: `"a"`,
|
||||
},
|
||||
},
|
||||
},
|
||||
Range: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
NameRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`foo = {}`,
|
||||
zcl.Attributes{
|
||||
"foo": &zcl.Attribute{
|
||||
Name: "foo",
|
||||
Expr: &expression{
|
||||
src: &hclast.ObjectType{
|
||||
List: &hclast.ObjectList{},
|
||||
Lbrace: hcltoken.Pos{
|
||||
Offset: 6,
|
||||
Line: 1,
|
||||
Column: 7,
|
||||
},
|
||||
Rbrace: hcltoken.Pos{
|
||||
Offset: 7,
|
||||
Line: 1,
|
||||
Column: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
Range: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
NameRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
0,
|
||||
},
|
||||
{
|
||||
`foo {}`,
|
||||
zcl.Attributes{
|
||||
"foo": &zcl.Attribute{
|
||||
Name: "foo",
|
||||
Expr: &expression{
|
||||
src: &hclast.ObjectType{
|
||||
List: &hclast.ObjectList{},
|
||||
Lbrace: hcltoken.Pos{
|
||||
Offset: 4,
|
||||
Line: 1,
|
||||
Column: 5,
|
||||
},
|
||||
Rbrace: hcltoken.Pos{
|
||||
Offset: 5,
|
||||
Line: 1,
|
||||
Column: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
Range: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
NameRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
|
||||
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
1, // warning about using block syntax
|
||||
},
|
||||
{
|
||||
`foo "bar" {}`,
|
||||
zcl.Attributes{},
|
||||
1, // blocks are not allowed here
|
||||
},
|
||||
{
|
||||
`
|
||||
foo = 1
|
||||
foo = 2
|
||||
`,
|
||||
zcl.Attributes{
|
||||
"foo": &zcl.Attribute{
|
||||
Name: "foo",
|
||||
Expr: &expression{
|
||||
src: &hclast.LiteralType{
|
||||
Token: hcltoken.Token{
|
||||
Type: hcltoken.NUMBER,
|
||||
Pos: hcltoken.Pos{
|
||||
Offset: 14,
|
||||
Line: 2,
|
||||
Column: 14,
|
||||
},
|
||||
Text: `1`,
|
||||
},
|
||||
},
|
||||
},
|
||||
Range: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 8, Line: 2, Column: 8},
|
||||
End: zcl.Pos{Byte: 9, Line: 2, Column: 9},
|
||||
},
|
||||
NameRange: zcl.Range{
|
||||
Start: zcl.Pos{Byte: 8, Line: 2, Column: 8},
|
||||
End: zcl.Pos{Byte: 9, Line: 2, Column: 9},
|
||||
},
|
||||
},
|
||||
},
|
||||
1, // duplicate definition of foo
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
||||
file, diags := Parse([]byte(test.Source), "test.hcl")
|
||||
if len(diags) != 0 {
|
||||
t.Fatalf("diagnostics from parse: %s", diags.Error())
|
||||
}
|
||||
|
||||
got, diags := file.Body.JustAttributes()
|
||||
if len(diags) != test.DiagCount {
|
||||
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
|
||||
for _, diag := range diags {
|
||||
t.Logf(" - %s", diag.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.Want) {
|
||||
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpressionValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
Source string // HCL source assigning a value to attribute "v"
|
||||
Want cty.Value
|
||||
DiagCount int
|
||||
}{
|
||||
{
|
||||
`v = 1`,
|
||||
cty.NumberIntVal(1),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = 1.5`,
|
||||
cty.NumberFloatVal(1.5),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = "hello"`,
|
||||
cty.StringVal("hello"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = <<EOT
|
||||
heredoc
|
||||
EOT
|
||||
`,
|
||||
cty.StringVal("heredoc\n"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = true`,
|
||||
cty.True,
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = false`,
|
||||
cty.False,
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = []`,
|
||||
cty.EmptyTupleVal,
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = ["hello", 5, true, 3.4]`,
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("hello"),
|
||||
cty.NumberIntVal(5),
|
||||
cty.True,
|
||||
cty.NumberFloatVal(3.4),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = {}`,
|
||||
cty.EmptyObjectVal,
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v = {
|
||||
string = "hello"
|
||||
int = 5
|
||||
bool = true
|
||||
float = 3.4
|
||||
list = []
|
||||
object = {}
|
||||
}`,
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"string": cty.StringVal("hello"),
|
||||
"int": cty.NumberIntVal(5),
|
||||
"bool": cty.True,
|
||||
"float": cty.NumberFloatVal(3.4),
|
||||
"list": cty.EmptyTupleVal,
|
||||
"object": cty.EmptyObjectVal,
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`v {}`,
|
||||
cty.EmptyObjectVal,
|
||||
0, // warns about using block syntax during content extraction, but we ignore that here
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
||||
file, diags := Parse([]byte(test.Source), "test.hcl")
|
||||
if len(diags) != 0 {
|
||||
t.Fatalf("diagnostics from parse: %s", diags.Error())
|
||||
}
|
||||
|
||||
content, diags := file.Body.Content(&zcl.BodySchema{
|
||||
Attributes: []zcl.AttributeSchema{
|
||||
{
|
||||
Name: "v",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expr := content.Attributes["v"].Expr
|
||||
|
||||
got, diags := expr.Value(nil)
|
||||
if len(diags) != test.DiagCount {
|
||||
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
|
||||
for _, diag := range diags {
|
||||
t.Logf(" - %s", diag.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !got.RawEquals(test.Want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -1,450 +0,0 @@
|
||||
package hclhil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
"github.com/hashicorp/hil"
|
||||
hilast "github.com/hashicorp/hil/ast"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
func parseTemplate(src []byte, filename string, startPos zcl.Pos) (zcl.Expression, zcl.Diagnostics) {
|
||||
hilStartPos := hilast.Pos{
|
||||
Filename: filename,
|
||||
Line: startPos.Line,
|
||||
Column: startPos.Column,
|
||||
// HIL positions don't have byte offsets, so we ignore startPos.Byte here
|
||||
}
|
||||
rootNode, err := hil.ParseWithPosition(string(src), hilStartPos)
|
||||
|
||||
if err != nil {
|
||||
return nil, zcl.Diagnostics{
|
||||
{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Syntax error in template",
|
||||
Detail: fmt.Sprintf("The template could not be parsed: %s", err),
|
||||
Subject: errorRange(err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &templateExpression{
|
||||
node: rootNode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type templateExpression struct {
|
||||
node hilast.Node
|
||||
}
|
||||
|
||||
func (e *templateExpression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||||
cfg := hilEvalConfig(ctx)
|
||||
return ctyValueFromHILNode(e.node, cfg)
|
||||
}
|
||||
|
||||
func (e *templateExpression) Variables() []zcl.Traversal {
|
||||
var vars []zcl.Traversal
|
||||
e.node.Accept(func(n hilast.Node) hilast.Node {
|
||||
vn, ok := n.(*hilast.VariableAccess)
|
||||
if !ok {
|
||||
return n
|
||||
}
|
||||
|
||||
rawName := vn.Name
|
||||
parts := strings.Split(rawName, ".")
|
||||
if len(parts) == 0 {
|
||||
return n
|
||||
}
|
||||
|
||||
tr := make(zcl.Traversal, 0, len(parts))
|
||||
tr = append(tr, zcl.TraverseRoot{
|
||||
Name: parts[0],
|
||||
SrcRange: rangeFromHILPos(n.Pos()),
|
||||
})
|
||||
|
||||
for _, name := range parts {
|
||||
if nv, err := strconv.Atoi(name); err == nil {
|
||||
// Turn this into a sequence index in zcl land, to save
|
||||
// callers from having to understand both HIL-style numeric
|
||||
// attributes and zcl-style indices.
|
||||
tr = append(tr, zcl.TraverseIndex{
|
||||
Key: cty.NumberIntVal(int64(nv)),
|
||||
SrcRange: rangeFromHILPos(n.Pos()),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if name == "*" {
|
||||
// TODO: support splat traversals, but that requires some
|
||||
// more work here because we need to then accumulate the
|
||||
// rest of the parts into the splat's own "Each" traversal.
|
||||
continue
|
||||
}
|
||||
|
||||
tr = append(tr, zcl.TraverseAttr{
|
||||
Name: name,
|
||||
SrcRange: rangeFromHILPos(n.Pos()),
|
||||
})
|
||||
}
|
||||
|
||||
vars = append(vars, tr)
|
||||
|
||||
return n
|
||||
})
|
||||
return vars
|
||||
}
|
||||
|
||||
func (e *templateExpression) Range() zcl.Range {
|
||||
return rangeFromHILPos(e.node.Pos())
|
||||
}
|
||||
func (e *templateExpression) StartRange() zcl.Range {
|
||||
return rangeFromHILPos(e.node.Pos())
|
||||
}
|
||||
|
||||
func hilEvalConfig(ctx *zcl.EvalContext) *hil.EvalConfig {
|
||||
cfg := &hil.EvalConfig{
|
||||
GlobalScope: &hilast.BasicScope{
|
||||
VarMap: map[string]hilast.Variable{},
|
||||
FuncMap: map[string]hilast.Function{},
|
||||
},
|
||||
}
|
||||
|
||||
if ctx == nil {
|
||||
return cfg
|
||||
}
|
||||
|
||||
if ctx.Variables != nil {
|
||||
for name, val := range ctx.Variables {
|
||||
cfg.GlobalScope.VarMap[name] = hilVariableForInput(hilVariableFromCtyValue(val))
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.Functions != nil {
|
||||
for name, hf := range ctx.Functions {
|
||||
cfg.GlobalScope.FuncMap[name] = hilFunctionFromCtyFunction(hf)
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func ctyValueFromHILNode(node hilast.Node, cfg *hil.EvalConfig) (cty.Value, zcl.Diagnostics) {
|
||||
result, err := hil.Eval(node, cfg)
|
||||
if err != nil {
|
||||
return cty.DynamicVal, zcl.Diagnostics{
|
||||
{
|
||||
Severity: zcl.DiagError,
|
||||
Summary: "Template evaluation failed",
|
||||
Detail: fmt.Sprintf("Error while evaluating template: %s", err),
|
||||
Subject: rangeFromHILPos(node.Pos()).Ptr(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return ctyValueFromHILResult(result), nil
|
||||
}
|
||||
|
||||
func ctyValueFromHILResult(result hil.EvaluationResult) cty.Value {
|
||||
switch result.Type {
|
||||
case hil.TypeString:
|
||||
return cty.StringVal(result.Value.(string))
|
||||
case hil.TypeBool:
|
||||
return cty.BoolVal(result.Value.(bool))
|
||||
case hil.TypeList:
|
||||
varsI := result.Value.([]interface{})
|
||||
if len(varsI) == 0 {
|
||||
return cty.ListValEmpty(cty.String)
|
||||
}
|
||||
vals := make([]cty.Value, len(varsI))
|
||||
for i, varI := range varsI {
|
||||
hv, err := hil.InterfaceToVariable(varI)
|
||||
if err != nil {
|
||||
panic("HIL returned type that can't be converted back to variable")
|
||||
}
|
||||
vals[i] = ctyValueFromHILVariable(hv)
|
||||
}
|
||||
return cty.TupleVal(vals)
|
||||
case hil.TypeMap:
|
||||
varsI := result.Value.(map[string]interface{})
|
||||
if len(varsI) == 0 {
|
||||
return cty.MapValEmpty(cty.String)
|
||||
}
|
||||
vals := make(map[string]cty.Value)
|
||||
for key, varI := range varsI {
|
||||
hv, err := hil.InterfaceToVariable(varI)
|
||||
if err != nil {
|
||||
panic("HIL returned type that can't be converted back to variable")
|
||||
}
|
||||
vals[key] = ctyValueFromHILVariable(hv)
|
||||
}
|
||||
return cty.ObjectVal(vals)
|
||||
case hil.TypeUnknown:
|
||||
// HIL doesn't have typed unknowns, so we have to return dynamic
|
||||
return cty.DynamicVal
|
||||
default:
|
||||
// should never happen
|
||||
panic(fmt.Sprintf("unsupported EvaluationResult type %s", result.Type))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ctyValueFromHILVariable(vr hilast.Variable) cty.Value {
|
||||
switch vr.Type {
|
||||
case hilast.TypeBool:
|
||||
return cty.BoolVal(vr.Value.(bool))
|
||||
case hilast.TypeString:
|
||||
return cty.StringVal(vr.Value.(string))
|
||||
case hilast.TypeInt:
|
||||
return cty.NumberIntVal(vr.Value.(int64))
|
||||
case hilast.TypeFloat:
|
||||
return cty.NumberFloatVal(vr.Value.(float64))
|
||||
case hilast.TypeList:
|
||||
vars := vr.Value.([]hilast.Variable)
|
||||
if len(vars) == 0 {
|
||||
return cty.ListValEmpty(cty.String)
|
||||
}
|
||||
vals := make([]cty.Value, len(vars))
|
||||
for i, v := range vars {
|
||||
vals[i] = ctyValueFromHILVariable(v)
|
||||
}
|
||||
return cty.TupleVal(vals)
|
||||
case hilast.TypeMap:
|
||||
vars := vr.Value.(map[string]hilast.Variable)
|
||||
if len(vars) == 0 {
|
||||
return cty.MapValEmpty(cty.String)
|
||||
}
|
||||
vals := make(map[string]cty.Value)
|
||||
for key, v := range vars {
|
||||
vals[key] = ctyValueFromHILVariable(v)
|
||||
}
|
||||
return cty.ObjectVal(vals)
|
||||
case hilast.TypeAny, hilast.TypeUnknown:
|
||||
return cty.DynamicVal
|
||||
default:
|
||||
// should never happen
|
||||
panic(fmt.Sprintf("unsupported HIL Variable type %s", vr.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func hilVariableFromCtyValue(val cty.Value) hilast.Variable {
|
||||
if !val.IsKnown() {
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeUnknown,
|
||||
Value: hil.UnknownValue,
|
||||
}
|
||||
}
|
||||
if val.IsNull() {
|
||||
// HIL doesn't actually support nulls, so we'll cheat a bit and
|
||||
// use an unknown. This is not quite right since nulls are supposed
|
||||
// to fail when evaluated, but it should suffice as a compatibility
|
||||
// shim since HIL-using applications probably won't be generating
|
||||
// nulls anyway.
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeUnknown,
|
||||
Value: hil.UnknownValue,
|
||||
}
|
||||
}
|
||||
|
||||
ty := val.Type()
|
||||
switch ty {
|
||||
case cty.String:
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeString,
|
||||
Value: val.AsString(),
|
||||
}
|
||||
case cty.Number:
|
||||
// cty doesn't distinguish between floats and ints, so we'll
|
||||
// just always use floats here and depend on automatic conversions
|
||||
// to produce ints where needed.
|
||||
bf := val.AsBigFloat()
|
||||
f, _ := bf.Float64()
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeFloat,
|
||||
Value: f,
|
||||
}
|
||||
case cty.Bool:
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeBool,
|
||||
Value: val.True(),
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
|
||||
// HIL doesn't have sets, so we'll just turn them into lists
|
||||
// HIL doesn't support tuples either, so any tuples without consistent
|
||||
// element types will fail HIL's check for consistent types, but that's
|
||||
// okay since we don't intend to change HIL semantics here.
|
||||
vars := []hilast.Variable{}
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
_, ev := it.Element()
|
||||
vars = append(vars, hilVariableFromCtyValue(ev))
|
||||
}
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeList,
|
||||
Value: vars,
|
||||
}
|
||||
case ty.IsMapType():
|
||||
vars := map[string]hilast.Variable{}
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
kv, ev := it.Element()
|
||||
k := kv.AsString()
|
||||
vars[k] = hilVariableFromCtyValue(ev)
|
||||
}
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeMap,
|
||||
Value: vars,
|
||||
}
|
||||
case ty.IsObjectType():
|
||||
// HIL doesn't support objects, so objects that don't have consistent
|
||||
// attribute types will fail HIL's check for consistent types. That's
|
||||
// okay since we don't intend to change HIL semantics here.
|
||||
vars := map[string]interface{}{}
|
||||
atys := ty.AttributeTypes()
|
||||
for k := range atys {
|
||||
vars[k] = hilVariableFromCtyValue(val.GetAttr(k))
|
||||
}
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeMap,
|
||||
Value: vars,
|
||||
}
|
||||
case ty.IsCapsuleType():
|
||||
// Can't do anything reasonable with capsule types, so we'll just
|
||||
// treat them as unknown and let the caller deal with it as an error.
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeUnknown,
|
||||
Value: hil.UnknownValue,
|
||||
}
|
||||
default:
|
||||
// Should never happen if we've done our job right here
|
||||
panic(fmt.Sprintf("don't know how to convert %#v into a HIL variable", ty))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// hilVariableForInput constrains the given variable to be of the types HIL
|
||||
// accepts as input, which entails converting all primitive types to string.
|
||||
func hilVariableForInput(v hilast.Variable) hilast.Variable {
|
||||
switch v.Type {
|
||||
case hilast.TypeFloat:
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeString,
|
||||
Value: strconv.FormatFloat(v.Value.(float64), 'f', -1, 64),
|
||||
}
|
||||
case hilast.TypeBool:
|
||||
if v.Value.(bool) {
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeString,
|
||||
Value: "true",
|
||||
}
|
||||
} else {
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeString,
|
||||
Value: "false",
|
||||
}
|
||||
}
|
||||
case hilast.TypeList:
|
||||
inVars := v.Value.([]hilast.Variable)
|
||||
outVars := make([]hilast.Variable, len(inVars))
|
||||
for i, inVar := range inVars {
|
||||
outVars[i] = hilVariableForInput(inVar)
|
||||
}
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeList,
|
||||
Value: outVars,
|
||||
}
|
||||
case hilast.TypeMap:
|
||||
inVars := v.Value.(map[string]hilast.Variable)
|
||||
outVars := make(map[string]hilast.Variable)
|
||||
for k, inVar := range inVars {
|
||||
outVars[k] = hilVariableForInput(inVar)
|
||||
}
|
||||
return hilast.Variable{
|
||||
Type: hilast.TypeMap,
|
||||
Value: outVars,
|
||||
}
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func hilTypeFromCtyType(ty cty.Type) hilast.Type {
|
||||
switch ty {
|
||||
case cty.String:
|
||||
return hilast.TypeString
|
||||
case cty.Number:
|
||||
return hilast.TypeFloat
|
||||
case cty.Bool:
|
||||
return hilast.TypeBool
|
||||
case cty.DynamicPseudoType:
|
||||
// Assume we're using this as a type specification, so we'd rather
|
||||
// have TypeAny than TypeUnknown.
|
||||
return hilast.TypeAny
|
||||
}
|
||||
|
||||
switch {
|
||||
case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
|
||||
return hilast.TypeList
|
||||
case ty.IsMapType(), ty.IsObjectType():
|
||||
return hilast.TypeMap
|
||||
default:
|
||||
return hilast.TypeUnknown
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func hilFunctionFromCtyFunction(f function.Function) hilast.Function {
|
||||
hf := hilast.Function{}
|
||||
params := f.Params()
|
||||
varParam := f.VarParam()
|
||||
|
||||
hf.ArgTypes = make([]hilast.Type, len(params))
|
||||
staticTypes := make([]cty.Type, len(params))
|
||||
for i, param := range params {
|
||||
hf.ArgTypes[i] = hilTypeFromCtyType(param.Type)
|
||||
staticTypes[i] = param.Type
|
||||
}
|
||||
if varParam != nil {
|
||||
hf.Variadic = true
|
||||
hf.VariadicType = hilTypeFromCtyType(varParam.Type)
|
||||
}
|
||||
|
||||
retType, err := f.ReturnType(staticTypes)
|
||||
if err == nil {
|
||||
hf.ReturnType = hilTypeFromCtyType(retType)
|
||||
} else {
|
||||
hf.ReturnType = hilTypeFromCtyType(cty.DynamicPseudoType)
|
||||
}
|
||||
|
||||
hf.Callback = func(hilArgs []interface{}) (interface{}, error) {
|
||||
args := make([]cty.Value, len(hilArgs))
|
||||
for i, hilArg := range hilArgs {
|
||||
var hilType hilast.Type
|
||||
if i < len(hf.ArgTypes) {
|
||||
hilType = hf.ArgTypes[i]
|
||||
} else {
|
||||
hilType = hf.VariadicType
|
||||
}
|
||||
args[i] = ctyValueFromHILVariable(hilast.Variable{
|
||||
Type: hilType,
|
||||
Value: hilArg,
|
||||
})
|
||||
}
|
||||
|
||||
result, err := f.Call(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hilResult := hilVariableFromCtyValue(result)
|
||||
return hilResult.Value, nil
|
||||
}
|
||||
|
||||
return hf
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
package hclhil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||
)
|
||||
|
||||
func TestTemplateExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
ctx *zcl.EvalContext
|
||||
want cty.Value
|
||||
diagCount int
|
||||
}{
|
||||
{
|
||||
``,
|
||||
nil,
|
||||
cty.StringVal(""),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`hello`,
|
||||
nil,
|
||||
cty.StringVal("hello"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`hello ${"world"}`,
|
||||
nil,
|
||||
cty.StringVal("hello world"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`${"hello"}`,
|
||||
nil,
|
||||
cty.StringVal("hello"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`Hello ${planet}!`,
|
||||
&zcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"planet": cty.StringVal("Earth"),
|
||||
},
|
||||
},
|
||||
cty.StringVal("Hello Earth!"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`${names}`,
|
||||
&zcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"names": cty.ListVal([]cty.Value{
|
||||
cty.StringVal("Ermintrude"),
|
||||
cty.StringVal("Tom"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("Ermintrude"),
|
||||
cty.StringVal("Tom"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`${doodads}`,
|
||||
&zcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"doodads": cty.MapVal(map[string]cty.Value{
|
||||
"Captain": cty.StringVal("Ermintrude"),
|
||||
"First Officer": cty.StringVal("Tom"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"Captain": cty.StringVal("Ermintrude"),
|
||||
"First Officer": cty.StringVal("Tom"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`${names}`,
|
||||
&zcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"names": cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("Ermintrude"),
|
||||
cty.NumberIntVal(5),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("Ermintrude"),
|
||||
cty.StringVal("5"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`${messytuple}`,
|
||||
&zcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"messytuple": cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("Ermintrude"),
|
||||
cty.ListValEmpty(cty.String),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("Ermintrude"),
|
||||
cty.ListValEmpty(cty.String), // HIL's sloppy type checker actually lets us get away with this
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`number ${num}`,
|
||||
&zcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"num": cty.NumberIntVal(5),
|
||||
},
|
||||
},
|
||||
cty.StringVal("number 5"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`${length("hello")}`,
|
||||
&zcl.EvalContext{
|
||||
Functions: map[string]function.Function{
|
||||
"length": stdlib.StrlenFunc,
|
||||
},
|
||||
},
|
||||
cty.StringVal("5"), // HIL always stringifies numbers on output
|
||||
0,
|
||||
},
|
||||
{
|
||||
`${true}`,
|
||||
nil,
|
||||
cty.StringVal("true"), // HIL always stringifies bools on output
|
||||
0,
|
||||
},
|
||||
{
|
||||
`cannot ${names}`,
|
||||
&zcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"names": cty.ListVal([]cty.Value{
|
||||
cty.StringVal("Ermintrude"),
|
||||
cty.StringVal("Tom"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
cty.DynamicVal,
|
||||
1, // can't concatenate a list
|
||||
},
|
||||
{
|
||||
`${syntax error`,
|
||||
nil,
|
||||
cty.NilVal,
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
expr, diags := ParseTemplate([]byte(test.input), "test.hil")
|
||||
if expr != nil {
|
||||
val, valDiags := expr.Value(test.ctx)
|
||||
diags = append(diags, valDiags...)
|
||||
if !val.RawEquals(test.want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", val, test.want)
|
||||
}
|
||||
} else {
|
||||
if test.want != cty.NilVal {
|
||||
t.Errorf("Unexpected diagnostics during parse: %s", diags.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(diags) != test.diagCount {
|
||||
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
|
||||
for _, diag := range diags {
|
||||
t.Logf(" - %s", diag.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
"github.com/hashicorp/hcl2/zcl/hclhil"
|
||||
"github.com/hashicorp/hcl2/zcl/zclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
@ -29,8 +28,7 @@ type body struct {
|
||||
// expression is the implementation of "Expression" used for files processed
|
||||
// with the JSON parser.
|
||||
type expression struct {
|
||||
src node
|
||||
useHIL bool
|
||||
src node
|
||||
}
|
||||
|
||||
func (b *body) Content(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Diagnostics) {
|
||||
@ -113,7 +111,7 @@ func (b *body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Bod
|
||||
}
|
||||
content.Attributes[attrS.Name] = &zcl.Attribute{
|
||||
Name: attrS.Name,
|
||||
Expr: &expression{src: jsonAttr.Value, useHIL: b.useHIL},
|
||||
Expr: &expression{src: jsonAttr.Value},
|
||||
Range: zcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
|
||||
NameRange: jsonAttr.NameRange,
|
||||
}
|
||||
@ -157,7 +155,7 @@ func (b *body) JustAttributes() (zcl.Attributes, zcl.Diagnostics) {
|
||||
}
|
||||
attrs[name] = &zcl.Attribute{
|
||||
Name: name,
|
||||
Expr: &expression{src: jsonAttr.Value, useHIL: b.useHIL},
|
||||
Expr: &expression{src: jsonAttr.Value},
|
||||
Range: zcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
|
||||
NameRange: jsonAttr.NameRange,
|
||||
}
|
||||
@ -270,27 +268,6 @@ func (b *body) unpackBlock(v node, typeName string, typeRange *zcl.Range, labels
|
||||
func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||||
switch v := e.src.(type) {
|
||||
case *stringVal:
|
||||
if e.useHIL && ctx != nil {
|
||||
// Legacy interface to parse HCL-style JSON with HIL expressions.
|
||||
templateSrc := v.Value
|
||||
hilExpr, diags := hclhil.ParseTemplateEmbedded(
|
||||
[]byte(templateSrc),
|
||||
v.SrcRange.Filename,
|
||||
zcl.Pos{
|
||||
// skip over the opening quote mark
|
||||
Byte: v.SrcRange.Start.Byte + 1,
|
||||
Line: v.SrcRange.Start.Line,
|
||||
Column: v.SrcRange.Start.Column,
|
||||
},
|
||||
)
|
||||
if hilExpr == nil {
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
val, evalDiags := hilExpr.Value(ctx)
|
||||
diags = append(diags, evalDiags...)
|
||||
return val, diags
|
||||
}
|
||||
|
||||
if ctx != nil {
|
||||
// Parse string contents as a zcl native language expression.
|
||||
// We only do this if we have a context, so passing a nil context
|
||||
@ -330,14 +307,14 @@ func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
|
||||
case *arrayVal:
|
||||
vals := []cty.Value{}
|
||||
for _, jsonVal := range v.Values {
|
||||
val, _ := (&expression{src: jsonVal, useHIL: e.useHIL}).Value(ctx)
|
||||
val, _ := (&expression{src: jsonVal}).Value(ctx)
|
||||
vals = append(vals, val)
|
||||
}
|
||||
return cty.TupleVal(vals), nil
|
||||
case *objectVal:
|
||||
attrs := map[string]cty.Value{}
|
||||
for name, jsonAttr := range v.Attrs {
|
||||
val, _ := (&expression{src: jsonAttr.Value, useHIL: e.useHIL}).Value(ctx)
|
||||
val, _ := (&expression{src: jsonAttr.Value}).Value(ctx)
|
||||
attrs[name] = val
|
||||
}
|
||||
return cty.ObjectVal(attrs), nil
|
||||
@ -353,34 +330,16 @@ func (e *expression) Variables() []zcl.Traversal {
|
||||
|
||||
switch v := e.src.(type) {
|
||||
case *stringVal:
|
||||
if e.useHIL {
|
||||
// Legacy interface to parse HCL-style JSON with HIL expressions.
|
||||
templateSrc := v.Value
|
||||
hilExpr, _ := hclhil.ParseTemplateEmbedded(
|
||||
[]byte(templateSrc),
|
||||
v.SrcRange.Filename,
|
||||
zcl.Pos{
|
||||
// skip over the opening quote mark
|
||||
Byte: v.SrcRange.Start.Byte + 1,
|
||||
Line: v.SrcRange.Start.Line,
|
||||
Column: v.SrcRange.Start.Column,
|
||||
},
|
||||
)
|
||||
if hilExpr != nil {
|
||||
vars = append(vars, hilExpr.Variables()...)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Once the native zcl template language parser is implemented,
|
||||
// parse with that and look for variables in there too,
|
||||
|
||||
case *arrayVal:
|
||||
for _, jsonVal := range v.Values {
|
||||
vars = append(vars, (&expression{src: jsonVal, useHIL: e.useHIL}).Variables()...)
|
||||
vars = append(vars, (&expression{src: jsonVal}).Variables()...)
|
||||
}
|
||||
case *objectVal:
|
||||
for _, jsonAttr := range v.Attrs {
|
||||
vars = append(vars, (&expression{src: jsonAttr.Value, useHIL: e.useHIL}).Variables()...)
|
||||
vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/hashicorp/hcl2/zcl"
|
||||
"github.com/hashicorp/hcl2/zcl/hclhil"
|
||||
"github.com/hashicorp/hcl2/zcl/json"
|
||||
"github.com/hashicorp/hcl2/zcl/zclsyntax"
|
||||
)
|
||||
@ -82,20 +81,6 @@ func (p *Parser) ParseJSON(src []byte, filename string) (*zcl.File, zcl.Diagnost
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// ParseJSONWithHIL parses the given JSON buffer (which is assumed to have been
|
||||
// loaded from the given filename) and returns the zcl.File object representing
|
||||
// it. Unlike ParseJSON, the strings within the file will be interpreted as
|
||||
// HIL templates rather than native zcl templates.
|
||||
func (p *Parser) ParseJSONWithHIL(src []byte, filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
if existing := p.files[filename]; existing != nil {
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
file, diags := json.ParseWithHIL(src, filename)
|
||||
p.files[filename] = file
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// ParseJSONFile reads the given filename and parses it as JSON, similarly to
|
||||
// ParseJSON. An error diagnostic is returned if the given file cannot be read.
|
||||
func (p *Parser) ParseJSONFile(filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
@ -108,47 +93,6 @@ func (p *Parser) ParseJSONFile(filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// ParseJSONFileWithHIL reads the given filename and parses it as JSON, similarly to
|
||||
// ParseJSONWithHIL. An error diagnostic is returned if the given file cannot be read.
|
||||
func (p *Parser) ParseJSONFileWithHIL(filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
if existing := p.files[filename]; existing != nil {
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
file, diags := json.ParseFileWithHIL(filename)
|
||||
p.files[filename] = file
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// ParseHCLHIL parses the given buffer (which is assumed to have been loaded
|
||||
// from the given filename) using the HCL and HIL parsers, and returns the
|
||||
// zcl.File object representing it.
|
||||
//
|
||||
// This HCL/HIL parser is a compatibility interface to ease migration for
|
||||
// apps that previously used HCL and HIL directly.
|
||||
func (p *Parser) ParseHCLHIL(src []byte, filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
if existing := p.files[filename]; existing != nil {
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
file, diags := hclhil.Parse(src, filename)
|
||||
p.files[filename] = file
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// ParseHCLHILFile reads the given filename and parses it as HCL/HIL, similarly
|
||||
// to ParseHCLHIL. An error diagnostic is returned if the given file cannot be
|
||||
// read.
|
||||
func (p *Parser) ParseHCLHILFile(filename string) (*zcl.File, zcl.Diagnostics) {
|
||||
if existing := p.files[filename]; existing != nil {
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
file, diags := hclhil.ParseFile(filename)
|
||||
p.files[filename] = file
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// AddFile allows a caller to record in a parser a file that was parsed some
|
||||
// other way, thus allowing it to be included in the registry of sources.
|
||||
func (p *Parser) AddFile(filename string, file *zcl.File) {
|
||||
|
Loading…
Reference in New Issue
Block a user