Skip to content

Commit 639ed76

Browse files
Copilotmikefarah
andauthored
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>
1 parent 34e0e23 commit 639ed76

File tree

2 files changed

+58
-12
lines changed

2 files changed

+58
-12
lines changed

pkg/yqlib/encoder_toml.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,27 @@ func (te *tomlEncoder) CanHandleAliases() bool {
6969

7070
// ---- helpers ----
7171

72+
// tomlKey returns the key quoted if it contains characters that are not valid
73+
// in a TOML bare key. TOML bare keys may only contain ASCII letters, ASCII
74+
// digits, underscores, and dashes.
75+
func tomlKey(key string) string {
76+
for _, r := range key {
77+
if !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-') {
78+
return fmt.Sprintf("%q", key)
79+
}
80+
}
81+
return key
82+
}
83+
84+
// tomlDottedKey joins path components, quoting any that require it.
85+
func tomlDottedKey(path []string) string {
86+
parts := make([]string, len(path))
87+
for i, p := range path {
88+
parts[i] = tomlKey(p)
89+
}
90+
return strings.Join(parts, ".")
91+
}
92+
7293
func (te *tomlEncoder) writeComment(w io.Writer, comment string) error {
7394
if comment == "" {
7495
return nil
@@ -148,9 +169,10 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can
148169
}
149170
if allMaps {
150171
key := path[len(path)-1]
172+
quotedKey := tomlKey(key)
151173
for _, it := range node.Content {
152174
// [[key]] then body
153-
if _, err := w.Write([]byte("[[" + key + "]]\n")); err != nil {
175+
if _, err := w.Write([]byte("[[" + quotedKey + "]]\n")); err != nil {
154176
return err
155177
}
156178
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
185207
}
186208

187209
// Write the attribute
188-
line := key + " = " + te.formatScalar(value)
210+
line := tomlKey(key) + " = " + te.formatScalar(value)
189211

190212
// Add line comment if present
191213
if value.LineComment != "" {
@@ -210,7 +232,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
210232

211233
// Handle empty arrays
212234
if len(seq.Content) == 0 {
213-
line := key + " = []"
235+
line := tomlKey(key) + " = []"
214236
if seq.LineComment != "" {
215237
lineComment := strings.TrimSpace(seq.LineComment)
216238
if !strings.HasPrefix(lineComment, "#") {
@@ -233,7 +255,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
233255

234256
if hasElementComments {
235257
// Write multiline array format with comments
236-
if _, err := w.Write([]byte(key + " = [\n")); err != nil {
258+
if _, err := w.Write([]byte(tomlKey(key) + " = [\n")); err != nil {
237259
return err
238260
}
239261

@@ -324,7 +346,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida
324346
}
325347
}
326348

327-
line := key + " = [" + strings.Join(items, ", ") + "]"
349+
line := tomlKey(key) + " = [" + strings.Join(items, ", ") + "]"
328350

329351
// Add line comment if present
330352
if seq.LineComment != "" {
@@ -372,21 +394,21 @@ func (te *tomlEncoder) mappingToInlineTable(m *CandidateNode) (string, error) {
372394
v := m.Content[i+1]
373395
switch v.Kind {
374396
case ScalarNode:
375-
parts = append(parts, fmt.Sprintf("%s = %s", k, te.formatScalar(v)))
397+
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), te.formatScalar(v)))
376398
case SequenceNode:
377399
// inline array in inline table
378400
arr, err := te.sequenceToInlineArray(v)
379401
if err != nil {
380402
return "", err
381403
}
382-
parts = append(parts, fmt.Sprintf("%s = %s", k, arr))
404+
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), arr))
383405
case MappingNode:
384406
// nested inline table
385407
inline, err := te.mappingToInlineTable(v)
386408
if err != nil {
387409
return "", err
388410
}
389-
parts = append(parts, fmt.Sprintf("%s = %s", k, inline))
411+
parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), inline))
390412
default:
391413
return "", fmt.Errorf("unsupported inline table value kind: %v", v.Kind)
392414
}
@@ -399,7 +421,7 @@ func (te *tomlEncoder) writeInlineTableAttribute(w io.Writer, key string, m *Can
399421
if err != nil {
400422
return err
401423
}
402-
_, err = w.Write([]byte(key + " = " + inline + "\n"))
424+
_, err = w.Write([]byte(tomlKey(key) + " = " + inline + "\n"))
403425
return err
404426
}
405427

@@ -421,7 +443,7 @@ func (te *tomlEncoder) writeTableHeader(w io.Writer, path []string, m *Candidate
421443
}
422444

423445
// Write table header [a.b.c]
424-
header := "[" + strings.Join(path, ".") + "]\n"
446+
header := "[" + tomlDottedKey(path) + "]\n"
425447
_, err := w.Write([]byte(header))
426448
return err
427449
}
@@ -488,7 +510,7 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand
488510
}
489511
}
490512
if allMaps {
491-
key := strings.Join(append(append([]string{}, path...), k), ".")
513+
key := tomlDottedKey(append(append([]string{}, path...), k))
492514
for _, it := range v.Content {
493515
if _, err := w.Write([]byte("[[" + key + "]]\n")); err != nil {
494516
return err
@@ -586,7 +608,7 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m *
586608
}
587609
}
588610
if allMaps {
589-
dotted := strings.Join(append(append([]string{}, path...), k), ".")
611+
dotted := tomlDottedKey(append(append([]string{}, path...), k))
590612
for _, it := range v.Content {
591613
if _, err := w.Write([]byte("[[" + dotted + "]]\n")); err != nil {
592614
return err

pkg/yqlib/toml_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,14 @@ var expectedSubArrays = `array:
287287
- {}
288288
`
289289

290+
// Keys with special characters that require quoting in TOML
291+
var rtSpecialKeyInlineTable = `host = { "http://sealos.hub:5000" = { capabilities = ["pull", "resolve", "push"], skip_verify = true } }
292+
`
293+
294+
var rtSpecialKeyTableSection = `["/tmp/blah"]
295+
value = "hello"
296+
`
297+
290298
var tomlScenarios = []formatScenario{
291299
{
292300
skipDoc: true,
@@ -614,6 +622,22 @@ var tomlScenarios = []formatScenario{
614622
expected: tomlTableWithComments,
615623
scenarioType: "roundtrip",
616624
},
625+
{
626+
skipDoc: true,
627+
description: "Roundtrip: key with special characters in inline table",
628+
input: rtSpecialKeyInlineTable,
629+
expression: ".",
630+
expected: rtSpecialKeyInlineTable,
631+
scenarioType: "roundtrip",
632+
},
633+
{
634+
skipDoc: true,
635+
description: "Roundtrip: key with special characters in table section",
636+
input: rtSpecialKeyTableSection,
637+
expression: ".",
638+
expected: rtSpecialKeyTableSection,
639+
scenarioType: "roundtrip",
640+
},
617641
}
618642

619643
func testTomlScenario(t *testing.T, s formatScenario) {

0 commit comments

Comments
 (0)