hclwrite: Body.AppendBlock
This method allows a caller to generate a nested block within a body. Since a nested block has its own content body, this now allows for deep structures to be generated.
This commit is contained in:
parent
98352801f3
commit
57c6d75eb8
@ -1,5 +1,10 @@
|
|||||||
package hclwrite
|
package hclwrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
type Block struct {
|
type Block struct {
|
||||||
inTree
|
inTree
|
||||||
|
|
||||||
@ -10,3 +15,53 @@ type Block struct {
|
|||||||
body *node
|
body *node
|
||||||
close *node
|
close *node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBlock() *Block {
|
||||||
|
return &Block{
|
||||||
|
inTree: newInTree(),
|
||||||
|
labels: newNodeSet(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Block) init(typeName string, labels []string) {
|
||||||
|
nameTok := newIdentToken(typeName)
|
||||||
|
nameObj := newIdentifier(nameTok)
|
||||||
|
b.leadComments = b.children.Append(newComments(nil))
|
||||||
|
b.typeName = b.children.Append(nameObj)
|
||||||
|
for _, label := range labels {
|
||||||
|
labelToks := TokensForValue(cty.StringVal(label))
|
||||||
|
labelObj := newQuoted(labelToks)
|
||||||
|
labelNode := b.children.Append(labelObj)
|
||||||
|
b.labels.Add(labelNode)
|
||||||
|
}
|
||||||
|
b.open = b.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
body := newBody() // initially totally empty; caller can append to it subsequently
|
||||||
|
b.body = b.children.Append(body)
|
||||||
|
b.close = b.children.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body returns the body that represents the content of the receiving block.
|
||||||
|
//
|
||||||
|
// Appending to or otherwise modifying this body will make changes to the
|
||||||
|
// tokens that are generated between the blocks open and close braces.
|
||||||
|
func (b *Block) Body() *Body {
|
||||||
|
return b.body.content.(*Body)
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package hclwrite
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,6 +12,13 @@ type Body struct {
|
|||||||
items nodeSet
|
items nodeSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBody() *Body {
|
||||||
|
return &Body{
|
||||||
|
inTree: newInTree(),
|
||||||
|
items: newNodeSet(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Body) appendItem(c nodeContent) *node {
|
func (b *Body) appendItem(c nodeContent) *node {
|
||||||
nn := b.children.Append(c)
|
nn := b.children.Append(c)
|
||||||
b.items.Add(nn)
|
b.items.Add(nn)
|
||||||
@ -66,7 +74,7 @@ func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetAttributeTraversal either replaces the expression of an existing attribute
|
// SetAttributeTraversal either replaces the expression of an existing attribute
|
||||||
// of the given name or adds a new attribute definition to the end of the block.
|
// of the given name or adds a new attribute definition to the end of the body.
|
||||||
//
|
//
|
||||||
// The new expression is given as a hcl.Traversal, which must be an absolute
|
// The new expression is given as a hcl.Traversal, which must be an absolute
|
||||||
// traversal. To set a literal value, use SetAttributeValue.
|
// traversal. To set a literal value, use SetAttributeValue.
|
||||||
@ -76,3 +84,27 @@ func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute {
|
|||||||
func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute {
|
func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute {
|
||||||
panic("Body.SetAttributeTraversal not yet implemented")
|
panic("Body.SetAttributeTraversal not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppendBlock appends a new nested block to the end of the receiving body.
|
||||||
|
//
|
||||||
|
// If blankLine is set, an additional empty line is added before the block
|
||||||
|
// for separation. Usual HCL style suggests that we group together blocks of
|
||||||
|
// the same type without intervening blank lines and then put blank lines
|
||||||
|
// between blocks of different types. In some languages, some different block
|
||||||
|
// types may be conceptually related and so may still be grouped together.
|
||||||
|
// It is the caller's responsibility to respect the usual conventions of the
|
||||||
|
// language being generated.
|
||||||
|
func (b *Body) AppendBlock(typeName string, labels []string, blankLine bool) *Block {
|
||||||
|
block := newBlock()
|
||||||
|
block.init(typeName, labels)
|
||||||
|
if blankLine {
|
||||||
|
b.AppendUnstructuredTokens(Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
b.appendItem(block)
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
@ -413,3 +413,314 @@ func TestBodySetAttributeValue(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBodyAppendBlock(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
src string
|
||||||
|
blockType string
|
||||||
|
labels []string
|
||||||
|
blank bool
|
||||||
|
want Tokens
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
"foo",
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(`foo`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenEOF,
|
||||||
|
Bytes: []byte{},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
"foo",
|
||||||
|
[]string{"bar"},
|
||||||
|
false,
|
||||||
|
Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(`foo`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOQuote,
|
||||||
|
Bytes: []byte(`"`),
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenQuotedLit,
|
||||||
|
Bytes: []byte(`bar`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCQuote,
|
||||||
|
Bytes: []byte(`"`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenEOF,
|
||||||
|
Bytes: []byte{},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
"foo",
|
||||||
|
[]string{"bar", "baz"},
|
||||||
|
false,
|
||||||
|
Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(`foo`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOQuote,
|
||||||
|
Bytes: []byte(`"`),
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenQuotedLit,
|
||||||
|
Bytes: []byte(`bar`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCQuote,
|
||||||
|
Bytes: []byte(`"`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOQuote,
|
||||||
|
Bytes: []byte(`"`),
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenQuotedLit,
|
||||||
|
Bytes: []byte(`baz`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCQuote,
|
||||||
|
Bytes: []byte(`"`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenEOF,
|
||||||
|
Bytes: []byte{},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bar {}\n",
|
||||||
|
"foo",
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(`bar`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(`foo`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenEOF,
|
||||||
|
Bytes: []byte{},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bar_blank_after {}\n",
|
||||||
|
"foo",
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
Tokens{
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(`bar_blank_after`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenIdent,
|
||||||
|
Bytes: []byte(`foo`),
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenOBrace,
|
||||||
|
Bytes: []byte{'{'},
|
||||||
|
SpacesBefore: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenNewline,
|
||||||
|
Bytes: []byte{'\n'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: hclsyntax.TokenCBrace,
|
||||||
|
Bytes: []byte{'}'},
|
||||||
|
SpacesBefore: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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.blockType, test.blockType, 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().AppendBlock(test.blockType, test.labels, test.blank)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user