From 625cfdac75c6eb7955c9c050ab59d3b805196954 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 31 Dec 2019 15:21:39 +1300 Subject: [PATCH] wip; --- commands_test.go | 25 ++++++++++++++++++----- pkg/yqlib/data_navigator.go | 35 +++++++++++++++++--------------- pkg/yqlib/lib.go | 10 ++++----- pkg/yqlib/navigation_strategy.go | 3 +++ pkg/yqlib/path_parser.go | 8 ++++++++ 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/commands_test.go b/commands_test.go index 5d0925d..3e89741 100644 --- a/commands_test.go +++ b/commands_test.go @@ -130,6 +130,18 @@ b.e.[1].value: 4 test.AssertResult(t, expectedOutput, result.Output) } +func TestReadDeepSplatWithSuffixCmd(t *testing.T) { + cmd := getRootCommand() + result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.**.name") + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `b.e.[0].name: fred +b.e.[1].name: sam +` + 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") @@ -408,15 +420,18 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { if result.Error == nil { t.Error("Expected command to fail due to missing arg") } - expectedOutput := `Error reading path in document index 0: strconv.ParseInt: parsing "x": invalid syntax` + expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for '': strconv.ParseInt: parsing "x": invalid syntax` test.AssertResult(t, expectedOutput, result.Error.Error()) } func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) + if result.Error == nil { + t.Error("Expected command to fail due to missing arg") + } + expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for '[0].roles': strconv.ParseInt: parsing "x": invalid syntax` + test.AssertResult(t, expectedOutput, result.Error.Error()) } func TestReadCmd_Error(t *testing.T) { @@ -469,8 +484,8 @@ func TestReadCmd_ErrorBadPath(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) - expectedOutput := `` - test.AssertResult(t, expectedOutput, result.Output) + expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for 'b.d.e': strconv.ParseInt: parsing "x": invalid syntax` + test.AssertResult(t, expectedOutput, result.Error.Error()) } func TestReadCmd_Verbose(t *testing.T) { diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index c17e7e5..835c924 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -1,9 +1,9 @@ package yqlib import ( - "fmt" "strconv" + errors "github.com/pkg/errors" yaml "gopkg.in/yaml.v3" ) @@ -34,8 +34,15 @@ func (n *navigator) Traverse(value *yaml.Node, path []string) error { func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { log.Debug("head %v", head) DebugNode(value) + var errorDeepSplatting error if head == "**" && value.Kind != yaml.ScalarNode { - return n.recurse(value, head, tail, pathStack) + errorDeepSplatting = n.recurse(value, head, tail, pathStack) + // ignore errors here, we are deep splatting so we may accidently give a string key + // to an array sequence + if len(tail) > 0 { + n.recurse(value, tail[0], tail[1:], pathStack) + } + return errorDeepSplatting } if len(tail) > 0 { @@ -61,11 +68,11 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt log.Debug("its a map with %v entries", len(value.Content)/2) return n.recurseMap(value, head, tail, pathStack) case yaml.SequenceNode: - log.Debug("its a sequence of %v things!, %v", len(value.Content)) + log.Debug("its a sequence of %v things!", len(value.Content)) if head == "*" || head == "**" { return n.splatArray(value, head, tail, pathStack) } else if head == "+" { - return n.appendArray(value, tail, pathStack) + return n.appendArray(value, head, tail, pathStack) } return n.recurseArray(value, head, tail, pathStack) case yaml.AliasNode: @@ -94,7 +101,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) == true { log.Debug("recurseMap: Going to traverse") traversedEntry = true - contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind)) + // contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(head, tail, contents[indexInMap+1].Kind)) errorTraversing := n.doTraverse(contents[indexInMap+1], head, tail, newPathStack) log.Debug("recurseMap: Finished traversing") n.navigationStrategy.DebugVisitedNodes() @@ -109,13 +116,13 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat return errorVisiting } - if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) == false { + if traversedEntry == true || head == "*" || head == "**" || n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) == false { return nil } mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode} value.Content = append(value.Content, &mapEntryKey) - mapEntryValue := yaml.Node{Kind: guessKind(tail, 0)} + mapEntryValue := yaml.Node{Kind: guessKind(head, tail, 0)} value.Content = append(value.Content, &mapEntryValue) log.Debug("adding new node %v", head) return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head)) @@ -206,8 +213,7 @@ func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pat for index, childValue := range value.Content { log.Debug("processing") DebugNode(childValue) - // head = fmt.Sprintf("%v", index) - childValue = n.getOrReplace(childValue, guessKind(tail, childValue.Kind)) + childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind)) var err = n.doTraverse(childValue, head, tail, append(pathStack, index)) if err != nil { return err @@ -216,25 +222,22 @@ func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pat return nil } -func (n *navigator) appendArray(value *yaml.Node, tail []string, pathStack []interface{}) error { - var newNode = yaml.Node{Kind: guessKind(tail, 0)} +func (n *navigator) appendArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { + var newNode = yaml.Node{Kind: guessKind(head, tail, 0)} value.Content = append(value.Content, &newNode) log.Debug("appending a new node, %v", value.Content) - head := fmt.Sprintf("%v", len(value.Content)-1) return n.doTraverse(&newNode, head, tail, append(pathStack, len(value.Content)-1)) } func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error { var index, err = strconv.ParseInt(head, 10, 64) // nolint if err != nil { - return err + return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, PathStackToString(pathStack)) } if index >= int64(len(value.Content)) { return nil } - value.Content[index] = n.getOrReplace(value.Content[index], guessKind(tail, value.Content[index].Kind)) + value.Content[index] = n.getOrReplace(value.Content[index], guessKind(head, tail, value.Content[index].Kind)) - // THERES SOMETHING WRONG HERE, ./yq read -p kv examples/sample.yaml b.e.1.* - // THERES SOMETHING WRONG HERE, ./yq read -p kv examples/sample.yaml b.e.1.name return n.doTraverse(value.Content[index], head, tail, append(pathStack, index)) } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 6d851b9..3f7737a 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -48,7 +48,7 @@ func PathStackToString(pathStack []interface{}) string { return sb.String() } -func guessKind(tail []string, guess yaml.Kind) yaml.Kind { +func guessKind(head string, tail []string, guess yaml.Kind) yaml.Kind { log.Debug("tail %v", tail) if len(tail) == 0 && guess == 0 { log.Debug("end of path, must be a scalar") @@ -61,7 +61,7 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind { if tail[0] == "+" || errorParsingInt == nil { return yaml.SequenceNode } - if (tail[0] == "*" || tail[0] == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { + if (tail[0] == "*" || tail[0] == "**" || head == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) { return guess } if guess == yaml.AliasNode { @@ -69,8 +69,8 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind { return guess } log.Debug("forcing a mapping node") - log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode) - log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode) + log.Debug("yaml.SequenceNode %v", guess == yaml.SequenceNode) + log.Debug("yaml.ScalarNode %v", guess == yaml.ScalarNode) return yaml.MappingNode } @@ -102,7 +102,7 @@ func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) { func (l *lib) New(path string) yaml.Node { var paths = l.parser.ParsePath(path) - newNode := yaml.Node{Kind: guessKind(paths, 0)} + newNode := yaml.Node{Kind: guessKind("", paths, 0)} return newNode } diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go index 91f942d..740d06f 100644 --- a/pkg/yqlib/navigation_strategy.go +++ b/pkg/yqlib/navigation_strategy.go @@ -78,12 +78,15 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool { if len(pathStack) == 0 { return true } + log.Debug("tail len %v", len(nodeContext.Tail)) + // SOMETHING HERE! if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 { return false } nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) + log.Debug("nodeKey: %v, nodeContext.Head: %v", nodeKey, nodeContext.Head) parser := NewPathParser() // only visit aliases if its an exact match diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go index 7c1bd4b..b18ff5d 100644 --- a/pkg/yqlib/path_parser.go +++ b/pkg/yqlib/path_parser.go @@ -1,6 +1,7 @@ package yqlib import ( + "strconv" "strings" ) @@ -27,6 +28,13 @@ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey str if head == "**" || head == "*" { return true } + if head == "+" { + log.Debug("head is +, nodeKey is %v", nodeKey) + var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint + if err == nil { + return true + } + } var prefixMatch = strings.TrimSuffix(head, "*") if prefixMatch != head { log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch))