From 34e0e239061329c12830217fb5c823810faf6010 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:34:00 +0000 Subject: [PATCH 1/3] Initial plan From 639ed76bf23bb4fd9102c15a1f2ae2580d28d109 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 09:40:06 +0000 Subject: [PATCH 2/3] 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> --- pkg/yqlib/encoder_toml.go | 46 +++++++++++++++++++++++++++++---------- pkg/yqlib/toml_test.go | 24 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/pkg/yqlib/encoder_toml.go b/pkg/yqlib/encoder_toml.go index b4ccc287e0..01ee65e569 100644 --- a/pkg/yqlib/encoder_toml.go +++ b/pkg/yqlib/encoder_toml.go @@ -69,6 +69,27 @@ func (te *tomlEncoder) CanHandleAliases() bool { // ---- 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 { if comment == "" { return nil @@ -148,9 +169,10 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can } if allMaps { key := path[len(path)-1] + quotedKey := tomlKey(key) for _, it := range node.Content { // [[key]] then body - if _, err := w.Write([]byte("[[" + key + "]]\n")); err != nil { + if _, err := w.Write([]byte("[[" + quotedKey + "]]\n")); err != nil { return err } 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 - line := key + " = " + te.formatScalar(value) + line := tomlKey(key) + " = " + te.formatScalar(value) // Add line comment if present if value.LineComment != "" { @@ -210,7 +232,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida // Handle empty arrays if len(seq.Content) == 0 { - line := key + " = []" + line := tomlKey(key) + " = []" if seq.LineComment != "" { lineComment := strings.TrimSpace(seq.LineComment) if !strings.HasPrefix(lineComment, "#") { @@ -233,7 +255,7 @@ func (te *tomlEncoder) writeArrayAttribute(w io.Writer, key string, seq *Candida if hasElementComments { // 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 } @@ -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 if seq.LineComment != "" { @@ -372,21 +394,21 @@ func (te *tomlEncoder) mappingToInlineTable(m *CandidateNode) (string, error) { v := m.Content[i+1] switch v.Kind { 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: // inline array in inline table arr, err := te.sequenceToInlineArray(v) if err != nil { return "", err } - parts = append(parts, fmt.Sprintf("%s = %s", k, arr)) + parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), arr)) case MappingNode: // nested inline table inline, err := te.mappingToInlineTable(v) if err != nil { return "", err } - parts = append(parts, fmt.Sprintf("%s = %s", k, inline)) + parts = append(parts, fmt.Sprintf("%s = %s", tomlKey(k), inline)) default: 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 { return err } - _, err = w.Write([]byte(key + " = " + inline + "\n")) + _, err = w.Write([]byte(tomlKey(key) + " = " + inline + "\n")) return err } @@ -421,7 +443,7 @@ func (te *tomlEncoder) writeTableHeader(w io.Writer, path []string, m *Candidate } // Write table header [a.b.c] - header := "[" + strings.Join(path, ".") + "]\n" + header := "[" + tomlDottedKey(path) + "]\n" _, err := w.Write([]byte(header)) return err } @@ -488,7 +510,7 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand } } if allMaps { - key := strings.Join(append(append([]string{}, path...), k), ".") + key := tomlDottedKey(append(append([]string{}, path...), k)) for _, it := range v.Content { if _, err := w.Write([]byte("[[" + key + "]]\n")); err != nil { return err @@ -586,7 +608,7 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m * } } if allMaps { - dotted := strings.Join(append(append([]string{}, path...), k), ".") + dotted := tomlDottedKey(append(append([]string{}, path...), k)) for _, it := range v.Content { if _, err := w.Write([]byte("[[" + dotted + "]]\n")); err != nil { return err diff --git a/pkg/yqlib/toml_test.go b/pkg/yqlib/toml_test.go index fe542a01c4..3711707861 100644 --- a/pkg/yqlib/toml_test.go +++ b/pkg/yqlib/toml_test.go @@ -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{ { skipDoc: true, @@ -614,6 +622,22 @@ var tomlScenarios = []formatScenario{ expected: tomlTableWithComments, 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) { From c7aa0cec1ecb05bcc4de60f314f00155e1317c66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 06:51:10 +0000 Subject: [PATCH 3/3] Add test for dotted table section header with special character key Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/12c783dd-8b7f-43bf-b71a-e7a0b5e55fea Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> --- pkg/yqlib/toml_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/yqlib/toml_test.go b/pkg/yqlib/toml_test.go index 3711707861..18be60436c 100644 --- a/pkg/yqlib/toml_test.go +++ b/pkg/yqlib/toml_test.go @@ -295,6 +295,10 @@ var rtSpecialKeyTableSection = `["/tmp/blah"] value = "hello" ` +var rtSpecialKeyDottedTableSection = `[servers."http://localhost:8080"] +ip = "127.0.0.1" +` + var tomlScenarios = []formatScenario{ { skipDoc: true, @@ -638,6 +642,14 @@ var tomlScenarios = []formatScenario{ expected: rtSpecialKeyTableSection, scenarioType: "roundtrip", }, + { + skipDoc: true, + description: "Roundtrip: special character key in dotted table section header", + input: rtSpecialKeyDottedTableSection, + expression: ".", + expected: rtSpecialKeyDottedTableSection, + scenarioType: "roundtrip", + }, } func testTomlScenario(t *testing.T, s formatScenario) {