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) 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) { func TestReadWithKeyCmd(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c") 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 { if result.Error == nil {
t.Error("Expected command to fail due to missing arg") 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()) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]")
expectedOutput := `` if result.Error == nil {
test.AssertResult(t, expectedOutput, result.Output) 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) { func TestReadCmd_Error(t *testing.T) {
@ -469,8 +484,8 @@ func TestReadCmd_ErrorBadPath(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
expectedOutput := `` 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.Output) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
func TestReadCmd_Verbose(t *testing.T) { func TestReadCmd_Verbose(t *testing.T) {

View File

@ -1,9 +1,9 @@
package yqlib package yqlib
import ( import (
"fmt"
"strconv" "strconv"
errors "github.com/pkg/errors"
yaml "gopkg.in/yaml.v3" 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 { func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
log.Debug("head %v", head) log.Debug("head %v", head)
DebugNode(value) DebugNode(value)
var errorDeepSplatting error
if head == "**" && value.Kind != yaml.ScalarNode { 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 { 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) log.Debug("its a map with %v entries", len(value.Content)/2)
return n.recurseMap(value, head, tail, pathStack) return n.recurseMap(value, head, tail, pathStack)
case yaml.SequenceNode: 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 == "**" { if head == "*" || head == "**" {
return n.splatArray(value, head, tail, pathStack) return n.splatArray(value, head, tail, pathStack)
} else if head == "+" { } else if head == "+" {
return n.appendArray(value, tail, pathStack) return n.appendArray(value, head, tail, pathStack)
} }
return n.recurseArray(value, head, tail, pathStack) return n.recurseArray(value, head, tail, pathStack)
case yaml.AliasNode: 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 { if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) == true {
log.Debug("recurseMap: Going to traverse") log.Debug("recurseMap: Going to traverse")
traversedEntry = true 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) errorTraversing := n.doTraverse(contents[indexInMap+1], head, tail, newPathStack)
log.Debug("recurseMap: Finished traversing") log.Debug("recurseMap: Finished traversing")
n.navigationStrategy.DebugVisitedNodes() n.navigationStrategy.DebugVisitedNodes()
@ -109,13 +116,13 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
return errorVisiting 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 return nil
} }
mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode} mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
value.Content = append(value.Content, &mapEntryKey) 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) value.Content = append(value.Content, &mapEntryValue)
log.Debug("adding new node %v", head) log.Debug("adding new node %v", head)
return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, 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 { for index, childValue := range value.Content {
log.Debug("processing") log.Debug("processing")
DebugNode(childValue) DebugNode(childValue)
// head = fmt.Sprintf("%v", index) childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind))
childValue = n.getOrReplace(childValue, guessKind(tail, childValue.Kind))
var err = n.doTraverse(childValue, head, tail, append(pathStack, index)) var err = n.doTraverse(childValue, head, tail, append(pathStack, index))
if err != nil { if err != nil {
return err return err
@ -216,25 +222,22 @@ func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pat
return nil return nil
} }
func (n *navigator) appendArray(value *yaml.Node, tail []string, pathStack []interface{}) error { func (n *navigator) appendArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
var newNode = yaml.Node{Kind: guessKind(tail, 0)} var newNode = yaml.Node{Kind: guessKind(head, tail, 0)}
value.Content = append(value.Content, &newNode) value.Content = append(value.Content, &newNode)
log.Debug("appending a new node, %v", value.Content) 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)) 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 { func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
var index, err = strconv.ParseInt(head, 10, 64) // nolint var index, err = strconv.ParseInt(head, 10, 64) // nolint
if err != nil { 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)) { if index >= int64(len(value.Content)) {
return nil 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)) 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() 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) log.Debug("tail %v", tail)
if len(tail) == 0 && guess == 0 { if len(tail) == 0 && guess == 0 {
log.Debug("end of path, must be a scalar") 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 { if tail[0] == "+" || errorParsingInt == nil {
return yaml.SequenceNode 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 return guess
} }
if guess == yaml.AliasNode { if guess == yaml.AliasNode {
@ -69,8 +69,8 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind {
return guess return guess
} }
log.Debug("forcing a mapping node") log.Debug("forcing a mapping node")
log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode) log.Debug("yaml.SequenceNode %v", guess == yaml.SequenceNode)
log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode) log.Debug("yaml.ScalarNode %v", guess == yaml.ScalarNode)
return yaml.MappingNode 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 { func (l *lib) New(path string) yaml.Node {
var paths = l.parser.ParsePath(path) var paths = l.parser.ParsePath(path)
newNode := yaml.Node{Kind: guessKind(paths, 0)} newNode := yaml.Node{Kind: guessKind("", paths, 0)}
return newNode return newNode
} }

View File

@ -78,12 +78,15 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
if len(pathStack) == 0 { if len(pathStack) == 0 {
return true return true
} }
log.Debug("tail len %v", len(nodeContext.Tail))
// SOMETHING HERE!
if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 { if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 {
return false return false
} }
nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1]) nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
log.Debug("nodeKey: %v, nodeContext.Head: %v", nodeKey, nodeContext.Head)
parser := NewPathParser() parser := NewPathParser()
// only visit aliases if its an exact match // only visit aliases if its an exact match

View File

@ -1,6 +1,7 @@
package yqlib package yqlib
import ( import (
"strconv"
"strings" "strings"
) )
@ -27,6 +28,13 @@ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey str
if head == "**" || head == "*" { if head == "**" || head == "*" {
return true 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, "*") var prefixMatch = strings.TrimSuffix(head, "*")
if prefixMatch != head { if prefixMatch != head {
log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch)) log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch))