mirror of
https://github.com/mikefarah/yq.git
synced 2026-03-10 15:54:26 +00:00
163 lines
4.2 KiB
Go
163 lines
4.2 KiB
Go
package yqlib
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
|
|
hclwrite "github.com/hashicorp/hcl/v2/hclwrite"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
type hclEncoder struct {
|
|
}
|
|
|
|
// NewHclEncoder creates a new HCL encoder
|
|
func NewHclEncoder() Encoder {
|
|
return &hclEncoder{}
|
|
}
|
|
|
|
func (he *hclEncoder) CanHandleAliases() bool {
|
|
return false
|
|
}
|
|
|
|
func (he *hclEncoder) PrintDocumentSeparator(_ io.Writer) error {
|
|
return nil
|
|
}
|
|
|
|
func (he *hclEncoder) PrintLeadingContent(_ io.Writer, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
func (he *hclEncoder) Encode(writer io.Writer, node *CandidateNode) error {
|
|
log.Debugf("I need to encode %v", NodeToString(node))
|
|
|
|
f := hclwrite.NewEmptyFile()
|
|
body := f.Body()
|
|
if err := he.encodeNode(body, node); err != nil {
|
|
return fmt.Errorf("failed to encode HCL: %w", err)
|
|
}
|
|
|
|
// Get the formatted output and remove extra spacing before '='
|
|
output := f.Bytes()
|
|
compactOutput := he.compactSpacing(output)
|
|
|
|
_, err := writer.Write(compactOutput)
|
|
return err
|
|
}
|
|
|
|
// compactSpacing removes extra whitespace before '=' in attribute assignments
|
|
func (he *hclEncoder) compactSpacing(input []byte) []byte {
|
|
// Use regex to replace multiple spaces before = with single space
|
|
re := regexp.MustCompile(`(\S)\s{2,}=`)
|
|
return re.ReplaceAll(input, []byte("$1 ="))
|
|
}
|
|
|
|
// encodeNode encodes a CandidateNode directly to HCL, preserving style information
|
|
func (he *hclEncoder) encodeNode(body *hclwrite.Body, node *CandidateNode) error {
|
|
if node.Kind != MappingNode {
|
|
return fmt.Errorf("HCL encoder expects a mapping at the root level")
|
|
}
|
|
|
|
for i := 0; i < len(node.Content); i += 2 {
|
|
keyNode := node.Content[i]
|
|
valueNode := node.Content[i+1]
|
|
key := keyNode.Value
|
|
|
|
// Check if value is a mapping without FlowStyle -> render as block
|
|
if valueNode.Kind == MappingNode && valueNode.Style != FlowStyle {
|
|
// Render as block: key { ... }
|
|
block := body.AppendNewBlock(key, nil)
|
|
if err := he.encodeNodeAttributes(block.Body(), valueNode); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Render as attribute: key = value
|
|
ctyValue, err := nodeToCtyValue(valueNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
body.SetAttributeValue(key, ctyValue)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// encodeNodeAttributes encodes the attributes of a mapping node (used for blocks)
|
|
func (he *hclEncoder) encodeNodeAttributes(body *hclwrite.Body, node *CandidateNode) error {
|
|
if node.Kind != MappingNode {
|
|
return fmt.Errorf("expected mapping node for block body")
|
|
}
|
|
|
|
for i := 0; i < len(node.Content); i += 2 {
|
|
keyNode := node.Content[i]
|
|
valueNode := node.Content[i+1]
|
|
key := keyNode.Value
|
|
|
|
ctyValue, err := nodeToCtyValue(valueNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
body.SetAttributeValue(key, ctyValue)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// nodeToCtyValue converts a CandidateNode directly to cty.Value, preserving order
|
|
func nodeToCtyValue(node *CandidateNode) (cty.Value, error) {
|
|
switch node.Kind {
|
|
case ScalarNode:
|
|
// Parse scalar value based on its tag
|
|
switch node.Tag {
|
|
case "!!bool":
|
|
return cty.BoolVal(node.Value == "true"), nil
|
|
case "!!int":
|
|
var i int64
|
|
_, err := fmt.Sscanf(node.Value, "%d", &i)
|
|
if err != nil {
|
|
return cty.NilVal, err
|
|
}
|
|
return cty.NumberIntVal(i), nil
|
|
case "!!float":
|
|
var f float64
|
|
_, err := fmt.Sscanf(node.Value, "%f", &f)
|
|
if err != nil {
|
|
return cty.NilVal, err
|
|
}
|
|
return cty.NumberFloatVal(f), nil
|
|
case "!!null":
|
|
return cty.NullVal(cty.DynamicPseudoType), nil
|
|
default:
|
|
// Default to string
|
|
return cty.StringVal(node.Value), nil
|
|
}
|
|
case MappingNode:
|
|
// Preserve order by iterating Content directly
|
|
m := make(map[string]cty.Value)
|
|
for i := 0; i < len(node.Content); i += 2 {
|
|
keyNode := node.Content[i]
|
|
valueNode := node.Content[i+1]
|
|
v, err := nodeToCtyValue(valueNode)
|
|
if err != nil {
|
|
return cty.NilVal, err
|
|
}
|
|
m[keyNode.Value] = v
|
|
}
|
|
return cty.ObjectVal(m), nil
|
|
case SequenceNode:
|
|
vals := make([]cty.Value, len(node.Content))
|
|
for i, item := range node.Content {
|
|
v, err := nodeToCtyValue(item)
|
|
if err != nil {
|
|
return cty.NilVal, err
|
|
}
|
|
vals[i] = v
|
|
}
|
|
return cty.TupleVal(vals), nil
|
|
case AliasNode:
|
|
return cty.NilVal, fmt.Errorf("HCL encoder does not support aliases")
|
|
default:
|
|
return cty.NilVal, fmt.Errorf("unsupported node kind: %v", node.Kind)
|
|
}
|
|
}
|