This commit is contained in:
Mike Farah 2019-12-16 21:09:23 +11:00
parent 290579ac7f
commit 19fe718cfb
4 changed files with 85 additions and 40 deletions

17
examples/merge-anchor.yml Normal file
View File

@ -0,0 +1,17 @@
foo: &foo
a: original
thing: coolasdf
bar: &bar
b: 2
overrideA:
<<: [*foo,*bar]
a: vanilla
c: 3
foobar:
<<: *foo
thing: ice
c: 3

View File

@ -96,7 +96,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
// need to delete in reverse - otherwise the matching indexes // need to delete in reverse - otherwise the matching indexes
// become incorrect. // become incorrect.
matchingIndices := make([]int, 0) matchingIndices := make([]int, 0)
_, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error { _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(matchingNode []*yaml.Node, indexInMap int) error {
matchingIndices = append(matchingIndices, indexInMap) matchingIndices = append(matchingIndices, indexInMap)
n.log.Debug("matchingIndices %v", indexInMap) n.log.Debug("matchingIndices %v", indexInMap)
return nil return nil
@ -153,6 +153,9 @@ func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind {
n.log.Debug("guess was an alias, okey doke.") n.log.Debug("guess was an alias, okey doke.")
return guess return guess
} }
n.log.Debug("forcing a mapping node")
n.log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode)
n.log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode)
return yaml.MappingNode return yaml.MappingNode
} }
@ -221,9 +224,9 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn)
} }
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error { func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(indexInMap int) error { visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error {
value.Content[indexInMap+1] = n.getOrReplace(value.Content[indexInMap+1], n.GuessKind(tail, value.Content[indexInMap+1].Kind)) contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind))
return n.Visit(value.Content[indexInMap+1], tail, visitor) return n.Visit(contents[indexInMap+1], tail, visitor)
}) })
if errorVisiting != nil { if errorVisiting != nil {
@ -242,18 +245,36 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
return n.Visit(&mapEntryValue, tail, visitor) return n.Visit(&mapEntryValue, tail, visitor)
} }
type mapVisitorFn func(int) error // need to pass the node in, as it may be aliased
type mapVisitorFn func([]*yaml.Node, int) error
func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) { func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
visited := false visited := false
n.log.Debug("visitMatchingEntries %v in %v", key, contents)
// value.Content is a concatenated array of key, value, // value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd. // so keys are in the even indexes, values in odd.
for index := 0; index < len(contents); index = index + 2 { // merge aliases are defined first, but we only want to traverse them
// if we dont find a match on this node first.
for index := len(contents) - 2; index >= 0; index = index - 2 {
content := contents[index] content := contents[index]
n.log.Debug("index %v, checking %v", index, content.Value)) n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
// only visit aliases if we didn't find a match in this object.
if n.followAliases && !visited && contents[index+1].Kind == yaml.AliasNode {
valueNode := contents[index+1]
n.log.Debug("need to visit the alias too")
n.DebugNode(valueNode)
visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit)
if errorInAlias != nil {
return false, errorInAlias
}
visited = visited || visitedAlias
}
if n.matchesKey(key, content.Value) { if n.matchesKey(key, content.Value) {
errorVisiting := visit(index) n.log.Debug("found a match! %v", content.Value)
errorVisiting := visit(contents, index)
if errorVisiting != nil { if errorVisiting != nil {
return visited, errorVisiting return visited, errorVisiting
} }

View File

@ -1,2 +1,4 @@
a: apple foo: &foo
c: cat a: 1
foobar: *foo

63
yq.go
View File

@ -79,7 +79,7 @@ func newCommandCLI() *cobra.Command {
createPrefixCmd(), createPrefixCmd(),
createDeleteCmd(), createDeleteCmd(),
createNewCmd(), createNewCmd(),
// createMergeCmd(), createMergeCmd(),
) )
rootCmd.SetOutput(os.Stdout) rootCmd.SetOutput(os.Stdout)
@ -211,36 +211,36 @@ Note that you can give a create script to perform more sophisticated yaml. This
return cmdNew return cmdNew
} }
// func createMergeCmd() *cobra.Command { 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] [--append/-a] 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 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).
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
// If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
// If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
// Note that if you set both flags only overwrite will take effect. Note that if you set both flags only overwrite will take effect.
// `, `,
// RunE: mergeProperties, RunE: mergeProperties,
// } }
// 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(&appendFlag, "append", "a", false, "update the yaml file by appending array values") // cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
// cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") // cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
// 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)")
// return cmdMerge return cmdMerge
// } }
func readProperty(cmd *cobra.Command, args []string) error { func readProperty(cmd *cobra.Command, args []string) error {
var path = "" var path = ""
@ -397,6 +397,11 @@ func writeProperty(cmd *cobra.Command, args []string) error {
return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
} }
func mergeProperties(cmd *cobra.Command, args []string) error {
// first generate update commands from the file
return nil
}
func newProperty(cmd *cobra.Command, args []string) error { func newProperty(cmd *cobra.Command, args []string) error {
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide <path_to_update> <value>") var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide <path_to_update> <value>")
if updateCommandsError != nil { if updateCommandsError != nil {