// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package hclwrite import ( "reflect" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/zclconf/go-cty/cty" ) type Body struct { inTree items nodeSet } func newBody() *Body { return &Body{ inTree: newInTree(), items: newNodeSet(), } } 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 } // Clear removes all of the items from the body, making it empty. func (b *Body) Clear() { b.children.Clear() } func (b *Body) AppendUnstructuredTokens(ts Tokens) { b.inTree.children.Append(ts) } // Attributes returns a new map of all of the attributes in the body, with // the attribute names as the keys. func (b *Body) Attributes() map[string]*Attribute { ret := make(map[string]*Attribute) for n := range b.items { if attr, isAttr := n.content.(*Attribute); isAttr { nameObj := attr.name.content.(*identifier) name := string(nameObj.token.Bytes) ret[name] = attr } } return ret } // Blocks returns a new slice of all the blocks in the body. func (b *Body) Blocks() []*Block { ret := make([]*Block, 0, len(b.items)) for _, n := range b.items.List() { if block, isBlock := n.content.(*Block); isBlock { ret = append(ret, block) } } return ret } // GetAttribute returns the attribute from the body that has the given name, // or returns nil if there is currently no matching attribute. func (b *Body) GetAttribute(name string) *Attribute { for n := range b.items { if attr, isAttr := n.content.(*Attribute); isAttr { nameObj := attr.name.content.(*identifier) if nameObj.hasName(name) { // We've found it! return attr } } } return nil } // getAttributeNode is like GetAttribute but it returns the node containing // the selected attribute (if one is found) rather than the attribute itself. func (b *Body) getAttributeNode(name string) *node { for n := range b.items { if attr, isAttr := n.content.(*Attribute); isAttr { nameObj := attr.name.content.(*identifier) if nameObj.hasName(name) { // We've found it! return n } } } return nil } // FirstMatchingBlock returns a first matching block from the body that has the // given name and labels or returns nil if there is currently no matching // block. func (b *Body) FirstMatchingBlock(typeName string, labels []string) *Block { for _, block := range b.Blocks() { if typeName == block.Type() { labelNames := block.Labels() if len(labels) == 0 && len(labelNames) == 0 { return block } if reflect.DeepEqual(labels, labelNames) { return block } } } return nil } // RemoveBlock removes the given block from the body, if it's in that body. // If it isn't present, this is a no-op. // // Returns true if it removed something, or false otherwise. func (b *Body) RemoveBlock(block *Block) bool { for n := range b.items { if n.content == block { n.Detach() b.items.Remove(n) return true } } 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. // // The value is given as a cty.Value, and must therefore be a literal. To set // a variable reference or other traversal, use SetAttributeTraversal. // // The return value is the attribute that was either modified in-place or // created. func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute { 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 // 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 // traversal. To set a literal value, use SetAttributeValue. // // The return value is the attribute that was either modified in-place or // created. func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute { attr := b.GetAttribute(name) expr := NewExpressionAbsTraversal(traversal) if attr != nil { attr.expr = attr.expr.ReplaceWith(expr) } else { attr := newAttribute() attr.init(name, expr) b.appendItem(attr) } return attr } // RemoveAttribute removes the attribute with the given name from the body. // // The return value is the attribute that was removed, or nil if there was // no such attribute (in which case the call was a no-op). func (b *Body) RemoveAttribute(name string) *Attribute { node := b.getAttributeNode(name) if node == nil { return nil } node.Detach() b.items.Remove(node) return node.content.(*Attribute) } // AppendBlock appends an existing block (which must not be already attached // to a body) to the end of the receiving body. func (b *Body) AppendBlock(block *Block) *Block { b.appendItem(block) return block } // AppendNewBlock appends a new nested block to the end of the receiving body // with the given type name and labels. func (b *Body) AppendNewBlock(typeName string, labels []string) *Block { block := newBlock() block.init(typeName, labels) b.appendItem(block) return block } // AppendNewline appends a newline token to th end of the receiving body, // which generally serves as a separator between different sets of body // contents. func (b *Body) AppendNewline() { b.AppendUnstructuredTokens(Tokens{ { Type: hclsyntax.TokenNewline, Bytes: []byte{'\n'}, }, }) }