mirror of
https://github.com/mikefarah/yq.git
synced 2026-07-07 06:10:36 +00:00
Fix TOML encoder to quote keys with special characters
Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/b2b52954-d13f-4e67-831a-16fdd3378de5 Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com>
This commit is contained in:
parent
34e0e23906
commit
639ed76bf2
@ -69,6 +69,27 @@ func (te *tomlEncoder) CanHandleAliases() bool {
|
|||||||
|
|
||||||
// ---- helpers ----
|
// ---- helpers ----
|
||||||
|
|
||||||
|
// tomlKey returns the key quoted if it contains characters that are not valid
|
||||||
|
// in a TOML bare key. TOML bare keys may only contain ASCII letters, ASCII
|
||||||
|
// digits, underscores, and dashes.
|
||||||
|
func tomlKey(key string) string {
|
||||||
|
for _, r := range key {
|
||||||
|
if !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-') {
|
||||||
|
return fmt.Sprintf("%q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// tomlDottedKey joins path components, quoting any that require it.
|
||||||
|
func tomlDottedKey(path []string) string {
|
||||||
|
parts := make([]string, len(path))
|
||||||
|
for i, p := range path {
|
||||||
|
parts[i] = tomlKey(p)
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ".")
|
||||||
|
}
|
||||||
|
|
||||||
func (te *tomlEncoder) writeComment(w io.Writer, comment string) error {
|
func (te *tomlEncoder) writeComment(w io.Writer, comment string) error {
|
||||||
if comment == "" {
|
if comment == "" {
|
||||||
return nil
|
return nil
|
||||||
@ -148,9 +169,10 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can
|
|||||||
}
|
}
|
||||||
if allMaps {
|
if allMaps {
|
||||||
key := path[len(path)-1]
|
key := path[len(path)-1]
|
||||||
|
quotedKey := tomlKey(key)
|
||||||
for _, it := range node.Content {
|
for _, it := range node.Content {
|
||||||
// [[key]] then body
|
// [[key]] then body
|
||||||
if _, err := w.Write([]byte("[[" + key + "]]\n")); err != nil {
|
if _, err := w.Write([]byte("[[" + quotedKey + "]]\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := te.encodeMappingBodyWithPath(w, []string{key}, it); err != nil {
|
if err := te.encodeMappingBodyWithPath(w, []string{key}, it); err != nil {
|
||||||
@ -185,7 +207,7 @@ func (te *tomlEncoder) writeAttribute(w io.Writer, key string, value *CandidateN
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write the attribute
|
// Write the attribute
|
||||||
line := key + " = " + te.formatScalar(value)
|
line := tomlKey(key) + " = " + te.formatScalar(value)
|
||||||
|
|
||||||
// Add line comment if present
|
// Add line comment if present
|
||||||
if value.LineComment != "" {
|
if value.LineComment != "" {
|
||||||
@ -210,7 +232,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
|
|||||||
|
|
||||||
// Handle empty arrays
|
// Handle empty arrays
|
||||||
if len(seq.Content) == 0 {
|
if len(seq.Content) == 0 {
|
||||||
line := key + " = []"
|
line := tomlKey(key) + " = []"
|
||||||
if seq.LineComment != "" {
|
if seq.LineComment != "" {
|
||||||
lineComment := strings.TrimSpace(seq.LineComment)
|
lineComment := strings.TrimSpace(seq.LineComment)
|
||||||
if !strings.HasPrefix(lineComment, "#") {
|
if !strings.HasPrefix(lineComment, "#") {
|
||||||
@ -233,7 +255,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
|
|||||||
|
|
||||||
if hasElementComments {
|
if hasElementComments {
|
||||||
// Write multiline array format with comments
|
// Write multiline array format with comments
|
||||||
if _, err := w.Write([]byte(key + " = [\n")); err != nil {
|
if _, err := w.Write([]byte(tomlKey(key) + " = [\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +346,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
line := key + " = [" + strings.Join(items, ", ") + "]"
|
line := tomlKey(key) + " = [" + strings.Join(items, ", ") + "]"
|
||||||
|
|
||||||
// Add line comment if present
|
// Add line comment if present
|
||||||
if seq.LineComment != "" {
|
if seq.LineComment != "" {
|
||||||
@ -372,21 +394,21 @@ func (te *tomlEncoder) mappingToInlineTable(m *CandidateNode) (string, error) {
|
|||||||
v := m.Content[i+1]
|
v := m.Content[i+1]
|
||||||
switch v.Kind {
|
switch v.Kind {
|
||||||
case ScalarNode:
|
case ScalarNode:
|
||||||
parts = append(parts, fmt.Sprintf("%s = %s", k, te.formatScalar(v)))
|
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), te.formatScalar(v)))
|
||||||
case SequenceNode:
|
case SequenceNode:
|
||||||
// inline array in inline table
|
// inline array in inline table
|
||||||
arr, err := te.sequenceToInlineArray(v)
|
arr, err := te.sequenceToInlineArray(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
parts = append(parts, fmt.Sprintf("%s = %s", k, arr))
|
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), arr))
|
||||||
case MappingNode:
|
case MappingNode:
|
||||||
// nested inline table
|
// nested inline table
|
||||||
inline, err := te.mappingToInlineTable(v)
|
inline, err := te.mappingToInlineTable(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
parts = append(parts, fmt.Sprintf("%s = %s", k, inline))
|
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), inline))
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unsupported inline table value kind: %v", v.Kind)
|
return "", fmt.Errorf("unsupported inline table value kind: %v", v.Kind)
|
||||||
}
|
}
|
||||||
@ -399,7 +421,7 @@ func (te *tomlEncoder) writeInlineTableAttribute(w io.Writer, key string, m *Can
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = w.Write([]byte(key + " = " + inline + "\n"))
|
_, err = w.Write([]byte(tomlKey(key) + " = " + inline + "\n"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,7 +443,7 @@ func (te *tomlEncoder) writeTableHeader(w io.Writer, path []string, m *Candidate
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write table header [a.b.c]
|
// Write table header [a.b.c]
|
||||||
header := "[" + strings.Join(path, ".") + "]\n"
|
header := "[" + tomlDottedKey(path) + "]\n"
|
||||||
_, err := w.Write([]byte(header))
|
_, err := w.Write([]byte(header))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -488,7 +510,7 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allMaps {
|
if allMaps {
|
||||||
key := strings.Join(append(append([]string{}, path...), k), ".")
|
key := tomlDottedKey(append(append([]string{}, path...), k))
|
||||||
for _, it := range v.Content {
|
for _, it := range v.Content {
|
||||||
if _, err := w.Write([]byte("[[" + key + "]]\n")); err != nil {
|
if _, err := w.Write([]byte("[[" + key + "]]\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -586,7 +608,7 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allMaps {
|
if allMaps {
|
||||||
dotted := strings.Join(append(append([]string{}, path...), k), ".")
|
dotted := tomlDottedKey(append(append([]string{}, path...), k))
|
||||||
for _, it := range v.Content {
|
for _, it := range v.Content {
|
||||||
if _, err := w.Write([]byte("[[" + dotted + "]]\n")); err != nil {
|
if _, err := w.Write([]byte("[[" + dotted + "]]\n")); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -287,6 +287,14 @@ var expectedSubArrays = `array:
|
|||||||
- {}
|
- {}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// Keys with special characters that require quoting in TOML
|
||||||
|
var rtSpecialKeyInlineTable = `host = { "http://sealos.hub:5000" = { capabilities = ["pull", "resolve", "push"], skip_verify = true } }
|
||||||
|
`
|
||||||
|
|
||||||
|
var rtSpecialKeyTableSection = `["/tmp/blah"]
|
||||||
|
value = "hello"
|
||||||
|
`
|
||||||
|
|
||||||
var tomlScenarios = []formatScenario{
|
var tomlScenarios = []formatScenario{
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
@ -614,6 +622,22 @@ var tomlScenarios = []formatScenario{
|
|||||||
expected: tomlTableWithComments,
|
expected: tomlTableWithComments,
|
||||||
scenarioType: "roundtrip",
|
scenarioType: "roundtrip",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
description: "Roundtrip: key with special characters in inline table",
|
||||||
|
input: rtSpecialKeyInlineTable,
|
||||||
|
expression: ".",
|
||||||
|
expected: rtSpecialKeyInlineTable,
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
description: "Roundtrip: key with special characters in table section",
|
||||||
|
input: rtSpecialKeyTableSection,
|
||||||
|
expression: ".",
|
||||||
|
expected: rtSpecialKeyTableSection,
|
||||||
|
scenarioType: "roundtrip",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTomlScenario(t *testing.T, s formatScenario) {
|
func testTomlScenario(t *testing.T, s formatScenario) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user