This commit is contained in:
Mike Farah 2019-12-31 15:21:39 +13:00
parent 4dbdd4a805
commit 625cfdac75
5 changed files with 55 additions and 26 deletions

View File

@ -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) {

View File

@ -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))
}

View File

@ -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
}

View File

@ -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

View File

@ -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))