Simplified

This commit is contained in:
Mike Farah 2019-12-15 18:24:23 +11:00
parent b7640946ac
commit 5988d0cffa
5 changed files with 132 additions and 69 deletions

View File

@ -172,6 +172,8 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
- lala - lala
- land - land
serial: 1 serial: 1
- become: false
gather_facts: true
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
@ -194,9 +196,9 @@ serial: 1
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestReadCmd_ArrayYaml_Splat(t *testing.T) { func TestReadCmd_ArrayYaml_SplatA(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*]") result := test.RunCmd(cmd, "read -v examples/array.yaml [*]")
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
@ -208,6 +210,8 @@ func TestReadCmd_ArrayYaml_Splat(t *testing.T) {
- lala - lala
- land - land
serial: 1 serial: 1
- become: false
gather_facts: true
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
@ -218,18 +222,17 @@ func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) {
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := "- false\n" expectedOutput := `- false
- true
`
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) { func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts") result := test.RunCmd(cmd, "read -v examples/array.yaml [x].gather_facts")
if result.Error == nil { expectedOutput := ``
t.Error("Expected command to fail due to invalid path") test.AssertResult(t, expectedOutput, result.Output)
}
expectedOutput := `Error reading path in document index 0: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, result.Error.Error())
} }
// func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) { // func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) {
@ -326,6 +329,34 @@ func TestReadCmd_Verbose(t *testing.T) {
// test.AssertResult(t, "2\n", result.Output) // test.AssertResult(t, "2\n", result.Output)
// } // }
func TestReadSplatPrefixYaml(t *testing.T) {
content := `a: 2
b:
hi:
c: things
d: something else
there:
c: more things
d: more something else
there2:
c: more things also
d: more something else also
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read -v %s b.there*.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `- more things
- more things also
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestPrefixCmd(t *testing.T) { func TestPrefixCmd(t *testing.T) {
content := `b: content := `b:
c: 3 c: 3

View File

@ -7,3 +7,5 @@
- lala - lala
- land - land
serial: 1 serial: 1
- become: false
gather_facts: true

View File

@ -2,3 +2,4 @@ a: other
b: [3, 4] b: [3, 4]
c: c:
test: 1 test: 1
tell: 1

View File

@ -3,6 +3,7 @@ package yqlib
import ( import (
"bytes" "bytes"
"strconv" "strconv"
"strings"
logging "gopkg.in/op/go-logging.v1" logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
@ -19,11 +20,7 @@ type navigator struct {
log *logging.Logger log *logging.Logger
} }
type VisitorFn func(*yaml.Node) (*yaml.Node, error) type VisitorFn func(*yaml.Node) error
func identityVisitor(value *yaml.Node) (*yaml.Node, error) {
return value, nil
}
func NewDataNavigator(l *logging.Logger) DataNavigator { func NewDataNavigator(l *logging.Logger) DataNavigator {
return &navigator{ return &navigator{
@ -32,11 +29,28 @@ func NewDataNavigator(l *logging.Logger) DataNavigator {
} }
func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) { func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
return n.Visit(value, path, identityVisitor) matchingNodes := make([]*yaml.Node, 0)
n.Visit(value, path, func(matchedNode *yaml.Node) error {
matchingNodes = append(matchingNodes, matchedNode)
n.log.Debug("Matched")
n.DebugNode(matchedNode)
return nil
})
n.log.Debug("finished iterating, found %v matches", len(matchingNodes))
if len(matchingNodes) == 0 {
return nil, nil
} else if len(matchingNodes) == 1 {
return matchingNodes[0], nil
}
// make a new node
var newNode = yaml.Node{Kind: yaml.SequenceNode}
newNode.Content = matchingNodes
return &newNode, nil
} }
func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error { func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error {
_, errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) error {
n.log.Debug("going to update") n.log.Debug("going to update")
n.DebugNode(nodeToUpdate) n.DebugNode(nodeToUpdate)
n.log.Debug("with") n.log.Debug("with")
@ -49,7 +63,7 @@ func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply ya
nodeToUpdate.HeadComment = changesToApply.HeadComment nodeToUpdate.HeadComment = changesToApply.HeadComment
nodeToUpdate.LineComment = changesToApply.LineComment nodeToUpdate.LineComment = changesToApply.LineComment
nodeToUpdate.FootComment = changesToApply.FootComment nodeToUpdate.FootComment = changesToApply.FootComment
return nodeToUpdate, nil return nil
}) })
return errorVisiting return errorVisiting
} }
@ -59,36 +73,39 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
lastBit, newTail := path[len(path)-1], path[:len(path)-1] lastBit, newTail := path[len(path)-1], path[:len(path)-1]
n.log.Debug("splitting path, %v", lastBit) n.log.Debug("splitting path, %v", lastBit)
n.log.Debug("new tail, %v", newTail) n.log.Debug("new tail, %v", newTail)
_, errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) { errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) error {
n.log.Debug("need to find %v in here", lastBit) n.log.Debug("need to find %v in here", lastBit)
n.DebugNode(nodeToUpdate) n.DebugNode(nodeToUpdate)
original := nodeToUpdate.Content original := nodeToUpdate.Content
if nodeToUpdate.Kind == yaml.SequenceNode { if nodeToUpdate.Kind == yaml.SequenceNode {
var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
if err != nil { if err != nil {
return nil, err return err
} }
if index >= int64(len(nodeToUpdate.Content)) { if index >= int64(len(nodeToUpdate.Content)) {
n.log.Debug("index %v is greater than content lenth %v", index, len(nodeToUpdate.Content)) n.log.Debug("index %v is greater than content length %v", index, len(nodeToUpdate.Content))
return nodeToUpdate, nil return nil
} }
nodeToUpdate.Content = append(original[:index], original[index+1:]...) nodeToUpdate.Content = append(original[:index], original[index+1:]...)
} else if nodeToUpdate.Kind == yaml.MappingNode { } else if nodeToUpdate.Kind == yaml.MappingNode {
//need to delete both the key and value children from Content
indexInMap := n.findIndexForKeyInMap(nodeToUpdate.Content, lastBit) _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error {
if indexInMap != -1 {
//skip two because its a key, value pair
nodeToUpdate.Content = append(original[:indexInMap], original[indexInMap+2:]...) nodeToUpdate.Content = append(original[:indexInMap], original[indexInMap+2:]...)
return nil
})
if errorVisiting != nil {
return errorVisiting
} }
} }
return nodeToUpdate, nil return nil
}) })
return errorVisiting return errorVisiting
} }
func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) (*yaml.Node, error) { func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) error {
realValue := value realValue := value
if realValue.Kind == yaml.DocumentNode { if realValue.Kind == yaml.DocumentNode {
n.log.Debugf("its a document! returning the first child") n.log.Debugf("its a document! returning the first child")
@ -130,7 +147,9 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y
} }
func (n *navigator) DebugNode(value *yaml.Node) { func (n *navigator) DebugNode(value *yaml.Node) {
if n.log.IsEnabledFor(logging.DEBUG) { if value == nil {
n.log.Debug("-- node is nil --")
} else if n.log.IsEnabledFor(logging.DEBUG) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
encoder := yaml.NewEncoder(buf) encoder := yaml.NewEncoder(buf)
encoder.Encode(value) encoder.Encode(value)
@ -140,7 +159,7 @@ func (n *navigator) DebugNode(value *yaml.Node) {
} }
} }
func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
switch value.Kind { switch value.Kind {
case yaml.MappingNode: case yaml.MappingNode:
n.log.Debug("its a map with %v entries", len(value.Content)/2) n.log.Debug("its a map with %v entries", len(value.Content)/2)
@ -157,32 +176,36 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
} }
return n.recurseArray(value, head, tail, visitor) return n.recurseArray(value, head, tail, visitor)
default: default:
return nil, nil return nil
} }
} }
func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) error {
var newNode = yaml.Node{Kind: yaml.SequenceNode}
for index, content := range value.Content { for index, content := range value.Content {
if index%2 == 0 { if index%2 == 0 {
continue continue
} }
content = n.getOrReplace(content, n.guessKind(tail, content.Kind)) content = n.getOrReplace(content, n.guessKind(tail, content.Kind))
var nestedValue, err = n.Visit(content, tail, visitor) var err = n.Visit(content, tail, visitor)
if err != nil { if err != nil {
return nil, err return err
} }
newNode.Content = append(newNode.Content, nestedValue)
} }
return &newNode, nil return nil
} }
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
indexInMap := n.findIndexForKeyInMap(value.Content, head) visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(indexInMap int) error {
if indexInMap != -1 {
value.Content[indexInMap+1] = n.getOrReplace(value.Content[indexInMap+1], n.guessKind(tail, value.Content[indexInMap+1].Kind)) value.Content[indexInMap+1] = n.getOrReplace(value.Content[indexInMap+1], n.guessKind(tail, value.Content[indexInMap+1].Kind))
return n.Visit(value.Content[indexInMap+1], tail, visitor) return n.Visit(value.Content[indexInMap+1], tail, visitor)
})
if errorVisiting != nil {
return errorVisiting
}
if visited {
return nil
} }
//didn't find it, lets add it. //didn't find it, lets add it.
@ -193,65 +216,64 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
return n.Visit(&mapEntryValue, tail, visitor) return n.Visit(&mapEntryValue, tail, visitor)
} }
func (n *navigator) findIndexForKeyInMap(contents []*yaml.Node, key string) int { type mapVisitorFn func(int) error
func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
visited := false
for index, content := range contents { for index, content := range contents {
// value.Content is a concatenated array of key, value, // value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd. // so keys are in the even indexes, values in odd.
if index%2 == 1 || (content.Value != key) { if index%2 == 0 && (n.matchesKey(key, content.Value)) {
continue errorVisiting := visit(index)
if errorVisiting != nil {
return visited, errorVisiting
}
visited = true
} }
return index
} }
return -1 return visited, nil
} }
func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { func (n *navigator) matchesKey(key string, actual string) bool {
var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style} var prefixMatch = strings.TrimSuffix(key, "*")
newNode.Content = make([]*yaml.Node, len(value.Content)) if prefixMatch != key {
return strings.HasPrefix(actual, prefixMatch)
}
return actual == key
}
for index, childValue := range value.Content { func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) error {
for _, childValue := range value.Content {
n.log.Debug("processing") n.log.Debug("processing")
n.DebugNode(childValue) n.DebugNode(childValue)
childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind)) childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind))
var nestedValue, err = n.Visit(childValue, tail, visitor) var err = n.Visit(childValue, tail, visitor)
n.log.Debug("nestedValue")
n.DebugNode(nestedValue)
if err != nil { if err != nil {
return nil, err return err
} }
newNode.Content[index] = nestedValue
} }
return &newNode, nil return nil
} }
func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) { func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) error {
var newNode = yaml.Node{Kind: n.guessKind(tail, 0)} var newNode = yaml.Node{Kind: n.guessKind(tail, 0)}
value.Content = append(value.Content, &newNode) value.Content = append(value.Content, &newNode)
n.log.Debug("appending a new node, %v", value.Content) n.log.Debug("appending a new node, %v", value.Content)
return n.Visit(&newNode, tail, visitor) return n.Visit(&newNode, tail, visitor)
} }
func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) { func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn) 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 nil, err return err
} }
if index >= int64(len(value.Content)) { if index >= int64(len(value.Content)) {
return nil, nil return nil
} }
value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind)) value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind))
return n.Visit(value.Content[index], tail, visitor) return n.Visit(value.Content[index], tail, visitor)
} }
// func matchesKey(key string, actual interface{}) bool {
// var actualString = fmt.Sprintf("%v", actual)
// var prefixMatch = strings.TrimSuffix(key, "*")
// if prefixMatch != key {
// return strings.HasPrefix(actualString, prefixMatch)
// }
// return actualString == key
// }
// func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem { // func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem {
// var matches = make([]*yaml.MapItem, 0) // var matches = make([]*yaml.MapItem, 0)
// for idx := range context { // for idx := range context {

9
yq.go
View File

@ -277,10 +277,14 @@ func readProperty(cmd *cobra.Command, args []string) error {
log.Debugf("reading %v in document %v", path, currentIndex) log.Debugf("reading %v in document %v", path, currentIndex)
mappedDoc, errorParsing := lib.Get(&dataBucket, path) mappedDoc, errorParsing := lib.Get(&dataBucket, path)
lib.DebugNode(mappedDoc) lib.DebugNode(mappedDoc)
log.Debugf("carry on")
if errorParsing != nil { if errorParsing != nil {
return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
} else if mappedDoc != nil {
mappedDocs = append(mappedDocs, mappedDoc)
} }
mappedDocs = append(mappedDocs, mappedDoc)
} }
currentIndex = currentIndex + 1 currentIndex = currentIndex + 1
} }
@ -288,6 +292,9 @@ func readProperty(cmd *cobra.Command, args []string) error {
if errorReadingStream != nil { if errorReadingStream != nil {
return errorReadingStream return errorReadingStream
} else if len(mappedDocs) == 0 {
log.Debug("no matching results, nothing to print")
return nil
} }
var encoder = yaml.NewEncoder(cmd.OutOrStdout()) var encoder = yaml.NewEncoder(cmd.OutOrStdout())