github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/ast_block.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclwrite 5 6 import ( 7 "github.com/hashicorp/hcl/v2/hclsyntax" 8 "github.com/zclconf/go-cty/cty" 9 ) 10 11 type Block struct { 12 inTree 13 14 leadComments *node 15 typeName *node 16 labels *node 17 open *node 18 body *node 19 close *node 20 } 21 22 func newBlock() *Block { 23 return &Block{ 24 inTree: newInTree(), 25 } 26 } 27 28 // NewBlock constructs a new, empty block with the given type name and labels. 29 func NewBlock(typeName string, labels []string) *Block { 30 block := newBlock() 31 block.init(typeName, labels) 32 return block 33 } 34 35 func (b *Block) init(typeName string, labels []string) { 36 nameTok := newIdentToken(typeName) 37 nameObj := newIdentifier(nameTok) 38 b.leadComments = b.children.Append(newComments(nil)) 39 b.typeName = b.children.Append(nameObj) 40 labelsObj := newBlockLabels(labels) 41 b.labels = b.children.Append(labelsObj) 42 b.open = b.children.AppendUnstructuredTokens(Tokens{ 43 { 44 Type: hclsyntax.TokenOBrace, 45 Bytes: []byte{'{'}, 46 }, 47 { 48 Type: hclsyntax.TokenNewline, 49 Bytes: []byte{'\n'}, 50 }, 51 }) 52 body := newBody() // initially totally empty; caller can append to it subsequently 53 b.body = b.children.Append(body) 54 b.close = b.children.AppendUnstructuredTokens(Tokens{ 55 { 56 Type: hclsyntax.TokenCBrace, 57 Bytes: []byte{'}'}, 58 }, 59 { 60 Type: hclsyntax.TokenNewline, 61 Bytes: []byte{'\n'}, 62 }, 63 }) 64 } 65 66 // Body returns the body that represents the content of the receiving block. 67 // 68 // Appending to or otherwise modifying this body will make changes to the 69 // tokens that are generated between the blocks open and close braces. 70 func (b *Block) Body() *Body { 71 return b.body.content.(*Body) 72 } 73 74 // Type returns the type name of the block. 75 func (b *Block) Type() string { 76 typeNameObj := b.typeName.content.(*identifier) 77 return string(typeNameObj.token.Bytes) 78 } 79 80 // SetType updates the type name of the block to a given name. 81 func (b *Block) SetType(typeName string) { 82 nameTok := newIdentToken(typeName) 83 nameObj := newIdentifier(nameTok) 84 b.typeName.ReplaceWith(nameObj) 85 } 86 87 // Labels returns the labels of the block. 88 func (b *Block) Labels() []string { 89 return b.labelsObj().Current() 90 } 91 92 // SetLabels updates the labels of the block to given labels. 93 // Since we cannot assume that old and new labels are equal in length, 94 // remove old labels and insert new ones before TokenOBrace. 95 func (b *Block) SetLabels(labels []string) { 96 b.labelsObj().Replace(labels) 97 } 98 99 // labelsObj returns the internal node content representation of the block 100 // labels. This is not part of the public API because we're intentionally 101 // exposing only a limited API to get/set labels on the block itself in a 102 // manner similar to the main hcl.Block type, but our block accessors all 103 // use this to get the underlying node content to work with. 104 func (b *Block) labelsObj() *blockLabels { 105 return b.labels.content.(*blockLabels) 106 } 107 108 type blockLabels struct { 109 inTree 110 111 items nodeSet 112 } 113 114 func newBlockLabels(labels []string) *blockLabels { 115 ret := &blockLabels{ 116 inTree: newInTree(), 117 items: newNodeSet(), 118 } 119 120 ret.Replace(labels) 121 return ret 122 } 123 124 func (bl *blockLabels) Replace(newLabels []string) { 125 bl.inTree.children.Clear() 126 bl.items.Clear() 127 128 for _, label := range newLabels { 129 labelToks := TokensForValue(cty.StringVal(label)) 130 // Force a new label to use the quoted form, which is the idiomatic 131 // form. The unquoted form is supported in HCL 2 only for compatibility 132 // with historical use in HCL 1. 133 labelObj := newQuoted(labelToks) 134 labelNode := bl.children.Append(labelObj) 135 bl.items.Add(labelNode) 136 } 137 } 138 139 func (bl *blockLabels) Current() []string { 140 labelNames := make([]string, 0, len(bl.items)) 141 list := bl.items.List() 142 143 for _, label := range list { 144 switch labelObj := label.content.(type) { 145 case *identifier: 146 if labelObj.token.Type == hclsyntax.TokenIdent { 147 labelString := string(labelObj.token.Bytes) 148 labelNames = append(labelNames, labelString) 149 } 150 151 case *quoted: 152 tokens := labelObj.tokens 153 if len(tokens) == 3 && 154 tokens[0].Type == hclsyntax.TokenOQuote && 155 tokens[1].Type == hclsyntax.TokenQuotedLit && 156 tokens[2].Type == hclsyntax.TokenCQuote { 157 // Note that TokenQuotedLit may contain escape sequences. 158 labelString, diags := hclsyntax.ParseStringLiteralToken(tokens[1].asHCLSyntax()) 159 160 // If parsing the string literal returns error diagnostics 161 // then we can just assume the label doesn't match, because it's invalid in some way. 162 if !diags.HasErrors() { 163 labelNames = append(labelNames, labelString) 164 } 165 } else if len(tokens) == 2 && 166 tokens[0].Type == hclsyntax.TokenOQuote && 167 tokens[1].Type == hclsyntax.TokenCQuote { 168 // An open quote followed immediately by a closing quote is a 169 // valid but unusual blank string label. 170 labelNames = append(labelNames, "") 171 } 172 173 default: 174 // If neither of the previous cases are true (should be impossible) 175 // then we can just ignore it, because it's invalid too. 176 } 177 } 178 179 return labelNames 180 }