diff --git a/cmd/constant.go b/cmd/constant.go index 98091704..ee177717 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -26,8 +26,7 @@ var defaultValue = "" var indent = 2 var overwriteFlag = false var autoCreateFlag = true -var appendFlag = false -var overwriteArrays = false +var arrayMergeStrategyFlag = "update" var verbose = false var version = false var docIndex = "0" diff --git a/cmd/merge.go b/cmd/merge.go index baf5abc9..3572d85b 100644 --- a/cmd/merge.go +++ b/cmd/merge.go @@ -11,14 +11,14 @@ func createMergeCmd() *cobra.Command { var cmdMerge = &cobra.Command{ Use: "merge [initial_yaml_file] [additional_yaml_file]...", Aliases: []string{"m"}, - Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml", + Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml", Example: ` yq merge things.yaml other.yaml yq merge --inplace things.yaml other.yaml yq m -i things.yaml other.yaml yq m --overwrite things.yaml other.yaml yq m -i -x things.yaml other.yaml -yq m -i -a things.yaml other.yaml +yq m -i -a=append things.yaml other.yaml yq m -i --autocreate=false things.yaml other.yaml `, Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). @@ -32,9 +32,8 @@ If append flag is set then existing arrays will be merged with the arrays from e cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") 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(&arrayMergeStrategyFlag, "array", "a", "update", "array merge strategy (update/append/overwrite)") 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 } @@ -42,9 +41,9 @@ If append flag is set then existing arrays will be merged with the arrays from e * We don't deeply traverse arrays when appending a merge, instead we want to * append the entire array element. */ -func createReadFunctionForMerge() func(*yaml.Node) ([]*yqlib.NodeContext, error) { +func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) { return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) { - return lib.GetForMerge(dataBucket, "**", !appendFlag, overwriteArrays) + return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy) } } @@ -54,13 +53,25 @@ func mergeProperties(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("Must provide at least 1 yaml file") } + var arrayMergeStrategy yqlib.ArrayMergeStrategy + + switch arrayMergeStrategyFlag { + case "update": + arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy + case "append": + arrayMergeStrategy = yqlib.AppendArrayMergeStrategy + case "overwrite": + arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy + default: + return errors.New("Array merge strategy must be one of: update/append/overwrite") + } if len(args) > 1 { // first generate update commands from the file var filesToMerge = args[1:] for _, fileToMerge := range filesToMerge { - matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(), false, 0) + matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0) if errorProcessingFile != nil { return errorProcessingFile } @@ -70,14 +81,14 @@ func mergeProperties(cmd *cobra.Command, args []string) error { yqlib.DebugNode(matchingNode.Node) } for _, matchingNode := range matchingNodes { - mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag) + mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy) updateCommands = append(updateCommands, yqlib.UpdateCommand{ Command: "merge", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag, // dont update the content for nodes midway, only leaf nodes - DontUpdateNodeContent: matchingNode.IsMiddleNode && (!overwriteArrays || matchingNode.Node.Kind != yaml.SequenceNode), + DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode), }) } } diff --git a/cmd/merge_test.go b/cmd/merge_test.go index 94d2129c..1729928e 100644 --- a/cmd/merge_test.go +++ b/cmd/merge_test.go @@ -97,7 +97,7 @@ func TestMergeOverwriteDeepExampleCmd(t *testing.T) { func TestMergeAppendCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --autocreate=false --append ../examples/data1.yaml ../examples/data2.yaml") + result := test.RunCmd(cmd, "merge --autocreate=false --array=append ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -123,7 +123,7 @@ func TestMergeAppendArraysCmd(t *testing.T) { defer test.RemoveTempYamlFile(mergeFilename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --append -d* %s %s", filename, mergeFilename)) + result := test.RunCmd(cmd, fmt.Sprintf("merge --array=append -d* %s %s", filename, mergeFilename)) if result.Error != nil { t.Error(result.Error) } @@ -181,7 +181,7 @@ usage: func TestMergeOverwriteAndAppendCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite ../examples/data1.yaml ../examples/data2.yaml") + result := test.RunCmd(cmd, "merge --autocreate=false --array=append --overwrite ../examples/data1.yaml ../examples/data2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -208,7 +208,7 @@ b: [6]` defer test.RemoveTempYamlFile(mergeFilename) cmd := getRootCommand() - result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --overwriteArrays --overwrite %s %s", filename, mergeFilename)) + result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --array=overwrite --overwrite %s %s", filename, mergeFilename)) if result.Error != nil { t.Error(result.Error) } @@ -223,7 +223,7 @@ c: func TestMergeRootArraysCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --append ../examples/sample_array.yaml ../examples/sample_array_2.yaml") + result := test.RunCmd(cmd, "merge --array=append ../examples/sample_array.yaml ../examples/sample_array_2.yaml") if result.Error != nil { t.Error(result.Error) } @@ -238,7 +238,7 @@ func TestMergeRootArraysCmd(t *testing.T) { func TestMergeOverwriteArraysCmd(t *testing.T) { cmd := getRootCommand() - result := test.RunCmd(cmd, "merge --overwriteArrays ../examples/sample_array.yaml ../examples/sample_array_2.yaml") + result := test.RunCmd(cmd, "merge --array=overwrite ../examples/sample_array.yaml ../examples/sample_array_2.yaml") if result.Error != nil { t.Error(result.Error) } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 589e8043..04ba8577 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -58,15 +58,15 @@ func DebugNode(value *yaml.Node) { } func pathStackToString(pathStack []interface{}) string { - return mergePathStackToString(pathStack, false) + return mergePathStackToString(pathStack, UpdateArrayMergeStrategy) } -func mergePathStackToString(pathStack []interface{}, appendArrays bool) string { +func mergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string { var sb strings.Builder for index, path := range pathStack { switch path.(type) { case int, int64: - if appendArrays { + if arrayMergeStrategy == AppendArrayMergeStrategy { sb.WriteString("[+]") } else { sb.WriteString(fmt.Sprintf("[%v]", path)) @@ -136,12 +136,12 @@ 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, overwriteArray bool) ([]*NodeContext, error) + GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error New(path string) yaml.Node PathStackToString(pathStack []interface{}) string - MergePathStackToString(pathStack []interface{}, appendArrays bool) string + MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string } type lib struct { @@ -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, overwriteArray bool) ([]*NodeContext, error) { +func (l *lib) GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) { var paths = l.parser.ParsePath(path) - navigationStrategy := ReadForMergeNavigationStrategy(deeplyTraverseArrays, overwriteArray) + navigationStrategy := ReadForMergeNavigationStrategy(arrayMergeStrategy) navigator := NewDataNavigator(navigationStrategy) error := navigator.Traverse(rootNode, paths) return navigationStrategy.GetVisitedNodes(), error @@ -174,8 +174,8 @@ func (l *lib) PathStackToString(pathStack []interface{}) string { return pathStackToString(pathStack) } -func (l *lib) MergePathStackToString(pathStack []interface{}, appendArrays bool) string { - return mergePathStackToString(pathStack, appendArrays) +func (l *lib) MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string { + return mergePathStackToString(pathStack, arrayMergeStrategy) } func (l *lib) New(path string) yaml.Node { diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go index 4adf7df5..52e7933c 100644 --- a/pkg/yqlib/lib_test.go +++ b/pkg/yqlib/lib_test.go @@ -30,7 +30,7 @@ func TestLib(t *testing.T) { array[0] = "a" array[1] = 0 array[2] = "b" - got := subject.MergePathStackToString(array, true) + got := subject.MergePathStackToString(array, AppendArrayMergeStrategy) test.AssertResult(t, `a.[+].b`, got) }) diff --git a/pkg/yqlib/merge_navigation_strategy.go b/pkg/yqlib/merge_navigation_strategy.go index 9d2934fd..bf87f9f0 100644 --- a/pkg/yqlib/merge_navigation_strategy.go +++ b/pkg/yqlib/merge_navigation_strategy.go @@ -2,6 +2,14 @@ package yqlib import "gopkg.in/yaml.v3" +type ArrayMergeStrategy uint32 + +const ( + UpdateArrayMergeStrategy ArrayMergeStrategy = 1 << iota + OverwriteArrayMergeStrategy + AppendArrayMergeStrategy +) + func MergeNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy { return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, diff --git a/pkg/yqlib/read_for_merge_navigation_strategy.go b/pkg/yqlib/read_for_merge_navigation_strategy.go index 6364884f..73061338 100644 --- a/pkg/yqlib/read_for_merge_navigation_strategy.go +++ b/pkg/yqlib/read_for_merge_navigation_strategy.go @@ -2,7 +2,7 @@ package yqlib import "gopkg.in/yaml.v3" -func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool, overwriteArray bool) NavigationStrategy { +func ReadForMergeNavigationStrategy(arrayMergeStrategy ArrayMergeStrategy) NavigationStrategy { return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, pathParser: NewPathParser(), @@ -16,7 +16,7 @@ func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool, overwriteArray bo return nil }, shouldDeeplyTraverse: func(nodeContext NodeContext) bool { - if nodeContext.Node.Kind == yaml.SequenceNode && overwriteArray { + if nodeContext.Node.Kind == yaml.SequenceNode && arrayMergeStrategy == OverwriteArrayMergeStrategy { nodeContext.IsMiddleNode = false return false } @@ -31,7 +31,7 @@ func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool, overwriteArray bo isInArray = false } } - return deeplyTraverseArrays || !isInArray + return arrayMergeStrategy == UpdateArrayMergeStrategy || !isInArray }, } }