hclwrite: Body.SetAttributeValue
For now, this is the only way to set an attribute, and so attributes can only be set to literal values. Later this will be generalized so that this is just a helper wrapper around a "SetAttribute" method that just uses a given expression, which then helps by constructing the expression from the value first.
This commit is contained in:
parent
77c0b55a59
commit
c8c208e083
@ -2,6 +2,7 @@ package hclwrite
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
@ -15,9 +16,17 @@ type Body struct {
|
||||
indentLevel int
|
||||
}
|
||||
|
||||
func (b *Body) appendItem(n *node) {
|
||||
b.inTree.children.AppendNode(n)
|
||||
b.items.Add(n)
|
||||
func (b *Body) appendItem(c nodeContent) *node {
|
||||
nn := b.children.Append(c)
|
||||
b.items.Add(nn)
|
||||
return nn
|
||||
}
|
||||
|
||||
func (b *Body) appendItemNode(nn *node) *node {
|
||||
nn.assertUnattached()
|
||||
b.children.AppendNode(nn)
|
||||
b.items.Add(nn)
|
||||
return nn
|
||||
}
|
||||
|
||||
func (b *Body) AppendUnstructuredTokens(ts Tokens) {
|
||||
@ -49,7 +58,16 @@ func (b *Body) GetAttribute(name string) *Attribute {
|
||||
// The return value is the attribute that was either modified in-place or
|
||||
// created.
|
||||
func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute {
|
||||
panic("Body.SetAttributeValue not yet implemented")
|
||||
attr := b.GetAttribute(name)
|
||||
expr := NewExpressionLiteral(val)
|
||||
if attr != nil {
|
||||
attr.expr = attr.expr.ReplaceWith(expr)
|
||||
} else {
|
||||
attr := newAttribute()
|
||||
attr.init(name, expr)
|
||||
b.appendItem(attr)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// SetAttributeTraversal either replaces the expression of an existing attribute
|
||||
@ -73,6 +91,40 @@ type Attribute struct {
|
||||
lineComments *node
|
||||
}
|
||||
|
||||
func newAttribute() *Attribute {
|
||||
return &Attribute{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Attribute) init(name string, expr *Expression) {
|
||||
expr.assertUnattached()
|
||||
|
||||
nameTok := newIdentToken(name)
|
||||
nameObj := newIdentifier(nameTok)
|
||||
a.leadComments = a.children.Append(newComments(nil))
|
||||
a.name = a.children.Append(nameObj)
|
||||
a.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenEqual,
|
||||
Bytes: []byte{'='},
|
||||
},
|
||||
})
|
||||
a.expr = a.children.Append(expr)
|
||||
a.expr.list = a.children
|
||||
a.lineComments = a.children.Append(newComments(nil))
|
||||
a.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenNewline,
|
||||
Bytes: []byte{'\n'},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Attribute) Expr() *Expression {
|
||||
return a.expr.content.(*Expression)
|
||||
}
|
||||
|
||||
type Block struct {
|
||||
inTree
|
||||
|
||||
|
@ -6,8 +6,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestBodyGetAttribute(t *testing.T) {
|
||||
@ -213,3 +215,200 @@ func TestBodyGetAttribute(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestBodySetAttributeValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
src string
|
||||
name string
|
||||
val cty.Value
|
||||
want Tokens
|
||||
}{
|
||||
{
|
||||
"",
|
||||
"a",
|
||||
cty.True,
|
||||
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 = false\n",
|
||||
"a",
|
||||
cty.True,
|
||||
Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte{'b'},
|
||||
SpacesBefore: 0,
|
||||
},
|
||||
{
|
||||
Type: hclsyntax.TokenEqual,
|
||||
Bytes: []byte{'='},
|
||||
SpacesBefore: 1,
|
||||
},
|
||||
{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte("false"),
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = false\n",
|
||||
"a",
|
||||
cty.True,
|
||||
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 = 1\nb = false\n",
|
||||
"a",
|
||||
cty.True,
|
||||
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.TokenIdent,
|
||||
Bytes: []byte{'b'},
|
||||
SpacesBefore: 0,
|
||||
},
|
||||
{
|
||||
Type: hclsyntax.TokenEqual,
|
||||
Bytes: []byte{'='},
|
||||
SpacesBefore: 1,
|
||||
},
|
||||
{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte("false"),
|
||||
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 = %#v in %s", test.name, test.val, 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().SetAttributeValue(test.name, test.val)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,22 @@ func newExpression() *Expression {
|
||||
|
||||
// NewExpressionLiteral constructs an an expression that represents the given
|
||||
// literal value.
|
||||
//
|
||||
// Since an unknown value cannot be represented in source code, this function
|
||||
// will panic if the given value is unknown or contains a nested unknown value.
|
||||
// Use val.IsWhollyKnown before calling to be sure.
|
||||
//
|
||||
// HCL native syntax does not directly represent lists, maps, and sets, and
|
||||
// instead relies on the automatic conversions to those collection types from
|
||||
// either list or tuple constructor syntax. Therefore converting collection
|
||||
// values to source code and re-reading them will lose type information, and
|
||||
// the reader must provide a suitable type at decode time to recover the
|
||||
// original value.
|
||||
func NewExpressionLiteral(val cty.Value) *Expression {
|
||||
panic("NewExpressionLiteral not yet implemented")
|
||||
toks := TokensForValue(val)
|
||||
expr := newExpression()
|
||||
expr.children.AppendUnstructuredTokens(toks)
|
||||
return expr
|
||||
}
|
||||
|
||||
// NewExpressionAbsTraversal constructs an expression that represents the
|
||||
|
@ -51,6 +51,35 @@ func (n *node) Detach() {
|
||||
n.after = nil
|
||||
}
|
||||
|
||||
// ReplaceWith removes the receiver from the list it currently belongs to and
|
||||
// inserts a new node with the given content in its place. If the node is not
|
||||
// currently in a list, this function will panic.
|
||||
//
|
||||
// The return value is the newly-constructed node, containing the given content.
|
||||
// After this function returns, the reciever is no longer attached to a list.
|
||||
func (n *node) ReplaceWith(c nodeContent) *node {
|
||||
if n.list == nil {
|
||||
panic("can't replace node that is not in a list")
|
||||
}
|
||||
|
||||
before := n.before
|
||||
after := n.after
|
||||
list := n.list
|
||||
n.before, n.after, n.list = nil, nil, nil
|
||||
|
||||
nn := newNode(c)
|
||||
nn.before = before
|
||||
nn.after = after
|
||||
nn.list = list
|
||||
if before != nil {
|
||||
before.after = nn
|
||||
}
|
||||
if after != nil {
|
||||
after.before = nn
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
func (n *node) assertUnattached() {
|
||||
if n.list != nil {
|
||||
panic(fmt.Sprintf("attempt to attach already-attached node %#v", n))
|
||||
@ -80,6 +109,7 @@ func (ns *nodes) Append(c nodeContent) *node {
|
||||
content: c,
|
||||
}
|
||||
ns.AppendNode(n)
|
||||
n.list = ns
|
||||
return n
|
||||
}
|
||||
|
||||
@ -101,6 +131,7 @@ func (ns *nodes) AppendUnstructuredTokens(tokens Tokens) *node {
|
||||
}
|
||||
n := newNode(tokens)
|
||||
ns.AppendNode(n)
|
||||
n.list = ns
|
||||
return n
|
||||
}
|
||||
|
||||
|
@ -197,7 +197,7 @@ func parseBody(nativeBody *hclsyntax.Body, from inputTokens) (inputTokens, *node
|
||||
if beforeItem.Len() > 0 {
|
||||
body.AppendUnstructuredTokens(beforeItem.Tokens())
|
||||
}
|
||||
body.appendItem(item)
|
||||
body.appendItemNode(item)
|
||||
|
||||
remain = afterItem
|
||||
}
|
||||
|
@ -95,3 +95,10 @@ func (ts Tokens) walkChildNodes(w internalWalkFunc) {
|
||||
func (ts Tokens) BuildTokens(to Tokens) Tokens {
|
||||
return append(to, ts...)
|
||||
}
|
||||
|
||||
func newIdentToken(name string) *Token {
|
||||
return &Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte(name),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user