From d1ffec12e6dce29cd7800bd169212fe833ab64ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Apr 2026 07:06:48 +0000 Subject: [PATCH] Fix TOML roundtrip: use TomlInline flag instead of FlowStyle to preserve inline tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FlowStyle affected YAML decode output (causing inline tables to appear as YAML flow mappings). Replace it with a new TOML-specific TomlInline bool on CandidateNode that: - Is set by the TOML decoder for inline tables (not FlowStyle) - Is copied by UpdateAttributesFrom so it survives DeeplyAssign merges - Is checked by the TOML encoder alongside FlowStyle (for YAML flow maps) - Has no effect on the YAML encoder, preserving existing TOML→YAML output TOML roundtrip tests are restored to their original expected values (inline tables stay inline, table sections stay as sections). Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/f59bdf62-6d16-4664-991b-38eb87c9d81c Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> --- pkg/yqlib/candidate_node.go | 6 ++++++ pkg/yqlib/decoder_toml.go | 8 ++++---- pkg/yqlib/doc/usage/toml.md | 9 ++------- pkg/yqlib/encoder_toml.go | 15 +++++++-------- pkg/yqlib/toml_test.go | 37 ++----------------------------------- 5 files changed, 21 insertions(+), 54 deletions(-) diff --git a/pkg/yqlib/candidate_node.go b/pkg/yqlib/candidate_node.go index 92321527..0518eaad 100644 --- a/pkg/yqlib/candidate_node.go +++ b/pkg/yqlib/candidate_node.go @@ -100,6 +100,9 @@ type CandidateNode struct { // For formats like HCL and TOML: indicates that child entries should be emitted as separate blocks/tables // rather than consolidated into nested mappings (default behaviour) EncodeSeparate bool + // For TOML: indicates that this mapping was originally a TOML inline table and should be + // re-encoded as an inline table rather than a separate table section. + TomlInline bool } func (n *CandidateNode) CreateChild() *CandidateNode { @@ -412,6 +415,7 @@ func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode { IsMapKey: n.IsMapKey, EncodeSeparate: n.EncodeSeparate, + TomlInline: n.TomlInline, } if cloneContent { @@ -467,6 +471,8 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP // Preserve EncodeSeparate flag for format-specific encoding hints n.EncodeSeparate = other.EncodeSeparate + // Preserve TomlInline flag for TOML inline table round-trips + n.TomlInline = other.TomlInline // merge will pickup the style of the new thing // when autocreating nodes diff --git a/pkg/yqlib/decoder_toml.go b/pkg/yqlib/decoder_toml.go index 0d583836..dc4de390 100644 --- a/pkg/yqlib/decoder_toml.go +++ b/pkg/yqlib/decoder_toml.go @@ -150,10 +150,10 @@ func (dec *tomlDecoder) createInlineTableMap(tomlNode *toml.Node) (*CandidateNod } return &CandidateNode{ - Kind: MappingNode, - Tag: "!!map", - Style: FlowStyle, - Content: content, + Kind: MappingNode, + Tag: "!!map", + TomlInline: true, + Content: content, }, nil } diff --git a/pkg/yqlib/doc/usage/toml.md b/pkg/yqlib/doc/usage/toml.md index 4c418589..dab4abc6 100644 --- a/pkg/yqlib/doc/usage/toml.md +++ b/pkg/yqlib/doc/usage/toml.md @@ -153,9 +153,7 @@ yq '.' sample.toml ``` will output ```yaml -[name] -first = "Tom" -last = "Preston-Werner" +name = { first = "Tom", last = "Preston-Werner" } ``` ## Roundtrip: table section @@ -374,10 +372,7 @@ dob = 1979-05-27T07:32:00-08:00 enabled = true ports = [8000, 8001, 8002] data = [["delta", "phi"], [3.14]] - -[database.temp_targets] -cpu = 79.5 -case = 72.0 +temp_targets = { cpu = 79.5, case = 72.0 } # [servers] yq can't do this one yet [servers.alpha] diff --git a/pkg/yqlib/encoder_toml.go b/pkg/yqlib/encoder_toml.go index de4096fe..afcfd777 100644 --- a/pkg/yqlib/encoder_toml.go +++ b/pkg/yqlib/encoder_toml.go @@ -162,10 +162,9 @@ func (te *tomlEncoder) encodeTopLevelEntry(w io.Writer, path []string, node *Can // Regular array attribute return te.writeArrayAttribute(w, path[len(path)-1], node) case MappingNode: - // Use inline table syntax only for nodes explicitly marked with flow/inline style - // (e.g. TOML inline tables or YAML flow mappings). All other mappings become - // readable TOML table sections. - if node.Style&FlowStyle != 0 { + // Use inline table syntax for nodes explicitly marked as TOML inline tables + // or YAML flow mappings. All other mappings become readable TOML table sections. + if node.TomlInline || node.Style&FlowStyle != 0 { return te.writeInlineTableAttribute(w, path[len(path)-1], node) } return te.encodeSeparateMapping(w, path, node) @@ -428,7 +427,7 @@ func (te *tomlEncoder) writeTableHeader(w io.Writer, path []string, m *Candidate // It emits the table header for this mapping if it has any content, then processes children. func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *CandidateNode) error { // Check if this mapping has any non-mapping, non-array-of-tables children (i.e., attributes). - // Flow-style (inline) mapping children also count as attributes since they render as key = { ... }. + // TomlInline mapping children also count as attributes since they render as key = { ... }. hasAttrs := false for i := 0; i < len(m.Content); i += 2 { v := m.Content[i+1] @@ -436,7 +435,7 @@ func (te *tomlEncoder) encodeSeparateMapping(w io.Writer, path []string, m *Cand hasAttrs = true break } - if v.Kind == MappingNode && v.Style&FlowStyle != 0 { + if v.Kind == MappingNode && (v.TomlInline || v.Style&FlowStyle != 0) { hasAttrs = true break } @@ -598,13 +597,13 @@ func (te *tomlEncoder) encodeMappingBodyWithPath(w io.Writer, path []string, m * } } - // Finally, child mappings: flow-style (inline) ones become inline table attributes, + // Finally, child mappings: TomlInline or flow-style ones become inline table attributes, // while all others are emitted as separate sub-table sections. for i := 0; i < len(m.Content); i += 2 { k := m.Content[i].Value v := m.Content[i+1] if v.Kind == MappingNode { - if v.Style&FlowStyle != 0 { + if v.TomlInline || v.Style&FlowStyle != 0 { if err := te.writeInlineTableAttribute(w, k, v); err != nil { return err } diff --git a/pkg/yqlib/toml_test.go b/pkg/yqlib/toml_test.go index c917f8fb..e77d92d0 100644 --- a/pkg/yqlib/toml_test.go +++ b/pkg/yqlib/toml_test.go @@ -182,12 +182,6 @@ var expectedSampleWithHeader = `servers: var rtInlineTableAttr = `name = { first = "Tom", last = "Preston-Werner" } ` -// Inline tables are converted to readable table sections on encode -var rtInlineTableAttrEncoded = `[name] -first = "Tom" -last = "Preston-Werner" -` - var rtTableSection = `[owner.contact] name = "Tom" age = 36 @@ -269,33 +263,6 @@ ip = "10.0.0.2" role = "backend" ` -// Inline table temp_targets is expanded to a readable sub-table when re-encoding -var sampleFromWebEncoded = `# This is a TOML document -title = "TOML Example" - -[owner] -name = "Tom Preston-Werner" -dob = 1979-05-27T07:32:00-08:00 - -[database] -enabled = true -ports = [8000, 8001, 8002] -data = [["delta", "phi"], [3.14]] - -[database.temp_targets] -cpu = 79.5 -case = 72.0 - -# [servers] yq can't do this one yet -[servers.alpha] -ip = "10.0.0.1" -role = "frontend" - -[servers.beta] -ip = "10.0.0.2" -role = "backend" -` - var subArrays = ` [[array]] @@ -539,7 +506,7 @@ var tomlScenarios = []formatScenario{ description: "Roundtrip: inline table attribute", input: rtInlineTableAttr, expression: ".", - expected: rtInlineTableAttrEncoded, + expected: rtInlineTableAttr, scenarioType: "roundtrip", }, { @@ -638,7 +605,7 @@ var tomlScenarios = []formatScenario{ description: "Roundtrip: sample from web", input: sampleFromWeb, expression: ".", - expected: sampleFromWebEncoded, + expected: sampleFromWeb, scenarioType: "roundtrip", }, {