mirror of
https://github.com/mikefarah/yq.git
synced 2026-03-10 15:54:26 +00:00
Refining
This commit is contained in:
parent
65e79845d4
commit
df3101ce53
13
examples/sample2.hcl
Normal file
13
examples/sample2.hcl
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
io_mode = "async"
|
||||||
|
|
||||||
|
service "http" "web_proxy" {
|
||||||
|
listen_addr = "127.0.0.1:8080"
|
||||||
|
|
||||||
|
process "main" {
|
||||||
|
command = ["/usr/local/bin/awesome-app", "server"]
|
||||||
|
}
|
||||||
|
|
||||||
|
process "mgmt" {
|
||||||
|
command = ["/usr/local/bin/awesome-app", "mgmt"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -237,6 +237,36 @@ func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode
|
|||||||
// This ensures HCL roundtrips preserve quotes, and YAML properly quotes strings with ${}
|
// This ensures HCL roundtrips preserve quotes, and YAML properly quotes strings with ${}
|
||||||
node.Style = DoubleQuotedStyle
|
node.Style = DoubleQuotedStyle
|
||||||
return node
|
return node
|
||||||
|
case *hclsyntax.ScopeTraversalExpr:
|
||||||
|
// Simple identifier/traversal (e.g. unquoted string literal in HCL)
|
||||||
|
r := e.Range()
|
||||||
|
start := r.Start.Byte
|
||||||
|
end := r.End.Byte
|
||||||
|
if start >= 0 && end >= start && end <= len(src) {
|
||||||
|
text := strings.TrimSpace(string(src[start:end]))
|
||||||
|
return createStringScalarNode(text)
|
||||||
|
}
|
||||||
|
// Fallback to root name if source unavailable
|
||||||
|
if len(e.Traversal) > 0 {
|
||||||
|
if root, ok := e.Traversal[0].(hcl.TraverseRoot); ok {
|
||||||
|
return createStringScalarNode(root.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createStringScalarNode("")
|
||||||
|
case *hclsyntax.FunctionCallExpr:
|
||||||
|
// Preserve function calls as raw expressions for roundtrip
|
||||||
|
r := e.Range()
|
||||||
|
start := r.Start.Byte
|
||||||
|
end := r.End.Byte
|
||||||
|
if start >= 0 && end >= start && end <= len(src) {
|
||||||
|
text := strings.TrimSpace(string(src[start:end]))
|
||||||
|
node := createStringScalarNode(text)
|
||||||
|
node.Style = LiteralStyle
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
node := createStringScalarNode(e.Name)
|
||||||
|
node.Style = LiteralStyle
|
||||||
|
return node
|
||||||
default:
|
default:
|
||||||
// try to evaluate the expression (handles unary, binary ops, etc.)
|
// try to evaluate the expression (handles unary, binary ops, etc.)
|
||||||
val, diags := expr.Value(nil)
|
val, diags := expr.Value(nil)
|
||||||
@ -250,8 +280,10 @@ func convertHclExprToNode(expr hclsyntax.Expression, src []byte) *CandidateNode
|
|||||||
end := r.End.Byte
|
end := r.End.Byte
|
||||||
if start >= 0 && end >= start && end <= len(src) {
|
if start >= 0 && end >= start && end <= len(src) {
|
||||||
text := string(src[start:end])
|
text := string(src[start:end])
|
||||||
// Unquoted identifier - no style
|
// Mark as raw expression so encoder can emit without quoting
|
||||||
return createStringScalarNode(text)
|
node := createStringScalarNode(text)
|
||||||
|
node.Style = LiteralStyle
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
return createStringScalarNode(fmt.Sprintf("%v", expr))
|
return createStringScalarNode(fmt.Sprintf("%v", expr))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,6 +88,56 @@ func isValidHCLIdentifier(s string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tokensForRawHCLExpr produces a minimal token stream for a simple HCL expression so we can
|
||||||
|
// write it without introducing quotes (e.g. function calls like upper(message)).
|
||||||
|
func tokensForRawHCLExpr(expr string) (hclwrite.Tokens, error) {
|
||||||
|
var tokens hclwrite.Tokens
|
||||||
|
for i := 0; i < len(expr); {
|
||||||
|
ch := expr[i]
|
||||||
|
switch {
|
||||||
|
case ch == ' ' || ch == '\t':
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
case isHCLIdentifierStart(rune(ch)):
|
||||||
|
start := i
|
||||||
|
i++
|
||||||
|
for i < len(expr) && isHCLIdentifierPart(rune(expr[i])) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenIdent, Bytes: []byte(expr[start:i])})
|
||||||
|
continue
|
||||||
|
case ch >= '0' && ch <= '9':
|
||||||
|
start := i
|
||||||
|
i++
|
||||||
|
for i < len(expr) && ((expr[i] >= '0' && expr[i] <= '9') || expr[i] == '.') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenNumberLit, Bytes: []byte(expr[start:i])})
|
||||||
|
continue
|
||||||
|
case ch == '(':
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenOParen, Bytes: []byte{'('}})
|
||||||
|
case ch == ')':
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenCParen, Bytes: []byte{')'}})
|
||||||
|
case ch == ',':
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenComma, Bytes: []byte{','}})
|
||||||
|
case ch == '.':
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenDot, Bytes: []byte{'.'}})
|
||||||
|
case ch == '+':
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenPlus, Bytes: []byte{'+'}})
|
||||||
|
case ch == '-':
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenMinus, Bytes: []byte{'-'}})
|
||||||
|
case ch == '*':
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenStar, Bytes: []byte{'*'}})
|
||||||
|
case ch == '/':
|
||||||
|
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenSlash, Bytes: []byte{'/'}})
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported character %q in raw HCL expression", ch)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return tokens, nil
|
||||||
|
}
|
||||||
|
|
||||||
// encodeNode encodes a CandidateNode directly to HCL, preserving style information
|
// encodeNode encodes a CandidateNode directly to HCL, preserving style information
|
||||||
func (he *hclEncoder) encodeNode(body *hclwrite.Body, node *CandidateNode) error {
|
func (he *hclEncoder) encodeNode(body *hclwrite.Body, node *CandidateNode) error {
|
||||||
if node.Kind != MappingNode {
|
if node.Kind != MappingNode {
|
||||||
@ -110,6 +160,14 @@ func (he *hclEncoder) encodeNode(body *hclwrite.Body, node *CandidateNode) error
|
|||||||
// Render as attribute: key = value
|
// Render as attribute: key = value
|
||||||
// Check the style to determine how to encode strings
|
// Check the style to determine how to encode strings
|
||||||
if valueNode.Kind == ScalarNode && valueNode.Tag == "!!str" {
|
if valueNode.Kind == ScalarNode && valueNode.Tag == "!!str" {
|
||||||
|
if valueNode.Style&LiteralStyle != 0 {
|
||||||
|
tokens, err := tokensForRawHCLExpr(valueNode.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body.SetAttributeRaw(key, tokens)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Check style: DoubleQuotedStyle means template, no style could be unquoted or regular
|
// Check style: DoubleQuotedStyle means template, no style could be unquoted or regular
|
||||||
// To distinguish unquoted from regular, we check if the value is a valid identifier
|
// To distinguish unquoted from regular, we check if the value is a valid identifier
|
||||||
if valueNode.Style&DoubleQuotedStyle != 0 && strings.Contains(valueNode.Value, "${") {
|
if valueNode.Style&DoubleQuotedStyle != 0 && strings.Contains(valueNode.Value, "${") {
|
||||||
@ -199,6 +257,14 @@ func (he *hclEncoder) encodeNodeAttributes(body *hclwrite.Body, node *CandidateN
|
|||||||
|
|
||||||
// Check if this is an unquoted identifier (no DoubleQuotedStyle)
|
// Check if this is an unquoted identifier (no DoubleQuotedStyle)
|
||||||
if valueNode.Kind == ScalarNode && valueNode.Tag == "!!str" && valueNode.Style&DoubleQuotedStyle == 0 {
|
if valueNode.Kind == ScalarNode && valueNode.Tag == "!!str" && valueNode.Style&DoubleQuotedStyle == 0 {
|
||||||
|
if valueNode.Style&LiteralStyle != 0 {
|
||||||
|
tokens, err := tokensForRawHCLExpr(valueNode.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body.SetAttributeRaw(key, tokens)
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Unquoted identifier - use traversal
|
// Unquoted identifier - use traversal
|
||||||
traversal := hcl.Traversal{
|
traversal := hcl.Traversal{
|
||||||
hcl.TraverseRoot{Name: valueNode.Value},
|
hcl.TraverseRoot{Name: valueNode.Value},
|
||||||
|
|||||||
@ -37,6 +37,18 @@ var hclFormatScenarios = []formatScenario{
|
|||||||
expected: "message = \"Hello, ${name}!\"\n",
|
expected: "message = \"Hello, ${name}!\"\n",
|
||||||
scenarioType: "roundtrip",
|
scenarioType: "roundtrip",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Function roundtrip",
|
||||||
|
input: `shouty_message = upper(message)`,
|
||||||
|
expected: "shouty_message = upper(message)\n",
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Arithmetic roundtrip",
|
||||||
|
input: `sum = 1 + addend`,
|
||||||
|
expected: "sum = 1 + addend\n",
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "number attribute",
|
description: "number attribute",
|
||||||
input: `port = 8080`,
|
input: `port = 8080`,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user