Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions assets/issues/issue-669/between/from.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
z_key: 1
a_key: 2
m_key: 3
4 changes: 4 additions & 0 deletions assets/issues/issue-669/between/to.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
a_key: 2
m_key: 4
z_key: 1
13 changes: 13 additions & 0 deletions assets/issues/issue-669/input.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
z_key: value_z
a_key: value_a
m_key: value_m
nested:
z_nested: 1
a_nested: 2
m_nested: 3
list:
- z_item: 1
a_item: 2
- a_item: 3
z_item: 4
13 changes: 13 additions & 0 deletions internal/cmd/between.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var betweenCmdUsageTemplate string
type betweenCmdOptions struct {
swap bool
translateListToDocuments bool
sort bool
chroot string
chrootFrom string
chrootTo string
Expand Down Expand Up @@ -70,6 +71,17 @@ types are: YAML (http://yaml.org/) and JSON (http://json.org/).
return fmt.Errorf("failed to load input files: %w", err)
}

// If the --sort flag is set, sort mapping keys in both input files
// to ensure consistent key ordering before comparison.
if betweenCmdSettings.sort {
for i := range from.Documents {
sortYAMLKeys(from.Documents[i])
}
for i := range to.Documents {
sortYAMLKeys(to.Documents[i])
}
}

// If the main change root flag is set, this (re-)sets the individual change roots of the two input files
if betweenCmdSettings.chroot != "" {
betweenCmdSettings.chrootFrom = betweenCmdSettings.chroot
Expand Down Expand Up @@ -133,6 +145,7 @@ func init() {
var groups = []*pflag.FlagSet{
flagSet("Input Documents Handling", func(fs *pflag.FlagSet) {
fs.BoolVar(&betweenCmdSettings.swap, "swap", false, "Swap 'from' and 'to' for comparison")
fs.BoolVar(&betweenCmdSettings.sort, "sort", false, "Sort YAML map keys alphabetically before comparison to eliminate false order-change entries")
fs.StringVar(&betweenCmdSettings.chroot, "chroot", "", "change the root level of the input file to another point in the document")
fs.StringVar(&betweenCmdSettings.chrootFrom, "chroot-of-from", "", "only change the root level of the from input file")
fs.StringVar(&betweenCmdSettings.chrootTo, "chroot-of-to", "", "only change the root level of the to input file")
Expand Down
15 changes: 15 additions & 0 deletions internal/cmd/cmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,5 +562,20 @@ foo: bar
_, err := dyff("last-applied", kubeYAML)
Expect(err).To(HaveOccurred())
})

It("should sort keys and avoid false order changes with --sort flag", func() {
out, err := dyff("between", "--omit-header", "--sort",
assets("issues", "issue-669", "between", "from.yml"),
assets("issues", "issue-669", "between", "to.yml"),
)
Expect(err).ToNot(HaveOccurred())
Expect(out).To(BeEquivalentTo(`
m_key
± value change
- 3
+ 4

`))
})
})
})
47 changes: 47 additions & 0 deletions internal/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"

"github.com/caarlos0/env/v11"
Expand Down Expand Up @@ -135,6 +136,7 @@ func reportOptionsFlags() []*pflag.FlagSet {
type OutputWriter struct {
PlainMode bool
Restructure bool
SortKeys bool
OmitIndentHelper bool
EnforceDocumentStartMarker bool
OutputStyle string
Expand Down Expand Up @@ -190,6 +192,10 @@ func (w *OutputWriter) write(writer io.Writer, filename string) error {
ytbx.RestructureObject(document)
}

if w.SortKeys {
sortYAMLKeys(document)
}

switch {
case w.PlainMode && w.OutputStyle == "json":
outputProcessor := neat.NewOutputProcessorWithDefaults()
Expand Down Expand Up @@ -349,3 +355,44 @@ func writeReport(cmd *cobra.Command, report dyff.Report) error {

return nil
}

// sortYAMLKeys recursively sorts mapping node keys alphabetically in place.
// MappingNode.Content is a flat [key, val, key, val, ...] slice; this function
// reorders pairs so that keys are in ascending alphabetical order.
func sortYAMLKeys(node *yamlv3.Node) {
switch node.Kind {
case yamlv3.DocumentNode:
for _, content := range node.Content {
sortYAMLKeys(content)
}

case yamlv3.MappingNode:
// Collect key-value pairs.
type pair struct {
key *yamlv3.Node
value *yamlv3.Node
}
pairs := make([]pair, 0, len(node.Content)/2)
for i := 0; i < len(node.Content); i += 2 {
pairs = append(pairs, pair{key: node.Content[i], value: node.Content[i+1]})
}
// Sort by key name.
sort.SliceStable(pairs, func(i, j int) bool {
return pairs[i].key.Value < pairs[j].key.Value
})
// Rebuild Content slice.
for i, p := range pairs {
node.Content[i*2] = p.key
node.Content[i*2+1] = p.value
}
// Recurse into values.
for _, p := range pairs {
sortYAMLKeys(p.value)
}

case yamlv3.SequenceNode:
for i := range node.Content {
sortYAMLKeys(node.Content[i])
}
}
}
3 changes: 3 additions & 0 deletions internal/cmd/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

type yamlCmdOptions struct {
restructure bool
sort bool
inplace bool

plainMode bool
Expand All @@ -55,6 +56,7 @@ Converts input document into YAML format while preserving the order of all keys.
OutputStyle: "yaml",
PlainMode: yamlCmdSettings.plainMode,
Restructure: yamlCmdSettings.restructure,
SortKeys: yamlCmdSettings.sort,
OmitIndentHelper: yamlCmdSettings.omitIndentHelper,
EnforceDocumentStartMarker: yamlCmdSettings.enforceDocumentStartMarker,
}
Expand Down Expand Up @@ -91,6 +93,7 @@ func init() {

yamlCmd.Flags().BoolVarP(&yamlCmdSettings.plainMode, "plain", "p", false, "output in plain style without any highlighting")
yamlCmd.Flags().BoolVarP(&yamlCmdSettings.restructure, "restructure", "r", false, "restructure map keys in reasonable order")
yamlCmd.Flags().BoolVarP(&yamlCmdSettings.sort, "sort", "s", false, "sort map keys alphabetically")
yamlCmd.Flags().BoolVarP(&yamlCmdSettings.inplace, "in-place", "i", false, "overwrite input file with output of this command")

yamlCmd.Flags().BoolVarP(&yamlCmdSettings.omitIndentHelper, "omit-indent-helper", "O", false, "omit indent helper lines in highlighted output")
Expand Down
18 changes: 18 additions & 0 deletions internal/cmd/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,24 @@ Bar: Foo
---
name: three
foobar: foobar
`))
})

It("should sort keys alphabetically with --sort flag", func() {
out, err := dyff("yaml", "--plain", "--sort", assets("issues", "issue-669", "input.yml"))
Expect(err).ToNot(HaveOccurred())
Expect(out).To(BeEquivalentTo(`a_key: value_a
list:
- a_item: 2
z_item: 1
- a_item: 3
z_item: 4
m_key: value_m
nested:
a_nested: 2
m_nested: 3
z_nested: 1
z_key: value_z
`))
})
})
Expand Down