mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-12 05:38:04 +00:00
refactored array merge flags into a strategy
This commit is contained in:
parent
2fc39b3865
commit
d66a709213
@ -26,8 +26,7 @@ var defaultValue = ""
|
|||||||
var indent = 2
|
var indent = 2
|
||||||
var overwriteFlag = false
|
var overwriteFlag = false
|
||||||
var autoCreateFlag = true
|
var autoCreateFlag = true
|
||||||
var appendFlag = false
|
var arrayMergeStrategyFlag = "update"
|
||||||
var overwriteArrays = false
|
|
||||||
var verbose = false
|
var verbose = false
|
||||||
var version = false
|
var version = false
|
||||||
var docIndex = "0"
|
var docIndex = "0"
|
||||||
|
29
cmd/merge.go
29
cmd/merge.go
@ -11,14 +11,14 @@ func createMergeCmd() *cobra.Command {
|
|||||||
var cmdMerge = &cobra.Command{
|
var cmdMerge = &cobra.Command{
|
||||||
Use: "merge [initial_yaml_file] [additional_yaml_file]...",
|
Use: "merge [initial_yaml_file] [additional_yaml_file]...",
|
||||||
Aliases: []string{"m"},
|
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: `
|
Example: `
|
||||||
yq merge things.yaml other.yaml
|
yq merge things.yaml other.yaml
|
||||||
yq merge --inplace things.yaml other.yaml
|
yq merge --inplace things.yaml other.yaml
|
||||||
yq m -i things.yaml other.yaml
|
yq m -i things.yaml other.yaml
|
||||||
yq m --overwrite things.yaml other.yaml
|
yq m --overwrite things.yaml other.yaml
|
||||||
yq m -i -x 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
|
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).
|
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(&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(&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(&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().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
|
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
|
* We don't deeply traverse arrays when appending a merge, instead we want to
|
||||||
* append the entire array element.
|
* 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 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 {
|
if len(args) < 1 {
|
||||||
return errors.New("Must provide at least 1 yaml file")
|
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 {
|
if len(args) > 1 {
|
||||||
// first generate update commands from the file
|
// first generate update commands from the file
|
||||||
var filesToMerge = args[1:]
|
var filesToMerge = args[1:]
|
||||||
|
|
||||||
for _, fileToMerge := range filesToMerge {
|
for _, fileToMerge := range filesToMerge {
|
||||||
matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(), false, 0)
|
matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0)
|
||||||
if errorProcessingFile != nil {
|
if errorProcessingFile != nil {
|
||||||
return errorProcessingFile
|
return errorProcessingFile
|
||||||
}
|
}
|
||||||
@ -70,14 +81,14 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
|
|||||||
yqlib.DebugNode(matchingNode.Node)
|
yqlib.DebugNode(matchingNode.Node)
|
||||||
}
|
}
|
||||||
for _, matchingNode := range matchingNodes {
|
for _, matchingNode := range matchingNodes {
|
||||||
mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag)
|
mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy)
|
||||||
updateCommands = append(updateCommands, yqlib.UpdateCommand{
|
updateCommands = append(updateCommands, yqlib.UpdateCommand{
|
||||||
Command: "merge",
|
Command: "merge",
|
||||||
Path: mergePath,
|
Path: mergePath,
|
||||||
Value: matchingNode.Node,
|
Value: matchingNode.Node,
|
||||||
Overwrite: overwriteFlag,
|
Overwrite: overwriteFlag,
|
||||||
// dont update the content for nodes midway, only leaf nodes
|
// 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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ func TestMergeOverwriteDeepExampleCmd(t *testing.T) {
|
|||||||
|
|
||||||
func TestMergeAppendCmd(t *testing.T) {
|
func TestMergeAppendCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
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 {
|
if result.Error != nil {
|
||||||
t.Error(result.Error)
|
t.Error(result.Error)
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ func TestMergeAppendArraysCmd(t *testing.T) {
|
|||||||
defer test.RemoveTempYamlFile(mergeFilename)
|
defer test.RemoveTempYamlFile(mergeFilename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
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 {
|
if result.Error != nil {
|
||||||
t.Error(result.Error)
|
t.Error(result.Error)
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ usage:
|
|||||||
|
|
||||||
func TestMergeOverwriteAndAppendCmd(t *testing.T) {
|
func TestMergeOverwriteAndAppendCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
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 {
|
if result.Error != nil {
|
||||||
t.Error(result.Error)
|
t.Error(result.Error)
|
||||||
}
|
}
|
||||||
@ -208,7 +208,7 @@ b: [6]`
|
|||||||
defer test.RemoveTempYamlFile(mergeFilename)
|
defer test.RemoveTempYamlFile(mergeFilename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
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 {
|
if result.Error != nil {
|
||||||
t.Error(result.Error)
|
t.Error(result.Error)
|
||||||
}
|
}
|
||||||
@ -223,7 +223,7 @@ c:
|
|||||||
|
|
||||||
func TestMergeRootArraysCmd(t *testing.T) {
|
func TestMergeRootArraysCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
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 {
|
if result.Error != nil {
|
||||||
t.Error(result.Error)
|
t.Error(result.Error)
|
||||||
}
|
}
|
||||||
@ -238,7 +238,7 @@ func TestMergeRootArraysCmd(t *testing.T) {
|
|||||||
|
|
||||||
func TestMergeOverwriteArraysCmd(t *testing.T) {
|
func TestMergeOverwriteArraysCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
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 {
|
if result.Error != nil {
|
||||||
t.Error(result.Error)
|
t.Error(result.Error)
|
||||||
}
|
}
|
||||||
|
@ -58,15 +58,15 @@ func DebugNode(value *yaml.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func pathStackToString(pathStack []interface{}) string {
|
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
|
var sb strings.Builder
|
||||||
for index, path := range pathStack {
|
for index, path := range pathStack {
|
||||||
switch path.(type) {
|
switch path.(type) {
|
||||||
case int, int64:
|
case int, int64:
|
||||||
if appendArrays {
|
if arrayMergeStrategy == AppendArrayMergeStrategy {
|
||||||
sb.WriteString("[+]")
|
sb.WriteString("[+]")
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString(fmt.Sprintf("[%v]", path))
|
sb.WriteString(fmt.Sprintf("[%v]", path))
|
||||||
@ -136,12 +136,12 @@ func guessKind(head interface{}, tail []interface{}, guess yaml.Kind) yaml.Kind
|
|||||||
|
|
||||||
type YqLib interface {
|
type YqLib interface {
|
||||||
Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
|
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
|
Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
|
||||||
New(path string) yaml.Node
|
New(path string) yaml.Node
|
||||||
|
|
||||||
PathStackToString(pathStack []interface{}) string
|
PathStackToString(pathStack []interface{}) string
|
||||||
MergePathStackToString(pathStack []interface{}, appendArrays bool) string
|
MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type lib struct {
|
type lib struct {
|
||||||
@ -162,9 +162,9 @@ func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
|
|||||||
return navigationStrategy.GetVisitedNodes(), 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)
|
var paths = l.parser.ParsePath(path)
|
||||||
navigationStrategy := ReadForMergeNavigationStrategy(deeplyTraverseArrays, overwriteArray)
|
navigationStrategy := ReadForMergeNavigationStrategy(arrayMergeStrategy)
|
||||||
navigator := NewDataNavigator(navigationStrategy)
|
navigator := NewDataNavigator(navigationStrategy)
|
||||||
error := navigator.Traverse(rootNode, paths)
|
error := navigator.Traverse(rootNode, paths)
|
||||||
return navigationStrategy.GetVisitedNodes(), error
|
return navigationStrategy.GetVisitedNodes(), error
|
||||||
@ -174,8 +174,8 @@ func (l *lib) PathStackToString(pathStack []interface{}) string {
|
|||||||
return pathStackToString(pathStack)
|
return pathStackToString(pathStack)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lib) MergePathStackToString(pathStack []interface{}, appendArrays bool) string {
|
func (l *lib) MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string {
|
||||||
return mergePathStackToString(pathStack, appendArrays)
|
return mergePathStackToString(pathStack, arrayMergeStrategy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lib) New(path string) yaml.Node {
|
func (l *lib) New(path string) yaml.Node {
|
||||||
|
@ -30,7 +30,7 @@ func TestLib(t *testing.T) {
|
|||||||
array[0] = "a"
|
array[0] = "a"
|
||||||
array[1] = 0
|
array[1] = 0
|
||||||
array[2] = "b"
|
array[2] = "b"
|
||||||
got := subject.MergePathStackToString(array, true)
|
got := subject.MergePathStackToString(array, AppendArrayMergeStrategy)
|
||||||
test.AssertResult(t, `a.[+].b`, got)
|
test.AssertResult(t, `a.[+].b`, got)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,6 +2,14 @@ package yqlib
|
|||||||
|
|
||||||
import "gopkg.in/yaml.v3"
|
import "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
type ArrayMergeStrategy uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
UpdateArrayMergeStrategy ArrayMergeStrategy = 1 << iota
|
||||||
|
OverwriteArrayMergeStrategy
|
||||||
|
AppendArrayMergeStrategy
|
||||||
|
)
|
||||||
|
|
||||||
func MergeNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
|
func MergeNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
|
||||||
return &NavigationStrategyImpl{
|
return &NavigationStrategyImpl{
|
||||||
visitedNodes: []*NodeContext{},
|
visitedNodes: []*NodeContext{},
|
||||||
|
@ -2,7 +2,7 @@ package yqlib
|
|||||||
|
|
||||||
import "gopkg.in/yaml.v3"
|
import "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool, overwriteArray bool) NavigationStrategy {
|
func ReadForMergeNavigationStrategy(arrayMergeStrategy ArrayMergeStrategy) NavigationStrategy {
|
||||||
return &NavigationStrategyImpl{
|
return &NavigationStrategyImpl{
|
||||||
visitedNodes: []*NodeContext{},
|
visitedNodes: []*NodeContext{},
|
||||||
pathParser: NewPathParser(),
|
pathParser: NewPathParser(),
|
||||||
@ -16,7 +16,7 @@ func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool, overwriteArray bo
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
|
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
|
||||||
if nodeContext.Node.Kind == yaml.SequenceNode && overwriteArray {
|
if nodeContext.Node.Kind == yaml.SequenceNode && arrayMergeStrategy == OverwriteArrayMergeStrategy {
|
||||||
nodeContext.IsMiddleNode = false
|
nodeContext.IsMiddleNode = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ func ReadForMergeNavigationStrategy(deeplyTraverseArrays bool, overwriteArray bo
|
|||||||
isInArray = false
|
isInArray = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return deeplyTraverseArrays || !isInArray
|
return arrayMergeStrategy == UpdateArrayMergeStrategy || !isInArray
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user