mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
* fix: preserve key order in YAML output
MlrmapToYAMLNative converted the ordered Mlrmap to map[string]interface{},
which loses insertion order. yaml.v3.Marshal then sorts Go map keys
alphabetically, so YAML output always had sorted keys despite other formats
(JSON, pretty-print) correctly preserving order.
Switch to building yaml.Node trees that preserve the Mlrmap's linked-list
iteration order. Use yaml.Node.Encode for scalar values to correctly handle
edge cases (NaN/Inf, non-UTF-8 strings).
Fixes #2028.
* Update pkg/mlrval/mlrval_yaml_test.go with copilot code-review feedback
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: lawrence3699 <lawrence3699@users.noreply.github.com>
Co-authored-by: John Kerl <kerl.john.r@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
93 lines
2.3 KiB
Go
93 lines
2.3 KiB
Go
package output
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/johnkerl/miller/v6/pkg/cli"
|
|
"github.com/johnkerl/miller/v6/pkg/mlrval"
|
|
"github.com/johnkerl/miller/v6/pkg/types"
|
|
)
|
|
|
|
type RecordWriterYAML struct {
|
|
writerOptions *cli.TWriterOptions
|
|
bufferedRecords []*yaml.Node // used when WrapYAMLOutputInOuterList is true
|
|
wroteAnyRecords bool // for multi-doc: emit "---\n" before 2nd and later docs
|
|
}
|
|
|
|
func NewRecordWriterYAML(writerOptions *cli.TWriterOptions) (*RecordWriterYAML, error) {
|
|
return &RecordWriterYAML{
|
|
writerOptions: writerOptions,
|
|
bufferedRecords: nil,
|
|
wroteAnyRecords: false,
|
|
}, nil
|
|
}
|
|
|
|
func (writer *RecordWriterYAML) Write(
|
|
outrec *mlrval.Mlrmap,
|
|
context *types.Context,
|
|
bufferedOutputStream *bufio.Writer,
|
|
outputIsStdout bool,
|
|
) error {
|
|
if writer.writerOptions.WrapYAMLOutputInOuterList {
|
|
writer.writeWithListWrap(outrec, bufferedOutputStream)
|
|
} else {
|
|
writer.writeWithoutListWrap(outrec, bufferedOutputStream)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (writer *RecordWriterYAML) writeWithListWrap(
|
|
outrec *mlrval.Mlrmap,
|
|
bufferedOutputStream *bufio.Writer,
|
|
) {
|
|
if outrec != nil {
|
|
if writer.bufferedRecords == nil {
|
|
writer.bufferedRecords = []*yaml.Node{}
|
|
}
|
|
native, err := mlrval.MlrmapToYAMLNative(outrec)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
writer.bufferedRecords = append(writer.bufferedRecords, native)
|
|
} else {
|
|
// End of stream: emit single YAML document as array
|
|
seqNode := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
|
seqNode.Content = writer.bufferedRecords
|
|
out, err := yaml.Marshal(seqNode)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
bufferedOutputStream.Write(out)
|
|
writer.bufferedRecords = nil
|
|
}
|
|
}
|
|
|
|
func (writer *RecordWriterYAML) writeWithoutListWrap(
|
|
outrec *mlrval.Mlrmap,
|
|
bufferedOutputStream *bufio.Writer,
|
|
) {
|
|
if outrec == nil {
|
|
return
|
|
}
|
|
if writer.wroteAnyRecords {
|
|
bufferedOutputStream.WriteString("---\n")
|
|
}
|
|
native, err := mlrval.MlrmapToYAMLNative(outrec)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
out, err := yaml.Marshal(native)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "mlr: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
bufferedOutputStream.Write(out)
|
|
writer.wroteAnyRecords = true
|
|
}
|