diff --git a/commands_test.go b/commands_test.go index a7c9f84f..5d0925da 100644 --- a/commands_test.go +++ b/commands_test.go @@ -112,6 +112,24 @@ func TestReadArrayCmd(t *testing.T) { test.AssertResult(t, "b.e.1.name: sam\n", result.Output) } +func TestReadDeepSplatCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.**") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `b.c: 2 +b.d.[0]: 3 +b.d.[1]: 4 +b.d.[2]: 5 +b.e.[0].name: fred +b.e.[0].value: 3 +b.e.[1].name: sam +b.e.[1].value: 4 +` + test.AssertResult(t, expectedOutput, result.Output) +} + func TestReadWithKeyCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c") diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index e92c4c2a..c17e7e5b 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -31,13 +31,19 @@ func (n *navigator) Traverse(value *yaml.Node, path []string) error { return n.doTraverse(value, "", path, emptyArray) } -func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pathStack []interface{}) error { - if len(path) > 0 { - log.Debugf("diving into %v", path[0]) - DebugNode(value) - return n.recurse(value, path[0], path[1:], pathStack) +func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { + log.Debug("head %v", head) + DebugNode(value) + if head == "**" && value.Kind != yaml.ScalarNode { + return n.recurse(value, head, tail, pathStack) } - return n.navigationStrategy.Visit(NewNodeContext(value, head, path, pathStack)) + + if len(tail) > 0 { + log.Debugf("diving into %v", tail[0]) + DebugNode(value) + return n.recurse(value, tail[0], tail[1:], pathStack) + } + return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack)) } func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node { @@ -56,8 +62,8 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt return n.recurseMap(value, head, tail, pathStack) case yaml.SequenceNode: log.Debug("its a sequence of %v things!, %v", len(value.Content)) - if head == "*" { - return n.splatArray(value, tail, pathStack) + if head == "*" || head == "**" { + return n.splatArray(value, head, tail, pathStack) } else if head == "+" { return n.appendArray(value, tail, pathStack) } @@ -196,11 +202,11 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head str return nil } -func (n *navigator) splatArray(value *yaml.Node, tail []string, pathStack []interface{}) error { +func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { for index, childValue := range value.Content { log.Debug("processing") DebugNode(childValue) - head := fmt.Sprintf("%v", index) + // head = fmt.Sprintf("%v", index) childValue = n.getOrReplace(childValue, guessKind(tail, childValue.Kind)) var err = n.doTraverse(childValue, head, tail, append(pathStack, index)) if err != nil { diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 2217a1b5..6d851b91 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -61,7 +61,7 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind { if tail[0] == "+" || errorParsingInt == nil { return yaml.SequenceNode } - if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { + if (tail[0] == "*" || tail[0] == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { return guess } if guess == yaml.AliasNode { diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index 85dd5b21..91f942d1 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -74,13 +74,12 @@ func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKe } func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { - // we should traverse aliases (if enabled), but not visit them :/ pathStack := nodeContext.PathStack if len(pathStack) == 0 { return true } - if ns.alreadyVisited(pathStack) { + if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 { return false } @@ -108,7 +107,7 @@ func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error { } func (ns *NavigationStrategyImpl) DebugVisitedNodes() { - log.Debug("%v", ns.visitedNodes) + log.Debug("Visited Nodes:") for _, candidate := range ns.visitedNodes { log.Debug(" - %v", PathStackToString(candidate.PathStack)) } diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index e4665ddb..7c1bd4ba 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -24,6 +24,9 @@ func NewPathParser() PathParser { */ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool { head := nodeContext.Head + if head == "**" || head == "*" { + return true + } var prefixMatch = strings.TrimSuffix(head, "*") if prefixMatch != head { log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch))