mirror of
https://github.com/mikefarah/yq.git
synced 2026-07-06 21:55:37 +00:00
hcl - sorted decoding
This commit is contained in:
parent
7d2c774e8f
commit
1852073f29
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -23,6 +24,23 @@ func NewHclDecoder() Decoder {
|
|||||||
return &hclDecoder{}
|
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 {
|
func (dec *hclDecoder) Init(reader io.Reader) error {
|
||||||
data, err := io.ReadAll(reader)
|
data, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,11 +69,11 @@ func (dec *hclDecoder) Decode() (*CandidateNode, error) {
|
|||||||
|
|
||||||
root := &CandidateNode{Kind: MappingNode}
|
root := &CandidateNode{Kind: MappingNode}
|
||||||
|
|
||||||
// process attributes
|
// process attributes in declaration order
|
||||||
body := dec.file.Body.(*hclsyntax.Body)
|
body := dec.file.Body.(*hclsyntax.Body)
|
||||||
for name, attr := range body.Attributes {
|
for _, attrWithName := range sortedAttributes(body.Attributes) {
|
||||||
keyNode := createStringScalarNode(name)
|
keyNode := createStringScalarNode(attrWithName.Name)
|
||||||
valNode := convertHclExprToNode(attr.Expr, dec.fileBytes)
|
valNode := convertHclExprToNode(attrWithName.Attr.Expr, dec.fileBytes)
|
||||||
root.AddKeyValueChild(keyNode, valNode)
|
root.AddKeyValueChild(keyNode, valNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,9 +96,9 @@ func (dec *hclDecoder) Decode() (*CandidateNode, error) {
|
|||||||
|
|
||||||
func hclBodyToNode(body *hclsyntax.Body, src []byte) *CandidateNode {
|
func hclBodyToNode(body *hclsyntax.Body, src []byte) *CandidateNode {
|
||||||
node := &CandidateNode{Kind: MappingNode}
|
node := &CandidateNode{Kind: MappingNode}
|
||||||
for name, attr := range body.Attributes {
|
for _, attrWithName := range sortedAttributes(body.Attributes) {
|
||||||
key := createStringScalarNode(name)
|
key := createStringScalarNode(attrWithName.Name)
|
||||||
val := convertHclExprToNode(attr.Expr, src)
|
val := convertHclExprToNode(attrWithName.Attr.Expr, src)
|
||||||
node.AddKeyValueChild(key, val)
|
node.AddKeyValueChild(key, val)
|
||||||
}
|
}
|
||||||
for _, block := range body.Blocks {
|
for _, block := range body.Blocks {
|
||||||
@ -209,6 +227,12 @@ func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode
|
|||||||
combined := strings.Join(parts, "")
|
combined := strings.Join(parts, "")
|
||||||
return createScalarNode(combined, combined)
|
return createScalarNode(combined, combined)
|
||||||
default:
|
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
|
// fallback: extract source text for the expression
|
||||||
r := expr.Range()
|
r := expr.Range()
|
||||||
start := r.Start.Byte
|
start := r.Start.Byte
|
||||||
|
|||||||
@ -67,6 +67,11 @@ var TomlFormat = &Format{"toml", []string{},
|
|||||||
func() Decoder { return NewTomlDecoder() },
|
func() Decoder { return NewTomlDecoder() },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var HclFormat = &Format{"hcl", []string{"h", "hcl"},
|
||||||
|
nil,
|
||||||
|
func() Decoder { return NewHclDecoder() },
|
||||||
|
}
|
||||||
|
|
||||||
var ShellVariablesFormat = &Format{"shell", []string{"s", "sh"},
|
var ShellVariablesFormat = &Format{"shell", []string{"s", "sh"},
|
||||||
func() Encoder { return NewShellVariablesEncoder() },
|
func() Encoder { return NewShellVariablesEncoder() },
|
||||||
nil,
|
nil,
|
||||||
@ -93,6 +98,7 @@ var Formats = []*Format{
|
|||||||
UriFormat,
|
UriFormat,
|
||||||
ShFormat,
|
ShFormat,
|
||||||
TomlFormat,
|
TomlFormat,
|
||||||
|
HclFormat,
|
||||||
ShellVariablesFormat,
|
ShellVariablesFormat,
|
||||||
LuaFormat,
|
LuaFormat,
|
||||||
INIFormat,
|
INIFormat,
|
||||||
|
|||||||
@ -49,6 +49,48 @@ var hclFormatScenarios = []formatScenario{
|
|||||||
expected: "server:\n port: 8080\n",
|
expected: "server:\n port: 8080\n",
|
||||||
scenarioType: "decode",
|
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) {
|
func testHclScenario(t *testing.T, s formatScenario) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user