Use HCL encoder

This commit is contained in:
Mike Farah 2025-12-07 19:22:00 +11:00
parent 8d430cf41c
commit c75a2fad86

View File

@ -73,11 +73,11 @@ func (he *hclEncoder) encodeNode(body *hclwrite.Body, node *CandidateNode) error
} }
} else { } else {
// Render as attribute: key = value // Render as attribute: key = value
goValue, err := candidateNodeToGoValue(valueNode) ctyValue, err := nodeToCtyValue(valueNode)
if err != nil { if err != nil {
return err return err
} }
body.SetAttributeValue(key, toCtyValue(goValue)) body.SetAttributeValue(key, ctyValue)
} }
} }
return nil return nil
@ -94,99 +94,69 @@ func (he *hclEncoder) encodeNodeAttributes(body *hclwrite.Body, node *CandidateN
valueNode := node.Content[i+1] valueNode := node.Content[i+1]
key := keyNode.Value key := keyNode.Value
goValue, err := candidateNodeToGoValue(valueNode) ctyValue, err := nodeToCtyValue(valueNode)
if err != nil { if err != nil {
return err return err
} }
body.SetAttributeValue(key, toCtyValue(goValue)) body.SetAttributeValue(key, ctyValue)
} }
return nil return nil
} }
// candidateNodeToGoValue converts a CandidateNode to a Go value suitable for HCL encoding // nodeToCtyValue converts a CandidateNode directly to cty.Value, preserving order
func candidateNodeToGoValue(node *CandidateNode) (interface{}, error) { func nodeToCtyValue(node *CandidateNode) (cty.Value, error) {
switch node.Kind { switch node.Kind {
case ScalarNode: case ScalarNode:
// Parse scalar value based on its tag // Parse scalar value based on its tag
switch node.Tag { switch node.Tag {
case "!!bool": case "!!bool":
return node.Value == "true", nil return cty.BoolVal(node.Value == "true"), nil
case "!!int": case "!!int":
var i int64 var i int64
_, err := fmt.Sscanf(node.Value, "%d", &i) _, err := fmt.Sscanf(node.Value, "%d", &i)
if err != nil { if err != nil {
return nil, err return cty.NilVal, err
} }
return i, nil return cty.NumberIntVal(i), nil
case "!!float": case "!!float":
var f float64 var f float64
_, err := fmt.Sscanf(node.Value, "%f", &f) _, err := fmt.Sscanf(node.Value, "%f", &f)
if err != nil { if err != nil {
return nil, err return cty.NilVal, err
} }
return f, nil return cty.NumberFloatVal(f), nil
case "!!null": case "!!null":
return nil, nil return cty.NullVal(cty.DynamicPseudoType), nil
default: default:
// Default to string // Default to string
return node.Value, nil return cty.StringVal(node.Value), nil
} }
case MappingNode: case MappingNode:
m := make(map[string]interface{}) // Preserve order by iterating Content directly
m := make(map[string]cty.Value)
for i := 0; i < len(node.Content); i += 2 { for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i] keyNode := node.Content[i]
valueNode := node.Content[i+1] valueNode := node.Content[i+1]
v, err := candidateNodeToGoValue(valueNode) v, err := nodeToCtyValue(valueNode)
if err != nil { if err != nil {
return nil, err return cty.NilVal, err
} }
m[keyNode.Value] = v m[keyNode.Value] = v
} }
return m, nil return cty.ObjectVal(m), nil
case SequenceNode: case SequenceNode:
arr := make([]interface{}, len(node.Content)) vals := make([]cty.Value, len(node.Content))
for i, item := range node.Content { for i, item := range node.Content {
v, err := candidateNodeToGoValue(item) v, err := nodeToCtyValue(item)
if err != nil { if err != nil {
return nil, err return cty.NilVal, err
} }
arr[i] = v vals[i] = v
} }
return arr, nil return cty.TupleVal(vals), nil
case AliasNode: case AliasNode:
return nil, fmt.Errorf("HCL encoder does not support aliases") return cty.NilVal, fmt.Errorf("HCL encoder does not support aliases")
default: default:
return nil, fmt.Errorf("unsupported node kind: %v", node.Kind) return cty.NilVal, fmt.Errorf("unsupported node kind: %v", node.Kind)
}
}
// toCtyValue converts Go values to cty.Value for hclwrite
func toCtyValue(val interface{}) cty.Value {
switch v := val.(type) {
case string:
return cty.StringVal(v)
case bool:
return cty.BoolVal(v)
case int:
return cty.NumberIntVal(int64(v))
case int64:
return cty.NumberIntVal(v)
case float64:
return cty.NumberFloatVal(v)
case []interface{}:
vals := make([]cty.Value, len(v))
for i, item := range v {
vals[i] = toCtyValue(item)
}
return cty.TupleVal(vals)
case map[string]interface{}:
m := make(map[string]cty.Value)
for k, item := range v {
m[k] = toCtyValue(item)
}
return cty.ObjectVal(m)
default:
// fallback: treat as string
return cty.StringVal(fmt.Sprintf("%v", v))
} }
} }