mirror of
https://github.com/mikefarah/yq.git
synced 2026-07-01 18:01:40 +00:00
hcl - sorted decoding
This commit is contained in:
parent
7d2c774e8f
commit
1852073f29
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -23,6 +24,23 @@ func NewHclDecoder() Decoder {
|
||||
return &hclDecoder{}
|
||||
}
|
||||
|
||||
// sortedAttributes returns attributes in declaration order by source position
|
||||
func sortedAttributes(attrs hclsyntax.Attributes) []*attributeWithName {
|
||||
var sorted []*attributeWithName
|
||||
for name, attr := range attrs {
|
||||
sorted = append(sorted, &attributeWithName{Name: name, Attr: attr})
|
||||
}
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
return sorted[i].Attr.Range().Start.Byte < sorted[j].Attr.Range().Start.Byte
|
||||
})
|
||||
return sorted
|
||||
}
|
||||
|
||||
type attributeWithName struct {
|
||||
Name string
|
||||
Attr *hclsyntax.Attribute
|
||||
}
|
||||
|
||||
func (dec *hclDecoder) Init(reader io.Reader) error {
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
@ -51,11 +69,11 @@ func (dec *hclDecoder) Decode() (*CandidateNode, error) {
|
||||
|
||||
root := &CandidateNode{Kind: MappingNode}
|
||||
|
||||
// process attributes
|
||||
// process attributes in declaration order
|
||||
body := dec.file.Body.(*hclsyntax.Body)
|
||||
for name, attr := range body.Attributes {
|
||||
keyNode := createStringScalarNode(name)
|
||||
valNode := convertHclExprToNode(attr.Expr, dec.fileBytes)
|
||||
for _, attrWithName := range sortedAttributes(body.Attributes) {
|
||||
keyNode := createStringScalarNode(attrWithName.Name)
|
||||
valNode := convertHclExprToNode(attrWithName.Attr.Expr, dec.fileBytes)
|
||||
root.AddKeyValueChild(keyNode, valNode)
|
||||
}
|
||||
|
||||
@ -78,9 +96,9 @@ func (dec *hclDecoder) Decode() (*CandidateNode, error) {
|
||||
|
||||
func hclBodyToNode(body *hclsyntax.Body, src []byte) *CandidateNode {
|
||||
node := &CandidateNode{Kind: MappingNode}
|
||||
for name, attr := range body.Attributes {
|
||||
key := createStringScalarNode(name)
|
||||
val := convertHclExprToNode(attr.Expr, src)
|
||||
for _, attrWithName := range sortedAttributes(body.Attributes) {
|
||||
key := createStringScalarNode(attrWithName.Name)
|
||||
val := convertHclExprToNode(attrWithName.Attr.Expr, src)
|
||||
node.AddKeyValueChild(key, val)
|
||||
}
|
||||
for _, block := range body.Blocks {
|
||||
@ -209,6 +227,12 @@ func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode
|
||||
combined := strings.Join(parts, "")
|
||||
return createScalarNode(combined, combined)
|
||||
default:
|
||||
// try to evaluate the expression (handles unary, binary ops, etc.)
|
||||
val, diags := expr.Value(nil)
|
||||
if diags == nil || !diags.HasErrors() {
|
||||
// successfully evaluated, convert cty.Value to node
|
||||
return convertCtyValueToNode(val)
|
||||
}
|
||||
// fallback: extract source text for the expression
|
||||
r := expr.Range()
|
||||
start := r.Start.Byte
|
||||
|
||||
@ -67,6 +67,11 @@ var TomlFormat = &Format{"toml", []string{},
|
||||
func() Decoder { return NewTomlDecoder() },
|
||||
}
|
||||
|
||||
var HclFormat = &Format{"hcl", []string{"h", "hcl"},
|
||||
nil,
|
||||
func() Decoder { return NewHclDecoder() },
|
||||
}
|
||||
|
||||
var ShellVariablesFormat = &Format{"shell", []string{"s", "sh"},
|
||||
func() Encoder { return NewShellVariablesEncoder() },
|
||||
nil,
|
||||
@ -93,6 +98,7 @@ var Formats = []*Format{
|
||||
UriFormat,
|
||||
ShFormat,
|
||||
TomlFormat,
|
||||
HclFormat,
|
||||
ShellVariablesFormat,
|
||||
LuaFormat,
|
||||
INIFormat,
|
||||
|
||||
@ -49,6 +49,48 @@ var hclFormatScenarios = []formatScenario{
|
||||
expected: "server:\n port: 8080\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "multiple attributes",
|
||||
input: "name = \"app\"\nversion = 1\nenabled = true",
|
||||
expected: "name: app\nversion: 1\nenabled: true\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "binary expression",
|
||||
input: `count = 0 - 42`,
|
||||
expected: "count: -42\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "negative number",
|
||||
input: `count = -42`,
|
||||
expected: "count: -42\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "scientific notation",
|
||||
input: `value = 1e-3`,
|
||||
expected: "value: 0.001\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "nested object",
|
||||
input: `config = { db = { host = "localhost", port = 5432 } }`,
|
||||
expected: "config:\n db:\n host: localhost\n port: 5432\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "mixed list",
|
||||
input: `values = [1, "two", true]`,
|
||||
expected: "values:\n - 1\n - two\n - true\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "block with labels",
|
||||
input: `resource "aws_instance" "example" { ami = "ami-12345" }`,
|
||||
expected: "resource aws_instance example:\n ami: ami-12345\n",
|
||||
scenarioType: "decode",
|
||||
},
|
||||
}
|
||||
|
||||
func testHclScenario(t *testing.T, s formatScenario) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user