diff --git a/cmd/constant.go b/cmd/constant.go index ee177717..a1f63119 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -27,6 +27,7 @@ var indent = 2 var overwriteFlag = false var autoCreateFlag = true var arrayMergeStrategyFlag = "update" +var commentsMergeStrategyFlag = "setWhenBlank" var verbose = false var version = false var docIndex = "0" diff --git a/cmd/merge.go b/cmd/merge.go index 770ae49d..64e82405 100644 --- a/cmd/merge.go +++ b/cmd/merge.go @@ -32,7 +32,15 @@ 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().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", "array merge strategy (update/append/overwrite)") + cmdMerge.PersistentFlags().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite) +update: recursively update arrays by their index +append: concatenate arrays together +overwrite: replace arrays`) + cmdMerge.PersistentFlags().StringVarP(&commentsMergeStrategyFlag, "comments", "", "setWhenBlank", `comments merge strategy (setWhenBlank/ignore/append/overwrite) +setWhenBlank: set comment if the original document has no comment at that node +ignore: leave comments as-is in the original +append: append comments together +overwrite: overwrite comments completely`) cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") return cmdMerge } @@ -66,6 +74,21 @@ func mergeProperties(cmd *cobra.Command, args []string) error { return errors.New("Array merge strategy must be one of: update/append/overwrite") } + var commentsMergeStrategy yqlib.CommentsMergeStrategy + + switch commentsMergeStrategyFlag { + case "setWhenBlank": + commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy + case "ignore": + commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy + case "append": + commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy + case "overwrite": + commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy + default: + return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite") + } + if len(args) > 1 { // first generate update commands from the file var filesToMerge = args[1:] @@ -83,10 +106,11 @@ func mergeProperties(cmd *cobra.Command, args []string) error { for _, matchingNode := range matchingNodes { mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy) updateCommands = append(updateCommands, yqlib.UpdateCommand{ - Command: "merge", - Path: mergePath, - Value: matchingNode.Node, - Overwrite: overwriteFlag, + Command: "merge", + Path: mergePath, + Value: matchingNode.Node, + Overwrite: overwriteFlag, + CommentsMergeStrategy: commentsMergeStrategy, // dont update the content for nodes midway, only leaf nodes DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode), }) diff --git a/cmd/merge_test.go b/cmd/merge_test.go index 3ef0e661..6a292190 100644 --- a/cmd/merge_test.go +++ b/cmd/merge_test.go @@ -60,7 +60,7 @@ func TestMergeOverwriteCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := `a: other # better than the original + expectedOutput := `a: other # just the best b: [3, 4] c: test: 1 @@ -185,7 +185,7 @@ func TestMergeOverwriteAndAppendCmd(t *testing.T) { if result.Error != nil { t.Error(result.Error) } - expectedOutput := `a: other # better than the original + expectedOutput := `a: other # just the best b: [1, 2, 3, 4] c: test: 1 @@ -193,6 +193,97 @@ c: test.AssertResult(t, expectedOutput, result.Output) } +var commentContentA = ` +a: valueA1 # commentA1 +b: valueB1 +` + +var commentContentB = ` +a: valueA2 # commentA2 +b: valueB2 # commentB2 +c: valueC2 # commentC2 +` + +func TestMergeCommentsSetWhenBlankCmd(t *testing.T) { + filename := test.WriteTempYamlFile(commentContentA) + defer test.RemoveTempYamlFile(filename) + + mergeFilename := test.WriteTempYamlFile(commentContentB) + defer test.RemoveTempYamlFile(mergeFilename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=setWhenBlank %s %s", filename, mergeFilename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `a: valueA1 # commentA1 +b: valueB1 # commentB2 +c: valueC2 # commentC2 +` + test.AssertResult(t, expectedOutput, result.Output) +} + +func TestMergeCommentsIgnoreCmd(t *testing.T) { + filename := test.WriteTempYamlFile(commentContentA) + defer test.RemoveTempYamlFile(filename) + + mergeFilename := test.WriteTempYamlFile(commentContentB) + defer test.RemoveTempYamlFile(mergeFilename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=ignore %s %s", filename, mergeFilename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `a: valueA1 # commentA1 +b: valueB1 +c: valueC2 +` + test.AssertResult(t, expectedOutput, result.Output) +} + +func TestMergeCommentsAppendCmd(t *testing.T) { + filename := test.WriteTempYamlFile(commentContentA) + defer test.RemoveTempYamlFile(filename) + + mergeFilename := test.WriteTempYamlFile(commentContentB) + defer test.RemoveTempYamlFile(mergeFilename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=append %s %s", filename, mergeFilename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `a: valueA1 # commentA1 # commentA2 +b: valueB1 # commentB2 +c: valueC2 # commentC2 +` + test.AssertResult(t, expectedOutput, result.Output) +} + +func TestMergeCommentsOverwriteCmd(t *testing.T) { + filename := test.WriteTempYamlFile(commentContentA) + defer test.RemoveTempYamlFile(filename) + + mergeFilename := test.WriteTempYamlFile(commentContentB) + defer test.RemoveTempYamlFile(mergeFilename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=overwrite %s %s", filename, mergeFilename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `a: valueA1 # commentA2 +b: valueB1 # commentB2 +c: valueC2 # commentC2 +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestMergeOverwriteArraysTooCmd(t *testing.T) { content := `a: simple # just the best b: [1, 2] @@ -213,7 +304,7 @@ b: [6]` t.Error(result.Error) } - expectedOutput := `a: things + expectedOutput := `a: things # just the best b: [6] c: test: 1 diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 04ba8577..3f548f6b 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -19,6 +19,7 @@ type UpdateCommand struct { Overwrite bool DontUpdateNodeValue bool DontUpdateNodeContent bool + CommentsMergeStrategy CommentsMergeStrategy } func KindString(kind yaml.Kind) string { diff --git a/pkg/yqlib/merge_navigation_strategy.go b/pkg/yqlib/merge_navigation_strategy.go index bf87f9f0..686ba4eb 100644 --- a/pkg/yqlib/merge_navigation_strategy.go +++ b/pkg/yqlib/merge_navigation_strategy.go @@ -10,6 +10,15 @@ const ( AppendArrayMergeStrategy ) +type CommentsMergeStrategy uint32 + +const ( + SetWhenBlankCommentsMergeStrategy CommentsMergeStrategy = 1 << iota + IgnoreCommentsMergeStrategy + OverwriteCommentsMergeStrategy + AppendCommentsMergeStrategy +) + func MergeNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy { return &NavigationStrategyImpl{ visitedNodes: []*NodeContext{}, @@ -31,40 +40,63 @@ func MergeNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navig node = node.Content[0] } + log.Debug("going to update") + DebugNode(node) + log.Debug("with") + DebugNode(changesToApply) + if updateCommand.Overwrite || node.Value == "" { - log.Debug("going to update") - DebugNode(node) - log.Debug("with") - DebugNode(changesToApply) node.Value = changesToApply.Value node.Tag = changesToApply.Tag node.Kind = changesToApply.Kind node.Style = changesToApply.Style node.Anchor = changesToApply.Anchor node.Alias = changesToApply.Alias - node.HeadComment = changesToApply.HeadComment - node.LineComment = changesToApply.LineComment - node.FootComment = changesToApply.FootComment if !updateCommand.DontUpdateNodeContent { node.Content = changesToApply.Content } - - // // TODO: mergeComments flag - // if node.HeadComment != "" && changesToApply.HeadComment != "" { - // node.HeadComment = node.HeadComment + "\n" + changesToApply.HeadComment - // log.Debug("merged comments with a space, %v", node.HeadComment) - // } else { - // node.HeadComment = node.HeadComment + changesToApply.HeadComment - // if node.HeadComment != "" { - // log.Debug("merged comments with no space, %v", node.HeadComment) - // } - // } - // node.LineComment = node.LineComment + changesToApply.LineComment - // node.FootComment = node.FootComment + changesToApply.FootComment } else { log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite) } + + switch updateCommand.CommentsMergeStrategy { + case OverwriteCommentsMergeStrategy: + node.HeadComment = changesToApply.HeadComment + node.LineComment = changesToApply.LineComment + node.FootComment = changesToApply.FootComment + case SetWhenBlankCommentsMergeStrategy: + if node.HeadComment == "" { + node.HeadComment = changesToApply.HeadComment + } + if node.LineComment == "" { + node.LineComment = changesToApply.LineComment + } + if node.FootComment == "" { + node.FootComment = changesToApply.FootComment + } + case AppendCommentsMergeStrategy: + if node.HeadComment == "" { + node.HeadComment = changesToApply.HeadComment + } else { + node.HeadComment = node.HeadComment + "\n" + changesToApply.HeadComment + } + if node.LineComment == "" { + node.LineComment = changesToApply.LineComment + } else { + node.LineComment = node.LineComment + " " + changesToApply.LineComment + } + if node.FootComment == "" { + node.FootComment = changesToApply.FootComment + } else { + node.FootComment = node.FootComment + "\n" + changesToApply.FootComment + } + default: + } + + log.Debug("result") + DebugNode(node) + return nil }, }