| package hclwrite |
| |
| import ( |
| "github.com/hashicorp/hcl/v2/hclsyntax" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| type Block struct { |
| inTree |
| |
| leadComments *node |
| typeName *node |
| labels *node |
| open *node |
| body *node |
| close *node |
| } |
| |
| func newBlock() *Block { |
| return &Block{ |
| inTree: newInTree(), |
| } |
| } |
| |
| // 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) |
| labelsObj := newBlockLabels(labels) |
| b.labels = b.children.Append(labelsObj) |
| 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 { |
| return b.labelsObj().Current() |
| } |
| |
| // 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) { |
| b.labelsObj().Replace(labels) |
| } |
| |
| // labelsObj returns the internal node content representation of the block |
| // labels. This is not part of the public API because we're intentionally |
| // exposing only a limited API to get/set labels on the block itself in a |
| // manner similar to the main hcl.Block type, but our block accessors all |
| // use this to get the underlying node content to work with. |
| func (b *Block) labelsObj() *blockLabels { |
| return b.labels.content.(*blockLabels) |
| } |
| |
| type blockLabels struct { |
| inTree |
| |
| items nodeSet |
| } |
| |
| func newBlockLabels(labels []string) *blockLabels { |
| ret := &blockLabels{ |
| inTree: newInTree(), |
| items: newNodeSet(), |
| } |
| |
| ret.Replace(labels) |
| return ret |
| } |
| |
| func (bl *blockLabels) Replace(newLabels []string) { |
| bl.inTree.children.Clear() |
| bl.items.Clear() |
| |
| for _, label := range newLabels { |
| labelToks := TokensForValue(cty.StringVal(label)) |
| // Force a new label to use the quoted form, which is the idiomatic |
| // form. The unquoted form is supported in HCL 2 only for compatibility |
| // with historical use in HCL 1. |
| labelObj := newQuoted(labelToks) |
| labelNode := bl.children.Append(labelObj) |
| bl.items.Add(labelNode) |
| } |
| } |
| |
| func (bl *blockLabels) Current() []string { |
| labelNames := make([]string, 0, len(bl.items)) |
| list := bl.items.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) |
| } |
| } else if len(tokens) == 2 && |
| tokens[0].Type == hclsyntax.TokenOQuote && |
| tokens[1].Type == hclsyntax.TokenCQuote { |
| // An open quote followed immediately by a closing quote is a |
| // valid but unusual blank string label. |
| labelNames = append(labelNames, "") |
| } |
| |
| 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 |
| } |