diff --git a/cmd/constant.go b/cmd/constant.go index 1b83a265..98091704 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -27,6 +27,7 @@ var indent = 2 var overwriteFlag = false var autoCreateFlag = true var appendFlag = false +var overwriteArrays = false var verbose = false var version = false var docIndex = "0" diff --git a/cmd/merge.go b/cmd/merge.go index b929c05e..baf5abc9 100644 --- a/cmd/merge.go +++ b/cmd/merge.go @@ -34,6 +34,7 @@ If append flag is set then existing arrays will be merged with the arrays from e cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries") cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + cmdMerge.PersistentFlags().BoolVarP(&overwriteArrays, "overwriteArrays", "", false, "overwrite arrays rather than update recursively") return cmdMerge } @@ -43,7 +44,7 @@ If append flag is set then existing arrays will be merged with the arrays from e */ func createReadFunctionForMerge() func(*yaml.Node) ([]*yqlib.NodeContext, error) { return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) { - return lib.GetForMerge(dataBucket, "**", !appendFlag) + return lib.GetForMerge(dataBucket, "**", !appendFlag, overwriteArrays) } } @@ -76,7 +77,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error { Value: matchingNode.Node, Overwrite: overwriteFlag, // dont update the content for nodes midway, only leaf nodes - DontUpdateNodeContent: matchingNode.IsMiddleNode, + DontUpdateNodeContent: matchingNode.IsMiddleNode && (!overwriteArrays || matchingNode.Node.Kind != yaml.SequenceNode), }) } } diff --git a/cmd/merge_test.go b/cmd/merge_test.go index f9718b0d..94d2129c 100644 --- a/cmd/merge_test.go +++ b/cmd/merge_test.go @@ -68,6 +68,33 @@ c: test.AssertResult(t, expectedOutput, result.Output) } +func TestMergeOverwriteDeepExampleCmd(t *testing.T) { + content := `c: + test: 1 + thing: whatever +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) + + mergeContent := `c: + test: 5 +` + mergeFilename := test.WriteTempYamlFile(mergeContent) + defer test.RemoveTempYamlFile(mergeFilename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --overwrite %s %s", filename, mergeFilename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `c: + test: 5 + thing: whatever +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestMergeAppendCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --autocreate=false --append ../examples/data1.yaml ../examples/data2.yaml") @@ -166,7 +193,35 @@ c: test.AssertResult(t, expectedOutput, result.Output) } -func TestMergeArraysCmd(t *testing.T) { +func TestMergeOverwriteArraysTooCmd(t *testing.T) { + content := `a: simple # just the best +b: [1, 2] +c: + test: 1 +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) + + mergeContent := `a: things +b: [6]` + mergeFilename := test.WriteTempYamlFile(mergeContent) + defer test.RemoveTempYamlFile(mergeFilename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --overwriteArrays --overwrite %s %s", filename, mergeFilename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `a: things +b: [6] +c: + test: 1 +` + test.AssertResult(t, expectedOutput, result.Output) +} + +func TestMergeRootArraysCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge --append ../examples/sample_array.yaml ../examples/sample_array_2.yaml") if result.Error != nil { @@ -181,6 +236,18 @@ func TestMergeArraysCmd(t *testing.T) { test.AssertResult(t, expectedOutput, result.Output) } +func TestMergeOverwriteArraysCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "merge --overwriteArrays ../examples/sample_array.yaml ../examples/sample_array_2.yaml") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `- 4 +- 5 +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestMergeCmd_Multi(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "merge -d1 ../examples/multiple_docs_small.yaml ../examples/data1.yaml") diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index a50e1f5a..589e8043 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -136,7 +136,7 @@ func guessKind(head interface{}, tail []interface{}, guess yaml.Kind) yaml.Kind type YqLib interface { Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) - GetForMerge(rootNode *yaml.Node, path string, deeplyTraverseArrays bool) ([]*NodeContext, error) + GetForMerge(rootNode *yaml.Node, path string, deeplyTraverseArrays bool, overwriteArray bool) ([]*NodeContext, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error New(path string) yaml.Node @@ -162,9 +162,9 @@ func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) { return navigationStrategy.GetVisitedNodes(), error } -func (l *lib) GetForMerge(rootNode *yaml.Node, path string, deeplyTraverseArrays bool) ([]*NodeContext, error) { +func (l *lib) GetForMerge(rootNode *yaml.Node, path string, deeplyTraverseArrays bool, overwriteArray bool) ([]*NodeContext, error) { var paths = l.parser.ParsePath(path) - navigationStrategy := ReadForMergeNavigationStrategy(deeplyTraverseArrays) + navigationStrategy := ReadForMergeNavigationStrategy(deeplyTraverseArrays, overwriteArray) navigator := NewDataNavigator(navigationStrategy) error := navigator.Traverse(rootNode, paths) return navigationStrategy.GetVisitedNodes(), error diff --git a/pkg/yqlib/read_for_merge_navigation_strategy.go b/pkg/yqlib/read_for_merge_navigation_strategy.go index 9ff46f5a..6364884f 100644 --- a/pkg/yqlib/read_for_merge_navigation_strategy.go +++ b/pkg/yqlib/read_for_merge_navigation_strategy.go @@ -1,6 +1,8 @@ package yqlib -func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool) NavigationStrategy { +import "gopkg.in/yaml.v3" + +func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool, overwriteArray bool) NavigationStrategy { return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, pathParser: NewPathParser(), @@ -14,6 +16,11 @@ func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool) NavigationStrateg return nil }, shouldDeeplyTraverse: func(nodeContext NodeContext) bool { + if nodeContext.Node.Kind == yaml.SequenceNode && overwriteArray { + nodeContext.IsMiddleNode = false + return false + } + var isInArray = false if len(nodeContext.PathStack) > 0 { var lastElement = nodeContext.PathStack[len(nodeContext.PathStack)-1]