c3cbe9a9e2
Fixes #338 Add methods to update block type and labels to enable us to refactor HCL configurations such as renaming Terraform resources. - `*Block.SetType(typeName string)` - `*Block.SetLabels(labels []string)` Some additional notes about SetLabels: Since we cannot assume that old and new labels are equal in length, remove old labels and insert new ones before TokenOBrace. To implement this, I also added the following methods. - `*nodes.Insert(pos *node, c nodeContent) *node` - `*nodes.InsertNode(pos *node, n *node) *node` They are similar to the existing Append / AppendNode, but insert a node before a given position.
148 lines
4.0 KiB
Go
148 lines
4.0 KiB
Go
package hclwrite
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
type Block struct {
|
|
inTree
|
|
|
|
leadComments *node
|
|
typeName *node
|
|
labels nodeSet
|
|
open *node
|
|
body *node
|
|
close *node
|
|
}
|
|
|
|
func newBlock() *Block {
|
|
return &Block{
|
|
inTree: newInTree(),
|
|
labels: newNodeSet(),
|
|
}
|
|
}
|
|
|
|
// NewBlock constructs a new, empty block with the given type name and labels.
|
|
func NewBlock(typeName string, labels []string) *Block {
|
|
block := newBlock()
|
|
block.init(typeName, labels)
|
|
return block
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// Type returns the type name of the block.
|
|
func (b *Block) Type() string {
|
|
typeNameObj := b.typeName.content.(*identifier)
|
|
return string(typeNameObj.token.Bytes)
|
|
}
|
|
|
|
// SetType updates the type name of the block to a given name.
|
|
func (b *Block) SetType(typeName string) {
|
|
nameTok := newIdentToken(typeName)
|
|
nameObj := newIdentifier(nameTok)
|
|
b.typeName.ReplaceWith(nameObj)
|
|
}
|
|
|
|
// Labels returns the labels of the block.
|
|
func (b *Block) Labels() []string {
|
|
labelNames := make([]string, 0, len(b.labels))
|
|
list := b.labels.List()
|
|
|
|
for _, label := range list {
|
|
switch labelObj := label.content.(type) {
|
|
case *identifier:
|
|
if labelObj.token.Type == hclsyntax.TokenIdent {
|
|
labelString := string(labelObj.token.Bytes)
|
|
labelNames = append(labelNames, labelString)
|
|
}
|
|
|
|
case *quoted:
|
|
tokens := labelObj.tokens
|
|
if len(tokens) == 3 &&
|
|
tokens[0].Type == hclsyntax.TokenOQuote &&
|
|
tokens[1].Type == hclsyntax.TokenQuotedLit &&
|
|
tokens[2].Type == hclsyntax.TokenCQuote {
|
|
// Note that TokenQuotedLit may contain escape sequences.
|
|
labelString, diags := hclsyntax.ParseStringLiteralToken(tokens[1].asHCLSyntax())
|
|
|
|
// If parsing the string literal returns error diagnostics
|
|
// then we can just assume the label doesn't match, because it's invalid in some way.
|
|
if !diags.HasErrors() {
|
|
labelNames = append(labelNames, labelString)
|
|
}
|
|
}
|
|
|
|
default:
|
|
// If neither of the previous cases are true (should be impossible)
|
|
// then we can just ignore it, because it's invalid too.
|
|
}
|
|
}
|
|
|
|
return labelNames
|
|
}
|
|
|
|
// SetLabels updates the labels of the block to given labels.
|
|
// Since we cannot assume that old and new labels are equal in length,
|
|
// remove old labels and insert new ones before TokenOBrace.
|
|
func (b *Block) SetLabels(labels []string) {
|
|
// Remove old labels
|
|
for oldLabel := range b.labels {
|
|
oldLabel.Detach()
|
|
b.labels.Remove(oldLabel)
|
|
}
|
|
|
|
// Insert new labels before TokenOBrace.
|
|
for _, label := range labels {
|
|
labelToks := TokensForValue(cty.StringVal(label))
|
|
// Force a new label to use the quoted form even if the old one is unquoted.
|
|
// The unquoted form is supported in HCL 2 only for compatibility with some
|
|
// historical use in HCL 1.
|
|
labelObj := newQuoted(labelToks)
|
|
labelNode := b.children.Insert(b.open, labelObj)
|
|
b.labels.Add(labelNode)
|
|
}
|
|
}
|