diff --git a/cmd/delete_test.go b/cmd/delete_test.go index d5f3291..ae5c617 100644 --- a/cmd/delete_test.go +++ b/cmd/delete_test.go @@ -100,6 +100,29 @@ b: test.AssertResult(t, expectedOutput, result.Output) } +func TestDeleteDeepSplatArrayYaml(t *testing.T) { + content := `thing: 123 +b: + hi: + - thing: item1 + name: fred +` + filename := test.WriteTempYamlFile(content) + defer test.RemoveTempYamlFile(filename) + + cmd := getRootCommand() + result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename)) + if result.Error != nil { + t.Error(result.Error) + } + + expectedOutput := `b: + hi: + - name: fred +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestDeleteSplatPrefixYaml(t *testing.T) { content := `a: 2 b: diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index e194e20..9239bcb 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -24,6 +24,7 @@ func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator { func (n *navigator) Traverse(value *yaml.Node, path []interface{}) error { realValue := value emptyArray := make([]interface{}, 0) + log.Debugf("Traversing path %v", pathStackToString(path)) if realValue.Kind == yaml.DocumentNode { log.Debugf("its a document! returning the first child") return n.doTraverse(value.Content[0], "", path, emptyArray) @@ -68,6 +69,16 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error { log.Debug("recursing, processing %v, pathStack %v", head, pathStackToString(pathStack)) + + nodeContext := NewNodeContext(value, head, tail, pathStack) + + if head == "**" && !n.navigationStrategy.ShouldOnlyDeeplyVisitLeaves(nodeContext) { + errorVisitingDeeply := n.navigationStrategy.Visit(nodeContext) + if errorVisitingDeeply != nil { + return errorVisitingDeeply + } + } + switch value.Kind { case yaml.MappingNode: log.Debug("its a map with %v entries", len(value.Content)/2) @@ -85,20 +96,20 @@ func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface if head == "+" { return n.appendArray(value, head, tail, pathStack) } else if len(value.Content) == 0 && head == "**" { - return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack)) + return n.navigationStrategy.Visit(nodeContext) } return n.splatArray(value, head, tail, pathStack) } case yaml.AliasNode: log.Debug("its an alias!") DebugNode(value.Alias) - if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) { + if n.navigationStrategy.FollowAlias(nodeContext) { log.Debug("following the alias") return n.recurse(value.Alias, head, tail, pathStack) } return nil default: - return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack)) + return n.navigationStrategy.Visit(nodeContext) } } diff --git a/pkg/yqlib/delete_navigation_strategy.go b/pkg/yqlib/delete_navigation_strategy.go index 4c64cf0..3f397b9 100644 --- a/pkg/yqlib/delete_navigation_strategy.go +++ b/pkg/yqlib/delete_navigation_strategy.go @@ -18,9 +18,12 @@ func DeleteNavigationStrategy(pathElementToDelete interface{}) NavigationStrateg shouldDeeplyTraverse: func(nodeContext NodeContext) bool { return true }, + shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool { + return false + }, visit: func(nodeContext NodeContext) error { node := nodeContext.Node - log.Debug("need to find and delete %v in here", pathElementToDelete) + log.Debug("need to find and delete %v in here (%v)", pathElementToDelete, pathStackToString(nodeContext.PathStack)) DebugNode(node) if node.Kind == yaml.SequenceNode { newContent := deleteFromArray(parser, node.Content, nodeContext.PathStack, pathElementToDelete) diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index 8f5efc5..81a1ce7 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -35,19 +35,22 @@ type NavigationStrategy interface { // we use it to match against the pathExpression in head. ShouldTraverse(nodeContext NodeContext, nodeKey string) bool ShouldDeeplyTraverse(nodeContext NodeContext) bool + // when deeply traversing, should we visit all matching nodes, or just leaves? + ShouldOnlyDeeplyVisitLeaves(NodeContext) bool GetVisitedNodes() []*NodeContext DebugVisitedNodes() GetPathParser() PathParser } type NavigationStrategyImpl struct { - followAlias func(nodeContext NodeContext) bool - autoCreateMap func(nodeContext NodeContext) bool - visit func(nodeContext NodeContext) error - shouldVisitExtraFn func(nodeContext NodeContext) bool - shouldDeeplyTraverse func(nodeContext NodeContext) bool - visitedNodes []*NodeContext - pathParser PathParser + followAlias func(nodeContext NodeContext) bool + autoCreateMap func(nodeContext NodeContext) bool + visit func(nodeContext NodeContext) error + shouldVisitExtraFn func(nodeContext NodeContext) bool + shouldDeeplyTraverse func(nodeContext NodeContext) bool + shouldOnlyDeeplyVisitLeaves func(nodeContext NodeContext) bool + visitedNodes []*NodeContext + pathParser PathParser } func (ns *NavigationStrategyImpl) GetPathParser() PathParser { @@ -70,6 +73,10 @@ func (ns *NavigationStrategyImpl) ShouldDeeplyTraverse(nodeContext NodeContext) return ns.shouldDeeplyTraverse(nodeContext) } +func (ns *NavigationStrategyImpl) ShouldOnlyDeeplyVisitLeaves(nodeContext NodeContext) bool { + return ns.shouldOnlyDeeplyVisitLeaves(nodeContext) +} + func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool { // we should traverse aliases (if enabled), but not visit them :/ if len(nodeContext.PathStack) == 0 { diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go index 0e02d52..98b2df3 100644 --- a/pkg/yqlib/read_navigation_strategy.go +++ b/pkg/yqlib/read_navigation_strategy.go @@ -26,5 +26,8 @@ func ReadNavigationStrategy(deeplyTraverseArrays bool) NavigationStrategy { } return deeplyTraverseArrays || !isInArray }, + shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool { + return true + }, } } diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go index 557ead5..e90c7bd 100644 --- a/pkg/yqlib/update_navigation_strategy.go +++ b/pkg/yqlib/update_navigation_strategy.go @@ -13,6 +13,9 @@ func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navi shouldDeeplyTraverse: func(nodeContext NodeContext) bool { return true }, + shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool { + return true + }, visit: func(nodeContext NodeContext) error { node := nodeContext.Node changesToApply := updateCommand.Value