github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/ast_body.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclwrite 5 6 import ( 7 "reflect" 8 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/hclsyntax" 11 "github.com/zclconf/go-cty/cty" 12 ) 13 14 type Body struct { 15 inTree 16 17 items nodeSet 18 } 19 20 func newBody() *Body { 21 return &Body{ 22 inTree: newInTree(), 23 items: newNodeSet(), 24 } 25 } 26 27 func (b *Body) appendItem(c nodeContent) *node { 28 nn := b.children.Append(c) 29 b.items.Add(nn) 30 return nn 31 } 32 33 func (b *Body) appendItemNode(nn *node) *node { 34 nn.assertUnattached() 35 b.children.AppendNode(nn) 36 b.items.Add(nn) 37 return nn 38 } 39 40 // Clear removes all of the items from the body, making it empty. 41 func (b *Body) Clear() { 42 b.children.Clear() 43 } 44 45 func (b *Body) AppendUnstructuredTokens(ts Tokens) { 46 b.inTree.children.Append(ts) 47 } 48 49 // Attributes returns a new map of all of the attributes in the body, with 50 // the attribute names as the keys. 51 func (b *Body) Attributes() map[string]*Attribute { 52 ret := make(map[string]*Attribute) 53 for n := range b.items { 54 if attr, isAttr := n.content.(*Attribute); isAttr { 55 nameObj := attr.name.content.(*identifier) 56 name := string(nameObj.token.Bytes) 57 ret[name] = attr 58 } 59 } 60 return ret 61 } 62 63 // Blocks returns a new slice of all the blocks in the body. 64 func (b *Body) Blocks() []*Block { 65 ret := make([]*Block, 0, len(b.items)) 66 for _, n := range b.items.List() { 67 if block, isBlock := n.content.(*Block); isBlock { 68 ret = append(ret, block) 69 } 70 } 71 return ret 72 } 73 74 // GetAttribute returns the attribute from the body that has the given name, 75 // or returns nil if there is currently no matching attribute. 76 func (b *Body) GetAttribute(name string) *Attribute { 77 for n := range b.items { 78 if attr, isAttr := n.content.(*Attribute); isAttr { 79 nameObj := attr.name.content.(*identifier) 80 if nameObj.hasName(name) { 81 // We've found it! 82 return attr 83 } 84 } 85 } 86 87 return nil 88 } 89 90 // getAttributeNode is like GetAttribute but it returns the node containing 91 // the selected attribute (if one is found) rather than the attribute itself. 92 func (b *Body) getAttributeNode(name string) *node { 93 for n := range b.items { 94 if attr, isAttr := n.content.(*Attribute); isAttr { 95 nameObj := attr.name.content.(*identifier) 96 if nameObj.hasName(name) { 97 // We've found it! 98 return n 99 } 100 } 101 } 102 103 return nil 104 } 105 106 // FirstMatchingBlock returns a first matching block from the body that has the 107 // given name and labels or returns nil if there is currently no matching 108 // block. 109 func (b *Body) FirstMatchingBlock(typeName string, labels []string) *Block { 110 for _, block := range b.Blocks() { 111 if typeName == block.Type() { 112 labelNames := block.Labels() 113 if len(labels) == 0 && len(labelNames) == 0 { 114 return block 115 } 116 if reflect.DeepEqual(labels, labelNames) { 117 return block 118 } 119 } 120 } 121 122 return nil 123 } 124 125 // RemoveBlock removes the given block from the body, if it's in that body. 126 // If it isn't present, this is a no-op. 127 // 128 // Returns true if it removed something, or false otherwise. 129 func (b *Body) RemoveBlock(block *Block) bool { 130 for n := range b.items { 131 if n.content == block { 132 n.Detach() 133 b.items.Remove(n) 134 return true 135 } 136 } 137 return false 138 } 139 140 // SetAttributeRaw either replaces the expression of an existing attribute 141 // of the given name or adds a new attribute definition to the end of the block, 142 // using the given tokens verbatim as the expression. 143 // 144 // The same caveats apply to this function as for NewExpressionRaw on which 145 // it is based. If possible, prefer to use SetAttributeValue or 146 // SetAttributeTraversal. 147 func (b *Body) SetAttributeRaw(name string, tokens Tokens) *Attribute { 148 attr := b.GetAttribute(name) 149 expr := NewExpressionRaw(tokens) 150 if attr != nil { 151 attr.expr = attr.expr.ReplaceWith(expr) 152 } else { 153 attr := newAttribute() 154 attr.init(name, expr) 155 b.appendItem(attr) 156 } 157 return attr 158 } 159 160 // SetAttributeValue either replaces the expression of an existing attribute 161 // of the given name or adds a new attribute definition to the end of the block. 162 // 163 // The value is given as a cty.Value, and must therefore be a literal. To set 164 // a variable reference or other traversal, use SetAttributeTraversal. 165 // 166 // The return value is the attribute that was either modified in-place or 167 // created. 168 func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute { 169 attr := b.GetAttribute(name) 170 expr := NewExpressionLiteral(val) 171 if attr != nil { 172 attr.expr = attr.expr.ReplaceWith(expr) 173 } else { 174 attr := newAttribute() 175 attr.init(name, expr) 176 b.appendItem(attr) 177 } 178 return attr 179 } 180 181 // SetAttributeTraversal either replaces the expression of an existing attribute 182 // of the given name or adds a new attribute definition to the end of the body. 183 // 184 // The new expression is given as a hcl.Traversal, which must be an absolute 185 // traversal. To set a literal value, use SetAttributeValue. 186 // 187 // The return value is the attribute that was either modified in-place or 188 // created. 189 func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute { 190 attr := b.GetAttribute(name) 191 expr := NewExpressionAbsTraversal(traversal) 192 if attr != nil { 193 attr.expr = attr.expr.ReplaceWith(expr) 194 } else { 195 attr := newAttribute() 196 attr.init(name, expr) 197 b.appendItem(attr) 198 } 199 return attr 200 } 201 202 // RemoveAttribute removes the attribute with the given name from the body. 203 // 204 // The return value is the attribute that was removed, or nil if there was 205 // no such attribute (in which case the call was a no-op). 206 func (b *Body) RemoveAttribute(name string) *Attribute { 207 node := b.getAttributeNode(name) 208 if node == nil { 209 return nil 210 } 211 node.Detach() 212 b.items.Remove(node) 213 return node.content.(*Attribute) 214 } 215 216 // AppendBlock appends an existing block (which must not be already attached 217 // to a body) to the end of the receiving body. 218 func (b *Body) AppendBlock(block *Block) *Block { 219 b.appendItem(block) 220 return block 221 } 222 223 // AppendNewBlock appends a new nested block to the end of the receiving body 224 // with the given type name and labels. 225 func (b *Body) AppendNewBlock(typeName string, labels []string) *Block { 226 block := newBlock() 227 block.init(typeName, labels) 228 b.appendItem(block) 229 return block 230 } 231 232 // AppendNewline appends a newline token to th end of the receiving body, 233 // which generally serves as a separator between different sets of body 234 // contents. 235 func (b *Body) AppendNewline() { 236 b.AppendUnstructuredTokens(Tokens{ 237 { 238 Type: hclsyntax.TokenNewline, 239 Bytes: []byte{'\n'}, 240 }, 241 }) 242 }