refactored array merge flags into a strategy

This commit is contained in:
Mike Farah 2020-07-17 13:26:20 +10:00
parent 2fc39b3865
commit d66a709213
7 changed files with 48 additions and 30 deletions

View File

@ -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"

View File

@ -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),
})
}
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)
})

View File

@ -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{},

View File

@ -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
},
}
}