251 lines
5.8 KiB
Go
251 lines
5.8 KiB
Go
package hclwrite
|
|
|
|
import (
|
|
"fmt"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// TokensForValue returns a sequence of tokens that represents the given
|
|
// constant value.
|
|
//
|
|
// This function only supports types that are used by HCL. In particular, it
|
|
// does not support capsule types and will panic if given one.
|
|
//
|
|
// It is not possible to express an unknown value in source code, so this
|
|
// function will panic if the given value is unknown or contains any unknown
|
|
// values. A caller can call the value's IsWhollyKnown method to verify that
|
|
// no unknown values are present before calling TokensForValue.
|
|
func TokensForValue(val cty.Value) Tokens {
|
|
toks := appendTokensForValue(val, nil)
|
|
format(toks) // fiddle with the SpacesBefore field to get canonical spacing
|
|
return toks
|
|
}
|
|
|
|
// TokensForTraversal returns a sequence of tokens that represents the given
|
|
// traversal.
|
|
//
|
|
// If the traversal is absolute then the result is a self-contained, valid
|
|
// reference expression. If the traversal is relative then the returned tokens
|
|
// could be appended to some other expression tokens to traverse into the
|
|
// represented expression.
|
|
func TokensForTraversal(traversal hcl.Traversal) Tokens {
|
|
toks := appendTokensForTraversal(traversal, nil)
|
|
format(toks) // fiddle with the SpacesBefore field to get canonical spacing
|
|
return toks
|
|
}
|
|
|
|
func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
|
|
switch {
|
|
|
|
case !val.IsKnown():
|
|
panic("cannot produce tokens for unknown value")
|
|
|
|
case val.IsNull():
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(`null`),
|
|
})
|
|
|
|
case val.Type() == cty.Bool:
|
|
var src []byte
|
|
if val.True() {
|
|
src = []byte(`true`)
|
|
} else {
|
|
src = []byte(`false`)
|
|
}
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: src,
|
|
})
|
|
|
|
case val.Type() == cty.Number:
|
|
bf := val.AsBigFloat()
|
|
srcStr := bf.Text('f', -1)
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenNumberLit,
|
|
Bytes: []byte(srcStr),
|
|
})
|
|
|
|
case val.Type() == cty.String:
|
|
// TODO: If it's a multi-line string ending in a newline, format
|
|
// it as a HEREDOC instead.
|
|
src := escapeQuotedStringLit(val.AsString())
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenOQuote,
|
|
Bytes: []byte{'"'},
|
|
})
|
|
if len(src) > 0 {
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenQuotedLit,
|
|
Bytes: src,
|
|
})
|
|
}
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenCQuote,
|
|
Bytes: []byte{'"'},
|
|
})
|
|
|
|
case val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType():
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenOBrack,
|
|
Bytes: []byte{'['},
|
|
})
|
|
|
|
i := 0
|
|
for it := val.ElementIterator(); it.Next(); {
|
|
if i > 0 {
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenComma,
|
|
Bytes: []byte{','},
|
|
})
|
|
}
|
|
_, eVal := it.Element()
|
|
toks = appendTokensForValue(eVal, toks)
|
|
i++
|
|
}
|
|
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenCBrack,
|
|
Bytes: []byte{']'},
|
|
})
|
|
|
|
case val.Type().IsMapType() || val.Type().IsObjectType():
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenOBrace,
|
|
Bytes: []byte{'{'},
|
|
})
|
|
|
|
i := 0
|
|
for it := val.ElementIterator(); it.Next(); {
|
|
if i > 0 {
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenComma,
|
|
Bytes: []byte{','},
|
|
})
|
|
}
|
|
eKey, eVal := it.Element()
|
|
if hclsyntax.ValidIdentifier(eKey.AsString()) {
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(eKey.AsString()),
|
|
})
|
|
} else {
|
|
toks = appendTokensForValue(eKey, toks)
|
|
}
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenEqual,
|
|
Bytes: []byte{'='},
|
|
})
|
|
toks = appendTokensForValue(eVal, toks)
|
|
i++
|
|
}
|
|
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenCBrace,
|
|
Bytes: []byte{'}'},
|
|
})
|
|
|
|
default:
|
|
panic(fmt.Sprintf("cannot produce tokens for %#v", val))
|
|
}
|
|
|
|
return toks
|
|
}
|
|
|
|
func appendTokensForTraversal(traversal hcl.Traversal, toks Tokens) Tokens {
|
|
for _, step := range traversal {
|
|
appendTokensForTraversalStep(step, toks)
|
|
}
|
|
return toks
|
|
}
|
|
|
|
func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) {
|
|
switch ts := step.(type) {
|
|
case hcl.TraverseRoot:
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(ts.Name),
|
|
})
|
|
case hcl.TraverseAttr:
|
|
toks = append(
|
|
toks,
|
|
&Token{
|
|
Type: hclsyntax.TokenDot,
|
|
Bytes: []byte{'.'},
|
|
},
|
|
&Token{
|
|
Type: hclsyntax.TokenIdent,
|
|
Bytes: []byte(ts.Name),
|
|
},
|
|
)
|
|
case hcl.TraverseIndex:
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenOBrack,
|
|
Bytes: []byte{'['},
|
|
})
|
|
appendTokensForValue(ts.Key, toks)
|
|
toks = append(toks, &Token{
|
|
Type: hclsyntax.TokenCBrack,
|
|
Bytes: []byte{']'},
|
|
})
|
|
default:
|
|
panic(fmt.Sprintf("unsupported traversal step type %T", step))
|
|
}
|
|
}
|
|
|
|
func escapeQuotedStringLit(s string) []byte {
|
|
if len(s) == 0 {
|
|
return nil
|
|
}
|
|
buf := make([]byte, 0, len(s))
|
|
for i, r := range s {
|
|
switch r {
|
|
case '\n':
|
|
buf = append(buf, '\\', 'n')
|
|
case '\r':
|
|
buf = append(buf, '\\', 'r')
|
|
case '\t':
|
|
buf = append(buf, '\\', 't')
|
|
case '"':
|
|
buf = append(buf, '\\', '"')
|
|
case '\\':
|
|
buf = append(buf, '\\', '\\')
|
|
case '$', '%':
|
|
buf = appendRune(buf, r)
|
|
remain := s[i+1:]
|
|
if len(remain) > 0 && remain[0] == '{' {
|
|
// Double up our template introducer symbol to escape it.
|
|
buf = appendRune(buf, r)
|
|
}
|
|
default:
|
|
if !unicode.IsPrint(r) {
|
|
var fmted string
|
|
if r < 65536 {
|
|
fmted = fmt.Sprintf("\\u%04x", r)
|
|
} else {
|
|
fmted = fmt.Sprintf("\\U%08x", r)
|
|
}
|
|
buf = append(buf, fmted...)
|
|
} else {
|
|
buf = appendRune(buf, r)
|
|
}
|
|
}
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func appendRune(b []byte, r rune) []byte {
|
|
l := utf8.RuneLen(r)
|
|
for i := 0; i < l; i++ {
|
|
b = append(b, 0) // make room at the end of our buffer
|
|
}
|
|
ch := b[len(b)-l:]
|
|
utf8.EncodeRune(ch, r)
|
|
return b
|
|
}
|