diff options
Diffstat (limited to 'hclwrite')
| -rw-r--r-- | hclwrite/ast_body.go | 20 | ||||
| -rw-r--r-- | hclwrite/ast_body_test.go | 164 | ||||
| -rw-r--r-- | hclwrite/ast_expression.go | 23 |
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. // |
