github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/generate.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclwrite 5 6 import ( 7 "fmt" 8 "unicode" 9 "unicode/utf8" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hclsyntax" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 // TokensForValue returns a sequence of tokens that represents the given 17 // constant value. 18 // 19 // This function only supports types that are used by HCL. In particular, it 20 // does not support capsule types and will panic if given one. 21 // 22 // It is not possible to express an unknown value in source code, so this 23 // function will panic if the given value is unknown or contains any unknown 24 // values. A caller can call the value's IsWhollyKnown method to verify that 25 // no unknown values are present before calling TokensForValue. 26 func TokensForValue(val cty.Value) Tokens { 27 toks := appendTokensForValue(val, nil) 28 format(toks) // fiddle with the SpacesBefore field to get canonical spacing 29 return toks 30 } 31 32 // TokensForTraversal returns a sequence of tokens that represents the given 33 // traversal. 34 // 35 // If the traversal is absolute then the result is a self-contained, valid 36 // reference expression. If the traversal is relative then the returned tokens 37 // could be appended to some other expression tokens to traverse into the 38 // represented expression. 39 func TokensForTraversal(traversal hcl.Traversal) Tokens { 40 toks := appendTokensForTraversal(traversal, nil) 41 format(toks) // fiddle with the SpacesBefore field to get canonical spacing 42 return toks 43 } 44 45 // TokensForIdentifier returns a sequence of tokens representing just the 46 // given identifier. 47 // 48 // In practice this function can only ever generate exactly one token, because 49 // an identifier is always a leaf token in the syntax tree. 50 // 51 // This is similar to calling TokensForTraversal with a single-step absolute 52 // traversal, but avoids the need to construct a separate traversal object 53 // for this simple common case. If you need to generate a multi-step traversal, 54 // use TokensForTraversal instead. 55 func TokensForIdentifier(name string) Tokens { 56 return Tokens{ 57 newIdentToken(name), 58 } 59 } 60 61 // TokensForTuple returns a sequence of tokens that represents a tuple 62 // constructor, with element expressions populated from the given list 63 // of tokens. 64 // 65 // TokensForTuple includes the given elements verbatim into the element 66 // positions in the resulting tuple expression, without any validation to 67 // ensure that they represent valid expressions. Use TokensForValue or 68 // TokensForTraversal to generate valid leaf expression values, or use 69 // TokensForTuple, TokensForObject, and TokensForFunctionCall to 70 // generate other nested compound expressions. 71 func TokensForTuple(elems []Tokens) Tokens { 72 var toks Tokens 73 toks = append(toks, &Token{ 74 Type: hclsyntax.TokenOBrack, 75 Bytes: []byte{'['}, 76 }) 77 for index, elem := range elems { 78 if index > 0 { 79 toks = append(toks, &Token{ 80 Type: hclsyntax.TokenComma, 81 Bytes: []byte{','}, 82 }) 83 } 84 toks = append(toks, elem...) 85 } 86 87 toks = append(toks, &Token{ 88 Type: hclsyntax.TokenCBrack, 89 Bytes: []byte{']'}, 90 }) 91 92 format(toks) // fiddle with the SpacesBefore field to get canonical spacing 93 return toks 94 } 95 96 // TokensForObject returns a sequence of tokens that represents an object 97 // constructor, with attribute name/value pairs populated from the given 98 // list of attribute token objects. 99 // 100 // TokensForObject includes the given tokens verbatim into the name and 101 // value positions in the resulting object expression, without any validation 102 // to ensure that they represent valid expressions. Use TokensForValue or 103 // TokensForTraversal to generate valid leaf expression values, or use 104 // TokensForTuple, TokensForObject, and TokensForFunctionCall to 105 // generate other nested compound expressions. 106 // 107 // Note that HCL requires placing a traversal expression in parentheses if 108 // you intend to use it as an attribute name expression, because otherwise 109 // the parser will interpret it as a literal attribute name. TokensForObject 110 // does not handle that situation automatically, so a caller must add the 111 // necessary `TokenOParen` and TokenCParen` manually if needed. 112 func TokensForObject(attrs []ObjectAttrTokens) Tokens { 113 var toks Tokens 114 toks = append(toks, &Token{ 115 Type: hclsyntax.TokenOBrace, 116 Bytes: []byte{'{'}, 117 }) 118 if len(attrs) > 0 { 119 toks = append(toks, &Token{ 120 Type: hclsyntax.TokenNewline, 121 Bytes: []byte{'\n'}, 122 }) 123 } 124 for _, attr := range attrs { 125 toks = append(toks, attr.Name...) 126 toks = append(toks, &Token{ 127 Type: hclsyntax.TokenEqual, 128 Bytes: []byte{'='}, 129 }) 130 toks = append(toks, attr.Value...) 131 toks = append(toks, &Token{ 132 Type: hclsyntax.TokenNewline, 133 Bytes: []byte{'\n'}, 134 }) 135 } 136 toks = append(toks, &Token{ 137 Type: hclsyntax.TokenCBrace, 138 Bytes: []byte{'}'}, 139 }) 140 141 format(toks) // fiddle with the SpacesBefore field to get canonical spacing 142 return toks 143 } 144 145 // TokensForFunctionCall returns a sequence of tokens that represents call 146 // to the function with the given name, using the argument tokens to 147 // populate the argument expressions. 148 // 149 // TokensForFunctionCall includes the given argument tokens verbatim into the 150 // positions in the resulting call expression, without any validation 151 // to ensure that they represent valid expressions. Use TokensForValue or 152 // TokensForTraversal to generate valid leaf expression values, or use 153 // TokensForTuple, TokensForObject, and TokensForFunctionCall to 154 // generate other nested compound expressions. 155 // 156 // This function doesn't include an explicit way to generate the expansion 157 // symbol "..." on the final argument. Currently, generating that requires 158 // manually appending a TokenEllipsis with the bytes "..." to the tokens for 159 // the final argument. 160 func TokensForFunctionCall(funcName string, args ...Tokens) Tokens { 161 var toks Tokens 162 toks = append(toks, TokensForIdentifier(funcName)...) 163 toks = append(toks, &Token{ 164 Type: hclsyntax.TokenOParen, 165 Bytes: []byte{'('}, 166 }) 167 for index, arg := range args { 168 if index > 0 { 169 toks = append(toks, &Token{ 170 Type: hclsyntax.TokenComma, 171 Bytes: []byte{','}, 172 }) 173 } 174 toks = append(toks, arg...) 175 } 176 toks = append(toks, &Token{ 177 Type: hclsyntax.TokenCParen, 178 Bytes: []byte{')'}, 179 }) 180 181 format(toks) // fiddle with the SpacesBefore field to get canonical spacing 182 return toks 183 } 184 185 func appendTokensForValue(val cty.Value, toks Tokens) Tokens { 186 switch { 187 188 case !val.IsKnown(): 189 panic("cannot produce tokens for unknown value") 190 191 case val.IsNull(): 192 toks = append(toks, &Token{ 193 Type: hclsyntax.TokenIdent, 194 Bytes: []byte(`null`), 195 }) 196 197 case val.Type() == cty.Bool: 198 var src []byte 199 if val.True() { 200 src = []byte(`true`) 201 } else { 202 src = []byte(`false`) 203 } 204 toks = append(toks, &Token{ 205 Type: hclsyntax.TokenIdent, 206 Bytes: src, 207 }) 208 209 case val.Type() == cty.Number: 210 bf := val.AsBigFloat() 211 srcStr := bf.Text('f', -1) 212 toks = append(toks, &Token{ 213 Type: hclsyntax.TokenNumberLit, 214 Bytes: []byte(srcStr), 215 }) 216 217 case val.Type() == cty.String: 218 // TODO: If it's a multi-line string ending in a newline, format 219 // it as a HEREDOC instead. 220 src := escapeQuotedStringLit(val.AsString()) 221 toks = append(toks, &Token{ 222 Type: hclsyntax.TokenOQuote, 223 Bytes: []byte{'"'}, 224 }) 225 if len(src) > 0 { 226 toks = append(toks, &Token{ 227 Type: hclsyntax.TokenQuotedLit, 228 Bytes: src, 229 }) 230 } 231 toks = append(toks, &Token{ 232 Type: hclsyntax.TokenCQuote, 233 Bytes: []byte{'"'}, 234 }) 235 236 case val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType(): 237 toks = append(toks, &Token{ 238 Type: hclsyntax.TokenOBrack, 239 Bytes: []byte{'['}, 240 }) 241 242 i := 0 243 for it := val.ElementIterator(); it.Next(); { 244 if i > 0 { 245 toks = append(toks, &Token{ 246 Type: hclsyntax.TokenComma, 247 Bytes: []byte{','}, 248 }) 249 } 250 _, eVal := it.Element() 251 toks = appendTokensForValue(eVal, toks) 252 i++ 253 } 254 255 toks = append(toks, &Token{ 256 Type: hclsyntax.TokenCBrack, 257 Bytes: []byte{']'}, 258 }) 259 260 case val.Type().IsMapType() || val.Type().IsObjectType(): 261 toks = append(toks, &Token{ 262 Type: hclsyntax.TokenOBrace, 263 Bytes: []byte{'{'}, 264 }) 265 if val.LengthInt() > 0 { 266 toks = append(toks, &Token{ 267 Type: hclsyntax.TokenNewline, 268 Bytes: []byte{'\n'}, 269 }) 270 } 271 272 i := 0 273 for it := val.ElementIterator(); it.Next(); { 274 eKey, eVal := it.Element() 275 if hclsyntax.ValidIdentifier(eKey.AsString()) { 276 toks = append(toks, &Token{ 277 Type: hclsyntax.TokenIdent, 278 Bytes: []byte(eKey.AsString()), 279 }) 280 } else { 281 toks = appendTokensForValue(eKey, toks) 282 } 283 toks = append(toks, &Token{ 284 Type: hclsyntax.TokenEqual, 285 Bytes: []byte{'='}, 286 }) 287 toks = appendTokensForValue(eVal, toks) 288 toks = append(toks, &Token{ 289 Type: hclsyntax.TokenNewline, 290 Bytes: []byte{'\n'}, 291 }) 292 i++ 293 } 294 295 toks = append(toks, &Token{ 296 Type: hclsyntax.TokenCBrace, 297 Bytes: []byte{'}'}, 298 }) 299 300 default: 301 panic(fmt.Sprintf("cannot produce tokens for %#v", val)) 302 } 303 304 return toks 305 } 306 307 func appendTokensForTraversal(traversal hcl.Traversal, toks Tokens) Tokens { 308 for _, step := range traversal { 309 toks = appendTokensForTraversalStep(step, toks) 310 } 311 return toks 312 } 313 314 func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) Tokens { 315 switch ts := step.(type) { 316 case hcl.TraverseRoot: 317 toks = append(toks, &Token{ 318 Type: hclsyntax.TokenIdent, 319 Bytes: []byte(ts.Name), 320 }) 321 case hcl.TraverseAttr: 322 toks = append( 323 toks, 324 &Token{ 325 Type: hclsyntax.TokenDot, 326 Bytes: []byte{'.'}, 327 }, 328 &Token{ 329 Type: hclsyntax.TokenIdent, 330 Bytes: []byte(ts.Name), 331 }, 332 ) 333 case hcl.TraverseIndex: 334 toks = append(toks, &Token{ 335 Type: hclsyntax.TokenOBrack, 336 Bytes: []byte{'['}, 337 }) 338 toks = appendTokensForValue(ts.Key, toks) 339 toks = append(toks, &Token{ 340 Type: hclsyntax.TokenCBrack, 341 Bytes: []byte{']'}, 342 }) 343 default: 344 panic(fmt.Sprintf("unsupported traversal step type %T", step)) 345 } 346 347 return toks 348 } 349 350 func escapeQuotedStringLit(s string) []byte { 351 if len(s) == 0 { 352 return nil 353 } 354 buf := make([]byte, 0, len(s)) 355 for i, r := range s { 356 switch r { 357 case '\n': 358 buf = append(buf, '\\', 'n') 359 case '\r': 360 buf = append(buf, '\\', 'r') 361 case '\t': 362 buf = append(buf, '\\', 't') 363 case '"': 364 buf = append(buf, '\\', '"') 365 case '\\': 366 buf = append(buf, '\\', '\\') 367 case '$', '%': 368 buf = appendRune(buf, r) 369 remain := s[i+1:] 370 if len(remain) > 0 && remain[0] == '{' { 371 // Double up our template introducer symbol to escape it. 372 buf = appendRune(buf, r) 373 } 374 default: 375 if !unicode.IsPrint(r) { 376 var fmted string 377 if r < 65536 { 378 fmted = fmt.Sprintf("\\u%04x", r) 379 } else { 380 fmted = fmt.Sprintf("\\U%08x", r) 381 } 382 buf = append(buf, fmted...) 383 } else { 384 buf = appendRune(buf, r) 385 } 386 } 387 } 388 return buf 389 } 390 391 func appendRune(b []byte, r rune) []byte { 392 l := utf8.RuneLen(r) 393 for i := 0; i < l; i++ { 394 b = append(b, 0) // make room at the end of our buffer 395 } 396 ch := b[len(b)-l:] 397 utf8.EncodeRune(ch, r) 398 return b 399 }