summaryrefslogtreecommitdiff
path: root/hclwrite
diff options
context:
space:
mode:
Diffstat (limited to 'hclwrite')
-rw-r--r--hclwrite/ast_body.go20
-rw-r--r--hclwrite/ast_body_test.go164
-rw-r--r--hclwrite/ast_expression.go23
3 files changed, 207 insertions, 0 deletions
diff --git a/hclwrite/ast_body.go b/hclwrite/ast_body.go
index 6c4f24d..119f53e 100644
--- a/hclwrite/ast_body.go
+++ b/hclwrite/ast_body.go
@@ -134,6 +134,26 @@ func (b *Body) RemoveBlock(block *Block) bool {
return false
}
+// SetAttributeRaw either replaces the expression of an existing attribute
+// of the given name or adds a new attribute definition to the end of the block,
+// using the given tokens verbatim as the expression.
+//
+// The same caveats apply to this function as for NewExpressionRaw on which
+// it is based. If possible, prefer to use SetAttributeValue or
+// SetAttributeTraversal.
+func (b *Body) SetAttributeRaw(name string, tokens Tokens) *Attribute {
+ attr := b.GetAttribute(name)
+ expr := NewExpressionRaw(tokens)
+ if attr != nil {
+ attr.expr = attr.expr.ReplaceWith(expr)
+ } else {
+ attr := newAttribute()
+ attr.init(name, expr)
+ b.appendItem(attr)
+ }
+ return attr
+}
+
// SetAttributeValue either replaces the expression of an existing attribute
// of the given name or adds a new attribute definition to the end of the block.
//
diff --git a/hclwrite/ast_body_test.go b/hclwrite/ast_body_test.go
index d6ff789..5336786 100644
--- a/hclwrite/ast_body_test.go
+++ b/hclwrite/ast_body_test.go
@@ -766,6 +766,170 @@ func TestBodySetAttributeTraversal(t *testing.T) {
}
}
+func TestBodySetAttributeRaw(t *testing.T) {
+ tests := []struct {
+ src string
+ name string
+ tokens Tokens
+ want Tokens
+ }{
+ {
+ "",
+ "a",
+ Tokens{
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte(`true`),
+ SpacesBefore: 0,
+ },
+ },
+ Tokens{
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte{'a'},
+ SpacesBefore: 0,
+ },
+ {
+ Type: hclsyntax.TokenEqual,
+ Bytes: []byte{'='},
+ SpacesBefore: 1,
+ },
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte("true"),
+ SpacesBefore: 1,
+ },
+ {
+ Type: hclsyntax.TokenNewline,
+ Bytes: []byte{'\n'},
+ SpacesBefore: 0,
+ },
+ {
+ Type: hclsyntax.TokenEOF,
+ Bytes: []byte{},
+ SpacesBefore: 0,
+ },
+ },
+ },
+ {
+ "a = 23\n",
+ "a",
+ Tokens{
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte(`true`),
+ SpacesBefore: 0,
+ },
+ },
+ Tokens{
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte{'a'},
+ SpacesBefore: 0,
+ },
+ {
+ Type: hclsyntax.TokenEqual,
+ Bytes: []byte{'='},
+ SpacesBefore: 1,
+ },
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte("true"),
+ SpacesBefore: 1,
+ },
+ {
+ Type: hclsyntax.TokenNewline,
+ Bytes: []byte{'\n'},
+ SpacesBefore: 0,
+ },
+ {
+ Type: hclsyntax.TokenEOF,
+ Bytes: []byte{},
+ SpacesBefore: 0,
+ },
+ },
+ },
+ {
+ "b = 23\n",
+ "a",
+ Tokens{
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte(`true`),
+ SpacesBefore: 0,
+ },
+ },
+ Tokens{
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte{'b'},
+ SpacesBefore: 0,
+ },
+ {
+ Type: hclsyntax.TokenEqual,
+ Bytes: []byte{'='},
+ SpacesBefore: 1,
+ },
+ {
+ Type: hclsyntax.TokenNumberLit,
+ Bytes: []byte("23"),
+ SpacesBefore: 1,
+ },
+ {
+ Type: hclsyntax.TokenNewline,
+ Bytes: []byte{'\n'},
+ SpacesBefore: 0,
+ },
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte{'a'},
+ SpacesBefore: 0,
+ },
+ {
+ Type: hclsyntax.TokenEqual,
+ Bytes: []byte{'='},
+ SpacesBefore: 1,
+ },
+ {
+ Type: hclsyntax.TokenIdent,
+ Bytes: []byte("true"),
+ SpacesBefore: 1,
+ },
+ {
+ Type: hclsyntax.TokenNewline,
+ Bytes: []byte{'\n'},
+ SpacesBefore: 0,
+ },
+ {
+ Type: hclsyntax.TokenEOF,
+ Bytes: []byte{},
+ SpacesBefore: 0,
+ },
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(fmt.Sprintf("%s = %s in %s", test.name, test.tokens.Bytes(), test.src), func(t *testing.T) {
+ f, diags := ParseConfig([]byte(test.src), "", hcl.Pos{Line: 1, Column: 1})
+ if len(diags) != 0 {
+ for _, diag := range diags {
+ t.Logf("- %s", diag.Error())
+ }
+ t.Fatalf("unexpected diagnostics")
+ }
+
+ f.Body().SetAttributeRaw(test.name, test.tokens)
+ got := f.BuildTokens(nil)
+ format(got)
+ if !reflect.DeepEqual(got, test.want) {
+ diff := cmp.Diff(test.want, got)
+ t.Errorf("wrong result\ngot: %s\nwant: %s\ndiff:\n%s", spew.Sdump(got), spew.Sdump(test.want), diff)
+ }
+ })
+ }
+}
+
func TestBodySetAttributeValueInBlock(t *testing.T) {
src := `service "label1" {
attr1 = "val1"
diff --git a/hclwrite/ast_expression.go b/hclwrite/ast_expression.go
index 854e716..073c308 100644
--- a/hclwrite/ast_expression.go
+++ b/hclwrite/ast_expression.go
@@ -21,6 +21,29 @@ func newExpression() *Expression {
}
}
+// NewExpressionRaw constructs an expression containing the given raw tokens.
+//
+// There is no automatic validation that the given tokens produce a valid
+// expression. Callers of thus function must take care to produce invalid
+// expression tokens. Where possible, use the higher-level functions
+// NewExpressionLiteral or NewExpressionAbsTraversal instead.
+//
+// Because NewExpressionRaw does not interpret the given tokens in any way,
+// an expression created by NewExpressionRaw will produce an empty result
+// for calls to its method Variables, even if the given token sequence
+// contains a subslice that would normally be interpreted as a traversal under
+// parsing.
+func NewExpressionRaw(tokens Tokens) *Expression {
+ expr := newExpression()
+ // We copy the tokens here in order to make sure that later mutations
+ // by the caller don't inadvertently cause our expression to become
+ // invalid.
+ copyTokens := make(Tokens, len(tokens))
+ copy(copyTokens, tokens)
+ expr.children.AppendUnstructuredTokens(copyTokens)
+ return expr
+}
+
// NewExpressionLiteral constructs an an expression that represents the given
// literal value.
//