From aad15ccc6e838552994ab5404a11f59fbfcc7519 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Fri, 6 Dec 2019 15:57:46 +1100
Subject: [PATCH 01/68] better v3
---
examples/instruction_sample.yaml | 7 +-
go.mod | 3 +-
go.sum | 5 +
pkg/yqlib/data_navigator.go | 751 ++++++++++++++++++-------------
pkg/yqlib/lib.go | 52 +--
yq.go | 556 ++++++++++++-----------
6 files changed, 753 insertions(+), 621 deletions(-)
diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml
index ee2f8e8d..b7864db4 100644
--- a/examples/instruction_sample.yaml
+++ b/examples/instruction_sample.yaml
@@ -1,2 +1,7 @@
-b.c: cat
+- command: update
+ path: b.c
+ value:
+ #great
+ things: frog # wow!
+
b.e[+].name: Mike Farah
diff --git a/go.mod b/go.mod
index 0b008769..efc8ffd2 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module github.com/mikefarah/yq/v2
+module github.com/mikefarah/yq/v3
require (
github.com/mikefarah/yaml/v2 v2.4.0
@@ -7,6 +7,7 @@ require (
golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 // indirect
gopkg.in/imdario/mergo.v0 v0.3.7
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
+ gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2
)
go 1.13
diff --git a/go.sum b/go.sum
index 40ff65e5..307ebb49 100644
--- a/go.sum
+++ b/go.sum
@@ -10,8 +10,11 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mikefarah/yaml v2.1.0+incompatible h1:nu2cqmzk4WlWJNgnevY88faMcdrDzYGcsUjYFxEpB7Y=
github.com/mikefarah/yaml/v2 v2.4.0 h1:eYqfooY0BnvKTJxr7+ABJs13n3dg9n347GScDaU2Lww=
github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RRf2RRxieJU=
+github.com/mikefarah/yq v2.4.0+incompatible h1:oBxbWy8R9hI3BIUUxEf0CzikWa2AgnGrGhvGQt5jgjk=
+github.com/mikefarah/yq/v2 v2.4.1 h1:tajDonaFK6WqitSZExB6fKlWQy/yCkptqxh2AXEe3N4=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -48,3 +51,5 @@ gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
+gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 203eb6c0..66741598 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -1,19 +1,20 @@
package yqlib
import (
- "fmt"
- "reflect"
"strconv"
- "strings"
- yaml "github.com/mikefarah/yaml/v2"
logging "gopkg.in/op/go-logging.v1"
+ yaml "gopkg.in/yaml.v3"
)
+type WriteCommand struct {
+ // Command string TODO
+ Value yaml.Node
+}
+
type DataNavigator interface {
- ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error)
- UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{}
- DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error)
+ Get(rootNode *yaml.Node, remainingPath []string) (*yaml.Node, error)
+ Update(rootNode *yaml.Node, remainingPath []string, writeCommand WriteCommand) error
}
type navigator struct {
@@ -26,351 +27,451 @@ func NewDataNavigator(l *logging.Logger) DataNavigator {
}
}
-func (n *navigator) ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error) {
- if len(remainingPaths) == 0 {
- return child, nil
+func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
+ realValue := value
+ if realValue.Kind == yaml.DocumentNode {
+ realValue = value.Content[0]
}
- return n.recurse(child, remainingPaths[0], remainingPaths[1:])
+ if len(path) > 0 {
+ n.log.Debug("diving into %v", path[0])
+ return n.recurse(realValue, path[0], path[1:])
+ }
+ return realValue, nil
}
-func (n *navigator) UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} {
- if len(remainingPaths) == 0 {
- return value
+func (n *navigator) guessKind(tail []string) yaml.Kind {
+ n.log.Debug("tail %v", tail)
+ if len(tail) == 0 {
+ n.log.Debug("scalar")
+ return yaml.ScalarNode
}
- n.log.Debugf("UpdatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value)
- n.log.Debugf("type of child is %v", reflect.TypeOf(child))
-
- switch child := child.(type) {
- case nil:
- if remainingPaths[0] == "+" || remainingPaths[0] == "*" {
- return n.writeArray(child, remainingPaths, value)
- }
- case []interface{}:
- _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64)
- arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*"
- if arrayCommand {
- return n.writeArray(child, remainingPaths, value)
- }
+ var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64)
+ if tail[0] == "*" || tail[0] == "+" || errorParsingInt == nil {
+ return yaml.SequenceNode
}
- return n.writeMap(child, remainingPaths, value)
+ return yaml.MappingNode
}
-func (n *navigator) DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) {
- n.log.Debugf("DeleteChildValue for %v for %v\n", remainingPaths, child)
- if len(remainingPaths) == 0 {
- return child, nil
+func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
+ // expected is a scalar when we reach the end of the path
+ // no need to clobber the original because:
+ // when reading, it should deal with the original kind
+ // when writing, it will clobber the kind anyway
+ if original.Kind != expectedKind && (expectedKind != yaml.ScalarNode) {
+ return &yaml.Node{Kind: expectedKind}
}
- var head = remainingPaths[0]
- var tail = remainingPaths[1:]
- switch child := child.(type) {
- case yaml.MapSlice:
- return n.deleteMap(child, remainingPaths)
- case []interface{}:
- if head == "*" {
- return n.deleteArraySplat(child, tail)
- }
- index, err := strconv.ParseInt(head, 10, 64)
- if err != nil {
- return nil, fmt.Errorf("error accessing array: %v", err)
- }
- return n.deleteArray(child, remainingPaths, index)
- }
- return child, nil
+ return original
}
-func (n *navigator) recurse(value interface{}, head string, tail []string) (interface{}, error) {
- switch value := value.(type) {
- case []interface{}:
- if head == "*" {
- return n.readArraySplat(value, tail)
- }
- index, err := strconv.ParseInt(head, 10, 64)
- if err != nil {
- return nil, fmt.Errorf("error accessing array: %v", err)
- }
- return n.readArray(value, index, tail)
- case yaml.MapSlice:
- return n.readMap(value, head, tail)
- default:
- return nil, nil
- }
-}
-
-func (n *navigator) 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 (n *navigator) entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem {
- var matches = make([]*yaml.MapItem, 0)
- for idx := range context {
- var entry = &context[idx]
- if n.matchesKey(key, entry.Key) {
- matches = append(matches, entry)
- }
- }
- return matches
-}
-
-func (n *navigator) getMapSlice(context interface{}) yaml.MapSlice {
- var mapSlice yaml.MapSlice
- switch context := context.(type) {
- case yaml.MapSlice:
- mapSlice = context
- default:
- mapSlice = make(yaml.MapSlice, 0)
- }
- return mapSlice
-}
-
-func (n *navigator) getArray(context interface{}) (array []interface{}, ok bool) {
- switch context := context.(type) {
- case []interface{}:
- array = context
- ok = true
- default:
- array = make([]interface{}, 0)
- ok = false
- }
- return
-}
-
-func (n *navigator) writeMap(context interface{}, paths []string, value interface{}) interface{} {
- n.log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value)
-
- mapSlice := n.getMapSlice(context)
-
- if len(paths) == 0 {
- return context
- }
-
- children := n.entriesInSlice(mapSlice, paths[0])
-
- if len(children) == 0 && paths[0] == "*" {
- n.log.Debugf("\tNo matches, return map as is")
- return context
- }
-
- if len(children) == 0 {
- newChild := yaml.MapItem{Key: paths[0]}
- mapSlice = append(mapSlice, newChild)
- children = n.entriesInSlice(mapSlice, paths[0])
- n.log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice)
- }
-
- remainingPaths := paths[1:]
- for _, child := range children {
- child.Value = n.UpdatedChildValue(child.Value, remainingPaths, value)
- }
- n.log.Debugf("\tReturning mapSlice %v\n", mapSlice)
- return mapSlice
-}
-
-func (n *navigator) writeArray(context interface{}, paths []string, value interface{}) []interface{} {
- n.log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value)
- array, _ := n.getArray(context)
-
- if len(paths) == 0 {
- return array
- }
-
- n.log.Debugf("\tarray %v\n", array)
-
- rawIndex := paths[0]
- remainingPaths := paths[1:]
- var index int64
- // the append array indicator
- if rawIndex == "+" {
- index = int64(len(array))
- } else if rawIndex == "*" {
- for index, oldChild := range array {
- array[index] = n.UpdatedChildValue(oldChild, remainingPaths, value)
- }
- return array
- } else {
- index, _ = strconv.ParseInt(rawIndex, 10, 64) // nolint
- // writeArray is only called by UpdatedChildValue which handles parsing the
- // index, as such this renders this dead code.
- }
-
- for index >= int64(len(array)) {
- array = append(array, nil)
- }
- currentChild := array[index]
-
- n.log.Debugf("\tcurrentChild %v\n", currentChild)
-
- array[index] = n.UpdatedChildValue(currentChild, remainingPaths, value)
- n.log.Debugf("\tReturning array %v\n", array)
- return array
-}
-
-func (n *navigator) readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) {
- n.log.Debugf("readingMap %v with key %v\n", context, head)
- if head == "*" {
- return n.readMapSplat(context, tail)
- }
-
- entries := n.entriesInSlice(context, head)
- if len(entries) == 1 {
- return n.calculateValue(entries[0].Value, tail)
- } else if len(entries) == 0 {
- return nil, nil
- }
- var errInIdx error
- values := make([]interface{}, len(entries))
- for idx, entry := range entries {
- values[idx], errInIdx = n.calculateValue(entry.Value, tail)
- if errInIdx != nil {
- n.log.Errorf("Error updating index %v in %v", idx, context)
- return nil, errInIdx
- }
-
- }
- return values, nil
-}
-
-func (n *navigator) readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) {
- var newArray = make([]interface{}, len(context))
- var i = 0
- for _, entry := range context {
- if len(tail) > 0 {
- val, err := n.recurse(entry.Value, tail[0], tail[1:])
- if err != nil {
- return nil, err
+func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml.Node, error) {
+ switch value.Kind {
+ case yaml.MappingNode:
+ n.log.Debug("its a map with %v entries", len(value.Content)/2)
+ for index, content := range value.Content {
+ // value.Content is a concatenated array of key, value,
+ // so keys are in the even indexes, values in odd.
+ if index%2 == 1 || content.Value != head {
+ continue
}
- newArray[i] = val
- } else {
- newArray[i] = entry.Value
+ value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail))
+ return n.Get(value.Content[index+1], tail)
}
- i++
- }
- return newArray, nil
-}
+ value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode})
+ mapEntryValue := yaml.Node{Kind: n.guessKind(tail)}
+ value.Content = append(value.Content, &mapEntryValue)
+ n.log.Debug("adding new node %v", value.Content)
+ return n.Get(&mapEntryValue, tail)
+ case yaml.SequenceNode:
+ n.log.Debug("its a sequence of %v things!", len(value.Content))
+ if head == "*" {
+ var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style}
+ newNode.Content = make([]*yaml.Node, len(value.Content))
-func (n *navigator) readArray(array []interface{}, head int64, tail []string) (interface{}, error) {
- if head >= int64(len(array)) {
- return nil, nil
- }
+ for index, value := range value.Content {
+ value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail))
+ var nestedValue, err = n.Get(value.Content[index], tail)
+ if err != nil {
+ return nil, err
+ }
+ newNode.Content[index] = nestedValue
+ }
+ return &newNode, nil
+ } else if head == "+" {
- value := array[head]
- return n.calculateValue(value, tail)
-}
-
-func (n *navigator) readArraySplat(array []interface{}, tail []string) (interface{}, error) {
- var newArray = make([]interface{}, len(array))
- for index, value := range array {
- val, err := n.calculateValue(value, tail)
+ var newNode = yaml.Node{Kind: n.guessKind(tail)}
+ value.Content = append(value.Content, &newNode)
+ n.log.Debug("appending a new node, %v", value.Content)
+ return n.Get(&newNode, tail)
+ }
+ var index, err = strconv.ParseInt(head, 10, 64) // nolint
if err != nil {
return nil, err
}
- newArray[index] = val
- }
- return newArray, nil
-}
-
-func (n *navigator) calculateValue(value interface{}, tail []string) (interface{}, error) {
- if len(tail) > 0 {
- return n.recurse(value, tail[0], tail[1:])
- }
- return value, nil
-}
-
-func (n *navigator) deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) {
- n.log.Debugf("deleteMap for %v for %v\n", paths, context)
-
- mapSlice := n.getMapSlice(context)
-
- if len(paths) == 0 {
- return mapSlice, nil
- }
-
- var index int
- var child yaml.MapItem
- for index, child = range mapSlice {
- if n.matchesKey(paths[0], child.Key) {
- n.log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index)
- var badDelete error
- mapSlice, badDelete = n.deleteEntryInMap(mapSlice, child, index, paths)
- if badDelete != nil {
- return nil, badDelete
- }
+ if index >= int64(len(value.Content)) {
+ return nil, nil
}
+ value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail))
+ return n.Get(value.Content[index], tail)
+ default:
+ return nil, nil
}
-
- return mapSlice, nil
-
}
-func (n *navigator) deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) {
- remainingPaths := paths[1:]
-
- var newSlice yaml.MapSlice
- if len(remainingPaths) > 0 {
- newChild := yaml.MapItem{Key: child.Key}
- var errorDeleting error
- newChild.Value, errorDeleting = n.DeleteChildValue(child.Value, remainingPaths)
- if errorDeleting != nil {
- return nil, errorDeleting
- }
-
- newSlice = make(yaml.MapSlice, len(original))
- for i := range original {
- item := original[i]
- if i == index {
- item = newChild
- }
- newSlice[i] = item
- }
- } else {
- // Delete item from slice at index
- newSlice = append(original[:index], original[index+1:]...)
- n.log.Debugf("\tDeleted item index %d from original", index)
+func (n *navigator) Update(dataBucket *yaml.Node, remainingPath []string, writeCommand WriteCommand) error {
+ nodeToUpdate, errorRecursing := n.Get(dataBucket, remainingPath)
+ if errorRecursing != nil {
+ return errorRecursing
}
+ // later, support ability to execute other commands
- n.log.Debugf("\tReturning original %v\n", original)
- return newSlice, nil
+ changesToApply := writeCommand.Value
+
+ nodeToUpdate.Value = changesToApply.Value
+ nodeToUpdate.Tag = changesToApply.Tag
+ nodeToUpdate.Kind = changesToApply.Kind
+ nodeToUpdate.Style = changesToApply.Style
+ nodeToUpdate.Content = changesToApply.Content
+ nodeToUpdate.HeadComment = changesToApply.HeadComment
+ nodeToUpdate.LineComment = changesToApply.LineComment
+ nodeToUpdate.FootComment = changesToApply.FootComment
+ return nil
}
-func (n *navigator) deleteArraySplat(array []interface{}, tail []string) (interface{}, error) {
- n.log.Debugf("deleteArraySplat for %v for %v\n", tail, array)
- var newArray = make([]interface{}, len(array))
- for index, value := range array {
- val, err := n.DeleteChildValue(value, tail)
- if err != nil {
- return nil, err
- }
- newArray[index] = val
- }
- return newArray, nil
-}
+// 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 (n *navigator) deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) {
- n.log.Debugf("deleteArray for %v for %v\n", paths, array)
+// func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem {
+// var matches = make([]*yaml.MapItem, 0)
+// for idx := range context {
+// var entry = &context[idx]
+// if matchesKey(key, entry.Key) {
+// matches = append(matches, entry)
+// }
+// }
+// return matches
+// }
- if index >= int64(len(array)) {
- return array, nil
- }
+// func getMapSlice(context interface{}) yaml.MapSlice {
+// var mapSlice yaml.MapSlice
+// switch context := context.(type) {
+// case yaml.MapSlice:
+// mapSlice = context
+// default:
+// mapSlice = make(yaml.MapSlice, 0)
+// }
+// return mapSlice
+// }
- remainingPaths := paths[1:]
- if len(remainingPaths) > 0 {
- // recurse into the array element at index
- var errorDeleting error
- array[index], errorDeleting = n.deleteMap(array[index], remainingPaths)
- if errorDeleting != nil {
- return nil, errorDeleting
- }
+// func getArray(context interface{}) (array []interface{}, ok bool) {
+// switch context := context.(type) {
+// case []interface{}:
+// array = context
+// ok = true
+// default:
+// array = make([]interface{}, 0)
+// ok = false
+// }
+// return
+// }
- } else {
- // Delete the array element at index
- array = append(array[:index], array[index+1:]...)
- n.log.Debugf("\tDeleted item index %d from array, leaving %v", index, array)
- }
+// func writeMap(context interface{}, paths []string, value interface{}) interface{} {
+// log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value)
- n.log.Debugf("\tReturning array: %v\n", array)
- return array, nil
-}
+// mapSlice := getMapSlice(context)
+
+// if len(paths) == 0 {
+// return context
+// }
+
+// children := entriesInSlice(mapSlice, paths[0])
+
+// if len(children) == 0 && paths[0] == "*" {
+// log.Debugf("\tNo matches, return map as is")
+// return context
+// }
+
+// if len(children) == 0 {
+// newChild := yaml.MapItem{Key: paths[0]}
+// mapSlice = append(mapSlice, newChild)
+// children = entriesInSlice(mapSlice, paths[0])
+// log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice)
+// }
+
+// remainingPaths := paths[1:]
+// for _, child := range children {
+// child.Value = updatedChildValue(child.Value, remainingPaths, value)
+// }
+// log.Debugf("\tReturning mapSlice %v\n", mapSlice)
+// return mapSlice
+// }
+
+// func updatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} {
+// if len(remainingPaths) == 0 {
+// return value
+// }
+// log.Debugf("updatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value)
+// log.Debugf("type of child is %v", reflect.TypeOf(child))
+
+// switch child := child.(type) {
+// case nil:
+// if remainingPaths[0] == "+" || remainingPaths[0] == "*" {
+// return writeArray(child, remainingPaths, value)
+// }
+// case []interface{}:
+// _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64)
+// arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*"
+// if arrayCommand {
+// return writeArray(child, remainingPaths, value)
+// }
+// }
+// return writeMap(child, remainingPaths, value)
+// }
+
+// func writeArray(context interface{}, paths []string, value interface{}) []interface{} {
+// log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value)
+// array, _ := getArray(context)
+
+// if len(paths) == 0 {
+// return array
+// }
+
+// log.Debugf("\tarray %v\n", array)
+
+// rawIndex := paths[0]
+// remainingPaths := paths[1:]
+// var index int64
+// // the append array indicator
+// if rawIndex == "+" {
+// index = int64(len(array))
+// } else if rawIndex == "*" {
+// for index, oldChild := range array {
+// array[index] = updatedChildValue(oldChild, remainingPaths, value)
+// }
+// return array
+// } else {
+// index, _ = strconv.ParseInt(rawIndex, 10, 64) // nolint
+// // writeArray is only called by updatedChildValue which handles parsing the
+// // index, as such this renders this dead code.
+// }
+
+// for index >= int64(len(array)) {
+// array = append(array, nil)
+// }
+// currentChild := array[index]
+
+// log.Debugf("\tcurrentChild %v\n", currentChild)
+
+// array[index] = updatedChildValue(currentChild, remainingPaths, value)
+// log.Debugf("\tReturning array %v\n", array)
+// return array
+// }
+
+// func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) {
+// log.Debugf("readingMap %v with key %v\n", context, head)
+// if head == "*" {
+// return readMapSplat(context, tail)
+// }
+
+// entries := entriesInSlice(context, head)
+// if len(entries) == 1 {
+// return calculateValue(entries[0].Value, tail)
+// } else if len(entries) == 0 {
+// return nil, nil
+// }
+// var errInIdx error
+// values := make([]interface{}, len(entries))
+// for idx, entry := range entries {
+// values[idx], errInIdx = calculateValue(entry.Value, tail)
+// if errInIdx != nil {
+// log.Errorf("Error updating index %v in %v", idx, context)
+// return nil, errInIdx
+// }
+
+// }
+// return values, nil
+// }
+
+// func readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) {
+// var newArray = make([]interface{}, len(context))
+// var i = 0
+// for _, entry := range context {
+// if len(tail) > 0 {
+// val, err := recurse(entry.Value, tail[0], tail[1:])
+// if err != nil {
+// return nil, err
+// }
+// newArray[i] = val
+// } else {
+// newArray[i] = entry.Value
+// }
+// i++
+// }
+// return newArray, nil
+// }
+
+// func recurse(value interface{}, head string, tail []string) (interface{}, error) {
+// switch value := value.(type) {
+// case []interface{}:
+// if head == "*" {
+// return readArraySplat(value, tail)
+// }
+// index, err := strconv.ParseInt(head, 10, 64)
+// if err != nil {
+// return nil, fmt.Errorf("error accessing array: %v", err)
+// }
+// return readArray(value, index, tail)
+// case yaml.MapSlice:
+// return readMap(value, head, tail)
+// default:
+// return nil, nil
+// }
+// }
+
+// func readArray(array []interface{}, head int64, tail []string) (interface{}, error) {
+// if head >= int64(len(array)) {
+// return nil, nil
+// }
+
+// value := array[head]
+// return calculateValue(value, tail)
+// }
+
+// func readArraySplat(array []interface{}, tail []string) (interface{}, error) {
+// var newArray = make([]interface{}, len(array))
+// for index, value := range array {
+// val, err := calculateValue(value, tail)
+// if err != nil {
+// return nil, err
+// }
+// newArray[index] = val
+// }
+// return newArray, nil
+// }
+
+// func calculateValue(value interface{}, tail []string) (interface{}, error) {
+// if len(tail) > 0 {
+// return recurse(value, tail[0], tail[1:])
+// }
+// return value, nil
+// }
+
+// func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) {
+// log.Debugf("deleteMap for %v for %v\n", paths, context)
+
+// mapSlice := getMapSlice(context)
+
+// if len(paths) == 0 {
+// return mapSlice, nil
+// }
+
+// var index int
+// var child yaml.MapItem
+// for index, child = range mapSlice {
+// if matchesKey(paths[0], child.Key) {
+// log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index)
+// var badDelete error
+// mapSlice, badDelete = deleteEntryInMap(mapSlice, child, index, paths)
+// if badDelete != nil {
+// return nil, badDelete
+// }
+// }
+// }
+
+// return mapSlice, nil
+
+// }
+
+// func deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) {
+// remainingPaths := paths[1:]
+
+// var newSlice yaml.MapSlice
+// if len(remainingPaths) > 0 {
+// newChild := yaml.MapItem{Key: child.Key}
+// var errorDeleting error
+// newChild.Value, errorDeleting = deleteChildValue(child.Value, remainingPaths)
+// if errorDeleting != nil {
+// return nil, errorDeleting
+// }
+
+// newSlice = make(yaml.MapSlice, len(original))
+// for i := range original {
+// item := original[i]
+// if i == index {
+// item = newChild
+// }
+// newSlice[i] = item
+// }
+// } else {
+// // Delete item from slice at index
+// newSlice = append(original[:index], original[index+1:]...)
+// log.Debugf("\tDeleted item index %d from original", index)
+// }
+
+// log.Debugf("\tReturning original %v\n", original)
+// return newSlice, nil
+// }
+
+// func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) {
+// log.Debugf("deleteArraySplat for %v for %v\n", tail, array)
+// var newArray = make([]interface{}, len(array))
+// for index, value := range array {
+// val, err := deleteChildValue(value, tail)
+// if err != nil {
+// return nil, err
+// }
+// newArray[index] = val
+// }
+// return newArray, nil
+// }
+
+// func deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) {
+// log.Debugf("deleteArray for %v for %v\n", paths, array)
+
+// if index >= int64(len(array)) {
+// return array, nil
+// }
+
+// remainingPaths := paths[1:]
+// if len(remainingPaths) > 0 {
+// // Recurse into the array element at index
+// var errorDeleting error
+// array[index], errorDeleting = deleteMap(array[index], remainingPaths)
+// if errorDeleting != nil {
+// return nil, errorDeleting
+// }
+
+// } else {
+// // Delete the array element at index
+// array = append(array[:index], array[index+1:]...)
+// log.Debugf("\tDeleted item index %d from array, leaving %v", index, array)
+// }
+
+// log.Debugf("\tReturning array: %v\n", array)
+// return array, nil
+// }
+
+// func deleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) {
+// log.Debugf("deleteChildValue for %v for %v\n", remainingPaths, child)
+// var head = remainingPaths[0]
+// var tail = remainingPaths[1:]
+// switch child := child.(type) {
+// case yaml.MapSlice:
+// return deleteMap(child, remainingPaths)
+// case []interface{}:
+// if head == "*" {
+// return deleteArraySplat(child, tail)
+// }
+// index, err := strconv.ParseInt(head, 10, 64)
+// if err != nil {
+// return nil, fmt.Errorf("error accessing array: %v", err)
+// }
+// return deleteArray(child, remainingPaths, index)
+// }
+// return child, nil
+// }
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 8d330063..a477b8dc 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -1,16 +1,13 @@
package yqlib
import (
- mergo "gopkg.in/imdario/mergo.v0"
logging "gopkg.in/op/go-logging.v1"
+ yaml "gopkg.in/yaml.v3"
)
type YqLib interface {
- ReadPath(dataBucket interface{}, path string) (interface{}, error)
- WritePath(dataBucket interface{}, path string, value interface{}) interface{}
- PrefixPath(dataBucket interface{}, prefix string) interface{}
- DeletePath(dataBucket interface{}, path string) (interface{}, error)
- Merge(dst interface{}, src interface{}, overwrite bool, append bool) error
+ Get(rootNode *yaml.Node, path string) (*yaml.Node, error)
+ Update(rootNode *yaml.Node, path string, writeCommand WriteCommand) error
}
type lib struct {
@@ -25,44 +22,15 @@ func NewYqLib(l *logging.Logger) YqLib {
}
}
-func (l *lib) ReadPath(dataBucket interface{}, path string) (interface{}, error) {
- var paths = l.parser.ParsePath(path)
- return l.navigator.ReadChildValue(dataBucket, paths)
-}
-
-func (l *lib) WritePath(dataBucket interface{}, path string, value interface{}) interface{} {
- var paths = l.parser.ParsePath(path)
- return l.navigator.UpdatedChildValue(dataBucket, paths, value)
-}
-
-func (l *lib) PrefixPath(dataBucket interface{}, prefix string) interface{} {
- var paths = l.parser.ParsePath(prefix)
-
- // Inverse order
- for i := len(paths)/2 - 1; i >= 0; i-- {
- opp := len(paths) - 1 - i
- paths[i], paths[opp] = paths[opp], paths[i]
+func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) {
+ if path == "" {
+ return rootNode, nil
}
-
- var mapDataBucket = dataBucket
- for _, key := range paths {
- singlePath := []string{key}
- mapDataBucket = l.navigator.UpdatedChildValue(nil, singlePath, mapDataBucket)
- }
-
- return mapDataBucket
-}
-
-func (l *lib) DeletePath(dataBucket interface{}, path string) (interface{}, error) {
var paths = l.parser.ParsePath(path)
- return l.navigator.DeleteChildValue(dataBucket, paths)
+ return l.navigator.Get(rootNode, paths)
}
-func (l *lib) Merge(dst interface{}, src interface{}, overwrite bool, append bool) error {
- if overwrite {
- return mergo.Merge(dst, src, mergo.WithOverride)
- } else if append {
- return mergo.Merge(dst, src, mergo.WithAppendSlice)
- }
- return mergo.Merge(dst, src)
+func (l *lib) Update(rootNode *yaml.Node, path string, writeCommand WriteCommand) error {
+ var paths = l.parser.ParsePath(path)
+ return l.navigator.Update(rootNode, paths, writeCommand)
}
diff --git a/yq.go b/yq.go
index 345706fa..f3b0c24d 100644
--- a/yq.go
+++ b/yq.go
@@ -6,20 +6,19 @@ import (
"io"
"io/ioutil"
"os"
- "reflect"
"strconv"
- "strings"
- "github.com/mikefarah/yq/v2/pkg/marshal"
- "github.com/mikefarah/yq/v2/pkg/yqlib"
+ "github.com/mikefarah/yq/v3/pkg/marshal"
+ "github.com/mikefarah/yq/v3/pkg/yqlib"
errors "github.com/pkg/errors"
- yaml "github.com/mikefarah/yaml/v2"
"github.com/spf13/cobra"
logging "gopkg.in/op/go-logging.v1"
+ yaml "gopkg.in/yaml.v3"
)
+var rawOutput = false
var trimOutput = true
var writeInplace = false
var writeScript = ""
@@ -45,7 +44,6 @@ func main() {
}
func newCommandCLI() *cobra.Command {
- yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{})
var rootCmd = &cobra.Command{
Use: "yq",
Short: "yq is a lightweight and portable command-line YAML processor.",
@@ -83,10 +81,10 @@ func newCommandCLI() *cobra.Command {
rootCmd.AddCommand(
createReadCmd(),
createWriteCmd(),
- createPrefixCmd(),
- createDeleteCmd(),
- createNewCmd(),
- createMergeCmd(),
+ // createPrefixCmd(),
+ // createDeleteCmd(),
+ // createNewCmd(),
+ // createMergeCmd(),
)
rootCmd.SetOutput(os.Stdout)
@@ -110,6 +108,7 @@ yq r -- things.yaml --key-starting-with-dashes
RunE: readProperty,
}
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
+ cmdRead.PersistentFlags().BoolVarP(&rawOutput, "raw", "r", false, "raw yaml output - prints out values instead of yaml")
cmdRead.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
return cmdRead
}
@@ -149,104 +148,104 @@ a.b.e:
return cmdWrite
}
-func createPrefixCmd() *cobra.Command {
- var cmdWrite = &cobra.Command{
- Use: "prefix [yaml_file] [path]",
- Aliases: []string{"p"},
- Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
- Example: `
-yq prefix things.yaml a.b.c
-yq prefix --inplace things.yaml a.b.c
-yq prefix --inplace -- things.yaml --key-starting-with-dash
-yq p -i things.yaml a.b.c
-yq p --doc 2 things.yaml a.b.d
-yq p -d2 things.yaml a.b.d
- `,
- Long: `Prefixes w.r.t to the yaml file at the given path.
-Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
-`,
- RunE: prefixProperty,
- }
- cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
- cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
- return cmdWrite
-}
+// func createPrefixCmd() *cobra.Command {
+// var cmdWrite = &cobra.Command{
+// Use: "prefix [yaml_file] [path]",
+// Aliases: []string{"p"},
+// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
+// Example: `
+// yq prefix things.yaml a.b.c
+// yq prefix --inplace things.yaml a.b.c
+// yq prefix --inplace -- things.yaml --key-starting-with-dash
+// yq p -i things.yaml a.b.c
+// yq p --doc 2 things.yaml a.b.d
+// yq p -d2 things.yaml a.b.d
+// `,
+// Long: `Prefixes w.r.t to the yaml file at the given path.
+// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
+// `,
+// RunE: prefixProperty,
+// }
+// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
+// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
+// return cmdWrite
+// }
-func createDeleteCmd() *cobra.Command {
- var cmdDelete = &cobra.Command{
- Use: "delete [yaml_file] [path]",
- Aliases: []string{"d"},
- Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
- Example: `
-yq delete things.yaml a.b.c
-yq delete --inplace things.yaml a.b.c
-yq delete --inplace -- things.yaml --key-starting-with-dash
-yq d -i things.yaml a.b.c
-yq d things.yaml a.b.c
- `,
- Long: `Deletes the given path from the YAML file.
-Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
-`,
- RunE: deleteProperty,
- }
- cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
- cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
- return cmdDelete
-}
+// func createDeleteCmd() *cobra.Command {
+// var cmdDelete = &cobra.Command{
+// Use: "delete [yaml_file] [path]",
+// Aliases: []string{"d"},
+// Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
+// Example: `
+// yq delete things.yaml a.b.c
+// yq delete --inplace things.yaml a.b.c
+// yq delete --inplace -- things.yaml --key-starting-with-dash
+// yq d -i things.yaml a.b.c
+// yq d things.yaml a.b.c
+// `,
+// Long: `Deletes the given path from the YAML file.
+// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
+// `,
+// RunE: deleteProperty,
+// }
+// cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
+// cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
+// return cmdDelete
+// }
-func createNewCmd() *cobra.Command {
- var cmdNew = &cobra.Command{
- Use: "new [path] [value]",
- Aliases: []string{"n"},
- Short: "yq n [--script/-s script_file] a.b.c newValue",
- Example: `
-yq new a.b.c cat
-yq n a.b.c cat
-yq n -- --key-starting-with-dash cat
-yq n --script create_script.yaml
- `,
- Long: `Creates a new yaml w.r.t the given path and value.
-Outputs to STDOUT
+// func createNewCmd() *cobra.Command {
+// var cmdNew = &cobra.Command{
+// Use: "new [path] [value]",
+// Aliases: []string{"n"},
+// Short: "yq n [--script/-s script_file] a.b.c newValue",
+// Example: `
+// yq new a.b.c cat
+// yq n a.b.c cat
+// yq n -- --key-starting-with-dash cat
+// yq n --script create_script.yaml
+// `,
+// Long: `Creates a new yaml w.r.t the given path and value.
+// Outputs to STDOUT
-Create Scripts:
-Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
-`,
- RunE: newProperty,
- }
- cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
- return cmdNew
-}
+// Create Scripts:
+// Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
+// `,
+// RunE: newProperty,
+// }
+// cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
+// return cmdNew
+// }
-func createMergeCmd() *cobra.Command {
- var cmdMerge = &cobra.Command{
- Use: "merge [initial_yaml_file] [additional_yaml_file]...",
- Aliases: []string{"m"},
- Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml",
- Example: `
-yq merge things.yaml other.yaml
-yq merge --inplace things.yaml other.yaml
-yq m -i things.yaml other.yaml
-yq m --overwrite things.yaml other.yaml
-yq m -i -x things.yaml other.yaml
-yq m -i -a things.yaml other.yaml
- `,
- Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
-Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
+// func createMergeCmd() *cobra.Command {
+// var cmdMerge = &cobra.Command{
+// Use: "merge [initial_yaml_file] [additional_yaml_file]...",
+// Aliases: []string{"m"},
+// Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml",
+// Example: `
+// yq merge things.yaml other.yaml
+// yq merge --inplace things.yaml other.yaml
+// yq m -i things.yaml other.yaml
+// yq m --overwrite things.yaml other.yaml
+// yq m -i -x things.yaml other.yaml
+// yq m -i -a things.yaml other.yaml
+// `,
+// Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
+// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
-If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
-If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
+// If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
+// If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
-Note that if you set both flags only overwrite will take effect.
-`,
- RunE: mergeProperties,
- }
- cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
- cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
- cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
- cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
- cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
- return cmdMerge
-}
+// Note that if you set both flags only overwrite will take effect.
+// `,
+// RunE: mergeProperties,
+// }
+// cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
+// cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
+// cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
+// cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
+// cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
+// return cmdMerge
+// }
func readProperty(cmd *cobra.Command, args []string) error {
var path = ""
@@ -261,8 +260,9 @@ func readProperty(cmd *cobra.Command, args []string) error {
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
- var mappedDocs []interface{}
- var dataBucket interface{}
+
+ var mappedDocs []*yaml.Node
+ var dataBucket yaml.Node
var currentIndex = 0
var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
for {
@@ -274,21 +274,14 @@ func readProperty(cmd *cobra.Command, args []string) error {
}
return nil
}
- log.Debugf("processing %v - requested index %v", currentIndex, docIndexInt)
+ log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
if updateAll || currentIndex == docIndexInt {
- log.Debugf("reading %v in index %v", path, currentIndex)
- if path == "" {
- log.Debug("no path")
- log.Debugf("%v", dataBucket)
- mappedDocs = append(mappedDocs, dataBucket)
- } else {
- mappedDoc, errorParsing := lib.ReadPath(dataBucket, path)
- log.Debugf("%v", mappedDoc)
- if errorParsing != nil {
- return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
- }
- mappedDocs = append(mappedDocs, mappedDoc)
+ log.Debugf("reading %v in document %v", path, currentIndex)
+ mappedDoc, errorParsing := lib.Get(&dataBucket, path)
+ if errorParsing != nil {
+ return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
}
+ mappedDocs = append(mappedDocs, mappedDoc)
}
currentIndex = currentIndex + 1
}
@@ -298,56 +291,64 @@ func readProperty(cmd *cobra.Command, args []string) error {
return errorReadingStream
}
- if !updateAll {
- dataBucket = mappedDocs[0]
- } else {
- dataBucket = mappedDocs
- }
+ var encoder = yaml.NewEncoder(cmd.OutOrStdout())
+ encoder.SetIndent(2)
+ var err error
- dataStr, err := toString(dataBucket)
+ if rawOutput {
+ for _, mappedDoc := range mappedDocs {
+ if mappedDoc != nil {
+ cmd.Println(mappedDoc.Value)
+ }
+ }
+ } else if !updateAll {
+ err = encoder.Encode(mappedDocs[0])
+ } else {
+ err = encoder.Encode(&yaml.Node{Kind: yaml.SequenceNode, Content: mappedDocs})
+ }
if err != nil {
return err
}
- cmd.Println(dataStr)
+ encoder.Close()
return nil
}
-func newProperty(cmd *cobra.Command, args []string) error {
- updatedData, err := newYaml(args)
- if err != nil {
- return err
- }
- dataStr, err := toString(updatedData)
- if err != nil {
- return err
- }
- cmd.Println(dataStr)
- return nil
-}
+// func newProperty(cmd *cobra.Command, args []string) error {
+// updatedData, err := newYaml(args)
+// if err != nil {
+// return err
+// }
+// dataStr, err := toString(updatedData)
+// if err != nil {
+// return err
+// }
+// cmd.Println(dataStr)
+// return nil
+// }
-func newYaml(args []string) (interface{}, error) {
- var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide ")
- if writeCommandsError != nil {
- return nil, writeCommandsError
- }
+// func newYaml(args []string) (interface{}, error) {
+// var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide ")
+// if writeCommandsError != nil {
+// return nil, writeCommandsError
+// }
- var dataBucket interface{}
- var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[")
- if isArray {
- dataBucket = make([]interface{}, 0)
- } else {
- dataBucket = make(yaml.MapSlice, 0)
- }
+// var dataBucket interface{}
+// var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[")
+// if isArray {
+// dataBucket = make([]interface{}, 0)
+// } else {
+// dataBucket = make(yaml.MapSlice, 0)
+// }
- for _, entry := range writeCommands {
- path := entry.Key.(string)
- value := entry.Value
- log.Debugf("setting %v to %v", path, value)
- dataBucket = lib.WritePath(dataBucket, path, value)
- }
+// for _, entry := range writeCommands {
+// path := entry.Key.(string)
+// value := entry.Value
+// log.Debugf("setting %v to %v", path, value)
+// dataBucket = lib.WritePath(dataBucket, path, value)
+// }
- return dataBucket, nil
-}
+// return dataBucket, nil
+// }
func parseDocumentIndex() (bool, int, error) {
if docIndex == "*" {
@@ -360,11 +361,11 @@ func parseDocumentIndex() (bool, int, error) {
return false, int(docIndexInt64), nil
}
-type updateDataFn func(dataBucket interface{}, currentIndex int) (interface{}, error)
+type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error
func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn {
return func(decoder *yaml.Decoder) error {
- var dataBucket interface{}
+ var dataBucket yaml.Node
var errorReading error
var errorWriting error
var errorUpdating error
@@ -387,12 +388,12 @@ func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderF
} else if errorReading != nil {
return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading)
}
- dataBucket, errorUpdating = updateData(dataBucket, currentIndex)
+ errorUpdating = updateData(&dataBucket, currentIndex)
if errorUpdating != nil {
return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex)
}
- errorWriting = encoder.Encode(dataBucket)
+ errorWriting = encoder.Encode(&dataBucket)
if errorWriting != nil {
return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting)
@@ -412,43 +413,47 @@ func writeProperty(cmd *cobra.Command, args []string) error {
return errorParsingDocIndex
}
- var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
+ var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Updating doc %v", currentIndex)
for _, entry := range writeCommands {
- path := entry.Key.(string)
- value := entry.Value
- log.Debugf("setting %v to %v", path, value)
- dataBucket = lib.WritePath(dataBucket, path, value)
+ path := entry.Key
+ changesToApply := entry.Value
+ var paths = parsePath(path)
+
+ errorUpdating := updateChild(dataBucket, paths, changesToApply)
+ if errorUpdating != nil {
+ return errorUpdating
+ }
}
}
- return dataBucket, nil
+ return nil
}
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
}
-func prefixProperty(cmd *cobra.Command, args []string) error {
- if len(args) != 2 {
- return errors.New("Must provide ")
- }
- prefixPath := args[1]
+// func prefixProperty(cmd *cobra.Command, args []string) error {
+// if len(args) != 2 {
+// return errors.New("Must provide ")
+// }
+// prefixPath := args[1]
- var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
- if errorParsingDocIndex != nil {
- return errorParsingDocIndex
- }
+// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
+// if errorParsingDocIndex != nil {
+// return errorParsingDocIndex
+// }
- var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
+// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
- if updateAll || currentIndex == docIndexInt {
- log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex)
- var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath)
- return mapDataBucket, nil
- }
- return dataBucket, nil
- }
- return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
-}
+// if updateAll || currentIndex == docIndexInt {
+// log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex)
+// var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath)
+// return mapDataBucket, nil
+// }
+// return dataBucket, nil
+// }
+// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
+// }
func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
var destination io.Writer
@@ -479,90 +484,137 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
defer safelyFlush(writer)
}
var encoder = yaml.NewEncoder(destination)
+ encoder.SetIndent(2)
log.Debugf("Writing to %v from %v", destinationName, inputFile)
return readStream(inputFile, mapYamlDecoder(updateData, encoder))
}
-func deleteProperty(cmd *cobra.Command, args []string) error {
- if len(args) < 2 {
- return errors.New("Must provide ")
- }
- var deletePath = args[1]
- var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
- if errorParsingDocIndex != nil {
- return errorParsingDocIndex
- }
+// func deleteProperty(cmd *cobra.Command, args []string) error {
+// if len(args) < 2 {
+// return errors.New("Must provide ")
+// }
+// var deletePath = args[1]
+// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
+// if errorParsingDocIndex != nil {
+// return errorParsingDocIndex
+// }
- var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
- if updateAll || currentIndex == docIndexInt {
- log.Debugf("Deleting path in doc %v", currentIndex)
- return lib.DeletePath(dataBucket, deletePath)
- }
- return dataBucket, nil
- }
+// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
+// if updateAll || currentIndex == docIndexInt {
+// log.Debugf("Deleting path in doc %v", currentIndex)
+// return lib.DeletePath(dataBucket, deletePath)
+// }
+// return dataBucket, nil
+// }
- return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
+// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
+// }
+
+// func mergeProperties(cmd *cobra.Command, args []string) error {
+// if len(args) < 2 {
+// return errors.New("Must provide at least 2 yaml files")
+// }
+// var input = args[0]
+// var filesToMerge = args[1:]
+// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
+// if errorParsingDocIndex != nil {
+// return errorParsingDocIndex
+// }
+
+// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
+// if updateAll || currentIndex == docIndexInt {
+// log.Debugf("Merging doc %v", currentIndex)
+// var mergedData map[interface{}]interface{}
+// // merge only works for maps, so put everything in a temporary
+// // map
+// var mapDataBucket = make(map[interface{}]interface{})
+// mapDataBucket["root"] = dataBucket
+// if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
+// return nil, err
+// }
+// for _, f := range filesToMerge {
+// var fileToMerge interface{}
+// if err := readData(f, 0, &fileToMerge); err != nil {
+// if allowEmptyFlag && err == io.EOF {
+// continue
+// }
+// return nil, err
+// }
+// mapDataBucket["root"] = fileToMerge
+// if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
+// return nil, err
+// }
+// }
+// return mergedData["root"], nil
+// }
+// return dataBucket, nil
+// }
+// return readAndUpdate(cmd.OutOrStdout(), input, updateData)
+// }
+
+type rawWriteCommand struct {
+ // Command string TODO
+ Key string
+ Value yaml.Node
}
-func mergeProperties(cmd *cobra.Command, args []string) error {
- if len(args) < 2 {
- return errors.New("Must provide at least 2 yaml files")
- }
- var input = args[0]
- var filesToMerge = args[1:]
- var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
- if errorParsingDocIndex != nil {
- return errorParsingDocIndex
- }
-
- var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
- if updateAll || currentIndex == docIndexInt {
- log.Debugf("Merging doc %v", currentIndex)
- var mergedData map[interface{}]interface{}
- // merge only works for maps, so put everything in a temporary
- // map
- var mapDataBucket = make(map[interface{}]interface{})
- mapDataBucket["root"] = dataBucket
- if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
- return nil, err
- }
- for _, f := range filesToMerge {
- var fileToMerge interface{}
- if err := readData(f, 0, &fileToMerge); err != nil {
- if allowEmptyFlag && err == io.EOF {
- continue
- }
- return nil, err
- }
- mapDataBucket["root"] = fileToMerge
- if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
- return nil, err
- }
- }
- return mergedData["root"], nil
- }
- return dataBucket, nil
- }
- yaml.DefaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
- defer func() { yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{}) }()
- return readAndUpdate(cmd.OutOrStdout(), input, updateData)
-}
-
-func readWriteCommands(args []string, expectedArgs int, badArgsMessage string) (yaml.MapSlice, error) {
- var writeCommands yaml.MapSlice
+func readWriteCommands(args []string, expectedArgs int, badArgsMessage string) ([]rawWriteCommand, error) {
+ var writeCommands []rawWriteCommand
if writeScript != "" {
- if err := readData(writeScript, 0, &writeCommands); err != nil {
+ var rawCommands yaml.Node
+ if err := readData(writeScript, 0, &rawCommands); err != nil {
return nil, err
}
+ log.Debugf("Read write commands file '%v'", rawCommands)
+ var key string
+ for index, content := range rawCommands.Content[0].Content {
+ if index%2 == 0 { // must be the key
+ key = content.Value
+ } else { // its the value
+ writeCommands = append(writeCommands, rawWriteCommand{Key: key, Value: *content})
+ }
+ }
+ log.Debugf("Read write commands '%v'", writeCommands)
} else if len(args) < expectedArgs {
return nil, errors.New(badArgsMessage)
} else {
- writeCommands = make(yaml.MapSlice, 1)
- writeCommands[0] = yaml.MapItem{Key: args[expectedArgs-2], Value: valueParser.ParseValue(args[expectedArgs-1])}
+ writeCommands = make([]rawWriteCommand, 1)
+ writeCommands[0] = rawWriteCommand{Key: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])}
}
return writeCommands, nil
}
+func parseValue(argument string) yaml.Node {
+ var err interface{}
+ var tag = customTag
+
+ var inQuotes = len(argument) > 0 && argument[0] == '"'
+ if tag == "" && !inQuotes {
+
+ _, err = strconv.ParseBool(argument)
+ if err == nil {
+ tag = "!!bool"
+ }
+ _, err = strconv.ParseFloat(argument, 64)
+ if err == nil {
+ tag = "!!float"
+ }
+ _, err = strconv.ParseInt(argument, 10, 64)
+ if err == nil {
+ tag = "!!int"
+ }
+
+ if argument == "null" {
+ tag = "!!null"
+ }
+ if argument == "[]" {
+ return yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
+ }
+ }
+ log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag)
+ return yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
+}
+
func toString(context interface{}) (string, error) {
if outputToJSON {
return jsonConverter.JsonToString(context)
@@ -572,7 +624,7 @@ func toString(context interface{}) (string, error) {
func safelyRenameFile(from string, to string) {
if renameError := os.Rename(from, to); renameError != nil {
- log.Debugf("Error renaming from %v to %v, attemting to copy contents", from, to)
+ log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
log.Debug(renameError.Error())
// can't do this rename when running in docker to a file targeted in a mounted volume,
// so gracefully degrade to copying the entire contents.
From 972e2b957561cf5353397cc074c12ecaf7db9be8 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Fri, 6 Dec 2019 16:36:42 +1100
Subject: [PATCH 02/68] wip
---
examples/instruction_sample.yaml | 9 ++++--
pkg/yqlib/data_navigator.go | 15 ++-------
pkg/yqlib/lib.go | 29 +++++++++++++++---
yq.go | 52 ++++++++++----------------------
4 files changed, 50 insertions(+), 55 deletions(-)
diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml
index b7864db4..9834c942 100644
--- a/examples/instruction_sample.yaml
+++ b/examples/instruction_sample.yaml
@@ -1,7 +1,10 @@
-- command: update
+- command: update
path: b.c
value:
#great
things: frog # wow!
-
-b.e[+].name: Mike Farah
+- command: update
+ path: b.e[+].name
+ value: Mike Farah
+- command: delete
+ path: a
\ No newline at end of file
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 66741598..744cdbcb 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -7,14 +7,9 @@ import (
yaml "gopkg.in/yaml.v3"
)
-type WriteCommand struct {
- // Command string TODO
- Value yaml.Node
-}
-
type DataNavigator interface {
Get(rootNode *yaml.Node, remainingPath []string) (*yaml.Node, error)
- Update(rootNode *yaml.Node, remainingPath []string, writeCommand WriteCommand) error
+ Update(rootNode *yaml.Node, remainingPath []string, changesToApply yaml.Node) error
}
type navigator struct {
@@ -33,7 +28,7 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
realValue = value.Content[0]
}
if len(path) > 0 {
- n.log.Debug("diving into %v", path[0])
+ n.log.Debugf("diving into %v", path[0])
return n.recurse(realValue, path[0], path[1:])
}
return realValue, nil
@@ -117,15 +112,11 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml
}
}
-func (n *navigator) Update(dataBucket *yaml.Node, remainingPath []string, writeCommand WriteCommand) error {
+func (n *navigator) Update(dataBucket *yaml.Node, remainingPath []string, changesToApply yaml.Node) error {
nodeToUpdate, errorRecursing := n.Get(dataBucket, remainingPath)
if errorRecursing != nil {
return errorRecursing
}
- // later, support ability to execute other commands
-
- changesToApply := writeCommand.Value
-
nodeToUpdate.Value = changesToApply.Value
nodeToUpdate.Tag = changesToApply.Tag
nodeToUpdate.Kind = changesToApply.Kind
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index a477b8dc..5b630e82 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -1,24 +1,34 @@
package yqlib
import (
+ "fmt"
+
logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
)
+type UpdateCommand struct {
+ Command string
+ Path string
+ Value yaml.Node
+}
+
type YqLib interface {
Get(rootNode *yaml.Node, path string) (*yaml.Node, error)
- Update(rootNode *yaml.Node, path string, writeCommand WriteCommand) error
+ Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
}
type lib struct {
navigator DataNavigator
parser PathParser
+ log *logging.Logger
}
func NewYqLib(l *logging.Logger) YqLib {
return &lib{
navigator: NewDataNavigator(l),
parser: NewPathParser(),
+ log: l,
}
}
@@ -30,7 +40,18 @@ func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) {
return l.navigator.Get(rootNode, paths)
}
-func (l *lib) Update(rootNode *yaml.Node, path string, writeCommand WriteCommand) error {
- var paths = l.parser.ParsePath(path)
- return l.navigator.Update(rootNode, paths, writeCommand)
+func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
+ // later - support other command types
+ l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
+ switch updateCommand.Command {
+ case "update":
+ var paths = l.parser.ParsePath(updateCommand.Path)
+ return l.navigator.Update(rootNode, paths, updateCommand.Value)
+ case "delete":
+ l.log.Debugf("need to implement delete")
+ return nil
+ default:
+ return fmt.Errorf("Unknown command %v", updateCommand.Command)
+ }
+
}
diff --git a/yq.go b/yq.go
index f3b0c24d..5a21169c 100644
--- a/yq.go
+++ b/yq.go
@@ -19,7 +19,7 @@ import (
)
var rawOutput = false
-var trimOutput = true
+var customTag = ""
var writeInplace = false
var writeScript = ""
var outputToJSON = false
@@ -74,7 +74,6 @@ func newCommandCLI() *cobra.Command {
},
}
- rootCmd.PersistentFlags().BoolVarP(&trimOutput, "trim", "t", true, "trim yaml output")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
@@ -144,6 +143,7 @@ a.b.e:
}
cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
+ cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdWrite
}
@@ -327,7 +327,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
// }
// func newYaml(args []string) (interface{}, error) {
-// var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide ")
+// var writeCommands, writeCommandsError = readUpdateCommands(args, 2, "Must provide ")
// if writeCommandsError != nil {
// return nil, writeCommandsError
// }
@@ -404,9 +404,9 @@ func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderF
}
func writeProperty(cmd *cobra.Command, args []string) error {
- var writeCommands, writeCommandsError = readWriteCommands(args, 3, "Must provide ")
- if writeCommandsError != nil {
- return writeCommandsError
+ var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ")
+ if updateCommandsError != nil {
+ return updateCommandsError
}
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
@@ -416,12 +416,8 @@ func writeProperty(cmd *cobra.Command, args []string) error {
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Updating doc %v", currentIndex)
- for _, entry := range writeCommands {
- path := entry.Key
- changesToApply := entry.Value
- var paths = parsePath(path)
-
- errorUpdating := updateChild(dataBucket, paths, changesToApply)
+ for _, updateCommand := range updateCommands {
+ errorUpdating := lib.Update(dataBucket, updateCommand)
if errorUpdating != nil {
return errorUpdating
}
@@ -552,36 +548,20 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
// return readAndUpdate(cmd.OutOrStdout(), input, updateData)
// }
-type rawWriteCommand struct {
- // Command string TODO
- Key string
- Value yaml.Node
-}
-
-func readWriteCommands(args []string, expectedArgs int, badArgsMessage string) ([]rawWriteCommand, error) {
- var writeCommands []rawWriteCommand
+func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) {
+ var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1)
if writeScript != "" {
- var rawCommands yaml.Node
- if err := readData(writeScript, 0, &rawCommands); err != nil {
+ if err := readData(writeScript, 0, &updateCommands); err != nil {
return nil, err
}
- log.Debugf("Read write commands file '%v'", rawCommands)
- var key string
- for index, content := range rawCommands.Content[0].Content {
- if index%2 == 0 { // must be the key
- key = content.Value
- } else { // its the value
- writeCommands = append(writeCommands, rawWriteCommand{Key: key, Value: *content})
- }
- }
- log.Debugf("Read write commands '%v'", writeCommands)
+ log.Debugf("Read write commands file '%v'", updateCommands)
} else if len(args) < expectedArgs {
return nil, errors.New(badArgsMessage)
} else {
- writeCommands = make([]rawWriteCommand, 1)
- writeCommands[0] = rawWriteCommand{Key: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])}
+ updateCommands = make([]yqlib.UpdateCommand, 1)
+ updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])}
}
- return writeCommands, nil
+ return updateCommands, nil
}
func parseValue(argument string) yaml.Node {
@@ -619,7 +599,7 @@ func toString(context interface{}) (string, error) {
if outputToJSON {
return jsonConverter.JsonToString(context)
}
- return yamlConverter.YamlToString(context, trimOutput)
+ return yamlConverter.YamlToString(context, true)
}
func safelyRenameFile(from string, to string) {
From 676fc63219634c0bf6850a69d4de1a36d99e5194 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Fri, 6 Dec 2019 16:41:21 +1100
Subject: [PATCH 03/68] remove json conversion for now
---
pkg/marshal/json_converter.go | 54 ------------------------------
pkg/marshal/json_converter_test.go | 48 --------------------------
pkg/marshal/yaml_converter.go | 43 ------------------------
pkg/marshal/yaml_converter_test.go | 52 ----------------------------
yq.go | 10 ------
5 files changed, 207 deletions(-)
delete mode 100644 pkg/marshal/json_converter.go
delete mode 100644 pkg/marshal/json_converter_test.go
delete mode 100644 pkg/marshal/yaml_converter.go
delete mode 100644 pkg/marshal/yaml_converter_test.go
diff --git a/pkg/marshal/json_converter.go b/pkg/marshal/json_converter.go
deleted file mode 100644
index 5ca9961d..00000000
--- a/pkg/marshal/json_converter.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package marshal
-
-import (
- "encoding/json"
- "fmt"
- "strconv"
-
- yaml "github.com/mikefarah/yaml/v2"
-)
-
-type JsonConverter interface {
- JsonToString(context interface{}) (string, error)
-}
-
-type jsonConverter struct{}
-
-func NewJsonConverter() JsonConverter {
- return &jsonConverter{}
-}
-
-func (j *jsonConverter) JsonToString(context interface{}) (string, error) {
- out, err := json.Marshal(j.toJSON(context))
- if err != nil {
- return "", fmt.Errorf("error printing yaml as json: %v", err)
- }
- return string(out), nil
-}
-
-func (j *jsonConverter) toJSON(context interface{}) interface{} {
- switch context := context.(type) {
- case []interface{}:
- oldArray := context
- newArray := make([]interface{}, len(oldArray))
- for index, value := range oldArray {
- newArray[index] = j.toJSON(value)
- }
- return newArray
- case yaml.MapSlice:
- oldMap := context
- newMap := make(map[string]interface{})
- for _, entry := range oldMap {
- if str, ok := entry.Key.(string); ok {
- newMap[str] = j.toJSON(entry.Value)
- } else if i, ok := entry.Key.(int); ok {
- newMap[strconv.Itoa(i)] = j.toJSON(entry.Value)
- } else if b, ok := entry.Key.(bool); ok {
- newMap[strconv.FormatBool(b)] = j.toJSON(entry.Value)
- }
- }
- return newMap
- default:
- return context
- }
-}
diff --git a/pkg/marshal/json_converter_test.go b/pkg/marshal/json_converter_test.go
deleted file mode 100644
index e49fbd28..00000000
--- a/pkg/marshal/json_converter_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package marshal
-
-import (
- "testing"
-
- "github.com/mikefarah/yq/v2/test"
-)
-
-func TestJsonToString(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- c: 2
-`)
- got, _ := NewJsonConverter().JsonToString(data)
- test.AssertResult(t, "{\"b\":{\"c\":2}}", got)
-}
-
-func TestJsonToString_withIntKey(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- 2: c
-`)
- got, _ := NewJsonConverter().JsonToString(data)
- test.AssertResult(t, `{"b":{"2":"c"}}`, got)
-}
-
-func TestJsonToString_withBoolKey(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- false: c
-`)
- got, _ := NewJsonConverter().JsonToString(data)
- test.AssertResult(t, `{"b":{"false":"c"}}`, got)
-}
-
-func TestJsonToString_withArray(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- - item: one
- - item: two
-`)
- got, _ := NewJsonConverter().JsonToString(data)
- test.AssertResult(t, "{\"b\":[{\"item\":\"one\"},{\"item\":\"two\"}]}", got)
-}
diff --git a/pkg/marshal/yaml_converter.go b/pkg/marshal/yaml_converter.go
deleted file mode 100644
index b5826da8..00000000
--- a/pkg/marshal/yaml_converter.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package marshal
-
-import (
- "strings"
-
- yaml "github.com/mikefarah/yaml/v2"
- errors "github.com/pkg/errors"
-)
-
-type YamlConverter interface {
- YamlToString(context interface{}, trimOutput bool) (string, error)
-}
-
-type yamlConverter struct{}
-
-func NewYamlConverter() YamlConverter {
- return &yamlConverter{}
-}
-
-func (y *yamlConverter) YamlToString(context interface{}, trimOutput bool) (string, error) {
- switch context := context.(type) {
- case string:
- return context, nil
- default:
- return y.marshalContext(context, trimOutput)
- }
-}
-
-func (y *yamlConverter) marshalContext(context interface{}, trimOutput bool) (string, error) {
- out, err := yaml.Marshal(context)
-
- if err != nil {
- return "", errors.Wrap(err, "error printing yaml")
- }
-
- outStr := string(out)
- // trim the trailing new line as it's easier for a script to add
- // it in if required than to remove it
- if trimOutput {
- return strings.Trim(outStr, "\n "), nil
- }
- return outStr, nil
-}
diff --git a/pkg/marshal/yaml_converter_test.go b/pkg/marshal/yaml_converter_test.go
deleted file mode 100644
index 35e9a4d1..00000000
--- a/pkg/marshal/yaml_converter_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package marshal
-
-import (
- "testing"
-
- "github.com/mikefarah/yq/v2/test"
-)
-
-func TestYamlToString(t *testing.T) {
- var raw = `b:
- c: 2
-`
- var data = test.ParseData(raw)
- got, _ := NewYamlConverter().YamlToString(data, false)
- test.AssertResult(t, raw, got)
-}
-
-func TestYamlToString_withTrim(t *testing.T) {
- var raw = `b:
- c: 2`
- var data = test.ParseData(raw)
- got, _ := NewYamlConverter().YamlToString(data, true)
- test.AssertResult(t, raw, got)
-}
-
-func TestYamlToString_withIntKey(t *testing.T) {
- var raw = `b:
- 2: c
-`
- var data = test.ParseData(raw)
- got, _ := NewYamlConverter().YamlToString(data, false)
- test.AssertResult(t, raw, got)
-}
-
-func TestYamlToString_withBoolKey(t *testing.T) {
- var raw = `b:
- false: c
-`
- var data = test.ParseData(raw)
- got, _ := NewYamlConverter().YamlToString(data, false)
- test.AssertResult(t, raw, got)
-}
-
-func TestYamlToString_withArray(t *testing.T) {
- var raw = `b:
-- item: one
-- item: two
-`
- var data = test.ParseData(raw)
- got, _ := NewYamlConverter().YamlToString(data, false)
- test.AssertResult(t, raw, got)
-}
diff --git a/yq.go b/yq.go
index 5a21169c..c0d576c4 100644
--- a/yq.go
+++ b/yq.go
@@ -8,7 +8,6 @@ import (
"os"
"strconv"
- "github.com/mikefarah/yq/v3/pkg/marshal"
"github.com/mikefarah/yq/v3/pkg/yqlib"
errors "github.com/pkg/errors"
@@ -31,8 +30,6 @@ var version = false
var docIndex = "0"
var log = logging.MustGetLogger("yq")
var lib = yqlib.NewYqLib(log)
-var jsonConverter = marshal.NewJsonConverter()
-var yamlConverter = marshal.NewYamlConverter()
var valueParser = yqlib.NewValueParser()
func main() {
@@ -595,13 +592,6 @@ func parseValue(argument string) yaml.Node {
return yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
}
-func toString(context interface{}) (string, error) {
- if outputToJSON {
- return jsonConverter.JsonToString(context)
- }
- return yamlConverter.YamlToString(context, true)
-}
-
func safelyRenameFile(from string, to string) {
if renameError := os.Rename(from, to); renameError != nil {
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
From dad61ec615a1ace97cd2505fb3308a0584463317 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Fri, 6 Dec 2019 16:52:00 +1100
Subject: [PATCH 04/68] remove json conversion for now
---
yq.go | 2 --
1 file changed, 2 deletions(-)
diff --git a/yq.go b/yq.go
index c0d576c4..d61dc7e1 100644
--- a/yq.go
+++ b/yq.go
@@ -21,7 +21,6 @@ var rawOutput = false
var customTag = ""
var writeInplace = false
var writeScript = ""
-var outputToJSON = false
var overwriteFlag = false
var allowEmptyFlag = false
var appendFlag = false
@@ -105,7 +104,6 @@ yq r -- things.yaml --key-starting-with-dashes
}
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
cmdRead.PersistentFlags().BoolVarP(&rawOutput, "raw", "r", false, "raw yaml output - prints out values instead of yaml")
- cmdRead.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
return cmdRead
}
From d97f1d8be2fd3cf0d1b0be6f658aab9ea42a9b84 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 8 Dec 2019 15:37:30 +1100
Subject: [PATCH 05/68] recurse
---
pkg/yqlib/data_navigator.go | 36 +++++++++++++++++++++++++++---------
1 file changed, 27 insertions(+), 9 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 744cdbcb..53160a3c 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -1,6 +1,7 @@
package yqlib
import (
+ "bytes"
"strconv"
logging "gopkg.in/op/go-logging.v1"
@@ -29,6 +30,7 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
}
if len(path) > 0 {
n.log.Debugf("diving into %v", path[0])
+ n.debugNode(value)
return n.recurse(realValue, path[0], path[1:])
}
return realValue, nil
@@ -37,7 +39,7 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
func (n *navigator) guessKind(tail []string) yaml.Kind {
n.log.Debug("tail %v", tail)
if len(tail) == 0 {
- n.log.Debug("scalar")
+ n.log.Debug("end of path, must be a scalar")
return yaml.ScalarNode
}
var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64)
@@ -53,11 +55,23 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y
// when reading, it should deal with the original kind
// when writing, it will clobber the kind anyway
if original.Kind != expectedKind && (expectedKind != yaml.ScalarNode) {
+ n.log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind)
return &yaml.Node{Kind: expectedKind}
}
return original
}
+func (n *navigator) debugNode(value *yaml.Node) {
+ if n.log.IsEnabledFor(logging.DEBUG) {
+ buf := new(bytes.Buffer)
+ encoder := yaml.NewEncoder(buf)
+ encoder.Encode(value)
+ encoder.Close()
+ n.log.Debug("Tag: %v", value.Tag)
+ n.log.Debug("%v", buf.String())
+ }
+}
+
func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml.Node, error) {
switch value.Kind {
case yaml.MappingNode:
@@ -77,20 +91,24 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml
n.log.Debug("adding new node %v", value.Content)
return n.Get(&mapEntryValue, tail)
case yaml.SequenceNode:
- n.log.Debug("its a sequence of %v things!", len(value.Content))
+ n.log.Debug("its a sequence of %v things!, %v", len(value.Content))
if head == "*" {
- var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style}
- newNode.Content = make([]*yaml.Node, len(value.Content))
+ originalContent := value.Content
+ value.Content = make([]*yaml.Node, len(value.Content))
- for index, value := range value.Content {
- value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail))
- var nestedValue, err = n.Get(value.Content[index], tail)
+ for index, childValue := range originalContent {
+ n.log.Debug("processing")
+ n.debugNode(childValue)
+ childValue = n.getOrReplace(childValue, n.guessKind(tail))
+ var nestedValue, err = n.Get(childValue, tail)
+ n.log.Debug("nestedValue")
+ n.debugNode(nestedValue)
if err != nil {
return nil, err
}
- newNode.Content[index] = nestedValue
+ value.Content[index] = nestedValue
}
- return &newNode, nil
+ return value, nil
} else if head == "+" {
var newNode = yaml.Node{Kind: n.guessKind(tail)}
From 8da9a8170276be238b01ed1cb30310869e21f8af Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 8 Dec 2019 15:59:24 +1100
Subject: [PATCH 06/68] visitor!
---
pkg/yqlib/data_navigator.go | 71 ++++++++++++++++++++++---------------
1 file changed, 42 insertions(+), 29 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 53160a3c..a49dd6f7 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -17,6 +17,12 @@ type navigator struct {
log *logging.Logger
}
+type VisitorFn func(*yaml.Node) (*yaml.Node, error)
+
+func identityVisitor(value *yaml.Node) (*yaml.Node, error) {
+ return value, nil
+}
+
func NewDataNavigator(l *logging.Logger) DataNavigator {
return &navigator{
log: l,
@@ -24,6 +30,29 @@ func NewDataNavigator(l *logging.Logger) DataNavigator {
}
func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
+ return n.Visit(value, path, identityVisitor)
+}
+
+func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml.Node) error {
+ _, errorVisiting := n.Visit(value, path, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) {
+ n.log.Debug("going to update")
+ n.debugNode(nodeToUpdate)
+ n.log.Debug("with")
+ n.debugNode(&changesToApply)
+ nodeToUpdate.Value = changesToApply.Value
+ nodeToUpdate.Tag = changesToApply.Tag
+ nodeToUpdate.Kind = changesToApply.Kind
+ nodeToUpdate.Style = changesToApply.Style
+ nodeToUpdate.Content = changesToApply.Content
+ nodeToUpdate.HeadComment = changesToApply.HeadComment
+ nodeToUpdate.LineComment = changesToApply.LineComment
+ nodeToUpdate.FootComment = changesToApply.FootComment
+ return nodeToUpdate, nil
+ })
+ return errorVisiting
+}
+
+func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) (*yaml.Node, error) {
realValue := value
if realValue.Kind == yaml.DocumentNode {
realValue = value.Content[0]
@@ -31,9 +60,9 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
if len(path) > 0 {
n.log.Debugf("diving into %v", path[0])
n.debugNode(value)
- return n.recurse(realValue, path[0], path[1:])
+ return n.recurse(realValue, path[0], path[1:], visitor)
}
- return realValue, nil
+ return visitor(realValue)
}
func (n *navigator) guessKind(tail []string) yaml.Kind {
@@ -72,7 +101,7 @@ func (n *navigator) debugNode(value *yaml.Node) {
}
}
-func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml.Node, error) {
+func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) {
switch value.Kind {
case yaml.MappingNode:
n.log.Debug("its a map with %v entries", len(value.Content)/2)
@@ -83,38 +112,38 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml
continue
}
value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail))
- return n.Get(value.Content[index+1], tail)
+ return n.Visit(value.Content[index+1], tail, visitor)
}
value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode})
mapEntryValue := yaml.Node{Kind: n.guessKind(tail)}
value.Content = append(value.Content, &mapEntryValue)
n.log.Debug("adding new node %v", value.Content)
- return n.Get(&mapEntryValue, tail)
+ return n.Visit(&mapEntryValue, tail, visitor)
case yaml.SequenceNode:
n.log.Debug("its a sequence of %v things!, %v", len(value.Content))
if head == "*" {
- originalContent := value.Content
- value.Content = make([]*yaml.Node, len(value.Content))
+ var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style}
+ newNode.Content = make([]*yaml.Node, len(value.Content))
- for index, childValue := range originalContent {
+ for index, childValue := range value.Content {
n.log.Debug("processing")
n.debugNode(childValue)
childValue = n.getOrReplace(childValue, n.guessKind(tail))
- var nestedValue, err = n.Get(childValue, tail)
+ var nestedValue, err = n.Visit(childValue, tail, visitor)
n.log.Debug("nestedValue")
n.debugNode(nestedValue)
if err != nil {
return nil, err
}
- value.Content[index] = nestedValue
+ newNode.Content[index] = nestedValue
}
- return value, nil
+ return &newNode, nil
} else if head == "+" {
var newNode = yaml.Node{Kind: n.guessKind(tail)}
value.Content = append(value.Content, &newNode)
n.log.Debug("appending a new node, %v", value.Content)
- return n.Get(&newNode, tail)
+ return n.Visit(&newNode, tail, visitor)
}
var index, err = strconv.ParseInt(head, 10, 64) // nolint
if err != nil {
@@ -124,28 +153,12 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string) (*yaml
return nil, nil
}
value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail))
- return n.Get(value.Content[index], tail)
+ return n.Visit(value.Content[index], tail, visitor)
default:
return nil, nil
}
}
-func (n *navigator) Update(dataBucket *yaml.Node, remainingPath []string, changesToApply yaml.Node) error {
- nodeToUpdate, errorRecursing := n.Get(dataBucket, remainingPath)
- if errorRecursing != nil {
- return errorRecursing
- }
- nodeToUpdate.Value = changesToApply.Value
- nodeToUpdate.Tag = changesToApply.Tag
- nodeToUpdate.Kind = changesToApply.Kind
- nodeToUpdate.Style = changesToApply.Style
- nodeToUpdate.Content = changesToApply.Content
- nodeToUpdate.HeadComment = changesToApply.HeadComment
- nodeToUpdate.LineComment = changesToApply.LineComment
- nodeToUpdate.FootComment = changesToApply.FootComment
- return nil
-}
-
// func matchesKey(key string, actual interface{}) bool {
// var actualString = fmt.Sprintf("%v", actual)
// var prefixMatch = strings.TrimSuffix(key, "*")
From 9771e7001c3f7ea9ecb88ff7ced976621d2f41f3 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 9 Dec 2019 13:44:53 +1100
Subject: [PATCH 07/68] splatting
---
commands_test.go | 135 +++---
compare.sh | 13 +
examples/sample.yaml | 13 +-
pkg/yqlib/data_navigator.go | 54 ++-
pkg/yqlib/data_navigator_test.go | 694 +++++++++++++++----------------
pkg/yqlib/lib.go | 8 +-
pkg/yqlib/lib_test.go | 2 +-
pkg/yqlib/path_parser.go | 3 +
pkg/yqlib/path_parser_test.go | 2 +-
pkg/yqlib/value_parser_test.go | 2 +-
test/utils.go | 6 +-
yq.go | 6 +-
yq_test.go | 104 ++---
13 files changed, 527 insertions(+), 515 deletions(-)
create mode 100755 compare.sh
diff --git a/commands_test.go b/commands_test.go
index 90409cb8..49f12578 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -7,7 +7,7 @@ import (
"strings"
"testing"
- "github.com/mikefarah/yq/v2/test"
+ "github.com/mikefarah/yq/v3/test"
"github.com/spf13/cobra"
)
@@ -63,30 +63,6 @@ func TestRootCmd_VerboseShort(t *testing.T) {
}
}
-func TestRootCmd_TrimLong(t *testing.T) {
- cmd := getRootCommand()
- result := test.RunCmd(cmd, "--trim")
- if result.Error != nil {
- t.Error(result.Error)
- }
-
- if !trimOutput {
- t.Error("Expected trimOutput to be true")
- }
-}
-
-func TestRootCmd_TrimShort(t *testing.T) {
- cmd := getRootCommand()
- result := test.RunCmd(cmd, "-t")
- if result.Error != nil {
- t.Error(result.Error)
- }
-
- if !trimOutput {
- t.Error("Expected trimOutput to be true")
- }
-}
-
func TestRootCmd_VersionShort(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "-V")
@@ -191,7 +167,7 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
expectedOutput := `- become: true
gather_facts: false
hosts: lalaland
- name: Apply smth
+ name: "Apply smth"
roles:
- lala
- land
@@ -209,7 +185,7 @@ func TestReadCmd_ArrayYaml_OneElement(t *testing.T) {
expectedOutput := `become: true
gather_facts: false
hosts: lalaland
-name: Apply smth
+name: "Apply smth"
roles:
- lala
- land
@@ -227,7 +203,7 @@ func TestReadCmd_ArrayYaml_Splat(t *testing.T) {
expectedOutput := `- become: true
gather_facts: false
hosts: lalaland
- name: Apply smth
+ name: "Apply smth"
roles:
- lala
- land
@@ -252,19 +228,19 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
- expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
+ 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) {
- cmd := getRootCommand()
- result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]")
- if result.Error == nil {
- t.Error("Expected command to fail due to invalid path")
- }
- expectedOutput := `Error reading path in document index 0: error accessing array: 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]")
+// if result.Error == nil {
+// t.Error("Expected command to fail due to invalid path")
+// }
+// expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
+// test.AssertResult(t, expectedOutput, result.Error.Error())
+// }
func TestReadCmd_Error(t *testing.T) {
cmd := getRootCommand()
@@ -301,27 +277,27 @@ func TestReadCmd_ErrorUnreadableFile(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error())
}
-func TestReadCmd_ErrorBadPath(t *testing.T) {
- content := `b:
- d:
- e:
- - 3
- - 4
- f:
- - 1
- - 2
-`
- filename := test.WriteTempYamlFile(content)
- defer test.RemoveTempYamlFile(filename)
+// func TestReadCmd_ErrorBadPath(t *testing.T) {
+// content := `b:
+// d:
+// e:
+// - 3
+// - 4
+// f:
+// - 1
+// - 2
+// `
+// filename := test.WriteTempYamlFile(content)
+// defer test.RemoveTempYamlFile(filename)
- cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
- if result.Error == nil {
- t.Fatal("Expected command to fail due to invalid path")
- }
- expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
- test.AssertResult(t, expectedOutput, result.Error.Error())
-}
+// cmd := getRootCommand()
+// result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
+// if result.Error == nil {
+// t.Fatal("Expected command to fail due to invalid path")
+// }
+// expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
+// test.AssertResult(t, expectedOutput, result.Error.Error())
+// }
func TestReadCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
@@ -332,32 +308,23 @@ func TestReadCmd_Verbose(t *testing.T) {
test.AssertResult(t, "2\n", result.Output)
}
-func TestReadCmd_NoTrim(t *testing.T) {
- cmd := getRootCommand()
- result := test.RunCmd(cmd, "--trim=false read examples/sample.yaml b.c")
- if result.Error != nil {
- t.Error(result.Error)
- }
- test.AssertResult(t, "2\n\n", result.Output)
-}
+// func TestReadCmd_ToJson(t *testing.T) {
+// cmd := getRootCommand()
+// result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c")
+// if result.Error != nil {
+// t.Error(result.Error)
+// }
+// test.AssertResult(t, "2\n", result.Output)
+// }
-func TestReadCmd_ToJson(t *testing.T) {
- cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c")
- if result.Error != nil {
- t.Error(result.Error)
- }
- test.AssertResult(t, "2\n", result.Output)
-}
-
-func TestReadCmd_ToJsonLong(t *testing.T) {
- cmd := getRootCommand()
- result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c")
- if result.Error != nil {
- t.Error(result.Error)
- }
- test.AssertResult(t, "2\n", result.Output)
-}
+// func TestReadCmd_ToJsonLong(t *testing.T) {
+// cmd := getRootCommand()
+// result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c")
+// if result.Error != nil {
+// t.Error(result.Error)
+// }
+// test.AssertResult(t, "2\n", result.Output)
+// }
func TestPrefixCmd(t *testing.T) {
content := `b:
@@ -851,7 +818,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `b:
- c: thing
+ c: {}
d: another thing
`
test.AssertResult(t, expectedOutput, result.Output)
diff --git a/compare.sh b/compare.sh
new file mode 100755
index 00000000..fc8cd8bf
--- /dev/null
+++ b/compare.sh
@@ -0,0 +1,13 @@
+GREEN='\033[0;32m'
+NC='\033[0m'
+
+echo "${GREEN}---Old---${NC}"
+yq $@ > /tmp/yq-old-output
+cat /tmp/yq-old-output
+
+echo "${GREEN}---New---${NC}"
+./yq $@ > /tmp/yq-new-output
+cat /tmp/yq-new-output
+
+echo "${GREEN}---Diff---${NC}"
+colordiff /tmp/yq-old-output /tmp/yq-new-output
\ No newline at end of file
diff --git a/examples/sample.yaml b/examples/sample.yaml
index b26830e8..e847c407 100644
--- a/examples/sample.yaml
+++ b/examples/sample.yaml
@@ -1,9 +1,8 @@
a: Easy! as one two three
b:
- c: 2
- d: [3, 4]
- e:
- - name: fred
- value: 3
- - name: sam
- value: 4
\ No newline at end of file
+ c:
+ name: c1
+ f: things
+ d:
+ name: d1
+ f: other
\ No newline at end of file
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index a49dd6f7..3776d4ef 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -9,6 +9,7 @@ import (
)
type DataNavigator interface {
+ DebugNode(node *yaml.Node)
Get(rootNode *yaml.Node, remainingPath []string) (*yaml.Node, error)
Update(rootNode *yaml.Node, remainingPath []string, changesToApply yaml.Node) error
}
@@ -36,9 +37,9 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml.Node) error {
_, errorVisiting := n.Visit(value, path, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) {
n.log.Debug("going to update")
- n.debugNode(nodeToUpdate)
+ n.DebugNode(nodeToUpdate)
n.log.Debug("with")
- n.debugNode(&changesToApply)
+ n.DebugNode(&changesToApply)
nodeToUpdate.Value = changesToApply.Value
nodeToUpdate.Tag = changesToApply.Tag
nodeToUpdate.Kind = changesToApply.Kind
@@ -55,26 +56,33 @@ func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml.
func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) (*yaml.Node, error) {
realValue := value
if realValue.Kind == yaml.DocumentNode {
+ n.log.Debugf("its a document! returning the first child")
realValue = value.Content[0]
}
if len(path) > 0 {
n.log.Debugf("diving into %v", path[0])
- n.debugNode(value)
+ n.DebugNode(value)
return n.recurse(realValue, path[0], path[1:], visitor)
}
return visitor(realValue)
}
-func (n *navigator) guessKind(tail []string) yaml.Kind {
+func (n *navigator) guessKind(tail []string, guess yaml.Kind) yaml.Kind {
n.log.Debug("tail %v", tail)
- if len(tail) == 0 {
+ if len(tail) == 0 && guess == 0 {
n.log.Debug("end of path, must be a scalar")
return yaml.ScalarNode
+ } else if len(tail) == 0 {
+ return guess
}
+
var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64)
- if tail[0] == "*" || tail[0] == "+" || errorParsingInt == nil {
+ if tail[0] == "+" || errorParsingInt == nil {
return yaml.SequenceNode
}
+ if tail[0] == "*" && guess == yaml.SequenceNode || guess == yaml.MappingNode {
+ return guess
+ }
return yaml.MappingNode
}
@@ -90,7 +98,7 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y
return original
}
-func (n *navigator) debugNode(value *yaml.Node) {
+func (n *navigator) DebugNode(value *yaml.Node) {
if n.log.IsEnabledFor(logging.DEBUG) {
buf := new(bytes.Buffer)
encoder := yaml.NewEncoder(buf)
@@ -105,17 +113,33 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
switch value.Kind {
case yaml.MappingNode:
n.log.Debug("its a map with %v entries", len(value.Content)/2)
+ if head == "*" {
+ var newNode = yaml.Node{Kind: yaml.SequenceNode}
+ for index, content := range value.Content {
+ if index%2 == 0 {
+ continue
+ }
+ content = n.getOrReplace(content, n.guessKind(tail, content.Kind))
+ var nestedValue, err = n.Visit(content, tail, visitor)
+ if err != nil {
+ return nil, err
+ }
+ newNode.Content = append(newNode.Content, nestedValue)
+ }
+ return &newNode, nil
+ }
+
for index, content := range value.Content {
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
- if index%2 == 1 || content.Value != head {
+ if index%2 == 1 || (content.Value != head) {
continue
}
- value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail))
+ value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail, value.Content[index+1].Kind))
return n.Visit(value.Content[index+1], tail, visitor)
}
value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode})
- mapEntryValue := yaml.Node{Kind: n.guessKind(tail)}
+ mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)}
value.Content = append(value.Content, &mapEntryValue)
n.log.Debug("adding new node %v", value.Content)
return n.Visit(&mapEntryValue, tail, visitor)
@@ -127,11 +151,11 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
for index, childValue := range value.Content {
n.log.Debug("processing")
- n.debugNode(childValue)
- childValue = n.getOrReplace(childValue, n.guessKind(tail))
+ n.DebugNode(childValue)
+ childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind))
var nestedValue, err = n.Visit(childValue, tail, visitor)
n.log.Debug("nestedValue")
- n.debugNode(nestedValue)
+ n.DebugNode(nestedValue)
if err != nil {
return nil, err
}
@@ -140,7 +164,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
return &newNode, nil
} else if head == "+" {
- var newNode = yaml.Node{Kind: n.guessKind(tail)}
+ var newNode = yaml.Node{Kind: n.guessKind(tail, 0)}
value.Content = append(value.Content, &newNode)
n.log.Debug("appending a new node, %v", value.Content)
return n.Visit(&newNode, tail, visitor)
@@ -152,7 +176,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
if index >= int64(len(value.Content)) {
return nil, nil
}
- value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail))
+ value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind))
return n.Visit(value.Content[index], tail, visitor)
default:
return nil, nil
diff --git a/pkg/yqlib/data_navigator_test.go b/pkg/yqlib/data_navigator_test.go
index 614a4977..d537d665 100644
--- a/pkg/yqlib/data_navigator_test.go
+++ b/pkg/yqlib/data_navigator_test.go
@@ -1,397 +1,397 @@
package yqlib
-import (
- "fmt"
- "sort"
- "testing"
+// import (
+// "fmt"
+// "sort"
+// "testing"
- "github.com/mikefarah/yq/v2/test"
- logging "gopkg.in/op/go-logging.v1"
-)
+// "github.com/mikefarah/yq/v2/test"
+// logging "gopkg.in/op/go-logging.v1"
+// )
-func TestDataNavigator(t *testing.T) {
- var log = logging.MustGetLogger("yq")
- subject := NewDataNavigator(log)
+// func TestDataNavigator(t *testing.T) {
+// var log = logging.MustGetLogger("yq")
+// subject := NewDataNavigator(log)
- t.Run("TestReadMap_simple", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- c: 2
-`)
- got, _ := subject.ReadChildValue(data, []string{"b", "c"})
- test.AssertResult(t, 2, got)
- })
+// t.Run("TestReadMap_simple", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// b:
+// c: 2
+// `)
+// got, _ := subject.ReadChildValue(data, []string{"b", "c"})
+// test.AssertResult(t, 2, got)
+// })
- t.Run("TestReadMap_numberKey", func(t *testing.T) {
- var data = test.ParseData(`
----
-200: things
-`)
- got, _ := subject.ReadChildValue(data, []string{"200"})
- test.AssertResult(t, "things", got)
- })
+// t.Run("TestReadMap_numberKey", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// 200: things
+// `)
+// got, _ := subject.ReadChildValue(data, []string{"200"})
+// test.AssertResult(t, "things", got)
+// })
- t.Run("TestReadMap_splat", func(t *testing.T) {
- var data = test.ParseData(`
----
-mapSplat:
- item1: things
- item2: whatever
- otherThing: cat
-`)
- res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"})
- test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res))
- })
+// t.Run("TestReadMap_splat", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// mapSplat:
+// item1: things
+// item2: whatever
+// otherThing: cat
+// `)
+// res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"})
+// test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res))
+// })
- t.Run("TestReadMap_prefixSplat", func(t *testing.T) {
- var data = test.ParseData(`
----
-mapSplat:
- item1: things
- item2: whatever
- otherThing: cat
-`)
- res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"})
- test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res))
- })
+// t.Run("TestReadMap_prefixSplat", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// mapSplat:
+// item1: things
+// item2: whatever
+// otherThing: cat
+// `)
+// res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"})
+// test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res))
+// })
- t.Run("TestReadMap_deep_splat", func(t *testing.T) {
- var data = test.ParseData(`
----
-mapSplatDeep:
- item1:
- cats: bananas
- item2:
- cats: apples
-`)
+// t.Run("TestReadMap_deep_splat", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// mapSplatDeep:
+// item1:
+// cats: bananas
+// item2:
+// cats: apples
+// `)
- res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"})
- result := res.([]interface{})
- var actual = []string{result[0].(string), result[1].(string)}
- sort.Strings(actual)
- test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual))
- })
+// res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"})
+// result := res.([]interface{})
+// var actual = []string{result[0].(string), result[1].(string)}
+// sort.Strings(actual)
+// test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual))
+// })
- t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- c: 2
-`)
- got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"})
- test.AssertResult(t, nil, got)
- })
+// t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// b:
+// c: 2
+// `)
+// got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"})
+// test.AssertResult(t, nil, got)
+// })
- t.Run("TestReadMap_recurse_against_string", func(t *testing.T) {
- var data = test.ParseData(`
----
-a: cat
-`)
- got, _ := subject.ReadChildValue(data, []string{"a", "b"})
- test.AssertResult(t, nil, got)
- })
+// t.Run("TestReadMap_recurse_against_string", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// a: cat
+// `)
+// got, _ := subject.ReadChildValue(data, []string{"a", "b"})
+// test.AssertResult(t, nil, got)
+// })
- t.Run("TestReadMap_with_array", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- d:
- - 3
- - 4
-`)
- got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"})
- test.AssertResult(t, 4, got)
- })
+// t.Run("TestReadMap_with_array", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// b:
+// d:
+// - 3
+// - 4
+// `)
+// got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"})
+// test.AssertResult(t, 4, got)
+// })
- t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- d:
- - 3
- - 4
-`)
- _, err := subject.ReadChildValue(data, []string{"b", "d", "x"})
- if err == nil {
- t.Fatal("Expected error due to invalid path")
- }
- expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
- test.AssertResult(t, expectedOutput, err.Error())
- })
+// t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// b:
+// d:
+// - 3
+// - 4
+// `)
+// _, err := subject.ReadChildValue(data, []string{"b", "d", "x"})
+// if err == nil {
+// t.Fatal("Expected error due to invalid path")
+// }
+// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
+// test.AssertResult(t, expectedOutput, err.Error())
+// })
- t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- d:
- e:
- - 3
- - 4
- f:
- - 1
- - 2
-`)
- _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"})
- if err == nil {
- t.Fatal("Expected error due to invalid path")
- }
- expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
- test.AssertResult(t, expectedOutput, err.Error())
- })
+// t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// b:
+// d:
+// e:
+// - 3
+// - 4
+// f:
+// - 1
+// - 2
+// `)
+// _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"})
+// if err == nil {
+// t.Fatal("Expected error due to invalid path")
+// }
+// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
+// test.AssertResult(t, expectedOutput, err.Error())
+// })
- t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- d:
- - names:
- - fred
- - smith
- - names:
- - sam
- - bo
-`)
- _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"})
- if err == nil {
- t.Fatal("Expected error due to invalid path")
- }
- expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
- test.AssertResult(t, expectedOutput, err.Error())
- })
+// t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// b:
+// d:
+// - names:
+// - fred
+// - smith
+// - names:
+// - sam
+// - bo
+// `)
+// _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"})
+// if err == nil {
+// t.Fatal("Expected error due to invalid path")
+// }
+// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
+// test.AssertResult(t, expectedOutput, err.Error())
+// })
- t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- d:
- - 3
- - 4
-`)
- got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"})
- test.AssertResult(t, nil, got)
- })
+// t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// b:
+// d:
+// - 3
+// - 4
+// `)
+// got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"})
+// test.AssertResult(t, nil, got)
+// })
- t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- d:
- - 3
- - 4
-`)
- got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"})
- test.AssertResult(t, nil, got)
- })
+// t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) {
+// var data = test.ParseData(`
+// ---
+// b:
+// d:
+// - 3
+// - 4
+// `)
+// got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"})
+// test.AssertResult(t, nil, got)
+// })
- t.Run("TestReadMap_with_array_splat", func(t *testing.T) {
- var data = test.ParseData(`
-e:
- -
- name: Fred
- thing: cat
- -
- name: Sam
- thing: dog
-`)
- got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"})
- test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got))
- })
+// t.Run("TestReadMap_with_array_splat", func(t *testing.T) {
+// var data = test.ParseData(`
+// e:
+// -
+// name: Fred
+// thing: cat
+// -
+// name: Sam
+// thing: dog
+// `)
+// got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"})
+// test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got))
+// })
- t.Run("TestWrite_really_simple", func(t *testing.T) {
- var data = test.ParseData(`
-b: 2
-`)
+// t.Run("TestWrite_really_simple", func(t *testing.T) {
+// var data = test.ParseData(`
+// b: 2
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b"}, "4")
- test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated))
- })
+// updated := subject.UpdatedChildValue(data, []string{"b"}, "4")
+// test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_simple", func(t *testing.T) {
- var data = test.ParseData(`
-b:
- c: 2
-`)
+// t.Run("TestWrite_simple", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// c: 2
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4")
- test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated))
- })
+// updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4")
+// test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_new", func(t *testing.T) {
- var data = test.ParseData(`
-b:
- c: 2
-`)
+// t.Run("TestWrite_new", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// c: 2
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4")
- test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated))
- })
+// updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4")
+// test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_new_deep", func(t *testing.T) {
- var data = test.ParseData(`
-b:
- c: 2
-`)
+// t.Run("TestWrite_new_deep", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// c: 2
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4")
- test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated))
- })
+// updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4")
+// test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_array", func(t *testing.T) {
- var data = test.ParseData(`
-b:
- - aa
-`)
+// t.Run("TestWrite_array", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// - aa
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "bb")
+// updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "bb")
- test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated))
- })
+// test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_new_array", func(t *testing.T) {
- var data = test.ParseData(`
-b:
- c: 2
-`)
+// t.Run("TestWrite_new_array", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// c: 2
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4")
- test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated))
- })
+// updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4")
+// test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_new_array_deep", func(t *testing.T) {
- var data = test.ParseData(`
-a: apple
-`)
+// t.Run("TestWrite_new_array_deep", func(t *testing.T) {
+// var data = test.ParseData(`
+// a: apple
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4")
- test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated))
- })
+// updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4")
+// test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_new_map_array_deep", func(t *testing.T) {
- var data = test.ParseData(`
-b:
- c: 2
-`)
+// t.Run("TestWrite_new_map_array_deep", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// c: 2
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4")
- test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated))
- })
+// updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4")
+// test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_add_to_array", func(t *testing.T) {
- var data = test.ParseData(`
-b:
- - aa
-`)
+// t.Run("TestWrite_add_to_array", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// - aa
+// `)
- updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb")
- test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated))
- })
+// updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb")
+// test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWrite_with_no_tail", func(t *testing.T) {
- var data = test.ParseData(`
-b:
- c: 2
-`)
- updated := subject.UpdatedChildValue(data, []string{"b"}, "4")
+// t.Run("TestWrite_with_no_tail", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// c: 2
+// `)
+// updated := subject.UpdatedChildValue(data, []string{"b"}, "4")
- test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated))
- })
+// test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated))
+// })
- t.Run("TestWriteMap_no_paths", func(t *testing.T) {
- var data = test.ParseData(`
-b: 5
-`)
- var new = test.ParseData(`
-c: 4
-`)
- result := subject.UpdatedChildValue(data, []string{}, new)
- test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
- })
+// t.Run("TestWriteMap_no_paths", func(t *testing.T) {
+// var data = test.ParseData(`
+// b: 5
+// `)
+// var new = test.ParseData(`
+// c: 4
+// `)
+// result := subject.UpdatedChildValue(data, []string{}, new)
+// test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
+// })
- t.Run("TestWriteArray_no_paths", func(t *testing.T) {
- var data = make([]interface{}, 1)
- data[0] = "mike"
- var new = test.ParseData(`
-c: 4
-`)
- result := subject.UpdatedChildValue(data, []string{}, new)
- test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
- })
+// t.Run("TestWriteArray_no_paths", func(t *testing.T) {
+// var data = make([]interface{}, 1)
+// data[0] = "mike"
+// var new = test.ParseData(`
+// c: 4
+// `)
+// result := subject.UpdatedChildValue(data, []string{}, new)
+// test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
+// })
- t.Run("TestDelete_MapItem", func(t *testing.T) {
- var data = test.ParseData(`
-a: 123
-b: 456
-`)
- var expected = test.ParseData(`
-b: 456
-`)
+// t.Run("TestDelete_MapItem", func(t *testing.T) {
+// var data = test.ParseData(`
+// a: 123
+// b: 456
+// `)
+// var expected = test.ParseData(`
+// b: 456
+// `)
- result, _ := subject.DeleteChildValue(data, []string{"a"})
- test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
- })
+// result, _ := subject.DeleteChildValue(data, []string{"a"})
+// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
+// })
- // Ensure deleting an index into a string does nothing
- t.Run("TestDelete_index_to_string", func(t *testing.T) {
- var data = test.ParseData(`
-a: mystring
-`)
- result, _ := subject.DeleteChildValue(data, []string{"a", "0"})
- test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
- })
+// // Ensure deleting an index into a string does nothing
+// t.Run("TestDelete_index_to_string", func(t *testing.T) {
+// var data = test.ParseData(`
+// a: mystring
+// `)
+// result, _ := subject.DeleteChildValue(data, []string{"a", "0"})
+// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
+// })
- t.Run("TestDelete_list_index", func(t *testing.T) {
- var data = test.ParseData(`
-a: [3, 4]
-`)
- var expected = test.ParseData(`
-a: [3]
-`)
- result, _ := subject.DeleteChildValue(data, []string{"a", "1"})
- test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
- })
+// t.Run("TestDelete_list_index", func(t *testing.T) {
+// var data = test.ParseData(`
+// a: [3, 4]
+// `)
+// var expected = test.ParseData(`
+// a: [3]
+// `)
+// result, _ := subject.DeleteChildValue(data, []string{"a", "1"})
+// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
+// })
- t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) {
- var data = test.ParseData(`
-a: [3, 4]
-`)
- result, _ := subject.DeleteChildValue(data, []string{"a", "5"})
- test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
- })
+// t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) {
+// var data = test.ParseData(`
+// a: [3, 4]
+// `)
+// result, _ := subject.DeleteChildValue(data, []string{"a", "5"})
+// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
+// })
- t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) {
- var data = test.ParseData(`
-a: [3, 4]
-`)
- result, _ := subject.DeleteChildValue(data, []string{"a", "2"})
- test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
- })
+// t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) {
+// var data = test.ParseData(`
+// a: [3, 4]
+// `)
+// result, _ := subject.DeleteChildValue(data, []string{"a", "2"})
+// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
+// })
- t.Run("TestDelete_no_paths", func(t *testing.T) {
- var data = test.ParseData(`
-a: [3, 4]
-b:
- - name: test
-`)
- result, _ := subject.DeleteChildValue(data, []string{})
- test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
- })
+// t.Run("TestDelete_no_paths", func(t *testing.T) {
+// var data = test.ParseData(`
+// a: [3, 4]
+// b:
+// - name: test
+// `)
+// result, _ := subject.DeleteChildValue(data, []string{})
+// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
+// })
- t.Run("TestDelete_array_map_item", func(t *testing.T) {
- var data = test.ParseData(`
-b:
-- name: fred
- value: blah
-- name: john
- value: test
-`)
- var expected = test.ParseData(`
-b:
-- value: blah
-- name: john
- value: test
-`)
- result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"})
- test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
- })
-}
+// t.Run("TestDelete_array_map_item", func(t *testing.T) {
+// var data = test.ParseData(`
+// b:
+// - name: fred
+// value: blah
+// - name: john
+// value: test
+// `)
+// var expected = test.ParseData(`
+// b:
+// - value: blah
+// - name: john
+// value: test
+// `)
+// result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"})
+// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
+// })
+// }
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 5b630e82..10d30eec 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -14,6 +14,7 @@ type UpdateCommand struct {
}
type YqLib interface {
+ DebugNode(node *yaml.Node)
Get(rootNode *yaml.Node, path string) (*yaml.Node, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
}
@@ -32,10 +33,11 @@ func NewYqLib(l *logging.Logger) YqLib {
}
}
+func (l *lib) DebugNode(node *yaml.Node) {
+ l.navigator.DebugNode(node)
+}
+
func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) {
- if path == "" {
- return rootNode, nil
- }
var paths = l.parser.ParsePath(path)
return l.navigator.Get(rootNode, paths)
}
diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go
index bcfac5ff..b9c3e4de 100644
--- a/pkg/yqlib/lib_test.go
+++ b/pkg/yqlib/lib_test.go
@@ -4,7 +4,7 @@ import (
"fmt"
"testing"
- "github.com/mikefarah/yq/v2/test"
+ "github.com/mikefarah/yq/v3/test"
logging "gopkg.in/op/go-logging.v1"
)
diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go
index f8163cca..83157054 100644
--- a/pkg/yqlib/path_parser.go
+++ b/pkg/yqlib/path_parser.go
@@ -11,6 +11,9 @@ func NewPathParser() PathParser {
}
func (p *pathParser) ParsePath(path string) []string {
+ if path == "" {
+ return []string{}
+ }
return p.parsePathAccum([]string{}, path)
}
diff --git a/pkg/yqlib/path_parser_test.go b/pkg/yqlib/path_parser_test.go
index c2d7fe26..460d3fe9 100644
--- a/pkg/yqlib/path_parser_test.go
+++ b/pkg/yqlib/path_parser_test.go
@@ -3,7 +3,7 @@ package yqlib
import (
"testing"
- "github.com/mikefarah/yq/v2/test"
+ "github.com/mikefarah/yq/v3/test"
)
var parsePathsTests = []struct {
diff --git a/pkg/yqlib/value_parser_test.go b/pkg/yqlib/value_parser_test.go
index 2246b398..24468e37 100644
--- a/pkg/yqlib/value_parser_test.go
+++ b/pkg/yqlib/value_parser_test.go
@@ -3,7 +3,7 @@ package yqlib
import (
"testing"
- "github.com/mikefarah/yq/v2/test"
+ "github.com/mikefarah/yq/v3/test"
)
var parseValueTests = []struct {
diff --git a/test/utils.go b/test/utils.go
index 490ce543..25093e2f 100644
--- a/test/utils.go
+++ b/test/utils.go
@@ -9,8 +9,8 @@ import (
"strings"
"testing"
- yaml "github.com/mikefarah/yaml/v2"
"github.com/spf13/cobra"
+ yaml "gopkg.in/yaml.v3"
)
type resulter struct {
@@ -30,8 +30,8 @@ func RunCmd(c *cobra.Command, input string) resulter {
return resulter{err, output, c}
}
-func ParseData(rawData string) yaml.MapSlice {
- var parsedData yaml.MapSlice
+func ParseData(rawData string) yaml.Node {
+ var parsedData yaml.Node
err := yaml.Unmarshal([]byte(rawData), &parsedData)
if err != nil {
fmt.Printf("Error parsing yaml: %v\n", err)
diff --git a/yq.go b/yq.go
index d61dc7e1..7fec3c1c 100644
--- a/yq.go
+++ b/yq.go
@@ -257,11 +257,14 @@ func readProperty(cmd *cobra.Command, args []string) error {
}
var mappedDocs []*yaml.Node
- var dataBucket yaml.Node
+
var currentIndex = 0
var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
for {
+ var dataBucket yaml.Node
errorReading := decoder.Decode(&dataBucket)
+ log.Debugf("decoded node for doc %v", currentIndex)
+ lib.DebugNode(&dataBucket)
if errorReading == io.EOF {
log.Debugf("done %v / %v", currentIndex, docIndexInt)
if !updateAll && currentIndex <= docIndexInt {
@@ -273,6 +276,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
if updateAll || currentIndex == docIndexInt {
log.Debugf("reading %v in document %v", path, currentIndex)
mappedDoc, errorParsing := lib.Get(&dataBucket, path)
+ lib.DebugNode(mappedDoc)
if errorParsing != nil {
return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
}
diff --git a/yq_test.go b/yq_test.go
index 677f7bd0..b131d01f 100644
--- a/yq_test.go
+++ b/yq_test.go
@@ -1,60 +1,60 @@
package main
-import (
- "fmt"
- "runtime"
- "testing"
+// import (
+// "fmt"
+// "runtime"
+// "testing"
- "github.com/mikefarah/yq/v2/pkg/marshal"
- "github.com/mikefarah/yq/v2/test"
-)
+// "github.com/mikefarah/yq/v2/pkg/marshal"
+// "github.com/mikefarah/yq/v2/test"
+// )
-func TestMultilineString(t *testing.T) {
- testString := `
- abcd
- efg`
- formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
- test.AssertResult(t, testString, formattedResult)
-}
+// func TestMultilineString(t *testing.T) {
+// testString := `
+// abcd
+// efg`
+// formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
+// test.AssertResult(t, testString, formattedResult)
+// }
-func TestNewYaml(t *testing.T) {
- result, _ := newYaml([]string{"b.c", "3"})
- formattedResult := fmt.Sprintf("%v", result)
- test.AssertResult(t,
- "[{b [{c 3}]}]",
- formattedResult)
-}
+// func TestNewYaml(t *testing.T) {
+// result, _ := newYaml([]string{"b.c", "3"})
+// formattedResult := fmt.Sprintf("%v", result)
+// test.AssertResult(t,
+// "[{b [{c 3}]}]",
+// formattedResult)
+// }
-func TestNewYamlArray(t *testing.T) {
- result, _ := newYaml([]string{"[0].cat", "meow"})
- formattedResult := fmt.Sprintf("%v", result)
- test.AssertResult(t,
- "[[{cat meow}]]",
- formattedResult)
-}
+// func TestNewYamlArray(t *testing.T) {
+// result, _ := newYaml([]string{"[0].cat", "meow"})
+// formattedResult := fmt.Sprintf("%v", result)
+// test.AssertResult(t,
+// "[[{cat meow}]]",
+// formattedResult)
+// }
-func TestNewYaml_WithScript(t *testing.T) {
- writeScript = "examples/instruction_sample.yaml"
- expectedResult := `b:
- c: cat
- e:
- - name: Mike Farah`
- result, _ := newYaml([]string{""})
- actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
- test.AssertResult(t, expectedResult, actualResult)
-}
+// func TestNewYaml_WithScript(t *testing.T) {
+// writeScript = "examples/instruction_sample.yaml"
+// expectedResult := `b:
+// c: cat
+// e:
+// - name: Mike Farah`
+// result, _ := newYaml([]string{""})
+// actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
+// test.AssertResult(t, expectedResult, actualResult)
+// }
-func TestNewYaml_WithUnknownScript(t *testing.T) {
- writeScript = "fake-unknown"
- _, err := newYaml([]string{""})
- if err == nil {
- t.Error("Expected error due to unknown file")
- }
- var expectedOutput string
- if runtime.GOOS == "windows" {
- expectedOutput = `open fake-unknown: The system cannot find the file specified.`
- } else {
- expectedOutput = `open fake-unknown: no such file or directory`
- }
- test.AssertResult(t, expectedOutput, err.Error())
-}
+// func TestNewYaml_WithUnknownScript(t *testing.T) {
+// writeScript = "fake-unknown"
+// _, err := newYaml([]string{""})
+// if err == nil {
+// t.Error("Expected error due to unknown file")
+// }
+// var expectedOutput string
+// if runtime.GOOS == "windows" {
+// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
+// } else {
+// expectedOutput = `open fake-unknown: no such file or directory`
+// }
+// test.AssertResult(t, expectedOutput, err.Error())
+// }
From 586ffb833b2d93bc5a37813a78469fef4ba426a4 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 9 Dec 2019 13:57:10 +1100
Subject: [PATCH 08/68] Refactoring
---
examples/sample.yaml | 13 ++--
pkg/yqlib/data_navigator.go | 140 ++++++++++++++++++++----------------
2 files changed, 84 insertions(+), 69 deletions(-)
diff --git a/examples/sample.yaml b/examples/sample.yaml
index e847c407..b26830e8 100644
--- a/examples/sample.yaml
+++ b/examples/sample.yaml
@@ -1,8 +1,9 @@
a: Easy! as one two three
b:
- c:
- name: c1
- f: things
- d:
- name: d1
- f: other
\ No newline at end of file
+ c: 2
+ d: [3, 4]
+ e:
+ - name: fred
+ value: 3
+ - name: sam
+ value: 4
\ No newline at end of file
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 3776d4ef..9486fb98 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -87,11 +87,7 @@ func (n *navigator) guessKind(tail []string, guess yaml.Kind) yaml.Kind {
}
func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
- // expected is a scalar when we reach the end of the path
- // no need to clobber the original because:
- // when reading, it should deal with the original kind
- // when writing, it will clobber the kind anyway
- if original.Kind != expectedKind && (expectedKind != yaml.ScalarNode) {
+ if original.Kind != expectedKind {
n.log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind)
return &yaml.Node{Kind: expectedKind}
}
@@ -114,75 +110,93 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
case yaml.MappingNode:
n.log.Debug("its a map with %v entries", len(value.Content)/2)
if head == "*" {
- var newNode = yaml.Node{Kind: yaml.SequenceNode}
- for index, content := range value.Content {
- if index%2 == 0 {
- continue
- }
- content = n.getOrReplace(content, n.guessKind(tail, content.Kind))
- var nestedValue, err = n.Visit(content, tail, visitor)
- if err != nil {
- return nil, err
- }
- newNode.Content = append(newNode.Content, nestedValue)
- }
- return &newNode, nil
+ return n.splatMap(value, tail, visitor)
}
-
- for index, content := range value.Content {
- // value.Content is a concatenated array of key, value,
- // so keys are in the even indexes, values in odd.
- if index%2 == 1 || (content.Value != head) {
- continue
- }
- value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail, value.Content[index+1].Kind))
- return n.Visit(value.Content[index+1], tail, visitor)
- }
- value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode})
- mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)}
- value.Content = append(value.Content, &mapEntryValue)
- n.log.Debug("adding new node %v", value.Content)
- return n.Visit(&mapEntryValue, tail, visitor)
+ return n.recurseMap(value, head, tail, visitor)
case yaml.SequenceNode:
n.log.Debug("its a sequence of %v things!, %v", len(value.Content))
if head == "*" {
- var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style}
- newNode.Content = make([]*yaml.Node, len(value.Content))
-
- for index, childValue := range value.Content {
- n.log.Debug("processing")
- n.DebugNode(childValue)
- childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind))
- var nestedValue, err = n.Visit(childValue, tail, visitor)
- n.log.Debug("nestedValue")
- n.DebugNode(nestedValue)
- if err != nil {
- return nil, err
- }
- newNode.Content[index] = nestedValue
- }
- return &newNode, nil
+ return n.splatArray(value, tail, visitor)
} else if head == "+" {
-
- var newNode = yaml.Node{Kind: n.guessKind(tail, 0)}
- value.Content = append(value.Content, &newNode)
- n.log.Debug("appending a new node, %v", value.Content)
- return n.Visit(&newNode, tail, visitor)
+ return n.appendArray(value, tail, visitor)
}
- var index, err = strconv.ParseInt(head, 10, 64) // nolint
- if err != nil {
- return nil, err
- }
- if index >= int64(len(value.Content)) {
- return nil, nil
- }
- 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.recurseArray(value, head, tail, visitor)
default:
return nil, nil
}
}
+func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) {
+ var newNode = yaml.Node{Kind: yaml.SequenceNode}
+ for index, content := range value.Content {
+ if index%2 == 0 {
+ continue
+ }
+ content = n.getOrReplace(content, n.guessKind(tail, content.Kind))
+ var nestedValue, err = n.Visit(content, tail, visitor)
+ if err != nil {
+ return nil, err
+ }
+ newNode.Content = append(newNode.Content, nestedValue)
+ }
+ return &newNode, nil
+}
+
+func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) {
+ for index, content := range value.Content {
+ // value.Content is a concatenated array of key, value,
+ // so keys are in the even indexes, values in odd.
+ if index%2 == 1 || (content.Value != head) {
+ continue
+ }
+ value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail, value.Content[index+1].Kind))
+ return n.Visit(value.Content[index+1], tail, visitor)
+ }
+ value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode})
+ mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)}
+ value.Content = append(value.Content, &mapEntryValue)
+ n.log.Debug("adding new node %v", value.Content)
+ return n.Visit(&mapEntryValue, tail, visitor)
+}
+
+func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) {
+ var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style}
+ newNode.Content = make([]*yaml.Node, len(value.Content))
+
+ for index, childValue := range value.Content {
+ n.log.Debug("processing")
+ n.DebugNode(childValue)
+ childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind))
+ var nestedValue, err = n.Visit(childValue, tail, visitor)
+ n.log.Debug("nestedValue")
+ n.DebugNode(nestedValue)
+ if err != nil {
+ return nil, err
+ }
+ newNode.Content[index] = nestedValue
+ }
+ return &newNode, nil
+}
+
+func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) {
+ var newNode = yaml.Node{Kind: n.guessKind(tail, 0)}
+ value.Content = append(value.Content, &newNode)
+ n.log.Debug("appending a new node, %v", value.Content)
+ return n.Visit(&newNode, tail, visitor)
+}
+
+func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) {
+ var index, err = strconv.ParseInt(head, 10, 64) // nolint
+ if err != nil {
+ return nil, err
+ }
+ if index >= int64(len(value.Content)) {
+ return nil, nil
+ }
+ value.Content[index] = n.getOrReplace(value.Content[index], n.guessKind(tail, value.Content[index].Kind))
+ 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, "*")
From 8c0046a622a3536bc2cf409c921969dea357890b Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 9 Dec 2019 13:57:38 +1100
Subject: [PATCH 09/68] Refactoring
---
pkg/yqlib/data_navigator.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 9486fb98..3a2d1845 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -80,7 +80,7 @@ func (n *navigator) 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] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) {
return guess
}
return yaml.MappingNode
From d061b2f9f9e88de58d5af7ac12eae0b51ad17127 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Thu, 12 Dec 2019 20:47:22 +1100
Subject: [PATCH 10/68] Can delete arrays
---
examples/sample.yaml | 2 +-
pkg/yqlib/data_navigator.go | 39 ++++++++++++++++--
pkg/yqlib/lib.go | 4 +-
yq.go | 81 +++++++++++++++++--------------------
4 files changed, 75 insertions(+), 51 deletions(-)
diff --git a/examples/sample.yaml b/examples/sample.yaml
index b26830e8..a08d1d1f 100644
--- a/examples/sample.yaml
+++ b/examples/sample.yaml
@@ -1,7 +1,7 @@
a: Easy! as one two three
b:
c: 2
- d: [3, 4]
+ d: [3, 4, 5]
e:
- name: fred
value: 3
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 3a2d1845..234405b3 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -10,8 +10,9 @@ import (
type DataNavigator interface {
DebugNode(node *yaml.Node)
- Get(rootNode *yaml.Node, remainingPath []string) (*yaml.Node, error)
- Update(rootNode *yaml.Node, remainingPath []string, changesToApply yaml.Node) error
+ Get(rootNode *yaml.Node, path []string) (*yaml.Node, error)
+ Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error
+ Delete(rootNode *yaml.Node, path []string) error
}
type navigator struct {
@@ -34,8 +35,8 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
return n.Visit(value, path, identityVisitor)
}
-func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml.Node) error {
- _, errorVisiting := n.Visit(value, path, func(nodeToUpdate *yaml.Node) (*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) {
n.log.Debug("going to update")
n.DebugNode(nodeToUpdate)
n.log.Debug("with")
@@ -53,6 +54,36 @@ func (n *navigator) Update(value *yaml.Node, path []string, changesToApply yaml.
return errorVisiting
}
+func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
+
+ lastBit, newTail := path[len(path)-1], path[:len(path)-1]
+ n.log.Debug("splitting path, %v", lastBit)
+ n.log.Debug("new tail, %v", newTail)
+ _, errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) {
+ n.log.Debug("need to find %v in here", lastBit)
+ n.DebugNode(nodeToUpdate)
+
+ if nodeToUpdate.Kind == yaml.SequenceNode {
+ var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
+ if err != nil {
+ return nil, err
+ }
+ if index >= int64(len(nodeToUpdate.Content)) {
+ n.log.Debug("index %v is greater than content lenth %v", index, len(nodeToUpdate.Content))
+ return nodeToUpdate, nil
+ }
+ original := nodeToUpdate.Content
+ nodeToUpdate.Content = append(original[:index], original[index+1:]...)
+
+ } else if nodeToUpdate.Kind == yaml.MappingNode {
+
+ }
+
+ return nodeToUpdate, nil
+ })
+ return errorVisiting
+}
+
func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) (*yaml.Node, error) {
realValue := value
if realValue.Kind == yaml.DocumentNode {
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 10d30eec..344aa248 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -50,8 +50,8 @@ func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
var paths = l.parser.ParsePath(updateCommand.Path)
return l.navigator.Update(rootNode, paths, updateCommand.Value)
case "delete":
- l.log.Debugf("need to implement delete")
- return nil
+ var paths = l.parser.ParsePath(updateCommand.Path)
+ return l.navigator.Delete(rootNode, paths)
default:
return fmt.Errorf("Unknown command %v", updateCommand.Command)
}
diff --git a/yq.go b/yq.go
index 7fec3c1c..6801d03f 100644
--- a/yq.go
+++ b/yq.go
@@ -77,7 +77,7 @@ func newCommandCLI() *cobra.Command {
createReadCmd(),
createWriteCmd(),
// createPrefixCmd(),
- // createDeleteCmd(),
+ createDeleteCmd(),
// createNewCmd(),
// createMergeCmd(),
)
@@ -166,27 +166,27 @@ a.b.e:
// return cmdWrite
// }
-// func createDeleteCmd() *cobra.Command {
-// var cmdDelete = &cobra.Command{
-// Use: "delete [yaml_file] [path]",
-// Aliases: []string{"d"},
-// Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
-// Example: `
-// yq delete things.yaml a.b.c
-// yq delete --inplace things.yaml a.b.c
-// yq delete --inplace -- things.yaml --key-starting-with-dash
-// yq d -i things.yaml a.b.c
-// yq d things.yaml a.b.c
-// `,
-// Long: `Deletes the given path from the YAML file.
-// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
-// `,
-// RunE: deleteProperty,
-// }
-// cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
-// cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
-// return cmdDelete
-// }
+func createDeleteCmd() *cobra.Command {
+ var cmdDelete = &cobra.Command{
+ Use: "delete [yaml_file] [path]",
+ Aliases: []string{"d"},
+ Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
+ Example: `
+yq delete things.yaml a.b.c
+yq delete --inplace things.yaml a.b.c
+yq delete --inplace -- things.yaml --key-starting-with-dash
+yq d -i things.yaml a.b.c
+yq d things.yaml a.b.c
+ `,
+ Long: `Deletes the given path from the YAML file.
+Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
+`,
+ RunE: deleteProperty,
+ }
+ cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
+ cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
+ return cmdDelete
+}
// func createNewCmd() *cobra.Command {
// var cmdNew = &cobra.Command{
@@ -407,6 +407,20 @@ func writeProperty(cmd *cobra.Command, args []string) error {
if updateCommandsError != nil {
return updateCommandsError
}
+ return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
+}
+
+func deleteProperty(cmd *cobra.Command, args []string) error {
+ if len(args) < 2 {
+ return errors.New("Must provide ")
+ }
+ var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1)
+ updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]}
+
+ return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
+}
+
+func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error {
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
@@ -424,7 +438,7 @@ func writeProperty(cmd *cobra.Command, args []string) error {
}
return nil
}
- return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
+ return readAndUpdate(writer, inputFile, updateData)
}
// func prefixProperty(cmd *cobra.Command, args []string) error {
@@ -484,27 +498,6 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
return readStream(inputFile, mapYamlDecoder(updateData, encoder))
}
-// func deleteProperty(cmd *cobra.Command, args []string) error {
-// if len(args) < 2 {
-// return errors.New("Must provide ")
-// }
-// var deletePath = args[1]
-// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
-// if errorParsingDocIndex != nil {
-// return errorParsingDocIndex
-// }
-
-// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
-// if updateAll || currentIndex == docIndexInt {
-// log.Debugf("Deleting path in doc %v", currentIndex)
-// return lib.DeletePath(dataBucket, deletePath)
-// }
-// return dataBucket, nil
-// }
-
-// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
-// }
-
// func mergeProperties(cmd *cobra.Command, args []string) error {
// if len(args) < 2 {
// return errors.New("Must provide at least 2 yaml files")
From b7640946ac53cb217e6f3857fe6131da87c8f47f Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 15 Dec 2019 17:31:26 +1100
Subject: [PATCH 11/68] Delete!
---
pkg/yqlib/data_navigator.go | 37 ++++++++++++++++++++++++++-----------
1 file changed, 26 insertions(+), 11 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 234405b3..73ae53b2 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -62,7 +62,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
_, errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) (*yaml.Node, error) {
n.log.Debug("need to find %v in here", lastBit)
n.DebugNode(nodeToUpdate)
-
+ original := nodeToUpdate.Content
if nodeToUpdate.Kind == yaml.SequenceNode {
var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
if err != nil {
@@ -72,11 +72,15 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
n.log.Debug("index %v is greater than content lenth %v", index, len(nodeToUpdate.Content))
return nodeToUpdate, nil
}
- original := nodeToUpdate.Content
nodeToUpdate.Content = append(original[:index], original[index+1:]...)
} else if nodeToUpdate.Kind == yaml.MappingNode {
-
+ //need to delete both the key and value children from Content
+ indexInMap := n.findIndexForKeyInMap(nodeToUpdate.Content, lastBit)
+ if indexInMap != -1 {
+ //skip two because its a key, value pair
+ nodeToUpdate.Content = append(original[:indexInMap], original[indexInMap+2:]...)
+ }
}
return nodeToUpdate, nil
@@ -174,15 +178,14 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn)
}
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) (*yaml.Node, error) {
- for index, content := range value.Content {
- // value.Content is a concatenated array of key, value,
- // so keys are in the even indexes, values in odd.
- if index%2 == 1 || (content.Value != head) {
- continue
- }
- value.Content[index+1] = n.getOrReplace(value.Content[index+1], n.guessKind(tail, value.Content[index+1].Kind))
- return n.Visit(value.Content[index+1], tail, visitor)
+ indexInMap := n.findIndexForKeyInMap(value.Content, head)
+
+ if indexInMap != -1 {
+ 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)
}
+
+ //didn't find it, lets add it.
value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode})
mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)}
value.Content = append(value.Content, &mapEntryValue)
@@ -190,6 +193,18 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
return n.Visit(&mapEntryValue, tail, visitor)
}
+func (n *navigator) findIndexForKeyInMap(contents []*yaml.Node, key string) int {
+ for index, content := range contents {
+ // value.Content is a concatenated array of key, value,
+ // so keys are in the even indexes, values in odd.
+ if index%2 == 1 || (content.Value != key) {
+ continue
+ }
+ return index
+ }
+ return -1
+}
+
func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) {
var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style}
newNode.Content = make([]*yaml.Node, len(value.Content))
From 5988d0cffacc876b4a54ac482fba8a8e1293a8f5 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 15 Dec 2019 18:24:23 +1100
Subject: [PATCH 12/68] Simplified
---
commands_test.go | 49 ++++++++++---
examples/array.yaml | 2 +
examples/data2.yaml | 1 +
pkg/yqlib/data_navigator.go | 140 +++++++++++++++++++++---------------
yq.go | 9 ++-
5 files changed, 132 insertions(+), 69 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 49f12578..ed3fd562 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -172,6 +172,8 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
- lala
- land
serial: 1
+- become: false
+ gather_facts: true
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -194,9 +196,9 @@ serial: 1
test.AssertResult(t, expectedOutput, result.Output)
}
-func TestReadCmd_ArrayYaml_Splat(t *testing.T) {
+func TestReadCmd_ArrayYaml_SplatA(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read examples/array.yaml [*]")
+ result := test.RunCmd(cmd, "read -v examples/array.yaml [*]")
if result.Error != nil {
t.Error(result.Error)
}
@@ -208,6 +210,8 @@ func TestReadCmd_ArrayYaml_Splat(t *testing.T) {
- lala
- land
serial: 1
+- become: false
+ gather_facts: true
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -218,18 +222,17 @@ func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := "- false\n"
+ expectedOutput := `- false
+- true
+`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts")
- if result.Error == nil {
- t.Error("Expected command to fail due to invalid path")
- }
- expectedOutput := `Error reading path in document index 0: strconv.ParseInt: parsing "x": invalid syntax`
- test.AssertResult(t, expectedOutput, result.Error.Error())
+ result := test.RunCmd(cmd, "read -v examples/array.yaml [x].gather_facts")
+ expectedOutput := ``
+ test.AssertResult(t, expectedOutput, result.Output)
}
// 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)
// }
+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) {
content := `b:
c: 3
diff --git a/examples/array.yaml b/examples/array.yaml
index 67c34c04..47f04423 100644
--- a/examples/array.yaml
+++ b/examples/array.yaml
@@ -7,3 +7,5 @@
- lala
- land
serial: 1
+- become: false
+ gather_facts: true
diff --git a/examples/data2.yaml b/examples/data2.yaml
index 9c51b1f3..7969f1ec 100644
--- a/examples/data2.yaml
+++ b/examples/data2.yaml
@@ -2,3 +2,4 @@ a: other
b: [3, 4]
c:
test: 1
+ tell: 1
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 73ae53b2..438dfd9d 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -3,6 +3,7 @@ package yqlib
import (
"bytes"
"strconv"
+ "strings"
logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
@@ -19,11 +20,7 @@ type navigator struct {
log *logging.Logger
}
-type VisitorFn func(*yaml.Node) (*yaml.Node, error)
-
-func identityVisitor(value *yaml.Node) (*yaml.Node, error) {
- return value, nil
-}
+type VisitorFn func(*yaml.Node) error
func NewDataNavigator(l *logging.Logger) DataNavigator {
return &navigator{
@@ -32,11 +29,28 @@ func NewDataNavigator(l *logging.Logger) DataNavigator {
}
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 {
- _, 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.DebugNode(nodeToUpdate)
n.log.Debug("with")
@@ -49,7 +63,7 @@ func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply ya
nodeToUpdate.HeadComment = changesToApply.HeadComment
nodeToUpdate.LineComment = changesToApply.LineComment
nodeToUpdate.FootComment = changesToApply.FootComment
- return nodeToUpdate, nil
+ return nil
})
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]
n.log.Debug("splitting path, %v", lastBit)
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.DebugNode(nodeToUpdate)
original := nodeToUpdate.Content
if nodeToUpdate.Kind == yaml.SequenceNode {
var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
if err != nil {
- return nil, err
+ return err
}
if index >= int64(len(nodeToUpdate.Content)) {
- n.log.Debug("index %v is greater than content lenth %v", index, len(nodeToUpdate.Content))
- return nodeToUpdate, nil
+ n.log.Debug("index %v is greater than content length %v", index, len(nodeToUpdate.Content))
+ return nil
}
nodeToUpdate.Content = append(original[:index], original[index+1:]...)
} else if nodeToUpdate.Kind == yaml.MappingNode {
- //need to delete both the key and value children from Content
- indexInMap := n.findIndexForKeyInMap(nodeToUpdate.Content, lastBit)
- if indexInMap != -1 {
- //skip two because its a key, value pair
+
+ _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error {
nodeToUpdate.Content = append(original[:indexInMap], original[indexInMap+2:]...)
+ return nil
+ })
+ if errorVisiting != nil {
+ return errorVisiting
}
+
}
- return nodeToUpdate, nil
+ return nil
})
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
if realValue.Kind == yaml.DocumentNode {
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) {
- 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)
encoder := yaml.NewEncoder(buf)
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 {
case yaml.MappingNode:
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)
default:
- return nil, nil
+ return nil
}
}
-func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) (*yaml.Node, error) {
- var newNode = yaml.Node{Kind: yaml.SequenceNode}
+func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) error {
for index, content := range value.Content {
if index%2 == 0 {
continue
}
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 {
- 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) {
- indexInMap := n.findIndexForKeyInMap(value.Content, head)
-
- if indexInMap != -1 {
+func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
+ visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(indexInMap int) error {
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)
+ })
+
+ if errorVisiting != nil {
+ return errorVisiting
+ }
+
+ if visited {
+ return nil
}
//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)
}
-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 {
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
- if index%2 == 1 || (content.Value != key) {
- continue
+ if index%2 == 0 && (n.matchesKey(key, content.Value)) {
+ 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) {
- var newNode = yaml.Node{Kind: yaml.SequenceNode, Style: value.Style}
- newNode.Content = make([]*yaml.Node, len(value.Content))
+func (n *navigator) matchesKey(key string, actual string) bool {
+ var prefixMatch = strings.TrimSuffix(key, "*")
+ 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.DebugNode(childValue)
childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind))
- var nestedValue, err = n.Visit(childValue, tail, visitor)
- n.log.Debug("nestedValue")
- n.DebugNode(nestedValue)
+ var err = n.Visit(childValue, tail, visitor)
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)}
value.Content = append(value.Content, &newNode)
n.log.Debug("appending a new node, %v", value.Content)
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
if err != nil {
- return nil, err
+ return err
}
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))
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 {
// var matches = make([]*yaml.MapItem, 0)
// for idx := range context {
diff --git a/yq.go b/yq.go
index 6801d03f..fc3cb256 100644
--- a/yq.go
+++ b/yq.go
@@ -277,10 +277,14 @@ func readProperty(cmd *cobra.Command, args []string) error {
log.Debugf("reading %v in document %v", path, currentIndex)
mappedDoc, errorParsing := lib.Get(&dataBucket, path)
lib.DebugNode(mappedDoc)
+ log.Debugf("carry on")
if errorParsing != nil {
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
}
@@ -288,6 +292,9 @@ func readProperty(cmd *cobra.Command, args []string) error {
if errorReadingStream != nil {
return errorReadingStream
+ } else if len(mappedDocs) == 0 {
+ log.Debug("no matching results, nothing to print")
+ return nil
}
var encoder = yaml.NewEncoder(cmd.OutOrStdout())
From 53a4a47ce306edead2dc9f059c488ec02f9d963d Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 15 Dec 2019 18:38:40 +1100
Subject: [PATCH 13/68] wip - prefix splat
---
commands_test.go | 89 +++++++++++++++++--------------------
examples/data2.yaml | 2 +
pkg/yqlib/data_navigator.go | 10 +++--
3 files changed, 48 insertions(+), 53 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index ed3fd562..8e09b81d 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -235,15 +235,14 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
-// func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) {
-// cmd := getRootCommand()
-// result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]")
-// if result.Error == nil {
-// t.Error("Expected command to fail due to invalid path")
-// }
-// expectedOutput := `Error reading path in document index 0: error accessing array: 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)
+}
func TestReadCmd_Error(t *testing.T) {
cmd := getRootCommand()
@@ -280,27 +279,26 @@ func TestReadCmd_ErrorUnreadableFile(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error())
}
-// func TestReadCmd_ErrorBadPath(t *testing.T) {
-// content := `b:
-// d:
-// e:
-// - 3
-// - 4
-// f:
-// - 1
-// - 2
-// `
-// filename := test.WriteTempYamlFile(content)
-// defer test.RemoveTempYamlFile(filename)
+func TestReadCmd_ErrorBadPath(t *testing.T) {
+ content := `b:
+ d:
+ e:
+ - 3
+ - 4
+ f:
+ - 1
+ - 2
+`
+ filename := test.WriteTempYamlFile(content)
+ defer test.RemoveTempYamlFile(filename)
-// cmd := getRootCommand()
-// result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
-// if result.Error == nil {
-// t.Fatal("Expected command to fail due to invalid path")
-// }
-// expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
-// test.AssertResult(t, expectedOutput, result.Error.Error())
-// }
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
+ expectedOutput := `-
+-
+`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
func TestReadCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
@@ -878,35 +876,28 @@ b:
}
func TestDeleteSplatYaml(t *testing.T) {
- content := `a: 2
-b:
- hi:
- c: things
- d: something else
- hello:
- c: things2
- d: something else2
- there:
- c: more things
- d: more something else
+ content := `a: other
+b: [3, 4]
+c:
+ toast: leave
+ test: 1
+ tell: 1
+ taco: cool
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.*.c", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s c.te*", filename))
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `a: 2
-b:
- hi:
- d: something else
- hello:
- d: something else2
- there:
- d: more something else
+ expectedOutput := `a: other
+b: [3, 4]
+c:
+ toast: leave
+ taco: cool
`
test.AssertResult(t, expectedOutput, result.Output)
}
diff --git a/examples/data2.yaml b/examples/data2.yaml
index 7969f1ec..93764e94 100644
--- a/examples/data2.yaml
+++ b/examples/data2.yaml
@@ -1,5 +1,7 @@
a: other
b: [3, 4]
c:
+ toast: leave
test: 1
tell: 1
+ taco: cool
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 438dfd9d..66ee359b 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -220,10 +220,12 @@ 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 {
- // value.Content is a concatenated array of key, value,
- // so keys are in the even indexes, values in odd.
- if index%2 == 0 && (n.matchesKey(key, content.Value)) {
+
+ // value.Content is a concatenated array of key, value,
+ // so keys are in the even indexes, values in odd.
+ for index := 0; index < len(contents); index = index + 2 {
+ content := contents[index]
+ if n.matchesKey(key, content.Value) {
errorVisiting := visit(index)
if errorVisiting != nil {
return visited, errorVisiting
From 8be006fba442855eb3b4d241ce34d013de266565 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 15 Dec 2019 18:52:37 +1100
Subject: [PATCH 14/68] Fixed delete splat
---
pkg/yqlib/data_navigator.go | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 66ee359b..17b2542b 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -89,14 +89,23 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
nodeToUpdate.Content = append(original[:index], original[index+1:]...)
} else if nodeToUpdate.Kind == yaml.MappingNode {
-
+ // need to delete in reverse...
+ matchingIndices := make([]int, 0)
_, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error {
- nodeToUpdate.Content = append(original[:indexInMap], original[indexInMap+2:]...)
+ matchingIndices = append(matchingIndices, indexInMap)
+ n.log.Debug("matchingIndices %v", indexInMap)
return nil
})
+ n.log.Debug("delete matching indices now")
+ n.log.Debug("%v", matchingIndices)
if errorVisiting != nil {
return errorVisiting
}
+ for i := len(matchingIndices) - 1; i >= 0; i-- {
+ indexToDelete := matchingIndices[i]
+ n.log.Debug("deleting index %v, %v", indexToDelete, nodeToUpdate.Content[indexToDelete].Value)
+ nodeToUpdate.Content = append(nodeToUpdate.Content[:indexToDelete], nodeToUpdate.Content[indexToDelete+2:]...)
+ }
}
@@ -225,6 +234,7 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi
// so keys are in the even indexes, values in odd.
for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
+ n.log.Debug("index %v, checking %v", index, content.Value)
if n.matchesKey(key, content.Value) {
errorVisiting := visit(index)
if errorVisiting != nil {
From 2344638da45f2d16e271748b2657665d0bd51cde Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 15 Dec 2019 18:53:49 +1100
Subject: [PATCH 15/68] Fixed delete splat
---
pkg/yqlib/data_navigator.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 17b2542b..6aa0ad0c 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -89,7 +89,8 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
nodeToUpdate.Content = append(original[:index], original[index+1:]...)
} else if nodeToUpdate.Kind == yaml.MappingNode {
- // need to delete in reverse...
+ // need to delete in reverse - otherwise the matching indexes
+ // become incorrect.
matchingIndices := make([]int, 0)
_, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error {
matchingIndices = append(matchingIndices, indexInMap)
From b81fd638d76f1943ae382848fdb2a7773ed4df98 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 15 Dec 2019 19:34:05 +1100
Subject: [PATCH 16/68] wip - new node
---
pkg/yqlib/data_navigator.go | 16 ++++----
pkg/yqlib/lib.go | 11 ++++++
yq.go | 75 ++++++++++++++++++++++++++-----------
3 files changed, 73 insertions(+), 29 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 6aa0ad0c..66d4c710 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -14,6 +14,7 @@ type DataNavigator interface {
Get(rootNode *yaml.Node, path []string) (*yaml.Node, error)
Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error
Delete(rootNode *yaml.Node, path []string) error
+ GuessKind(tail []string, guess yaml.Kind) yaml.Kind
}
type navigator struct {
@@ -68,6 +69,7 @@ func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply ya
return errorVisiting
}
+// TODO: refactor delete..
func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
lastBit, newTail := path[len(path)-1], path[:len(path)-1]
@@ -129,7 +131,7 @@ func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) er
return visitor(realValue)
}
-func (n *navigator) guessKind(tail []string, guess yaml.Kind) yaml.Kind {
+func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind {
n.log.Debug("tail %v", tail)
if len(tail) == 0 && guess == 0 {
n.log.Debug("end of path, must be a scalar")
@@ -195,7 +197,7 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn)
if index%2 == 0 {
continue
}
- content = n.getOrReplace(content, n.guessKind(tail, content.Kind))
+ content = n.getOrReplace(content, n.GuessKind(tail, content.Kind))
var err = n.Visit(content, tail, visitor)
if err != nil {
return err
@@ -206,7 +208,7 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn)
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(indexInMap int) error {
- 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)
})
@@ -220,7 +222,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
//didn't find it, lets add it.
value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode})
- mapEntryValue := yaml.Node{Kind: n.guessKind(tail, 0)}
+ mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)}
value.Content = append(value.Content, &mapEntryValue)
n.log.Debug("adding new node %v", value.Content)
return n.Visit(&mapEntryValue, tail, visitor)
@@ -259,7 +261,7 @@ func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorF
for _, childValue := range value.Content {
n.log.Debug("processing")
n.DebugNode(childValue)
- childValue = n.getOrReplace(childValue, n.guessKind(tail, childValue.Kind))
+ childValue = n.getOrReplace(childValue, n.GuessKind(tail, childValue.Kind))
var err = n.Visit(childValue, tail, visitor)
if err != nil {
return err
@@ -269,7 +271,7 @@ func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorF
}
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)
n.log.Debug("appending a new node, %v", value.Content)
return n.Visit(&newNode, tail, visitor)
@@ -283,7 +285,7 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v
if index >= int64(len(value.Content)) {
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)
}
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 344aa248..9fb1591d 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -17,6 +17,7 @@ type YqLib interface {
DebugNode(node *yaml.Node)
Get(rootNode *yaml.Node, path string) (*yaml.Node, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
+ New(updateCommand UpdateCommand) (yaml.Node, error)
}
type lib struct {
@@ -42,6 +43,16 @@ func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) {
return l.navigator.Get(rootNode, paths)
}
+func (l *lib) New(updateCommand UpdateCommand) (yaml.Node, error) {
+ var paths = l.parser.ParsePath(updateCommand.Path)
+ newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)}
+ errorUpdating := l.navigator.Update(&newNode, paths, updateCommand.Value)
+ if errorUpdating != nil {
+ return newNode, errorUpdating
+ }
+ return newNode, nil
+}
+
func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
// later - support other command types
l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
diff --git a/yq.go b/yq.go
index fc3cb256..3e5cbe9f 100644
--- a/yq.go
+++ b/yq.go
@@ -78,7 +78,7 @@ func newCommandCLI() *cobra.Command {
createWriteCmd(),
// createPrefixCmd(),
createDeleteCmd(),
- // createNewCmd(),
+ createNewCmd(),
// createMergeCmd(),
)
rootCmd.SetOutput(os.Stdout)
@@ -188,28 +188,28 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd
return cmdDelete
}
-// func createNewCmd() *cobra.Command {
-// var cmdNew = &cobra.Command{
-// Use: "new [path] [value]",
-// Aliases: []string{"n"},
-// Short: "yq n [--script/-s script_file] a.b.c newValue",
-// Example: `
-// yq new a.b.c cat
-// yq n a.b.c cat
-// yq n -- --key-starting-with-dash cat
-// yq n --script create_script.yaml
-// `,
-// Long: `Creates a new yaml w.r.t the given path and value.
-// Outputs to STDOUT
+func createNewCmd() *cobra.Command {
+ var cmdNew = &cobra.Command{
+ Use: "new [path] [value]",
+ Aliases: []string{"n"},
+ Short: "yq n [--script/-s script_file] a.b.c newValue",
+ Example: `
+yq new a.b.c cat
+yq n a.b.c cat
+yq n -- --key-starting-with-dash cat
+yq n --script create_script.yaml
+ `,
+ Long: `Creates a new yaml w.r.t the given path and value.
+Outputs to STDOUT
-// Create Scripts:
-// Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
-// `,
-// RunE: newProperty,
-// }
-// cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
-// return cmdNew
-// }
+Create Scripts:
+Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
+`,
+ RunE: newProperty,
+ }
+ cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
+ return cmdNew
+}
// func createMergeCmd() *cobra.Command {
// var cmdMerge = &cobra.Command{
@@ -417,6 +417,34 @@ func writeProperty(cmd *cobra.Command, args []string) error {
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
}
+func newProperty(cmd *cobra.Command, args []string) error {
+ var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ")
+ if updateCommandsError != nil {
+ return updateCommandsError
+ }
+ firstCommand, restOfCommands := updateCommands[0], updateCommands[1:]
+ newNode, errorCreating := lib.New(firstCommand)
+ if errorCreating != nil {
+ return errorCreating
+ }
+
+ for _, updateCommand := range restOfCommands {
+
+ errorUpdating := lib.Update(&newNode, updateCommand)
+
+ if errorUpdating != nil {
+ return errorUpdating
+ }
+ }
+
+ var encoder = yaml.NewEncoder(cmd.OutOrStdout())
+ encoder.SetIndent(2)
+ encoder.Encode(&newNode)
+ encoder.Close()
+ return nil
+
+}
+
func deleteProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide ")
@@ -558,6 +586,9 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
return nil, errors.New(badArgsMessage)
} else {
updateCommands = make([]yqlib.UpdateCommand, 1)
+ log.Debug("args %v", args)
+ log.Debug("path %v", args[expectedArgs-2])
+ log.Debug("Value %v", args[expectedArgs-1])
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])}
}
return updateCommands, nil
From a3cebec2fd68592e75b289b2b6ad460e6e0b2d1d Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 16 Dec 2019 16:17:01 +1100
Subject: [PATCH 17/68] Added prefix command
---
commands_test.go | 16 ++
examples/instruction_sample.yaml | 5 +-
pkg/yqlib/data_navigator.go | 336 +------------------------------
pkg/yqlib/lib.go | 14 +-
yq.go | 114 +++++++----
5 files changed, 106 insertions(+), 379 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 8e09b81d..9002d816 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -605,6 +605,22 @@ func TestWriteCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
+func TestWriteCmdScript(t *testing.T) {
+ content := `b:
+ c: 3
+`
+ filename := test.WriteTempYamlFile(content)
+ defer test.RemoveTempYamlFile(filename)
+
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `IMPLEMENT ME`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
func TestWriteMultiCmd(t *testing.T) {
content := `b:
c: 3
diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml
index 9834c942..24445bca 100644
--- a/examples/instruction_sample.yaml
+++ b/examples/instruction_sample.yaml
@@ -6,5 +6,6 @@
- command: update
path: b.e[+].name
value: Mike Farah
-- command: delete
- path: a
\ No newline at end of file
+- command: update
+ path: d.a
+ value: Cow
\ No newline at end of file
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 66d4c710..1e2d03e0 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -12,7 +12,7 @@ import (
type DataNavigator interface {
DebugNode(node *yaml.Node)
Get(rootNode *yaml.Node, path []string) (*yaml.Node, error)
- Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error
+ Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error
Delete(rootNode *yaml.Node, path []string) error
GuessKind(tail []string, guess yaml.Kind) yaml.Kind
}
@@ -50,12 +50,12 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
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) error {
n.log.Debug("going to update")
n.DebugNode(nodeToUpdate)
n.log.Debug("with")
- n.DebugNode(&changesToApply)
+ n.DebugNode(changesToApply)
nodeToUpdate.Value = changesToApply.Value
nodeToUpdate.Tag = changesToApply.Tag
nodeToUpdate.Kind = changesToApply.Kind
@@ -288,333 +288,3 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v
value.Content[index] = n.getOrReplace(value.Content[index], n.GuessKind(tail, value.Content[index].Kind))
return n.Visit(value.Content[index], tail, visitor)
}
-
-// func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem {
-// var matches = make([]*yaml.MapItem, 0)
-// for idx := range context {
-// var entry = &context[idx]
-// if matchesKey(key, entry.Key) {
-// matches = append(matches, entry)
-// }
-// }
-// return matches
-// }
-
-// func getMapSlice(context interface{}) yaml.MapSlice {
-// var mapSlice yaml.MapSlice
-// switch context := context.(type) {
-// case yaml.MapSlice:
-// mapSlice = context
-// default:
-// mapSlice = make(yaml.MapSlice, 0)
-// }
-// return mapSlice
-// }
-
-// func getArray(context interface{}) (array []interface{}, ok bool) {
-// switch context := context.(type) {
-// case []interface{}:
-// array = context
-// ok = true
-// default:
-// array = make([]interface{}, 0)
-// ok = false
-// }
-// return
-// }
-
-// func writeMap(context interface{}, paths []string, value interface{}) interface{} {
-// log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value)
-
-// mapSlice := getMapSlice(context)
-
-// if len(paths) == 0 {
-// return context
-// }
-
-// children := entriesInSlice(mapSlice, paths[0])
-
-// if len(children) == 0 && paths[0] == "*" {
-// log.Debugf("\tNo matches, return map as is")
-// return context
-// }
-
-// if len(children) == 0 {
-// newChild := yaml.MapItem{Key: paths[0]}
-// mapSlice = append(mapSlice, newChild)
-// children = entriesInSlice(mapSlice, paths[0])
-// log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice)
-// }
-
-// remainingPaths := paths[1:]
-// for _, child := range children {
-// child.Value = updatedChildValue(child.Value, remainingPaths, value)
-// }
-// log.Debugf("\tReturning mapSlice %v\n", mapSlice)
-// return mapSlice
-// }
-
-// func updatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} {
-// if len(remainingPaths) == 0 {
-// return value
-// }
-// log.Debugf("updatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value)
-// log.Debugf("type of child is %v", reflect.TypeOf(child))
-
-// switch child := child.(type) {
-// case nil:
-// if remainingPaths[0] == "+" || remainingPaths[0] == "*" {
-// return writeArray(child, remainingPaths, value)
-// }
-// case []interface{}:
-// _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64)
-// arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*"
-// if arrayCommand {
-// return writeArray(child, remainingPaths, value)
-// }
-// }
-// return writeMap(child, remainingPaths, value)
-// }
-
-// func writeArray(context interface{}, paths []string, value interface{}) []interface{} {
-// log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value)
-// array, _ := getArray(context)
-
-// if len(paths) == 0 {
-// return array
-// }
-
-// log.Debugf("\tarray %v\n", array)
-
-// rawIndex := paths[0]
-// remainingPaths := paths[1:]
-// var index int64
-// // the append array indicator
-// if rawIndex == "+" {
-// index = int64(len(array))
-// } else if rawIndex == "*" {
-// for index, oldChild := range array {
-// array[index] = updatedChildValue(oldChild, remainingPaths, value)
-// }
-// return array
-// } else {
-// index, _ = strconv.ParseInt(rawIndex, 10, 64) // nolint
-// // writeArray is only called by updatedChildValue which handles parsing the
-// // index, as such this renders this dead code.
-// }
-
-// for index >= int64(len(array)) {
-// array = append(array, nil)
-// }
-// currentChild := array[index]
-
-// log.Debugf("\tcurrentChild %v\n", currentChild)
-
-// array[index] = updatedChildValue(currentChild, remainingPaths, value)
-// log.Debugf("\tReturning array %v\n", array)
-// return array
-// }
-
-// func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) {
-// log.Debugf("readingMap %v with key %v\n", context, head)
-// if head == "*" {
-// return readMapSplat(context, tail)
-// }
-
-// entries := entriesInSlice(context, head)
-// if len(entries) == 1 {
-// return calculateValue(entries[0].Value, tail)
-// } else if len(entries) == 0 {
-// return nil, nil
-// }
-// var errInIdx error
-// values := make([]interface{}, len(entries))
-// for idx, entry := range entries {
-// values[idx], errInIdx = calculateValue(entry.Value, tail)
-// if errInIdx != nil {
-// log.Errorf("Error updating index %v in %v", idx, context)
-// return nil, errInIdx
-// }
-
-// }
-// return values, nil
-// }
-
-// func readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) {
-// var newArray = make([]interface{}, len(context))
-// var i = 0
-// for _, entry := range context {
-// if len(tail) > 0 {
-// val, err := recurse(entry.Value, tail[0], tail[1:])
-// if err != nil {
-// return nil, err
-// }
-// newArray[i] = val
-// } else {
-// newArray[i] = entry.Value
-// }
-// i++
-// }
-// return newArray, nil
-// }
-
-// func recurse(value interface{}, head string, tail []string) (interface{}, error) {
-// switch value := value.(type) {
-// case []interface{}:
-// if head == "*" {
-// return readArraySplat(value, tail)
-// }
-// index, err := strconv.ParseInt(head, 10, 64)
-// if err != nil {
-// return nil, fmt.Errorf("error accessing array: %v", err)
-// }
-// return readArray(value, index, tail)
-// case yaml.MapSlice:
-// return readMap(value, head, tail)
-// default:
-// return nil, nil
-// }
-// }
-
-// func readArray(array []interface{}, head int64, tail []string) (interface{}, error) {
-// if head >= int64(len(array)) {
-// return nil, nil
-// }
-
-// value := array[head]
-// return calculateValue(value, tail)
-// }
-
-// func readArraySplat(array []interface{}, tail []string) (interface{}, error) {
-// var newArray = make([]interface{}, len(array))
-// for index, value := range array {
-// val, err := calculateValue(value, tail)
-// if err != nil {
-// return nil, err
-// }
-// newArray[index] = val
-// }
-// return newArray, nil
-// }
-
-// func calculateValue(value interface{}, tail []string) (interface{}, error) {
-// if len(tail) > 0 {
-// return recurse(value, tail[0], tail[1:])
-// }
-// return value, nil
-// }
-
-// func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) {
-// log.Debugf("deleteMap for %v for %v\n", paths, context)
-
-// mapSlice := getMapSlice(context)
-
-// if len(paths) == 0 {
-// return mapSlice, nil
-// }
-
-// var index int
-// var child yaml.MapItem
-// for index, child = range mapSlice {
-// if matchesKey(paths[0], child.Key) {
-// log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index)
-// var badDelete error
-// mapSlice, badDelete = deleteEntryInMap(mapSlice, child, index, paths)
-// if badDelete != nil {
-// return nil, badDelete
-// }
-// }
-// }
-
-// return mapSlice, nil
-
-// }
-
-// func deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) {
-// remainingPaths := paths[1:]
-
-// var newSlice yaml.MapSlice
-// if len(remainingPaths) > 0 {
-// newChild := yaml.MapItem{Key: child.Key}
-// var errorDeleting error
-// newChild.Value, errorDeleting = deleteChildValue(child.Value, remainingPaths)
-// if errorDeleting != nil {
-// return nil, errorDeleting
-// }
-
-// newSlice = make(yaml.MapSlice, len(original))
-// for i := range original {
-// item := original[i]
-// if i == index {
-// item = newChild
-// }
-// newSlice[i] = item
-// }
-// } else {
-// // Delete item from slice at index
-// newSlice = append(original[:index], original[index+1:]...)
-// log.Debugf("\tDeleted item index %d from original", index)
-// }
-
-// log.Debugf("\tReturning original %v\n", original)
-// return newSlice, nil
-// }
-
-// func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) {
-// log.Debugf("deleteArraySplat for %v for %v\n", tail, array)
-// var newArray = make([]interface{}, len(array))
-// for index, value := range array {
-// val, err := deleteChildValue(value, tail)
-// if err != nil {
-// return nil, err
-// }
-// newArray[index] = val
-// }
-// return newArray, nil
-// }
-
-// func deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) {
-// log.Debugf("deleteArray for %v for %v\n", paths, array)
-
-// if index >= int64(len(array)) {
-// return array, nil
-// }
-
-// remainingPaths := paths[1:]
-// if len(remainingPaths) > 0 {
-// // Recurse into the array element at index
-// var errorDeleting error
-// array[index], errorDeleting = deleteMap(array[index], remainingPaths)
-// if errorDeleting != nil {
-// return nil, errorDeleting
-// }
-
-// } else {
-// // Delete the array element at index
-// array = append(array[:index], array[index+1:]...)
-// log.Debugf("\tDeleted item index %d from array, leaving %v", index, array)
-// }
-
-// log.Debugf("\tReturning array: %v\n", array)
-// return array, nil
-// }
-
-// func deleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) {
-// log.Debugf("deleteChildValue for %v for %v\n", remainingPaths, child)
-// var head = remainingPaths[0]
-// var tail = remainingPaths[1:]
-// switch child := child.(type) {
-// case yaml.MapSlice:
-// return deleteMap(child, remainingPaths)
-// case []interface{}:
-// if head == "*" {
-// return deleteArraySplat(child, tail)
-// }
-// index, err := strconv.ParseInt(head, 10, 64)
-// if err != nil {
-// return nil, fmt.Errorf("error accessing array: %v", err)
-// }
-// return deleteArray(child, remainingPaths, index)
-// }
-// return child, nil
-// }
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 9fb1591d..2415daea 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -10,14 +10,14 @@ import (
type UpdateCommand struct {
Command string
Path string
- Value yaml.Node
+ Value *yaml.Node
}
type YqLib interface {
DebugNode(node *yaml.Node)
Get(rootNode *yaml.Node, path string) (*yaml.Node, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
- New(updateCommand UpdateCommand) (yaml.Node, error)
+ New(path string) yaml.Node
}
type lib struct {
@@ -43,14 +43,10 @@ func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) {
return l.navigator.Get(rootNode, paths)
}
-func (l *lib) New(updateCommand UpdateCommand) (yaml.Node, error) {
- var paths = l.parser.ParsePath(updateCommand.Path)
+func (l *lib) New(path string) yaml.Node {
+ var paths = l.parser.ParsePath(path)
newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)}
- errorUpdating := l.navigator.Update(&newNode, paths, updateCommand.Value)
- if errorUpdating != nil {
- return newNode, errorUpdating
- }
- return newNode, nil
+ return newNode
}
func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
diff --git a/yq.go b/yq.go
index 3e5cbe9f..a9f14528 100644
--- a/yq.go
+++ b/yq.go
@@ -76,7 +76,7 @@ func newCommandCLI() *cobra.Command {
rootCmd.AddCommand(
createReadCmd(),
createWriteCmd(),
- // createPrefixCmd(),
+ createPrefixCmd(),
createDeleteCmd(),
createNewCmd(),
// createMergeCmd(),
@@ -143,28 +143,28 @@ a.b.e:
return cmdWrite
}
-// func createPrefixCmd() *cobra.Command {
-// var cmdWrite = &cobra.Command{
-// Use: "prefix [yaml_file] [path]",
-// Aliases: []string{"p"},
-// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
-// Example: `
-// yq prefix things.yaml a.b.c
-// yq prefix --inplace things.yaml a.b.c
-// yq prefix --inplace -- things.yaml --key-starting-with-dash
-// yq p -i things.yaml a.b.c
-// yq p --doc 2 things.yaml a.b.d
-// yq p -d2 things.yaml a.b.d
-// `,
-// Long: `Prefixes w.r.t to the yaml file at the given path.
-// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
-// `,
-// RunE: prefixProperty,
-// }
-// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
-// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
-// return cmdWrite
-// }
+func createPrefixCmd() *cobra.Command {
+ var cmdPrefix = &cobra.Command{
+ Use: "prefix [yaml_file] [path]",
+ Aliases: []string{"p"},
+ Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
+ Example: `
+yq prefix things.yaml a.b.c
+yq prefix --inplace things.yaml a.b.c
+yq prefix --inplace -- things.yaml --key-starting-with-dash
+yq p -i things.yaml a.b.c
+yq p --doc 2 things.yaml a.b.d
+yq p -d2 things.yaml a.b.d
+ `,
+ Long: `Prefixes w.r.t to the yaml file at the given path.
+Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
+`,
+ RunE: prefixProperty,
+ }
+ cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
+ cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
+ return cmdPrefix
+}
func createDeleteCmd() *cobra.Command {
var cmdDelete = &cobra.Command{
@@ -418,17 +418,13 @@ func writeProperty(cmd *cobra.Command, args []string) error {
}
func newProperty(cmd *cobra.Command, args []string) error {
- var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ")
+ var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ")
if updateCommandsError != nil {
return updateCommandsError
}
- firstCommand, restOfCommands := updateCommands[0], updateCommands[1:]
- newNode, errorCreating := lib.New(firstCommand)
- if errorCreating != nil {
- return errorCreating
- }
+ newNode := lib.New(updateCommands[0].Path)
- for _, updateCommand := range restOfCommands {
+ for _, updateCommand := range updateCommands {
errorUpdating := lib.Update(&newNode, updateCommand)
@@ -442,6 +438,40 @@ func newProperty(cmd *cobra.Command, args []string) error {
encoder.Encode(&newNode)
encoder.Close()
return nil
+}
+
+func prefixProperty(cmd *cobra.Command, args []string) error {
+
+ if len(args) < 2 {
+ return errors.New("Must provide ")
+ }
+ updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]}
+ log.Debugf("args %v", args)
+
+ var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
+ if errorParsingDocIndex != nil {
+ return errorParsingDocIndex
+ }
+
+ var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
+ if updateAll || currentIndex == docIndexInt {
+ log.Debugf("Prefixing document %v", currentIndex)
+ lib.DebugNode(dataBucket)
+ updateCommand.Value = dataBucket.Content[0]
+ dataBucket.Content = make([]*yaml.Node, 1)
+
+ newNode := lib.New(updateCommand.Path)
+ dataBucket.Content[0] = &newNode
+
+ errorUpdating := lib.Update(dataBucket, updateCommand)
+ if errorUpdating != nil {
+ return errorUpdating
+ }
+
+ }
+ return nil
+ }
+ return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
}
@@ -575,12 +605,26 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
// return readAndUpdate(cmd.OutOrStdout(), input, updateData)
// }
+type updateCommandParsed struct {
+ Command string
+ Path string
+ Value yaml.Node
+}
+
func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) {
- var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1)
+ var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
if writeScript != "" {
- if err := readData(writeScript, 0, &updateCommands); err != nil {
+ var parsedCommands = make([]updateCommandParsed, 0)
+ if err := readData(writeScript, 0, &parsedCommands); err != nil {
return nil, err
}
+ log.Debugf("Read write commands file '%v'", parsedCommands)
+ for index := range parsedCommands {
+ parsedCommand := parsedCommands[index]
+ updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value}
+ updateCommands = append(updateCommands, updateCommand)
+ }
+
log.Debugf("Read write commands file '%v'", updateCommands)
} else if len(args) < expectedArgs {
return nil, errors.New(badArgsMessage)
@@ -594,7 +638,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
return updateCommands, nil
}
-func parseValue(argument string) yaml.Node {
+func parseValue(argument string) *yaml.Node {
var err interface{}
var tag = customTag
@@ -618,11 +662,11 @@ func parseValue(argument string) yaml.Node {
tag = "!!null"
}
if argument == "[]" {
- return yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
+ return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
}
}
log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag)
- return yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
+ return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
}
func safelyRenameFile(from string, to string) {
From d7392f7b58e62b23677fc67b7d987e1a0d04fde6 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 16 Dec 2019 16:46:20 +1100
Subject: [PATCH 18/68] Refactoring
---
commands_test.go | 18 ++++
go.mod | 6 +-
go.sum | 7 ++
pkg/yqlib/value_parser.go | 46 ++++++---
yq.go | 190 ++++++++++++--------------------------
5 files changed, 118 insertions(+), 149 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 9002d816..58d58e14 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -94,6 +94,24 @@ func TestReadCmd(t *testing.T) {
test.AssertResult(t, "2\n", result.Output)
}
+// func TestReadRawCmd(t *testing.T) {
+// cmd := getRootCommand()
+// result := test.RunCmd(cmd, "read examples/sample.yaml b.c")
+// if result.Error != nil {
+// t.Error(result.Error)
+// }
+// test.AssertResult(t, "21\n", result.Output)
+// }
+
+// func TestReadRawMultiCmd(t *testing.T) {
+// cmd := getRootCommand()
+// result := test.RunCmd(cmd, "read examples/sample.yaml b.c")
+// if result.Error != nil {
+// t.Error(result.Error)
+// }
+// test.AssertResult(t, "21\n", result.Output)
+// }
+
func TestReadInvalidDocumentIndexCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c")
diff --git a/go.mod b/go.mod
index efc8ffd2..61719058 100644
--- a/go.mod
+++ b/go.mod
@@ -1,11 +1,11 @@
module github.com/mikefarah/yq/v3
require (
- github.com/mikefarah/yaml/v2 v2.4.0
+ github.com/mikefarah/yaml/v2 v2.4.0 // indirect
github.com/pkg/errors v0.8.1
github.com/spf13/cobra v0.0.5
- golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 // indirect
- gopkg.in/imdario/mergo.v0 v0.3.7
+ golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 // indirect
+ gopkg.in/imdario/mergo.v0 v0.3.7 // indirect
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2
)
diff --git a/go.sum b/go.sum
index 307ebb49..2f045e24 100644
--- a/go.sum
+++ b/go.sum
@@ -35,14 +35,21 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 h1:s5lp4ug7qHzUccgyFdjsX7OZDzHXRaePrF3B3vmUiuM=
golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 h1:kJQZhwFzSwJS2BxboKjdZzWczQOZx8VuH7Y8hhuGUtM=
+golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/imdario/mergo.v0 v0.3.7 h1:QDotlIZtaO/p+Um0ok18HRTpq5i5/SAk/qprsor+9c8=
diff --git a/pkg/yqlib/value_parser.go b/pkg/yqlib/value_parser.go
index d08da020..7a51d7c9 100644
--- a/pkg/yqlib/value_parser.go
+++ b/pkg/yqlib/value_parser.go
@@ -2,34 +2,50 @@ package yqlib
import (
"strconv"
+
+ logging "gopkg.in/op/go-logging.v1"
+ yaml "gopkg.in/yaml.v3"
)
type ValueParser interface {
- ParseValue(argument string) interface{}
+ Parse(argument string, customTag string) *yaml.Node
}
-type valueParser struct{}
-
-func NewValueParser() ValueParser {
- return &valueParser{}
+type valueParser struct {
+ log *logging.Logger
}
-func (v *valueParser) ParseValue(argument string) interface{} {
- var value, err interface{}
+func NewValueParser(l *logging.Logger) ValueParser {
+ return &valueParser{log: l}
+}
+
+func (v *valueParser) Parse(argument string, customTag string) *yaml.Node {
+ var err interface{}
+ var tag = customTag
+
var inQuotes = len(argument) > 0 && argument[0] == '"'
- if !inQuotes {
- value, err = strconv.ParseFloat(argument, 64)
+ if tag == "" && !inQuotes {
+
+ _, err = strconv.ParseBool(argument)
if err == nil {
- return value
+ tag = "!!bool"
}
- value, err = strconv.ParseBool(argument)
+ _, err = strconv.ParseFloat(argument, 64)
if err == nil {
- return value
+ tag = "!!float"
+ }
+ _, err = strconv.ParseInt(argument, 10, 64)
+ if err == nil {
+ tag = "!!int"
+ }
+
+ if argument == "null" {
+ tag = "!!null"
}
if argument == "[]" {
- return make([]interface{}, 0)
+ return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
}
- return argument
}
- return argument[1 : len(argument)-1]
+ v.log.Debugf("parsed value '%v', tag: '%v'", argument, tag)
+ return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
}
diff --git a/yq.go b/yq.go
index a9f14528..d4998e69 100644
--- a/yq.go
+++ b/yq.go
@@ -29,7 +29,7 @@ var version = false
var docIndex = "0"
var log = logging.MustGetLogger("yq")
var lib = yqlib.NewYqLib(log)
-var valueParser = yqlib.NewValueParser()
+var valueParser = yqlib.NewValueParser(log)
func main() {
cmd := newCommandCLI()
@@ -263,28 +263,14 @@ func readProperty(cmd *cobra.Command, args []string) error {
for {
var dataBucket yaml.Node
errorReading := decoder.Decode(&dataBucket)
- log.Debugf("decoded node for doc %v", currentIndex)
- lib.DebugNode(&dataBucket)
+
if errorReading == io.EOF {
- log.Debugf("done %v / %v", currentIndex, docIndexInt)
- if !updateAll && currentIndex <= docIndexInt {
- return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex)
- }
- return nil
+ return handleEOF(updateAll, docIndexInt, currentIndex)
}
- log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
- if updateAll || currentIndex == docIndexInt {
- log.Debugf("reading %v in document %v", path, currentIndex)
- mappedDoc, errorParsing := lib.Get(&dataBucket, path)
- lib.DebugNode(mappedDoc)
- log.Debugf("carry on")
- if errorParsing != nil {
- return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
- } else if mappedDoc != nil {
-
- mappedDocs = append(mappedDocs, mappedDoc)
- }
-
+ var errorParsing error
+ mappedDocs, errorParsing = appendDocument(mappedDocs, dataBucket, path, updateAll, docIndexInt, currentIndex)
+ if errorParsing != nil {
+ return errorParsing
}
currentIndex = currentIndex + 1
}
@@ -292,7 +278,38 @@ func readProperty(cmd *cobra.Command, args []string) error {
if errorReadingStream != nil {
return errorReadingStream
- } else if len(mappedDocs) == 0 {
+ }
+
+ return printResults(mappedDocs, cmd)
+}
+
+func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
+ log.Debugf("done %v / %v", currentIndex, docIndexInt)
+ if !updateAll && currentIndex <= docIndexInt {
+ return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex)
+ }
+ return nil
+}
+
+func appendDocument(mappedDocs []*yaml.Node, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yaml.Node, error) {
+ log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
+ lib.DebugNode(&dataBucket)
+ if !updateAll && currentIndex != docIndexInt {
+ return mappedDocs, nil
+ }
+ log.Debugf("reading %v in document %v", path, currentIndex)
+ mappedDoc, errorParsing := lib.Get(&dataBucket, path)
+ lib.DebugNode(mappedDoc)
+ if errorParsing != nil {
+ return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
+ } else if mappedDoc != nil {
+ return append(mappedDocs, mappedDoc), nil
+ }
+ return mappedDocs, nil
+}
+
+func printResults(mappedDocs []*yaml.Node, cmd *cobra.Command) error {
+ if len(mappedDocs) == 0 {
log.Debug("no matching results, nothing to print")
return nil
}
@@ -307,7 +324,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
cmd.Println(mappedDoc.Value)
}
}
- } else if !updateAll {
+ } else if len(mappedDocs) == 1 {
err = encoder.Encode(mappedDocs[0])
} else {
err = encoder.Encode(&yaml.Node{Kind: yaml.SequenceNode, Content: mappedDocs})
@@ -319,43 +336,6 @@ func readProperty(cmd *cobra.Command, args []string) error {
return nil
}
-// func newProperty(cmd *cobra.Command, args []string) error {
-// updatedData, err := newYaml(args)
-// if err != nil {
-// return err
-// }
-// dataStr, err := toString(updatedData)
-// if err != nil {
-// return err
-// }
-// cmd.Println(dataStr)
-// return nil
-// }
-
-// func newYaml(args []string) (interface{}, error) {
-// var writeCommands, writeCommandsError = readUpdateCommands(args, 2, "Must provide ")
-// if writeCommandsError != nil {
-// return nil, writeCommandsError
-// }
-
-// var dataBucket interface{}
-// var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[")
-// if isArray {
-// dataBucket = make([]interface{}, 0)
-// } else {
-// dataBucket = make(yaml.MapSlice, 0)
-// }
-
-// for _, entry := range writeCommands {
-// path := entry.Key.(string)
-// value := entry.Value
-// log.Debugf("setting %v to %v", path, value)
-// dataBucket = lib.WritePath(dataBucket, path, value)
-// }
-
-// return dataBucket, nil
-// }
-
func parseDocumentIndex() (bool, int, error) {
if docIndex == "*" {
return true, -1, nil
@@ -454,25 +434,27 @@ func prefixProperty(cmd *cobra.Command, args []string) error {
}
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
- if updateAll || currentIndex == docIndexInt {
- log.Debugf("Prefixing document %v", currentIndex)
- lib.DebugNode(dataBucket)
- updateCommand.Value = dataBucket.Content[0]
- dataBucket.Content = make([]*yaml.Node, 1)
-
- newNode := lib.New(updateCommand.Path)
- dataBucket.Content[0] = &newNode
-
- errorUpdating := lib.Update(dataBucket, updateCommand)
- if errorUpdating != nil {
- return errorUpdating
- }
-
- }
- return nil
+ return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand)
}
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
+}
+func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error {
+ if updateAll || currentIndex == docIndexInt {
+ log.Debugf("Prefixing document %v", currentIndex)
+ lib.DebugNode(dataBucket)
+ updateCommand.Value = dataBucket.Content[0]
+ dataBucket.Content = make([]*yaml.Node, 1)
+
+ newNode := lib.New(updateCommand.Path)
+ dataBucket.Content[0] = &newNode
+
+ errorUpdating := lib.Update(dataBucket, updateCommand)
+ if errorUpdating != nil {
+ return errorUpdating
+ }
+ }
+ return nil
}
func deleteProperty(cmd *cobra.Command, args []string) error {
@@ -506,29 +488,6 @@ func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io
return readAndUpdate(writer, inputFile, updateData)
}
-// func prefixProperty(cmd *cobra.Command, args []string) error {
-// if len(args) != 2 {
-// return errors.New("Must provide ")
-// }
-// prefixPath := args[1]
-
-// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
-// if errorParsingDocIndex != nil {
-// return errorParsingDocIndex
-// }
-
-// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
-
-// if updateAll || currentIndex == docIndexInt {
-// log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex)
-// var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath)
-// return mapDataBucket, nil
-// }
-// return dataBucket, nil
-// }
-// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
-// }
-
func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
var destination io.Writer
var destinationName string
@@ -633,42 +592,11 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
log.Debug("args %v", args)
log.Debug("path %v", args[expectedArgs-2])
log.Debug("Value %v", args[expectedArgs-1])
- updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])}
+ updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag)}
}
return updateCommands, nil
}
-func parseValue(argument string) *yaml.Node {
- var err interface{}
- var tag = customTag
-
- var inQuotes = len(argument) > 0 && argument[0] == '"'
- if tag == "" && !inQuotes {
-
- _, err = strconv.ParseBool(argument)
- if err == nil {
- tag = "!!bool"
- }
- _, err = strconv.ParseFloat(argument, 64)
- if err == nil {
- tag = "!!float"
- }
- _, err = strconv.ParseInt(argument, 10, 64)
- if err == nil {
- tag = "!!int"
- }
-
- if argument == "null" {
- tag = "!!null"
- }
- if argument == "[]" {
- return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
- }
- }
- log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag)
- return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
-}
-
func safelyRenameFile(from string, to string) {
if renameError := os.Rename(from, to); renameError != nil {
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
From 290579ac7fc21b9770dd35aa4634b23aa69aa183 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 16 Dec 2019 20:38:55 +1100
Subject: [PATCH 19/68] Handle simple aliases
---
commands_test.go | 9 +++++++++
examples/simple-anchor.yaml | 4 ++++
pkg/yqlib/data_navigator.go | 22 ++++++++++++++++++----
pkg/yqlib/lib.go | 20 +++++++++++---------
4 files changed, 42 insertions(+), 13 deletions(-)
create mode 100644 examples/simple-anchor.yaml
diff --git a/commands_test.go b/commands_test.go
index 58d58e14..fa497bfd 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -112,6 +112,15 @@ func TestReadCmd(t *testing.T) {
// test.AssertResult(t, "21\n", result.Output)
// }
+func TestReadAnchorsCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "1\n", result.Output)
+}
+
func TestReadInvalidDocumentIndexCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c")
diff --git a/examples/simple-anchor.yaml b/examples/simple-anchor.yaml
new file mode 100644
index 00000000..c83b2dcf
--- /dev/null
+++ b/examples/simple-anchor.yaml
@@ -0,0 +1,4 @@
+foo: &foo
+ a: 1
+
+foobar: *foo
\ No newline at end of file
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 1e2d03e0..e1493eb3 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -18,14 +18,16 @@ type DataNavigator interface {
}
type navigator struct {
- log *logging.Logger
+ log *logging.Logger
+ followAliases bool
}
type VisitorFn func(*yaml.Node) error
-func NewDataNavigator(l *logging.Logger) DataNavigator {
+func NewDataNavigator(l *logging.Logger, followAliases bool) DataNavigator {
return &navigator{
- log: l,
+ log: l,
+ followAliases: followAliases,
}
}
@@ -147,6 +149,10 @@ func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind {
if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) {
return guess
}
+ if guess == yaml.AliasNode {
+ n.log.Debug("guess was an alias, okey doke.")
+ return guess
+ }
return yaml.MappingNode
}
@@ -172,6 +178,7 @@ func (n *navigator) DebugNode(value *yaml.Node) {
}
func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
+ n.log.Debug("recursing, processing %v", head)
switch value.Kind {
case yaml.MappingNode:
n.log.Debug("its a map with %v entries", len(value.Content)/2)
@@ -187,6 +194,13 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
return n.appendArray(value, tail, visitor)
}
return n.recurseArray(value, head, tail, visitor)
+ case yaml.AliasNode:
+ n.log.Debug("its an alias, followAliases: %v", n.followAliases)
+ n.DebugNode(value.Alias)
+ if n.followAliases == true {
+ return n.recurse(value.Alias, head, tail, visitor)
+ }
+ return nil
default:
return nil
}
@@ -237,7 +251,7 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi
// so keys are in the even indexes, values in odd.
for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
- n.log.Debug("index %v, checking %v", index, content.Value)
+ n.log.Debug("index %v, checking %v", index, content.Value))
if n.matchesKey(key, content.Value) {
errorVisiting := visit(index)
if errorVisiting != nil {
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 2415daea..eb3dc6db 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -28,37 +28,39 @@ type lib struct {
func NewYqLib(l *logging.Logger) YqLib {
return &lib{
- navigator: NewDataNavigator(l),
- parser: NewPathParser(),
- log: l,
+ parser: NewPathParser(),
+ log: l,
}
}
func (l *lib) DebugNode(node *yaml.Node) {
- l.navigator.DebugNode(node)
+ navigator := NewDataNavigator(l.log, false)
+ navigator.DebugNode(node)
}
func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) {
var paths = l.parser.ParsePath(path)
- return l.navigator.Get(rootNode, paths)
+ navigator := NewDataNavigator(l.log, true)
+ return navigator.Get(rootNode, paths)
}
func (l *lib) New(path string) yaml.Node {
var paths = l.parser.ParsePath(path)
- newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)}
+ navigator := NewDataNavigator(l.log, false)
+ newNode := yaml.Node{Kind: navigator.GuessKind(paths, 0)}
return newNode
}
func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
- // later - support other command types
+ navigator := NewDataNavigator(l.log, false)
l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
switch updateCommand.Command {
case "update":
var paths = l.parser.ParsePath(updateCommand.Path)
- return l.navigator.Update(rootNode, paths, updateCommand.Value)
+ return navigator.Update(rootNode, paths, updateCommand.Value)
case "delete":
var paths = l.parser.ParsePath(updateCommand.Path)
- return l.navigator.Delete(rootNode, paths)
+ return navigator.Delete(rootNode, paths)
default:
return fmt.Errorf("Unknown command %v", updateCommand.Command)
}
From 19fe718cfb25256091c9c7fbf4e6627459a7a742 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 16 Dec 2019 21:09:23 +1100
Subject: [PATCH 20/68] Aliases!
---
examples/merge-anchor.yml | 17 ++++++++++
pkg/yqlib/data_navigator.go | 39 +++++++++++++++++------
test.yml | 6 ++--
yq.go | 63 ++++++++++++++++++++-----------------
4 files changed, 85 insertions(+), 40 deletions(-)
create mode 100644 examples/merge-anchor.yml
diff --git a/examples/merge-anchor.yml b/examples/merge-anchor.yml
new file mode 100644
index 00000000..cbfcc0e6
--- /dev/null
+++ b/examples/merge-anchor.yml
@@ -0,0 +1,17 @@
+foo: &foo
+ a: original
+ thing: coolasdf
+
+bar: &bar
+ b: 2
+
+
+overrideA:
+ <<: [*foo,*bar]
+ a: vanilla
+ c: 3
+
+foobar:
+ <<: *foo
+ thing: ice
+ c: 3
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index e1493eb3..d8524749 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -96,7 +96,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
// need to delete in reverse - otherwise the matching indexes
// become incorrect.
matchingIndices := make([]int, 0)
- _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(indexInMap int) error {
+ _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(matchingNode []*yaml.Node, indexInMap int) error {
matchingIndices = append(matchingIndices, indexInMap)
n.log.Debug("matchingIndices %v", indexInMap)
return nil
@@ -153,6 +153,9 @@ func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind {
n.log.Debug("guess was an alias, okey doke.")
return guess
}
+ n.log.Debug("forcing a mapping node")
+ n.log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode)
+ n.log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode)
return yaml.MappingNode
}
@@ -221,9 +224,9 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn)
}
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
- visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(indexInMap int) error {
- 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)
+ visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error {
+ contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind))
+ return n.Visit(contents[indexInMap+1], tail, visitor)
})
if errorVisiting != nil {
@@ -242,18 +245,36 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
return n.Visit(&mapEntryValue, tail, visitor)
}
-type mapVisitorFn func(int) error
+// need to pass the node in, as it may be aliased
+type mapVisitorFn func([]*yaml.Node, int) error
func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
visited := false
-
+ n.log.Debug("visitMatchingEntries %v in %v", key, contents)
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
- for index := 0; index < len(contents); index = index + 2 {
+ // merge aliases are defined first, but we only want to traverse them
+ // if we dont find a match on this node first.
+ for index := len(contents) - 2; index >= 0; index = index - 2 {
content := contents[index]
- n.log.Debug("index %v, checking %v", index, content.Value))
+ n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
+
+ // only visit aliases if we didn't find a match in this object.
+ if n.followAliases && !visited && contents[index+1].Kind == yaml.AliasNode {
+ valueNode := contents[index+1]
+
+ n.log.Debug("need to visit the alias too")
+ n.DebugNode(valueNode)
+ visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit)
+ if errorInAlias != nil {
+ return false, errorInAlias
+ }
+ visited = visited || visitedAlias
+ }
+
if n.matchesKey(key, content.Value) {
- errorVisiting := visit(index)
+ n.log.Debug("found a match! %v", content.Value)
+ errorVisiting := visit(contents, index)
if errorVisiting != nil {
return visited, errorVisiting
}
diff --git a/test.yml b/test.yml
index 71f1098c..c83b2dcf 100644
--- a/test.yml
+++ b/test.yml
@@ -1,2 +1,4 @@
-a: apple
-c: cat
+foo: &foo
+ a: 1
+
+foobar: *foo
\ No newline at end of file
diff --git a/yq.go b/yq.go
index d4998e69..14a862bc 100644
--- a/yq.go
+++ b/yq.go
@@ -79,7 +79,7 @@ func newCommandCLI() *cobra.Command {
createPrefixCmd(),
createDeleteCmd(),
createNewCmd(),
- // createMergeCmd(),
+ createMergeCmd(),
)
rootCmd.SetOutput(os.Stdout)
@@ -211,36 +211,36 @@ Note that you can give a create script to perform more sophisticated yaml. This
return cmdNew
}
-// func createMergeCmd() *cobra.Command {
-// var cmdMerge = &cobra.Command{
-// Use: "merge [initial_yaml_file] [additional_yaml_file]...",
-// Aliases: []string{"m"},
-// Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml",
-// Example: `
-// yq merge things.yaml other.yaml
-// yq merge --inplace things.yaml other.yaml
-// yq m -i things.yaml other.yaml
-// yq m --overwrite things.yaml other.yaml
-// yq m -i -x things.yaml other.yaml
-// yq m -i -a things.yaml other.yaml
-// `,
-// Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
-// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
+func createMergeCmd() *cobra.Command {
+ var cmdMerge = &cobra.Command{
+ Use: "merge [initial_yaml_file] [additional_yaml_file]...",
+ Aliases: []string{"m"},
+ Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml",
+ Example: `
+yq merge things.yaml other.yaml
+yq merge --inplace things.yaml other.yaml
+yq m -i things.yaml other.yaml
+yq m --overwrite things.yaml other.yaml
+yq m -i -x things.yaml other.yaml
+yq m -i -a things.yaml other.yaml
+ `,
+ Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
+Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
-// If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
-// If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
+If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
+If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
-// Note that if you set both flags only overwrite will take effect.
-// `,
-// RunE: mergeProperties,
-// }
-// cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
-// cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
-// cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
-// cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
-// cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
-// return cmdMerge
-// }
+Note that if you set both flags only overwrite will take effect.
+`,
+ RunE: mergeProperties,
+ }
+ cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
+ // cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
+ // cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
+ // cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
+ cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
+ return cmdMerge
+}
func readProperty(cmd *cobra.Command, args []string) error {
var path = ""
@@ -397,6 +397,11 @@ func writeProperty(cmd *cobra.Command, args []string) error {
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
}
+func mergeProperties(cmd *cobra.Command, args []string) error {
+ // first generate update commands from the file
+ return nil
+}
+
func newProperty(cmd *cobra.Command, args []string) error {
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ")
if updateCommandsError != nil {
From 949bf1c1d7afb5ddc4d211ef726cd42bc180cfe9 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 22 Dec 2019 15:15:15 +1100
Subject: [PATCH 21/68] Merge anchors - wip
---
commands_test.go | 45 +++++++++++
.../{merge-anchor.yml => merge-anchor.yaml} | 8 +-
pkg/yqlib/data_navigator.go | 77 +++++++++++++++----
3 files changed, 110 insertions(+), 20 deletions(-)
rename examples/{merge-anchor.yml => merge-anchor.yaml} (69%)
diff --git a/commands_test.go b/commands_test.go
index fa497bfd..9f1349eb 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -121,6 +121,51 @@ func TestReadAnchorsCmd(t *testing.T) {
test.AssertResult(t, "1\n", result.Output)
}
+func TestReadMergeAnchorsOriginalCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.a")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "original\n", result.Output)
+}
+
+func TestReadMergeAnchorsOverrideCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.thing")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "ice\n", result.Output)
+}
+
+func TestReadMergeAnchorsListOriginalCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.a")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "original\n", result.Output)
+}
+
+func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.thing")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "coconut\n", result.Output)
+}
+
+func TestReadMergeAnchorsListOverrideCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.c")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "newbar\n", result.Output)
+}
+
func TestReadInvalidDocumentIndexCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c")
diff --git a/examples/merge-anchor.yml b/examples/merge-anchor.yaml
similarity index 69%
rename from examples/merge-anchor.yml
rename to examples/merge-anchor.yaml
index cbfcc0e6..2459c9a9 100644
--- a/examples/merge-anchor.yml
+++ b/examples/merge-anchor.yaml
@@ -4,12 +4,12 @@ foo: &foo
bar: &bar
b: 2
+ thing: coconut
+ c: oldbar
-
-overrideA:
+foobarList:
<<: [*foo,*bar]
- a: vanilla
- c: 3
+ c: newbar
foobar:
<<: *foo
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index d8524749..abbf0524 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -254,24 +254,11 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
- // if we dont find a match on this node first.
- for index := len(contents) - 2; index >= 0; index = index - 2 {
+ // if we don't find a match on this node first.
+ for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
- // only visit aliases if we didn't find a match in this object.
- if n.followAliases && !visited && contents[index+1].Kind == yaml.AliasNode {
- valueNode := contents[index+1]
-
- n.log.Debug("need to visit the alias too")
- n.DebugNode(valueNode)
- visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit)
- if errorInAlias != nil {
- return false, errorInAlias
- }
- visited = visited || visitedAlias
- }
-
if n.matchesKey(key, content.Value) {
n.log.Debug("found a match! %v", content.Value)
errorVisiting := visit(contents, index)
@@ -281,7 +268,65 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi
visited = true
}
}
- return visited, nil
+
+ if visited == true || n.followAliases == false {
+ return visited, nil
+ }
+
+ // didnt find a match, lets check the aliases.
+ // merge aliases are defined first, but we only want to traverse them
+ // if we don't find a match on this node first.
+ // traverse them backwards so that the last alias overrides the preceding.
+
+ n.log.Debug("no entry in the map, checking for aliases")
+
+ for index := len(contents) - 2; index >= 0; index = index - 2 {
+ // content := contents[index]
+ n.log.Debug("looking for %v", yaml.AliasNode)
+
+ n.log.Debug("searching for aliases key %v kind %v", contents[index].Value, contents[index].Kind)
+ n.log.Debug("searching for aliases value %v kind %v", contents[index+1].Value, contents[index+1].Kind)
+
+ // only visit aliases if we didn't find a match in this object.
+
+ // NEED TO HANDLE A SEQUENCE OF ALIASES, and search each one.
+ // probably stop searching after we find a match, because overrides.
+
+ if contents[index+1].Kind == yaml.AliasNode {
+ valueNode := contents[index+1]
+
+ n.log.Debug("found an alias")
+ n.DebugNode(contents[index])
+ n.DebugNode(valueNode)
+ visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit)
+ if errorInAlias != nil {
+ return false, errorInAlias
+ }
+ if visitedAlias == true {
+ return true, nil
+ }
+ } else if contents[index+1].Kind == yaml.SequenceNode {
+ // could be an array of aliases...need to search this backwards too!
+ possibleAliasArray := contents[index+1].Content
+ for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
+ child := possibleAliasArray[aliasIndex]
+ if child.Kind == yaml.AliasNode {
+ n.log.Debug("found an alias")
+ n.DebugNode(child)
+ visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit)
+ if errorInAlias != nil {
+ return false, errorInAlias
+ }
+ if visitedAlias == true {
+ return true, nil
+ }
+ }
+ }
+ }
+
+ }
+ n.log.Debug("no aliases")
+ return false, nil
}
func (n *navigator) matchesKey(key string, actual string) bool {
From 865a55645c6749b5ca4626b4673db82cd5648d7c Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 22 Dec 2019 15:33:54 +1100
Subject: [PATCH 22/68] Merge anchors - refactored
---
pkg/yqlib/data_navigator.go | 88 ++++++++++++++++++-------------------
1 file changed, 44 insertions(+), 44 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index abbf0524..edf341f4 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -248,13 +248,8 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
// need to pass the node in, as it may be aliased
type mapVisitorFn func([]*yaml.Node, int) error
-func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
+func (n *navigator) visitDirectMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
visited := false
- n.log.Debug("visitMatchingEntries %v in %v", key, contents)
- // value.Content is a concatenated array of key, value,
- // so keys are in the even indexes, values in odd.
- // merge aliases are defined first, but we only want to traverse them
- // if we don't find a match on this node first.
for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
@@ -268,64 +263,69 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi
visited = true
}
}
+ return visited, nil
+}
- if visited == true || n.followAliases == false {
- return visited, nil
+func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
+
+ n.log.Debug("visitMatchingEntries %v in %v", key, contents)
+ // value.Content is a concatenated array of key, value,
+ // so keys are in the even indexes, values in odd.
+ // merge aliases are defined first, but we only want to traverse them
+ // if we don't find a match directly on this node first.
+ visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(contents, key, visit)
+ if errorVisitedDirectEntries != nil || visited == true || n.followAliases == false {
+ return visited, errorVisitedDirectEntries
}
-
// didnt find a match, lets check the aliases.
+
+ return n.visitAliases(contents, key, visit)
+}
+
+func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match on this node first.
// traverse them backwards so that the last alias overrides the preceding.
-
- n.log.Debug("no entry in the map, checking for aliases")
-
+ // a node can either be
+ // an alias to one other node (e.g. <<: *blah)
+ // or a sequence of aliases (e.g. <<: [*blah, *foo])
+ n.log.Debug("checking for aliases")
for index := len(contents) - 2; index >= 0; index = index - 2 {
- // content := contents[index]
- n.log.Debug("looking for %v", yaml.AliasNode)
-
- n.log.Debug("searching for aliases key %v kind %v", contents[index].Value, contents[index].Kind)
- n.log.Debug("searching for aliases value %v kind %v", contents[index+1].Value, contents[index+1].Kind)
-
- // only visit aliases if we didn't find a match in this object.
-
- // NEED TO HANDLE A SEQUENCE OF ALIASES, and search each one.
- // probably stop searching after we find a match, because overrides.
if contents[index+1].Kind == yaml.AliasNode {
valueNode := contents[index+1]
-
n.log.Debug("found an alias")
n.DebugNode(contents[index])
n.DebugNode(valueNode)
+
visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit)
- if errorInAlias != nil {
- return false, errorInAlias
- }
- if visitedAlias == true {
- return true, nil
+ if visitedAlias == true || errorInAlias != nil {
+ return visitedAlias, errorInAlias
}
} else if contents[index+1].Kind == yaml.SequenceNode {
// could be an array of aliases...need to search this backwards too!
- possibleAliasArray := contents[index+1].Content
- for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
- child := possibleAliasArray[aliasIndex]
- if child.Kind == yaml.AliasNode {
- n.log.Debug("found an alias")
- n.DebugNode(child)
- visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit)
- if errorInAlias != nil {
- return false, errorInAlias
- }
- if visitedAlias == true {
- return true, nil
- }
- }
+ visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, key, visit)
+ if visitedAliasSeq == true || errorVisitingAliasSeq != nil {
+ return visitedAliasSeq, errorVisitingAliasSeq
+ }
+ }
+ }
+ n.log.Debug("nope no matching aliases found")
+ return false, nil
+}
+
+func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
+ for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
+ child := possibleAliasArray[aliasIndex]
+ if child.Kind == yaml.AliasNode {
+ n.log.Debug("found an alias")
+ n.DebugNode(child)
+ visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit)
+ if visitedAlias == true || errorInAlias != nil {
+ return visitedAlias, errorInAlias
}
}
-
}
- n.log.Debug("no aliases")
return false, nil
}
From 784513dd1853e18880defc78e631ff507dde04da Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 22 Dec 2019 15:35:16 +1100
Subject: [PATCH 23/68] Merge anchors - refactored
---
pkg/yqlib/data_navigator.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index edf341f4..b0bd6d0c 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -303,7 +303,7 @@ func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVis
return visitedAlias, errorInAlias
}
} else if contents[index+1].Kind == yaml.SequenceNode {
- // could be an array of aliases...need to search this backwards too!
+ // could be an array of aliases...
visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, key, visit)
if visitedAliasSeq == true || errorVisitingAliasSeq != nil {
return visitedAliasSeq, errorVisitingAliasSeq
@@ -315,6 +315,7 @@ func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVis
}
func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
+ // need to search this backwards too, so that aliases defined last override the preceding.
for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
child := possibleAliasArray[aliasIndex]
if child.Kind == yaml.AliasNode {
From 4fb44dbc47035ec0964c8259fc9643a03ecf17fb Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 22 Dec 2019 17:13:11 +1100
Subject: [PATCH 24/68] Return path, smart print
---
pkg/yqlib/data_navigator.go | 89 ++++++++++++++++++++-----------------
pkg/yqlib/lib.go | 4 +-
yq.go | 47 ++++++++------------
3 files changed, 68 insertions(+), 72 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index b0bd6d0c..e3181e3b 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -11,7 +11,7 @@ import (
type DataNavigator interface {
DebugNode(node *yaml.Node)
- Get(rootNode *yaml.Node, path []string) (*yaml.Node, error)
+ Get(rootNode *yaml.Node, path []string) ([]MatchingNode, error)
Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error
Delete(rootNode *yaml.Node, path []string) error
GuessKind(tail []string, guess yaml.Kind) yaml.Kind
@@ -22,7 +22,7 @@ type navigator struct {
followAliases bool
}
-type VisitorFn func(*yaml.Node) error
+type VisitorFn func(matchingNode *yaml.Node, pathStack []interface{}) error
func NewDataNavigator(l *logging.Logger, followAliases bool) DataNavigator {
return &navigator{
@@ -31,29 +31,28 @@ func NewDataNavigator(l *logging.Logger, followAliases bool) DataNavigator {
}
}
-func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
- matchingNodes := make([]*yaml.Node, 0)
+type MatchingNode struct {
+ Node *yaml.Node
+ PathStack []interface{}
+}
- n.Visit(value, path, func(matchedNode *yaml.Node) error {
- matchingNodes = append(matchingNodes, matchedNode)
+func (n *navigator) Get(value *yaml.Node, path []string) ([]MatchingNode, error) {
+ matchingNodes := make([]MatchingNode, 0)
+
+ n.Visit(value, path, func(matchedNode *yaml.Node, pathStack []interface{}) error {
+ matchingNodes = append(matchingNodes, MatchingNode{matchedNode, pathStack})
n.log.Debug("Matched")
+ for _, pathElement := range pathStack {
+ n.log.Debug("%v", pathElement)
+ }
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
+ return matchingNodes, nil
}
func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error {
- errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) error {
+ errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node, pathStack []interface{}) error {
n.log.Debug("going to update")
n.DebugNode(nodeToUpdate)
n.log.Debug("with")
@@ -77,7 +76,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
lastBit, newTail := path[len(path)-1], path[:len(path)-1]
n.log.Debug("splitting path, %v", lastBit)
n.log.Debug("new tail, %v", newTail)
- errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node) error {
+ errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node, pathStack []interface{}) error {
n.log.Debug("need to find %v in here", lastBit)
n.DebugNode(nodeToUpdate)
original := nodeToUpdate.Content
@@ -121,16 +120,21 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) error {
realValue := value
+ emptyArray := make([]interface{}, 0)
if realValue.Kind == yaml.DocumentNode {
n.log.Debugf("its a document! returning the first child")
- realValue = value.Content[0]
+ return n.doVisit(value.Content[0], path, visitor, emptyArray)
}
+ return n.doVisit(value, path, visitor, emptyArray)
+}
+
+func (n *navigator) doVisit(value *yaml.Node, path []string, visitor VisitorFn, pathStack []interface{}) error {
if len(path) > 0 {
n.log.Debugf("diving into %v", path[0])
n.DebugNode(value)
- return n.recurse(realValue, path[0], path[1:], visitor)
+ return n.recurse(value, path[0], path[1:], visitor, pathStack)
}
- return visitor(realValue)
+ return visitor(value, pathStack)
}
func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind {
@@ -180,28 +184,28 @@ func (n *navigator) DebugNode(value *yaml.Node) {
}
}
-func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
+func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error {
n.log.Debug("recursing, processing %v", head)
switch value.Kind {
case yaml.MappingNode:
n.log.Debug("its a map with %v entries", len(value.Content)/2)
if head == "*" {
- return n.splatMap(value, tail, visitor)
+ return n.splatMap(value, tail, visitor, pathStack)
}
- return n.recurseMap(value, head, tail, visitor)
+ return n.recurseMap(value, head, tail, visitor, pathStack)
case yaml.SequenceNode:
n.log.Debug("its a sequence of %v things!, %v", len(value.Content))
if head == "*" {
- return n.splatArray(value, tail, visitor)
+ return n.splatArray(value, tail, visitor, pathStack)
} else if head == "+" {
- return n.appendArray(value, tail, visitor)
+ return n.appendArray(value, tail, visitor, pathStack)
}
- return n.recurseArray(value, head, tail, visitor)
+ return n.recurseArray(value, head, tail, visitor, pathStack)
case yaml.AliasNode:
n.log.Debug("its an alias, followAliases: %v", n.followAliases)
n.DebugNode(value.Alias)
if n.followAliases == true {
- return n.recurse(value.Alias, head, tail, visitor)
+ return n.recurse(value.Alias, head, tail, visitor, pathStack)
}
return nil
default:
@@ -209,13 +213,13 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
}
}
-func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn) error {
+func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error {
for index, content := range value.Content {
if index%2 == 0 {
continue
}
content = n.getOrReplace(content, n.GuessKind(tail, content.Kind))
- var err = n.Visit(content, tail, visitor)
+ var err = n.doVisit(content, tail, visitor, append(pathStack, value.Content[index-1].Value))
if err != nil {
return err
}
@@ -223,10 +227,10 @@ func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn)
return nil
}
-func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
+func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error {
visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error {
contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind))
- return n.Visit(contents[indexInMap+1], tail, visitor)
+ return n.doVisit(contents[indexInMap+1], tail, visitor, append(pathStack, contents[indexInMap].Value))
})
if errorVisiting != nil {
@@ -238,15 +242,16 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
}
//didn't find it, lets add it.
- value.Content = append(value.Content, &yaml.Node{Value: head, Kind: yaml.ScalarNode})
+ mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
+ value.Content = append(value.Content, &mapEntryKey)
mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)}
value.Content = append(value.Content, &mapEntryValue)
n.log.Debug("adding new node %v", value.Content)
- return n.Visit(&mapEntryValue, tail, visitor)
+ return n.doVisit(&mapEntryValue, tail, visitor, append(pathStack, head))
}
// need to pass the node in, as it may be aliased
-type mapVisitorFn func([]*yaml.Node, int) error
+type mapVisitorFn func(contents []*yaml.Node, index int) error
func (n *navigator) visitDirectMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
visited := false
@@ -338,12 +343,12 @@ func (n *navigator) matchesKey(key string, actual string) bool {
return actual == key
}
-func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn) error {
- for _, childValue := range value.Content {
+func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error {
+ for index, childValue := range value.Content {
n.log.Debug("processing")
n.DebugNode(childValue)
childValue = n.getOrReplace(childValue, n.GuessKind(tail, childValue.Kind))
- var err = n.Visit(childValue, tail, visitor)
+ var err = n.doVisit(childValue, tail, visitor, append(pathStack, index))
if err != nil {
return err
}
@@ -351,14 +356,14 @@ func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorF
return nil
}
-func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn) error {
+func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error {
var newNode = yaml.Node{Kind: n.GuessKind(tail, 0)}
value.Content = append(value.Content, &newNode)
n.log.Debug("appending a new node, %v", value.Content)
- return n.Visit(&newNode, tail, visitor)
+ return n.doVisit(&newNode, tail, visitor, append(pathStack, len(value.Content)-1))
}
-func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn) error {
+func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error {
var index, err = strconv.ParseInt(head, 10, 64) // nolint
if err != nil {
return err
@@ -367,5 +372,5 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v
return nil
}
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.doVisit(value.Content[index], tail, visitor, append(pathStack, index))
}
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index eb3dc6db..637d8e1c 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -15,7 +15,7 @@ type UpdateCommand struct {
type YqLib interface {
DebugNode(node *yaml.Node)
- Get(rootNode *yaml.Node, path string) (*yaml.Node, error)
+ Get(rootNode *yaml.Node, path string) ([]MatchingNode, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
New(path string) yaml.Node
}
@@ -38,7 +38,7 @@ func (l *lib) DebugNode(node *yaml.Node) {
navigator.DebugNode(node)
}
-func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) {
+func (l *lib) Get(rootNode *yaml.Node, path string) ([]MatchingNode, error) {
var paths = l.parser.ParsePath(path)
navigator := NewDataNavigator(l.log, true)
return navigator.Get(rootNode, paths)
diff --git a/yq.go b/yq.go
index 14a862bc..becedccd 100644
--- a/yq.go
+++ b/yq.go
@@ -17,7 +17,6 @@ import (
yaml "gopkg.in/yaml.v3"
)
-var rawOutput = false
var customTag = ""
var writeInplace = false
var writeScript = ""
@@ -103,7 +102,6 @@ yq r -- things.yaml --key-starting-with-dashes
RunE: readProperty,
}
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
- cmdRead.PersistentFlags().BoolVarP(&rawOutput, "raw", "r", false, "raw yaml output - prints out values instead of yaml")
return cmdRead
}
@@ -256,7 +254,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
return errorParsingDocIndex
}
- var mappedDocs []*yaml.Node
+ var matchingNodes []yqlib.MatchingNode
var currentIndex = 0
var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
@@ -268,7 +266,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
return handleEOF(updateAll, docIndexInt, currentIndex)
}
var errorParsing error
- mappedDocs, errorParsing = appendDocument(mappedDocs, dataBucket, path, updateAll, docIndexInt, currentIndex)
+ matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, path, updateAll, docIndexInt, currentIndex)
if errorParsing != nil {
return errorParsing
}
@@ -280,7 +278,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
return errorReadingStream
}
- return printResults(mappedDocs, cmd)
+ return printResults(matchingNodes, cmd)
}
func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
@@ -291,48 +289,41 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
return nil
}
-func appendDocument(mappedDocs []*yaml.Node, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yaml.Node, error) {
+func appendDocument(originalMatchingNodes []yqlib.MatchingNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]yqlib.MatchingNode, error) {
log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
lib.DebugNode(&dataBucket)
if !updateAll && currentIndex != docIndexInt {
- return mappedDocs, nil
+ return originalMatchingNodes, nil
}
log.Debugf("reading %v in document %v", path, currentIndex)
- mappedDoc, errorParsing := lib.Get(&dataBucket, path)
- lib.DebugNode(mappedDoc)
+ matchingNodes, errorParsing := lib.Get(&dataBucket, path)
if errorParsing != nil {
return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
- } else if mappedDoc != nil {
- return append(mappedDocs, mappedDoc), nil
}
- return mappedDocs, nil
+ return append(originalMatchingNodes, matchingNodes...), nil
}
-func printResults(mappedDocs []*yaml.Node, cmd *cobra.Command) error {
- if len(mappedDocs) == 0 {
+func printResults(matchingNodes []yqlib.MatchingNode, cmd *cobra.Command) error {
+ if len(matchingNodes) == 0 {
log.Debug("no matching results, nothing to print")
return nil
}
- var encoder = yaml.NewEncoder(cmd.OutOrStdout())
- encoder.SetIndent(2)
var err error
- if rawOutput {
- for _, mappedDoc := range mappedDocs {
- if mappedDoc != nil {
- cmd.Println(mappedDoc.Value)
+ for _, mappedDoc := range matchingNodes {
+ if mappedDoc.Node.Kind == yaml.ScalarNode {
+ cmd.Println(mappedDoc.Node.Value)
+ } else {
+ var encoder = yaml.NewEncoder(cmd.OutOrStdout())
+ encoder.SetIndent(2)
+ if err = encoder.Encode(mappedDoc.Node); err != nil {
+ return err
}
+ encoder.Close()
}
- } else if len(mappedDocs) == 1 {
- err = encoder.Encode(mappedDocs[0])
- } else {
- err = encoder.Encode(&yaml.Node{Kind: yaml.SequenceNode, Content: mappedDocs})
}
- if err != nil {
- return err
- }
- encoder.Close()
+
return nil
}
From 3d3eaf3034259f4ec3019c7b39719ab2872118a1 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 22 Dec 2019 17:16:03 +1100
Subject: [PATCH 25/68] Return path, smart print
---
yq.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/yq.go b/yq.go
index becedccd..ed6a1e62 100644
--- a/yq.go
+++ b/yq.go
@@ -313,7 +313,7 @@ func printResults(matchingNodes []yqlib.MatchingNode, cmd *cobra.Command) error
for _, mappedDoc := range matchingNodes {
if mappedDoc.Node.Kind == yaml.ScalarNode {
- cmd.Println(mappedDoc.Node.Value)
+ cmd.Print(mappedDoc.Node.Value)
} else {
var encoder = yaml.NewEncoder(cmd.OutOrStdout())
encoder.SetIndent(2)
From 5204a13685cebc9c28929cfdcc5437ec674eba55 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 23 Dec 2019 09:08:00 +1100
Subject: [PATCH 26/68] Show paths
---
pkg/yqlib/data_navigator.go | 2 +-
yq.go | 61 +++++++++++++++++++++++++++++++------
2 files changed, 52 insertions(+), 11 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index e3181e3b..9dfe75c8 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -241,7 +241,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
return nil
}
- //didn't find it, lets add it.
+ //TODO: have option to NOT do this... didn't find it, lets add it.
mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
value.Content = append(value.Content, &mapEntryKey)
mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)}
diff --git a/yq.go b/yq.go
index ed6a1e62..42a9987a 100644
--- a/yq.go
+++ b/yq.go
@@ -7,6 +7,7 @@ import (
"io/ioutil"
"os"
"strconv"
+ "strings"
"github.com/mikefarah/yq/v3/pkg/yqlib"
@@ -18,6 +19,7 @@ import (
)
var customTag = ""
+var printMode = "v"
var writeInplace = false
var writeScript = ""
var overwriteFlag = false
@@ -102,6 +104,7 @@ yq r -- things.yaml --key-starting-with-dashes
RunE: readProperty,
}
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
+ cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), k (keys), kv (key and value pairs)")
return cmdRead
}
@@ -303,24 +306,62 @@ func appendDocument(originalMatchingNodes []yqlib.MatchingNode, dataBucket yaml.
return append(originalMatchingNodes, matchingNodes...), nil
}
+func pathToString(pathStack []interface{}) string {
+ var sb strings.Builder
+ for index, path := range pathStack {
+ sb.WriteString(fmt.Sprintf("%v", path))
+ if index < len(pathStack)-1 {
+ sb.WriteString(".")
+ }
+ }
+ return sb.String()
+}
+
+func printValue(node *yaml.Node, cmd *cobra.Command) error {
+ if node.Kind == yaml.ScalarNode {
+ cmd.Print(node.Value)
+ return nil
+ }
+ var encoder = yaml.NewEncoder(cmd.OutOrStdout())
+ encoder.SetIndent(2)
+ if err := encoder.Encode(node); err != nil {
+ return err
+ }
+ encoder.Close()
+ return nil
+}
+
func printResults(matchingNodes []yqlib.MatchingNode, cmd *cobra.Command) error {
if len(matchingNodes) == 0 {
log.Debug("no matching results, nothing to print")
return nil
}
- var err error
-
- for _, mappedDoc := range matchingNodes {
- if mappedDoc.Node.Kind == yaml.ScalarNode {
- cmd.Print(mappedDoc.Node.Value)
- } else {
- var encoder = yaml.NewEncoder(cmd.OutOrStdout())
- encoder.SetIndent(2)
- if err = encoder.Encode(mappedDoc.Node); err != nil {
+ for index, mappedDoc := range matchingNodes {
+ switch printMode {
+ case "k":
+ cmd.Print(pathToString(mappedDoc.PathStack))
+ if index < len(matchingNodes)-1 {
+ cmd.Print("\n")
+ }
+ case "kv", "vk":
+ // put it into a node and print that.
+ var parentNode = yaml.Node{Kind: yaml.MappingNode}
+ parentNode.Content = make([]*yaml.Node, 2)
+ parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: pathToString(mappedDoc.PathStack)}
+ parentNode.Content[1] = mappedDoc.Node
+ if err := printValue(&parentNode, cmd); err != nil {
return err
}
- encoder.Close()
+ default:
+ if err := printValue(mappedDoc.Node, cmd); err != nil {
+ return err
+ }
+ // Printing our Scalars does not print a new line at the end
+ // we only want to do that if there are more values (so users can easily script extraction of values in the yaml)
+ if index < len(matchingNodes)-1 && mappedDoc.Node.Kind == yaml.ScalarNode {
+ cmd.Print("\n")
+ }
}
}
From 1e541cd65f03f46a95e63ddf50470bb9f9b02937 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 23 Dec 2019 09:25:44 +1100
Subject: [PATCH 27/68] wip handle aliases when printing keys
---
pkg/yqlib/data_navigator.go | 28 ++++++++++------------------
yq.go | 8 +++++++-
2 files changed, 17 insertions(+), 19 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 9dfe75c8..740fbc0b 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -189,9 +189,6 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
switch value.Kind {
case yaml.MappingNode:
n.log.Debug("its a map with %v entries", len(value.Content)/2)
- if head == "*" {
- return n.splatMap(value, tail, visitor, pathStack)
- }
return n.recurseMap(value, head, tail, visitor, pathStack)
case yaml.SequenceNode:
n.log.Debug("its a sequence of %v things!, %v", len(value.Content))
@@ -213,20 +210,6 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
}
}
-func (n *navigator) splatMap(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error {
- for index, content := range value.Content {
- if index%2 == 0 {
- continue
- }
- content = n.getOrReplace(content, n.GuessKind(tail, content.Kind))
- var err = n.doVisit(content, tail, visitor, append(pathStack, value.Content[index-1].Value))
- if err != nil {
- return err
- }
- }
- return nil
-}
-
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error {
visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error {
contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind))
@@ -279,7 +262,11 @@ func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visi
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(contents, key, visit)
- if errorVisitedDirectEntries != nil || visited == true || n.followAliases == false {
+
+ //TODO: crap we have to remember what we visited so we dont print the same key in the alias
+ // eff
+
+ if errorVisitedDirectEntries != nil || n.followAliases == false {
return visited, errorVisitedDirectEntries
}
// didnt find a match, lets check the aliases.
@@ -336,6 +323,11 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key stri
}
func (n *navigator) matchesKey(key string, actual string) bool {
+ n.log.Debug("key: (%v), actual: (%v)", key, actual)
+ if n.followAliases == true && actual == "<<" {
+ // dont match alias keys, as we'll follow them instead
+ return false
+ }
var prefixMatch = strings.TrimSuffix(key, "*")
if prefixMatch != key {
return strings.HasPrefix(actual, prefixMatch)
diff --git a/yq.go b/yq.go
index 42a9987a..99c96fed 100644
--- a/yq.go
+++ b/yq.go
@@ -309,7 +309,13 @@ func appendDocument(originalMatchingNodes []yqlib.MatchingNode, dataBucket yaml.
func pathToString(pathStack []interface{}) string {
var sb strings.Builder
for index, path := range pathStack {
- sb.WriteString(fmt.Sprintf("%v", path))
+ switch path.(type) {
+ case int:
+ sb.WriteString(fmt.Sprintf("[%v]", path))
+ default:
+ sb.WriteString(fmt.Sprintf("%v", path))
+ }
+
if index < len(pathStack)-1 {
sb.WriteString(".")
}
From 93dbe80a777def79715ea294411a402dc32306b5 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Tue, 24 Dec 2019 10:35:57 +1100
Subject: [PATCH 28/68] wip
---
commands_test.go | 139 ++++++++++++++-----------------
pkg/yqlib/data_navigator.go | 64 ++++++--------
pkg/yqlib/lib.go | 8 +-
pkg/yqlib/navigation_settings.go | 78 +++++++++++++++++
test.yml | 15 +++-
5 files changed, 180 insertions(+), 124 deletions(-)
create mode 100644 pkg/yqlib/navigation_settings.go
diff --git a/commands_test.go b/commands_test.go
index 9f1349eb..6421817c 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -91,34 +91,16 @@ func TestReadCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "2\n", result.Output)
+ test.AssertResult(t, "2", result.Output)
}
-// func TestReadRawCmd(t *testing.T) {
-// cmd := getRootCommand()
-// result := test.RunCmd(cmd, "read examples/sample.yaml b.c")
-// if result.Error != nil {
-// t.Error(result.Error)
-// }
-// test.AssertResult(t, "21\n", result.Output)
-// }
-
-// func TestReadRawMultiCmd(t *testing.T) {
-// cmd := getRootCommand()
-// result := test.RunCmd(cmd, "read examples/sample.yaml b.c")
-// if result.Error != nil {
-// t.Error(result.Error)
-// }
-// test.AssertResult(t, "21\n", result.Output)
-// }
-
func TestReadAnchorsCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a")
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "1\n", result.Output)
+ test.AssertResult(t, "1", result.Output)
}
func TestReadMergeAnchorsOriginalCmd(t *testing.T) {
@@ -127,7 +109,7 @@ func TestReadMergeAnchorsOriginalCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "original\n", result.Output)
+ test.AssertResult(t, "original", result.Output)
}
func TestReadMergeAnchorsOverrideCmd(t *testing.T) {
@@ -136,7 +118,7 @@ func TestReadMergeAnchorsOverrideCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "ice\n", result.Output)
+ test.AssertResult(t, "ice", result.Output)
}
func TestReadMergeAnchorsListOriginalCmd(t *testing.T) {
@@ -145,7 +127,7 @@ func TestReadMergeAnchorsListOriginalCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "original\n", result.Output)
+ test.AssertResult(t, "original", result.Output)
}
func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) {
@@ -154,7 +136,7 @@ func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "coconut\n", result.Output)
+ test.AssertResult(t, "coconut", result.Output)
}
func TestReadMergeAnchorsListOverrideCmd(t *testing.T) {
@@ -163,7 +145,7 @@ func TestReadMergeAnchorsListOverrideCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "newbar\n", result.Output)
+ test.AssertResult(t, "newbar", result.Output)
}
func TestReadInvalidDocumentIndexCmd(t *testing.T) {
@@ -205,7 +187,7 @@ func TestReadMultiCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "here\n", result.Output)
+ test.AssertResult(t, "here", result.Output)
}
func TestReadMultiAllCmd(t *testing.T) {
@@ -215,10 +197,9 @@ func TestReadMultiAllCmd(t *testing.T) {
t.Error(result.Error)
}
test.AssertResult(t,
- `- first document
-- second document
-- third document
-`, result.Output)
+ `first document
+second document
+third document`, result.Output)
}
func TestReadCmd_ArrayYaml(t *testing.T) {
@@ -227,7 +208,7 @@ func TestReadCmd_ArrayYaml(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "false\n", result.Output)
+ test.AssertResult(t, "false", result.Output)
}
func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
@@ -270,20 +251,20 @@ serial: 1
func TestReadCmd_ArrayYaml_SplatA(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -v examples/array.yaml [*]")
+ result := test.RunCmd(cmd, "read examples/array.yaml [*]")
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `- become: true
- gather_facts: false
- hosts: lalaland
- name: "Apply smth"
- roles:
- - lala
- - land
- serial: 1
-- become: false
- gather_facts: true
+ expectedOutput := `become: true
+gather_facts: false
+hosts: lalaland
+name: "Apply smth"
+roles:
+- lala
+- land
+serial: 1
+become: false
+gather_facts: true
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -294,15 +275,14 @@ func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `- false
-- true
-`
+ expectedOutput := `false
+true`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -v examples/array.yaml [x].gather_facts")
+ result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts")
expectedOutput := ``
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -310,9 +290,7 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]")
- expectedOutput := `-
--
-`
+ expectedOutput := ``
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -366,19 +344,17 @@ func TestReadCmd_ErrorBadPath(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
- expectedOutput := `-
--
-`
+ expectedOutput := ``
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "-v read examples/sample.yaml b.c")
+ result := test.RunCmd(cmd, "read examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "2\n", result.Output)
+ test.AssertResult(t, "2", result.Output)
}
// func TestReadCmd_ToJson(t *testing.T) {
@@ -416,14 +392,13 @@ b:
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("read -v %s b.there*.c", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("read %s b.there*.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `- more things
-- more things also
-`
+ expectedOutput := `more things
+more things also`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -595,7 +570,7 @@ func TestPrefixCmd_Verbose(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("-v prefix %s x", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("prefix %s x", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -684,12 +659,20 @@ func TestWriteCmdScript(t *testing.T) {
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
+ updateScript := `- command: update
+ path: b.c
+ value: 7`
+ scriptFilename := test.WriteTempYamlFile(updateScript)
+ defer test.RemoveTempYamlFile(scriptFilename)
+
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `IMPLEMENT ME`
+ expectedOutput := `b:
+ c: 7
+`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -870,7 +853,7 @@ func TestWriteCmd_AppendEmptyArray(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b[+] v", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -890,7 +873,7 @@ func TestWriteCmd_SplatArray(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b[*].c new", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -910,7 +893,7 @@ func TestWriteCmd_SplatMap(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b.* new", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -930,7 +913,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b.c.* new", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -976,7 +959,7 @@ c:
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s c.te*", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1119,7 +1102,7 @@ something: else`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
-func TestMergeCmd(t *testing.T) {
+func xTestMergeCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
@@ -1135,7 +1118,7 @@ c:
test.AssertResult(t, expectedOutput, result.Output)
}
-func TestMergeOverwriteCmd(t *testing.T) {
+func xTestMergeOverwriteCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --overwrite examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
@@ -1151,7 +1134,7 @@ c:
test.AssertResult(t, expectedOutput, result.Output)
}
-func TestMergeAppendCmd(t *testing.T) {
+func xTestMergeAppendCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --append examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
@@ -1168,7 +1151,7 @@ c:
`
test.AssertResult(t, expectedOutput, result.Output)
}
-func TestMergeArraysCmd(t *testing.T) {
+func xTestMergeArraysCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml")
if result.Error != nil {
@@ -1183,7 +1166,7 @@ func TestMergeArraysCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
-func TestMergeCmd_Multi(t *testing.T) {
+func xTestMergeCmd_Multi(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml")
if result.Error != nil {
@@ -1205,7 +1188,7 @@ c:
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
-func TestMergeYamlMultiAllCmd(t *testing.T) {
+func xTestMergeYamlMultiAllCmd(t *testing.T) {
content := `b:
c: 3
apples: green
@@ -1234,7 +1217,7 @@ something: else`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
-func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) {
+func xTestMergeYamlMultiAllOverwriteCmd(t *testing.T) {
content := `b:
c: 3
apples: green
@@ -1263,7 +1246,7 @@ something: good`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
-func TestMergeCmd_Error(t *testing.T) {
+func xTestMergeCmd_Error(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge examples/data1.yaml")
if result.Error == nil {
@@ -1273,7 +1256,7 @@ func TestMergeCmd_Error(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error())
}
-func TestMergeCmd_ErrorUnreadableFile(t *testing.T) {
+func xTestMergeCmd_ErrorUnreadableFile(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge examples/data1.yaml fake-unknown")
if result.Error == nil {
@@ -1288,7 +1271,7 @@ func TestMergeCmd_ErrorUnreadableFile(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error())
}
-func TestMergeCmd_Verbose(t *testing.T) {
+func xTestMergeCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "-v merge examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
@@ -1304,7 +1287,7 @@ c:
test.AssertResult(t, expectedOutput, result.Output)
}
-func TestMergeCmd_Inplace(t *testing.T) {
+func xTestMergeCmd_Inplace(t *testing.T) {
filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml"))
err := os.Chmod(filename, os.FileMode(int(0666)))
if err != nil {
@@ -1329,7 +1312,7 @@ c:
test.AssertResult(t, os.FileMode(int(0666)), info.Mode())
}
-func TestMergeAllowEmptyCmd(t *testing.T) {
+func xTestMergeAllowEmptyCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --allow-empty examples/data1.yaml examples/empty.yaml")
if result.Error != nil {
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 740fbc0b..ac21dbb7 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -3,7 +3,6 @@ package yqlib
import (
"bytes"
"strconv"
- "strings"
logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
@@ -18,16 +17,16 @@ type DataNavigator interface {
}
type navigator struct {
- log *logging.Logger
- followAliases bool
+ log *logging.Logger
+ navigationSettings NavigationSettings
}
type VisitorFn func(matchingNode *yaml.Node, pathStack []interface{}) error
-func NewDataNavigator(l *logging.Logger, followAliases bool) DataNavigator {
+func NewDataNavigator(l *logging.Logger, navigationSettings NavigationSettings) DataNavigator {
return &navigator{
- log: l,
- followAliases: followAliases,
+ log: l,
+ navigationSettings: navigationSettings,
}
}
@@ -95,7 +94,7 @@ func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
// need to delete in reverse - otherwise the matching indexes
// become incorrect.
matchingIndices := make([]int, 0)
- _, errorVisiting := n.visitMatchingEntries(nodeToUpdate.Content, lastBit, func(matchingNode []*yaml.Node, indexInMap int) error {
+ _, errorVisiting := n.visitMatchingEntries(nodeToUpdate, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error {
matchingIndices = append(matchingIndices, indexInMap)
n.log.Debug("matchingIndices %v", indexInMap)
return nil
@@ -199,9 +198,10 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
}
return n.recurseArray(value, head, tail, visitor, pathStack)
case yaml.AliasNode:
- n.log.Debug("its an alias, followAliases: %v", n.followAliases)
+ n.log.Debug("its an alias!")
n.DebugNode(value.Alias)
- if n.followAliases == true {
+ if n.navigationSettings.FollowAlias(value, head, tail, pathStack) == true {
+ n.log.Debug("following the alias")
return n.recurse(value.Alias, head, tail, visitor, pathStack)
}
return nil
@@ -211,7 +211,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
}
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error {
- visited, errorVisiting := n.visitMatchingEntries(value.Content, head, func(contents []*yaml.Node, indexInMap int) error {
+ visited, errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error {
contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind))
return n.doVisit(contents[indexInMap+1], tail, visitor, append(pathStack, contents[indexInMap].Value))
})
@@ -220,11 +220,10 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
return errorVisiting
}
- if visited {
+ if visited || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false {
return nil
}
- //TODO: have option to NOT do this... didn't find it, lets add it.
mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
value.Content = append(value.Content, &mapEntryKey)
mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)}
@@ -236,13 +235,14 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
// need to pass the node in, as it may be aliased
type mapVisitorFn func(contents []*yaml.Node, index int) error
-func (n *navigator) visitDirectMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
+func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
+ var contents = node.Content
visited := false
for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
- if n.matchesKey(key, content.Value) {
+ if n.navigationSettings.ShouldVisit(content, head, tail, pathStack) == true {
n.log.Debug("found a match! %v", content.Value)
errorVisiting := visit(contents, index)
if errorVisiting != nil {
@@ -254,27 +254,28 @@ func (n *navigator) visitDirectMatchingEntries(contents []*yaml.Node, key string
return visited, nil
}
-func (n *navigator) visitMatchingEntries(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
-
- n.log.Debug("visitMatchingEntries %v in %v", key, contents)
+func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
+ var contents = node.Content
+ n.log.Debug("visitMatchingEntries %v", head)
+ n.DebugNode(node)
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
- visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(contents, key, visit)
+ visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
//TODO: crap we have to remember what we visited so we dont print the same key in the alias
// eff
- if errorVisitedDirectEntries != nil || n.followAliases == false {
+ if errorVisitedDirectEntries != nil || visited == true || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false {
return visited, errorVisitedDirectEntries
}
// didnt find a match, lets check the aliases.
- return n.visitAliases(contents, key, visit)
+ return n.visitAliases(contents, head, tail, pathStack, visit)
}
-func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
+func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match on this node first.
// traverse them backwards so that the last alias overrides the preceding.
@@ -290,13 +291,13 @@ func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVis
n.DebugNode(contents[index])
n.DebugNode(valueNode)
- visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias.Content, key, visit)
+ visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit)
if visitedAlias == true || errorInAlias != nil {
return visitedAlias, errorInAlias
}
} else if contents[index+1].Kind == yaml.SequenceNode {
// could be an array of aliases...
- visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, key, visit)
+ visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit)
if visitedAliasSeq == true || errorVisitingAliasSeq != nil {
return visitedAliasSeq, errorVisitingAliasSeq
}
@@ -306,14 +307,14 @@ func (n *navigator) visitAliases(contents []*yaml.Node, key string, visit mapVis
return false, nil
}
-func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key string, visit mapVisitorFn) (bool, error) {
+func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
// need to search this backwards too, so that aliases defined last override the preceding.
for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
child := possibleAliasArray[aliasIndex]
if child.Kind == yaml.AliasNode {
n.log.Debug("found an alias")
n.DebugNode(child)
- visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias.Content, key, visit)
+ visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit)
if visitedAlias == true || errorInAlias != nil {
return visitedAlias, errorInAlias
}
@@ -322,19 +323,6 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, key stri
return false, nil
}
-func (n *navigator) matchesKey(key string, actual string) bool {
- n.log.Debug("key: (%v), actual: (%v)", key, actual)
- if n.followAliases == true && actual == "<<" {
- // dont match alias keys, as we'll follow them instead
- return false
- }
- var prefixMatch = strings.TrimSuffix(key, "*")
- if prefixMatch != key {
- return strings.HasPrefix(actual, prefixMatch)
- }
- return actual == key
-}
-
func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error {
for index, childValue := range value.Content {
n.log.Debug("processing")
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 637d8e1c..b4d4dc9e 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -34,25 +34,25 @@ func NewYqLib(l *logging.Logger) YqLib {
}
func (l *lib) DebugNode(node *yaml.Node) {
- navigator := NewDataNavigator(l.log, false)
+ navigator := NewDataNavigator(l.log, ReadNavigationSettings(l.log))
navigator.DebugNode(node)
}
func (l *lib) Get(rootNode *yaml.Node, path string) ([]MatchingNode, error) {
var paths = l.parser.ParsePath(path)
- navigator := NewDataNavigator(l.log, true)
+ navigator := NewDataNavigator(l.log, ReadNavigationSettings(l.log))
return navigator.Get(rootNode, paths)
}
func (l *lib) New(path string) yaml.Node {
var paths = l.parser.ParsePath(path)
- navigator := NewDataNavigator(l.log, false)
+ navigator := NewDataNavigator(l.log, UpdateNavigationSettings(l.log))
newNode := yaml.Node{Kind: navigator.GuessKind(paths, 0)}
return newNode
}
func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
- navigator := NewDataNavigator(l.log, false)
+ navigator := NewDataNavigator(l.log, UpdateNavigationSettings(l.log))
l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
switch updateCommand.Command {
case "update":
diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go
new file mode 100644
index 00000000..e48c88c8
--- /dev/null
+++ b/pkg/yqlib/navigation_settings.go
@@ -0,0 +1,78 @@
+package yqlib
+
+import (
+ "strings"
+
+ logging "gopkg.in/op/go-logging.v1"
+ yaml "gopkg.in/yaml.v3"
+)
+
+type NavigationSettings interface {
+ FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+ AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+ ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+}
+
+type NavigationSettingsImpl struct {
+ followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+ autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+ shouldVisit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+}
+
+func (ns NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return ns.followAlias(node, head, tail, pathStack)
+}
+
+func (ns NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return ns.autoCreateMap(node, head, tail, pathStack)
+}
+
+func (ns NavigationSettingsImpl) ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return ns.shouldVisit(node, head, tail, pathStack)
+}
+
+func UpdateNavigationSettings(l *logging.Logger) NavigationSettings {
+ return NavigationSettingsImpl{
+ followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return false
+ },
+ autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return true
+ },
+ shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ var prefixMatch = strings.TrimSuffix(head, "*")
+ if prefixMatch != head {
+ l.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
+ return strings.HasPrefix(node.Value, prefixMatch)
+ }
+ l.Debug("equals match, %v", node.Value == head)
+ return node.Value == head
+ },
+ }
+}
+
+func ReadNavigationSettings(l *logging.Logger) NavigationSettings {
+ return NavigationSettingsImpl{
+ followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return true
+ },
+ autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return false
+ },
+ shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ l.Debug("shouldVisit h: %v, actual: %v", head, node.Value)
+ if node.Value == "<<" {
+ l.Debug("its an alias, skip it")
+ // dont match alias keys, as we'll follow them instead
+ return false
+ }
+ var prefixMatch = strings.TrimSuffix(head, "*")
+ if prefixMatch != head {
+ l.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
+ return strings.HasPrefix(node.Value, prefixMatch)
+ }
+ l.Debug("equals match, %v", node.Value == head)
+ return node.Value == head
+ },
+ }
+}
diff --git a/test.yml b/test.yml
index c83b2dcf..804fbe36 100644
--- a/test.yml
+++ b/test.yml
@@ -1,4 +1,11 @@
-foo: &foo
- a: 1
-
-foobar: *foo
\ No newline at end of file
+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
\ No newline at end of file
From 9925b26b9dc2624f057793034b37a2e283b989fe Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Tue, 24 Dec 2019 10:46:21 +1100
Subject: [PATCH 29/68] Added Key and Value printing tests
---
commands_test.go | 143 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 140 insertions(+), 3 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 6421817c..62bb6a04 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -94,6 +94,24 @@ func TestReadCmd(t *testing.T) {
test.AssertResult(t, "2", result.Output)
}
+func TestReadWithKeyAndValueCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.c")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "b.c: 2\n", result.Output)
+}
+
+func TestReadWithKeyCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "b.c", result.Output)
+}
+
func TestReadAnchorsCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a")
@@ -103,6 +121,15 @@ func TestReadAnchorsCmd(t *testing.T) {
test.AssertResult(t, "1", result.Output)
}
+func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -p kv examples/simple-anchor.yaml foobar.a")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "foobar.a: 1\n", result.Output)
+}
+
func TestReadMergeAnchorsOriginalCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.a")
@@ -190,6 +217,15 @@ func TestReadMultiCmd(t *testing.T) {
test.AssertResult(t, "here", result.Output)
}
+func TestReadMultiWithKeyAndValueCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -p vk -d 1 examples/multiple_docs.yaml another.document")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "another.document: here\n", result.Output)
+}
+
func TestReadMultiAllCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -d* examples/multiple_docs.yaml commonKey")
@@ -202,6 +238,19 @@ second document
third document`, result.Output)
}
+func TestReadMultiAllWithKeyAndValueCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -p kv -d* examples/multiple_docs.yaml commonKey")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t,
+ `commonKey: first document
+commonKey: second document
+commonKey: third document
+`, result.Output)
+}
+
func TestReadCmd_ArrayYaml(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [0].gather_facts")
@@ -249,7 +298,7 @@ serial: 1
test.AssertResult(t, expectedOutput, result.Output)
}
-func TestReadCmd_ArrayYaml_SplatA(t *testing.T) {
+func TestReadCmd_ArrayYaml_SplatCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*]")
if result.Error != nil {
@@ -269,6 +318,39 @@ gather_facts: true
test.AssertResult(t, expectedOutput, result.Output)
}
+func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -p kv examples/array.yaml [*]")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `'[0]':
+ become: true
+ gather_facts: false
+ hosts: lalaland
+ name: "Apply smth"
+ roles:
+ - lala
+ - land
+ serial: 1
+'[1]':
+ become: false
+ gather_facts: true
+`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
+func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -p k examples/array.yaml [*]")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `[0]
+[1]`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*].gather_facts")
@@ -350,7 +432,7 @@ func TestReadCmd_ErrorBadPath(t *testing.T) {
func TestReadCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read examples/sample.yaml b.c")
+ result := test.RunCmd(cmd, "read -v examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
@@ -375,7 +457,7 @@ func TestReadCmd_Verbose(t *testing.T) {
// test.AssertResult(t, "2\n", result.Output)
// }
-func TestReadSplatPrefixYaml(t *testing.T) {
+func TestReadSplatPrefixCmd(t *testing.T) {
content := `a: 2
b:
hi:
@@ -402,6 +484,61 @@ more things also`
test.AssertResult(t, expectedOutput, result.Output)
}
+func TestReadSplatPrefixWithKeyAndValueCmd(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 -p kv %s b.there*.c", filename))
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+
+ expectedOutput := `b.there.c: more things
+b.there2.c: more things also
+`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
+func TestReadSplatPrefixWithKeyCmd(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 -p k %s b.there*.c", filename))
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+
+ expectedOutput := `b.there.c
+b.there2.c`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
func TestPrefixCmd(t *testing.T) {
content := `b:
c: 3
From ff5b23251b71d45ecaa00b364b25d9127d2ee147 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Wed, 25 Dec 2019 12:11:04 +1100
Subject: [PATCH 30/68] Refactor wip
---
commands_test.go | 19 +-
examples/merge-anchor.yaml | 2 +
pkg/yqlib/data_navigator.go | 245 ++++++------------------
pkg/yqlib/delete_navigation_settings.go | 72 +++++++
pkg/yqlib/lib.go | 75 ++++++--
pkg/yqlib/navigation_settings.go | 86 ++++-----
pkg/yqlib/read_navigation_strategy.go | 37 ++++
pkg/yqlib/update_navigation_strategy.go | 43 +++++
yq.go | 10 +-
9 files changed, 331 insertions(+), 258 deletions(-)
create mode 100644 pkg/yqlib/delete_navigation_settings.go
create mode 100644 pkg/yqlib/read_navigation_strategy.go
create mode 100644 pkg/yqlib/update_navigation_strategy.go
diff --git a/commands_test.go b/commands_test.go
index 62bb6a04..6c08c86e 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -148,6 +148,18 @@ func TestReadMergeAnchorsOverrideCmd(t *testing.T) {
test.AssertResult(t, "ice", result.Output)
}
+func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "r -p kv examples/merge-anchor.yaml foobar.th*")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `foobar.thing: ice
+foobar.thirty: well beyond
+foobar.thirsty: yep`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
func TestReadMergeAnchorsListOriginalCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.a")
@@ -365,8 +377,11 @@ true`
func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts")
- 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: strconv.ParseInt: parsing "x": invalid syntax`
+ test.AssertResult(t, expectedOutput, result.Error.Error())
}
func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) {
diff --git a/examples/merge-anchor.yaml b/examples/merge-anchor.yaml
index 2459c9a9..048b02e2 100644
--- a/examples/merge-anchor.yaml
+++ b/examples/merge-anchor.yaml
@@ -1,6 +1,7 @@
foo: &foo
a: original
thing: coolasdf
+ thirsty: yep
bar: &bar
b: 2
@@ -14,4 +15,5 @@ foobarList:
foobar:
<<: *foo
thing: ice
+ thirty: well beyond
c: 3
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index ac21dbb7..0bace7c4 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -1,208 +1,73 @@
package yqlib
import (
- "bytes"
+ "fmt"
"strconv"
- logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
)
type DataNavigator interface {
- DebugNode(node *yaml.Node)
- Get(rootNode *yaml.Node, path []string) ([]MatchingNode, error)
- Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error
- Delete(rootNode *yaml.Node, path []string) error
- GuessKind(tail []string, guess yaml.Kind) yaml.Kind
+ Traverse(value *yaml.Node, path []string) error
}
type navigator struct {
- log *logging.Logger
navigationSettings NavigationSettings
}
-type VisitorFn func(matchingNode *yaml.Node, pathStack []interface{}) error
-
-func NewDataNavigator(l *logging.Logger, navigationSettings NavigationSettings) DataNavigator {
+func NewDataNavigator(navigationSettings NavigationSettings) DataNavigator {
return &navigator{
- log: l,
navigationSettings: navigationSettings,
}
}
-type MatchingNode struct {
- Node *yaml.Node
- PathStack []interface{}
-}
-
-func (n *navigator) Get(value *yaml.Node, path []string) ([]MatchingNode, error) {
- matchingNodes := make([]MatchingNode, 0)
-
- n.Visit(value, path, func(matchedNode *yaml.Node, pathStack []interface{}) error {
- matchingNodes = append(matchingNodes, MatchingNode{matchedNode, pathStack})
- n.log.Debug("Matched")
- for _, pathElement := range pathStack {
- n.log.Debug("%v", pathElement)
- }
- n.DebugNode(matchedNode)
- return nil
- })
- return matchingNodes, nil
-}
-
-func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error {
- errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node, pathStack []interface{}) error {
- n.log.Debug("going to update")
- n.DebugNode(nodeToUpdate)
- n.log.Debug("with")
- n.DebugNode(changesToApply)
- nodeToUpdate.Value = changesToApply.Value
- nodeToUpdate.Tag = changesToApply.Tag
- nodeToUpdate.Kind = changesToApply.Kind
- nodeToUpdate.Style = changesToApply.Style
- nodeToUpdate.Content = changesToApply.Content
- nodeToUpdate.HeadComment = changesToApply.HeadComment
- nodeToUpdate.LineComment = changesToApply.LineComment
- nodeToUpdate.FootComment = changesToApply.FootComment
- return nil
- })
- return errorVisiting
-}
-
-// TODO: refactor delete..
-func (n *navigator) Delete(rootNode *yaml.Node, path []string) error {
-
- lastBit, newTail := path[len(path)-1], path[:len(path)-1]
- n.log.Debug("splitting path, %v", lastBit)
- n.log.Debug("new tail, %v", newTail)
- errorVisiting := n.Visit(rootNode, newTail, func(nodeToUpdate *yaml.Node, pathStack []interface{}) error {
- n.log.Debug("need to find %v in here", lastBit)
- n.DebugNode(nodeToUpdate)
- original := nodeToUpdate.Content
- if nodeToUpdate.Kind == yaml.SequenceNode {
- var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
- if err != nil {
- return err
- }
- if index >= int64(len(nodeToUpdate.Content)) {
- n.log.Debug("index %v is greater than content length %v", index, len(nodeToUpdate.Content))
- return nil
- }
- nodeToUpdate.Content = append(original[:index], original[index+1:]...)
-
- } else if nodeToUpdate.Kind == yaml.MappingNode {
- // need to delete in reverse - otherwise the matching indexes
- // become incorrect.
- matchingIndices := make([]int, 0)
- _, errorVisiting := n.visitMatchingEntries(nodeToUpdate, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error {
- matchingIndices = append(matchingIndices, indexInMap)
- n.log.Debug("matchingIndices %v", indexInMap)
- return nil
- })
- n.log.Debug("delete matching indices now")
- n.log.Debug("%v", matchingIndices)
- if errorVisiting != nil {
- return errorVisiting
- }
- for i := len(matchingIndices) - 1; i >= 0; i-- {
- indexToDelete := matchingIndices[i]
- n.log.Debug("deleting index %v, %v", indexToDelete, nodeToUpdate.Content[indexToDelete].Value)
- nodeToUpdate.Content = append(nodeToUpdate.Content[:indexToDelete], nodeToUpdate.Content[indexToDelete+2:]...)
- }
-
- }
-
- return nil
- })
- return errorVisiting
-}
-
-func (n *navigator) Visit(value *yaml.Node, path []string, visitor VisitorFn) error {
+func (n *navigator) Traverse(value *yaml.Node, path []string) error {
realValue := value
emptyArray := make([]interface{}, 0)
if realValue.Kind == yaml.DocumentNode {
- n.log.Debugf("its a document! returning the first child")
- return n.doVisit(value.Content[0], path, visitor, emptyArray)
+ log.Debugf("its a document! returning the first child")
+ return n.doTraverse(value.Content[0], "", path, emptyArray)
}
- return n.doVisit(value, path, visitor, emptyArray)
+ return n.doTraverse(value, "", path, emptyArray)
}
-func (n *navigator) doVisit(value *yaml.Node, path []string, visitor VisitorFn, pathStack []interface{}) error {
+func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pathStack []interface{}) error {
if len(path) > 0 {
- n.log.Debugf("diving into %v", path[0])
- n.DebugNode(value)
- return n.recurse(value, path[0], path[1:], visitor, pathStack)
+ log.Debugf("diving into %v", path[0])
+ DebugNode(value)
+ return n.recurse(value, path[0], path[1:], pathStack)
}
- return visitor(value, pathStack)
-}
-
-func (n *navigator) GuessKind(tail []string, guess yaml.Kind) yaml.Kind {
- n.log.Debug("tail %v", tail)
- if len(tail) == 0 && guess == 0 {
- n.log.Debug("end of path, must be a scalar")
- return yaml.ScalarNode
- } else if len(tail) == 0 {
- return guess
- }
-
- var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64)
- if tail[0] == "+" || errorParsingInt == nil {
- return yaml.SequenceNode
- }
- if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) {
- return guess
- }
- if guess == yaml.AliasNode {
- n.log.Debug("guess was an alias, okey doke.")
- return guess
- }
- n.log.Debug("forcing a mapping node")
- n.log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode)
- n.log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode)
- return yaml.MappingNode
+ return n.navigationSettings.Visit(value, head, path, pathStack)
}
func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
if original.Kind != expectedKind {
- n.log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind)
+ log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind)
return &yaml.Node{Kind: expectedKind}
}
return original
}
-func (n *navigator) DebugNode(value *yaml.Node) {
- if value == nil {
- n.log.Debug("-- node is nil --")
- } else if n.log.IsEnabledFor(logging.DEBUG) {
- buf := new(bytes.Buffer)
- encoder := yaml.NewEncoder(buf)
- encoder.Encode(value)
- encoder.Close()
- n.log.Debug("Tag: %v", value.Tag)
- n.log.Debug("%v", buf.String())
- }
-}
-
-func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error {
- n.log.Debug("recursing, processing %v", head)
+func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
+ log.Debug("recursing, processing %v", head)
switch value.Kind {
case yaml.MappingNode:
- n.log.Debug("its a map with %v entries", len(value.Content)/2)
- return n.recurseMap(value, head, tail, visitor, pathStack)
+ log.Debug("its a map with %v entries", len(value.Content)/2)
+ return n.recurseMap(value, head, tail, pathStack)
case yaml.SequenceNode:
- n.log.Debug("its a sequence of %v things!, %v", len(value.Content))
+ log.Debug("its a sequence of %v things!, %v", len(value.Content))
if head == "*" {
- return n.splatArray(value, tail, visitor, pathStack)
+ return n.splatArray(value, tail, pathStack)
} else if head == "+" {
- return n.appendArray(value, tail, visitor, pathStack)
+ return n.appendArray(value, tail, pathStack)
}
- return n.recurseArray(value, head, tail, visitor, pathStack)
+ return n.recurseArray(value, head, tail, pathStack)
case yaml.AliasNode:
- n.log.Debug("its an alias!")
- n.DebugNode(value.Alias)
+ log.Debug("its an alias!")
+ DebugNode(value.Alias)
if n.navigationSettings.FollowAlias(value, head, tail, pathStack) == true {
- n.log.Debug("following the alias")
- return n.recurse(value.Alias, head, tail, visitor, pathStack)
+ log.Debug("following the alias")
+ return n.recurse(value.Alias, head, tail, pathStack)
}
return nil
default:
@@ -210,10 +75,10 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, visito
}
}
-func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, visitor VisitorFn, pathStack []interface{}) error {
+func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
visited, errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error {
- contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], n.GuessKind(tail, contents[indexInMap+1].Kind))
- return n.doVisit(contents[indexInMap+1], tail, visitor, append(pathStack, contents[indexInMap].Value))
+ contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind))
+ return n.doTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value))
})
if errorVisiting != nil {
@@ -226,10 +91,10 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, vis
mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
value.Content = append(value.Content, &mapEntryKey)
- mapEntryValue := yaml.Node{Kind: n.GuessKind(tail, 0)}
+ mapEntryValue := yaml.Node{Kind: guessKind(tail, 0)}
value.Content = append(value.Content, &mapEntryValue)
- n.log.Debug("adding new node %v", value.Content)
- return n.doVisit(&mapEntryValue, tail, visitor, append(pathStack, head))
+ log.Debug("adding new node %v", value.Content)
+ return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head))
}
// need to pass the node in, as it may be aliased
@@ -240,10 +105,10 @@ func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tai
visited := false
for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
- n.log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
+ log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
if n.navigationSettings.ShouldVisit(content, head, tail, pathStack) == true {
- n.log.Debug("found a match! %v", content.Value)
+ log.Debug("found a match! %v", content.Value)
errorVisiting := visit(contents, index)
if errorVisiting != nil {
return visited, errorVisiting
@@ -256,8 +121,8 @@ func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tai
func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
var contents = node.Content
- n.log.Debug("visitMatchingEntries %v", head)
- n.DebugNode(node)
+ log.Debug("visitMatchingEntries %v", head)
+ DebugNode(node)
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
@@ -282,14 +147,14 @@ func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []stri
// a node can either be
// an alias to one other node (e.g. <<: *blah)
// or a sequence of aliases (e.g. <<: [*blah, *foo])
- n.log.Debug("checking for aliases")
+ log.Debug("checking for aliases")
for index := len(contents) - 2; index >= 0; index = index - 2 {
if contents[index+1].Kind == yaml.AliasNode {
valueNode := contents[index+1]
- n.log.Debug("found an alias")
- n.DebugNode(contents[index])
- n.DebugNode(valueNode)
+ log.Debug("found an alias")
+ DebugNode(contents[index])
+ DebugNode(valueNode)
visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit)
if visitedAlias == true || errorInAlias != nil {
@@ -303,7 +168,7 @@ func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []stri
}
}
}
- n.log.Debug("nope no matching aliases found")
+ log.Debug("nope no matching aliases found")
return false, nil
}
@@ -312,8 +177,8 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head str
for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
child := possibleAliasArray[aliasIndex]
if child.Kind == yaml.AliasNode {
- n.log.Debug("found an alias")
- n.DebugNode(child)
+ log.Debug("found an alias")
+ DebugNode(child)
visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit)
if visitedAlias == true || errorInAlias != nil {
return visitedAlias, errorInAlias
@@ -323,12 +188,13 @@ func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head str
return false, nil
}
-func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error {
+func (n *navigator) splatArray(value *yaml.Node, tail []string, pathStack []interface{}) error {
for index, childValue := range value.Content {
- n.log.Debug("processing")
- n.DebugNode(childValue)
- childValue = n.getOrReplace(childValue, n.GuessKind(tail, childValue.Kind))
- var err = n.doVisit(childValue, tail, visitor, append(pathStack, index))
+ log.Debug("processing")
+ DebugNode(childValue)
+ 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 {
return err
}
@@ -336,14 +202,15 @@ func (n *navigator) splatArray(value *yaml.Node, tail []string, visitor VisitorF
return nil
}
-func (n *navigator) appendArray(value *yaml.Node, tail []string, visitor VisitorFn, pathStack []interface{}) error {
- var newNode = yaml.Node{Kind: n.GuessKind(tail, 0)}
+func (n *navigator) appendArray(value *yaml.Node, tail []string, pathStack []interface{}) error {
+ var newNode = yaml.Node{Kind: guessKind(tail, 0)}
value.Content = append(value.Content, &newNode)
- n.log.Debug("appending a new node, %v", value.Content)
- return n.doVisit(&newNode, tail, visitor, append(pathStack, len(value.Content)-1))
+ 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, visitor VisitorFn, 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
if err != nil {
return err
@@ -351,6 +218,6 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v
if index >= int64(len(value.Content)) {
return nil
}
- value.Content[index] = n.getOrReplace(value.Content[index], n.GuessKind(tail, value.Content[index].Kind))
- return n.doVisit(value.Content[index], tail, visitor, append(pathStack, index))
+ value.Content[index] = n.getOrReplace(value.Content[index], guessKind(tail, value.Content[index].Kind))
+ return n.doTraverse(value.Content[index], head, tail, append(pathStack, index))
}
diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_settings.go
new file mode 100644
index 00000000..ad1c8ff9
--- /dev/null
+++ b/pkg/yqlib/delete_navigation_settings.go
@@ -0,0 +1,72 @@
+package yqlib
+
+import (
+ "strconv"
+ "strings"
+
+ yaml "gopkg.in/yaml.v3"
+)
+
+func DeleteNavigationSettings(lastBit string) NavigationSettings {
+ return &NavigationSettingsImpl{
+ visitedNodes: []*VisitedNode{},
+ followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return false
+ },
+ autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return true
+ },
+ shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ var prefixMatch = strings.TrimSuffix(head, "*")
+ if prefixMatch != head {
+ log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
+ return strings.HasPrefix(node.Value, prefixMatch)
+ }
+ log.Debug("equals match, %v", node.Value == head)
+ return node.Value == head
+ },
+ visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
+ log.Debug("need to find %v in here", lastBit)
+ DebugNode(node)
+ if node.Kind == yaml.SequenceNode {
+ newContent, errorDeleting := deleteFromArray(node.Content, lastBit)
+ if errorDeleting != nil {
+ return errorDeleting
+ }
+ node.Content = newContent
+ } else if node.Kind == yaml.MappingNode {
+ // need to delete in reverse - otherwise the matching indexes
+ // become incorrect.
+ // matchingIndices := make([]int, 0)
+ // _, errorVisiting := n.visitMatchingEntries(node, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error {
+ // matchingIndices = append(matchingIndices, indexInMap)
+ // log.Debug("matchingIndices %v", indexInMap)
+ // return nil
+ // })
+ // log.Debug("delete matching indices now")
+ // log.Debug("%v", matchingIndices)
+ // if errorVisiting != nil {
+ // return errorVisiting
+ // }
+ // for i := len(matchingIndices) - 1; i >= 0; i-- {
+ // indexToDelete := matchingIndices[i]
+ // log.Debug("deleting index %v, %v", indexToDelete, node.Content[indexToDelete].Value)
+ // node.Content = append(node.Content[:indexToDelete], node.Content[indexToDelete+2:]...)
+ // }
+ }
+ return nil
+ },
+ }
+}
+
+func deleteFromArray(content []*yaml.Node, lastBit string) ([]*yaml.Node, error) {
+ var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
+ if err != nil {
+ return content, err
+ }
+ if index >= int64(len(content)) {
+ log.Debug("index %v is greater than content length %v", index, len(content))
+ return content, nil
+ }
+ return append(content[:index], content[index+1:]...), nil
+}
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index b4d4dc9e..c11773d1 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -1,21 +1,63 @@
package yqlib
import (
+ "bytes"
"fmt"
+ "strconv"
logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
)
+var log = logging.MustGetLogger("yq")
+
type UpdateCommand struct {
Command string
Path string
Value *yaml.Node
}
+func DebugNode(value *yaml.Node) {
+ if value == nil {
+ log.Debug("-- node is nil --")
+ } else if log.IsEnabledFor(logging.DEBUG) {
+ buf := new(bytes.Buffer)
+ encoder := yaml.NewEncoder(buf)
+ encoder.Encode(value)
+ encoder.Close()
+ log.Debug("Tag: %v", value.Tag)
+ log.Debug("%v", buf.String())
+ }
+}
+
+func guessKind(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")
+ return yaml.ScalarNode
+ } else if len(tail) == 0 {
+ return guess
+ }
+
+ var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64)
+ if tail[0] == "+" || errorParsingInt == nil {
+ return yaml.SequenceNode
+ }
+ if tail[0] == "*" && (guess == yaml.SequenceNode || guess == yaml.MappingNode) {
+ return guess
+ }
+ if guess == yaml.AliasNode {
+ log.Debug("guess was an alias, okey doke.")
+ return guess
+ }
+ log.Debug("forcing a mapping node")
+ log.Debug("yaml.SequenceNode ?", guess == yaml.SequenceNode)
+ log.Debug("yaml.ScalarNode ?", guess == yaml.ScalarNode)
+ return yaml.MappingNode
+}
+
type YqLib interface {
- DebugNode(node *yaml.Node)
- Get(rootNode *yaml.Node, path string) ([]MatchingNode, error)
+ Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
New(path string) yaml.Node
}
@@ -23,44 +65,41 @@ type YqLib interface {
type lib struct {
navigator DataNavigator
parser PathParser
- log *logging.Logger
}
func NewYqLib(l *logging.Logger) YqLib {
return &lib{
parser: NewPathParser(),
- log: l,
}
}
-func (l *lib) DebugNode(node *yaml.Node) {
- navigator := NewDataNavigator(l.log, ReadNavigationSettings(l.log))
- navigator.DebugNode(node)
-}
-
-func (l *lib) Get(rootNode *yaml.Node, path string) ([]MatchingNode, error) {
+func (l *lib) Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error) {
var paths = l.parser.ParsePath(path)
- navigator := NewDataNavigator(l.log, ReadNavigationSettings(l.log))
- return navigator.Get(rootNode, paths)
+ navigationSettings := ReadNavigationSettings()
+ navigator := NewDataNavigator(navigationSettings)
+ error := navigator.Traverse(rootNode, paths)
+ return navigationSettings.GetVisitedNodes(), error
+
}
func (l *lib) New(path string) yaml.Node {
var paths = l.parser.ParsePath(path)
- navigator := NewDataNavigator(l.log, UpdateNavigationSettings(l.log))
- newNode := yaml.Node{Kind: navigator.GuessKind(paths, 0)}
+ newNode := yaml.Node{Kind: guessKind(paths, 0)}
return newNode
}
func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
- navigator := NewDataNavigator(l.log, UpdateNavigationSettings(l.log))
- l.log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
+ log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
switch updateCommand.Command {
case "update":
var paths = l.parser.ParsePath(updateCommand.Path)
- return navigator.Update(rootNode, paths, updateCommand.Value)
+ navigator := NewDataNavigator(UpdateNavigationSettings(updateCommand.Value))
+ return navigator.Traverse(rootNode, paths)
case "delete":
var paths = l.parser.ParsePath(updateCommand.Path)
- return navigator.Delete(rootNode, paths)
+ lastBit, newTail := paths[len(paths)-1], paths[:len(paths)-1]
+ navigator := NewDataNavigator(DeleteNavigationSettings(lastBit))
+ return navigator.Traverse(rootNode, newTail)
default:
return fmt.Errorf("Unknown command %v", updateCommand.Command)
}
diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go
index e48c88c8..5ee1d6dc 100644
--- a/pkg/yqlib/navigation_settings.go
+++ b/pkg/yqlib/navigation_settings.go
@@ -3,76 +3,74 @@ package yqlib
import (
"strings"
- logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
)
+type VisitedNode struct {
+ Node *yaml.Node
+ Head string
+ Tail []string
+ PathStack []interface{}
+}
+
type NavigationSettings interface {
FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+ Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
+ GetVisitedNodes() []*VisitedNode
}
type NavigationSettingsImpl struct {
followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
shouldVisit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+ visit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
+ visitedNodes []*VisitedNode
}
-func (ns NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+func matches(node *yaml.Node, head string) bool {
+ var prefixMatch = strings.TrimSuffix(head, "*")
+ if prefixMatch != head {
+ log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
+ return strings.HasPrefix(node.Value, prefixMatch)
+ }
+ log.Debug("equals match, %v", node.Value == head)
+ return node.Value == head
+}
+
+func (ns *NavigationSettingsImpl) GetVisitedNodes() []*VisitedNode {
+ return ns.visitedNodes
+}
+
+func (ns *NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
return ns.followAlias(node, head, tail, pathStack)
}
-func (ns NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
return ns.autoCreateMap(node, head, tail, pathStack)
}
-func (ns NavigationSettingsImpl) ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- return ns.shouldVisit(node, head, tail, pathStack)
+func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
+ ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack})
+ log.Debug("adding to visited nodes")
+ return ns.visit(node, head, tail, pathStack)
}
-func UpdateNavigationSettings(l *logging.Logger) NavigationSettings {
- return NavigationSettingsImpl{
- followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- return false
- },
- autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- return true
- },
- shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- var prefixMatch = strings.TrimSuffix(head, "*")
- if prefixMatch != head {
- l.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
- return strings.HasPrefix(node.Value, prefixMatch)
- }
- l.Debug("equals match, %v", node.Value == head)
- return node.Value == head
- },
+func (ns *NavigationSettingsImpl) ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ if !ns.alreadyVisited(node) {
+ return ns.shouldVisit(node, head, tail, pathStack)
+ } else {
+ log.Debug("Skipping over %v as we have seen it already", node.Value)
}
+ return false
}
-func ReadNavigationSettings(l *logging.Logger) NavigationSettings {
- return NavigationSettingsImpl{
- followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+func (ns *NavigationSettingsImpl) alreadyVisited(node *yaml.Node) bool {
+ for _, candidate := range ns.visitedNodes {
+ if candidate.Node.Value == node.Value {
return true
- },
- autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- return false
- },
- shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- l.Debug("shouldVisit h: %v, actual: %v", head, node.Value)
- if node.Value == "<<" {
- l.Debug("its an alias, skip it")
- // dont match alias keys, as we'll follow them instead
- return false
- }
- var prefixMatch = strings.TrimSuffix(head, "*")
- if prefixMatch != head {
- l.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
- return strings.HasPrefix(node.Value, prefixMatch)
- }
- l.Debug("equals match, %v", node.Value == head)
- return node.Value == head
- },
+ }
}
+ return false
}
diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go
new file mode 100644
index 00000000..08d12929
--- /dev/null
+++ b/pkg/yqlib/read_navigation_strategy.go
@@ -0,0 +1,37 @@
+package yqlib
+
+import (
+ "strings"
+
+ yaml "gopkg.in/yaml.v3"
+)
+
+func ReadNavigationSettings() NavigationSettings {
+ return &NavigationSettingsImpl{
+ visitedNodes: []*VisitedNode{},
+ followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return true
+ },
+ autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return false
+ },
+ shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ log.Debug("shouldVisit h: %v, actual: %v", head, node.Value)
+ if node.Value == "<<" {
+ log.Debug("its an alias, skip it")
+ // dont match alias keys, as we'll follow them instead
+ return false
+ }
+ var prefixMatch = strings.TrimSuffix(head, "*")
+ if prefixMatch != head {
+ log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
+ return strings.HasPrefix(node.Value, prefixMatch)
+ }
+ log.Debug("equals match, %v", node.Value == head)
+ return node.Value == head
+ },
+ visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
+ return nil
+ },
+ }
+}
diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go
new file mode 100644
index 00000000..021b78ce
--- /dev/null
+++ b/pkg/yqlib/update_navigation_strategy.go
@@ -0,0 +1,43 @@
+package yqlib
+
+import (
+ "strings"
+
+ yaml "gopkg.in/yaml.v3"
+)
+
+func UpdateNavigationSettings(changesToApply *yaml.Node) NavigationSettings {
+ return &NavigationSettingsImpl{
+ visitedNodes: []*VisitedNode{},
+ followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return false
+ },
+ autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return true
+ },
+ shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ var prefixMatch = strings.TrimSuffix(head, "*")
+ if prefixMatch != head {
+ log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
+ return strings.HasPrefix(node.Value, prefixMatch)
+ }
+ log.Debug("equals match, %v", node.Value == head)
+ return node.Value == head
+ },
+ visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
+ log.Debug("going to update")
+ DebugNode(node)
+ log.Debug("with")
+ DebugNode(changesToApply)
+ node.Value = changesToApply.Value
+ node.Tag = changesToApply.Tag
+ node.Kind = changesToApply.Kind
+ node.Style = changesToApply.Style
+ node.Content = changesToApply.Content
+ node.HeadComment = changesToApply.HeadComment
+ node.LineComment = changesToApply.LineComment
+ node.FootComment = changesToApply.FootComment
+ return nil
+ },
+ }
+}
diff --git a/yq.go b/yq.go
index 99c96fed..64614d09 100644
--- a/yq.go
+++ b/yq.go
@@ -257,7 +257,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
return errorParsingDocIndex
}
- var matchingNodes []yqlib.MatchingNode
+ var matchingNodes []*yqlib.VisitedNode
var currentIndex = 0
var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
@@ -292,9 +292,9 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
return nil
}
-func appendDocument(originalMatchingNodes []yqlib.MatchingNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]yqlib.MatchingNode, error) {
+func appendDocument(originalMatchingNodes []*yqlib.VisitedNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.VisitedNode, error) {
log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
- lib.DebugNode(&dataBucket)
+ yqlib.DebugNode(&dataBucket)
if !updateAll && currentIndex != docIndexInt {
return originalMatchingNodes, nil
}
@@ -337,7 +337,7 @@ func printValue(node *yaml.Node, cmd *cobra.Command) error {
return nil
}
-func printResults(matchingNodes []yqlib.MatchingNode, cmd *cobra.Command) error {
+func printResults(matchingNodes []*yqlib.VisitedNode, cmd *cobra.Command) error {
if len(matchingNodes) == 0 {
log.Debug("no matching results, nothing to print")
return nil
@@ -485,7 +485,7 @@ func prefixProperty(cmd *cobra.Command, args []string) error {
func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Prefixing document %v", currentIndex)
- lib.DebugNode(dataBucket)
+ yqlib.DebugNode(dataBucket)
updateCommand.Value = dataBucket.Content[0]
dataBucket.Content = make([]*yaml.Node, 1)
From cf389bed4a59f800205be63d32355017f360c677 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Fri, 27 Dec 2019 19:06:08 +1100
Subject: [PATCH 31/68] Refactor wip
---
commands_test.go | 7 +-
examples/merge-anchor.yaml | 2 +-
examples/sample2.yaml | 1 +
pkg/yqlib/data_navigator.go | 78 ++++++++++----------
pkg/yqlib/delete_navigation_settings.go | 10 ---
pkg/yqlib/navigation_settings.go | 94 ++++++++++++++++++++-----
pkg/yqlib/read_navigation_strategy.go | 17 -----
pkg/yqlib/update_navigation_strategy.go | 11 ---
test.yml | 12 +---
9 files changed, 125 insertions(+), 107 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 6c08c86e..33aeb4a8 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -154,9 +154,10 @@ func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `foobar.thing: ice
-foobar.thirty: well beyond
-foobar.thirsty: yep`
+ expectedOutput := `foobar.thirty: well beyond
+foobar.thing: ice
+foobar.thirsty: yep
+`
test.AssertResult(t, expectedOutput, result.Output)
}
diff --git a/examples/merge-anchor.yaml b/examples/merge-anchor.yaml
index 048b02e2..6d3f426d 100644
--- a/examples/merge-anchor.yaml
+++ b/examples/merge-anchor.yaml
@@ -14,6 +14,6 @@ foobarList:
foobar:
<<: *foo
- thing: ice
thirty: well beyond
+ thing: ice
c: 3
diff --git a/examples/sample2.yaml b/examples/sample2.yaml
index fea95f2e..e16e0b69 100644
--- a/examples/sample2.yaml
+++ b/examples/sample2.yaml
@@ -3,6 +3,7 @@ b:
c: things
d: whatever
things:
+ borg: snorg
thing1:
cat: 'fred'
thing2:
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 0bace7c4..23734111 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -37,6 +37,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pat
DebugNode(value)
return n.recurse(value, path[0], path[1:], pathStack)
}
+ log.Debug("should I visit?")
return n.navigationSettings.Visit(value, head, path, pathStack)
}
@@ -76,16 +77,27 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt
}
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
- visited, errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error {
- contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind))
- return n.doTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value))
+ traversedEntry := false
+ errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error {
+
+ log.Debug("should I traverse? %v", head)
+ DebugNode(value)
+ if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value)) == true {
+ log.Debug("yep!")
+ traversedEntry = true
+ contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind))
+ return n.doTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value))
+ } else {
+ log.Debug("nope not traversing")
+ }
+ return nil
})
if errorVisiting != nil {
return errorVisiting
}
- if visited || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false {
+ if traversedEntry == true || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false {
return nil
}
@@ -93,33 +105,27 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
value.Content = append(value.Content, &mapEntryKey)
mapEntryValue := yaml.Node{Kind: guessKind(tail, 0)}
value.Content = append(value.Content, &mapEntryValue)
- log.Debug("adding new node %v", value.Content)
+ log.Debug("adding new node %v", head)
return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head))
}
// need to pass the node in, as it may be aliased
type mapVisitorFn func(contents []*yaml.Node, index int) error
-func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
+func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
var contents = node.Content
- visited := false
for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
-
- if n.navigationSettings.ShouldVisit(content, head, tail, pathStack) == true {
- log.Debug("found a match! %v", content.Value)
- errorVisiting := visit(contents, index)
- if errorVisiting != nil {
- return visited, errorVisiting
- }
- visited = true
+ errorVisiting := visit(contents, index)
+ if errorVisiting != nil {
+ return errorVisiting
}
}
- return visited, nil
+ return nil
}
-func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
+func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
var contents = node.Content
log.Debug("visitMatchingEntries %v", head)
DebugNode(node)
@@ -127,20 +133,15 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
- visited, errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
+ errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
- //TODO: crap we have to remember what we visited so we dont print the same key in the alias
- // eff
-
- if errorVisitedDirectEntries != nil || visited == true || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false {
- return visited, errorVisitedDirectEntries
+ if errorVisitedDirectEntries != nil || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false {
+ return errorVisitedDirectEntries
}
- // didnt find a match, lets check the aliases.
-
return n.visitAliases(contents, head, tail, pathStack, visit)
}
-func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
+func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match on this node first.
// traverse them backwards so that the last alias overrides the preceding.
@@ -156,36 +157,35 @@ func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []stri
DebugNode(contents[index])
DebugNode(valueNode)
- visitedAlias, errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit)
- if visitedAlias == true || errorInAlias != nil {
- return visitedAlias, errorInAlias
+ errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit)
+ if errorInAlias != nil {
+ return errorInAlias
}
} else if contents[index+1].Kind == yaml.SequenceNode {
// could be an array of aliases...
- visitedAliasSeq, errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit)
- if visitedAliasSeq == true || errorVisitingAliasSeq != nil {
- return visitedAliasSeq, errorVisitingAliasSeq
+ errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit)
+ if errorVisitingAliasSeq != nil {
+ return errorVisitingAliasSeq
}
}
}
- log.Debug("nope no matching aliases found")
- return false, nil
+ return nil
}
-func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) (bool, error) {
+func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
// need to search this backwards too, so that aliases defined last override the preceding.
for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
child := possibleAliasArray[aliasIndex]
if child.Kind == yaml.AliasNode {
log.Debug("found an alias")
DebugNode(child)
- visitedAlias, errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit)
- if visitedAlias == true || errorInAlias != nil {
- return visitedAlias, errorInAlias
+ errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit)
+ if errorInAlias != nil {
+ return errorInAlias
}
}
}
- return false, nil
+ return nil
}
func (n *navigator) splatArray(value *yaml.Node, tail []string, pathStack []interface{}) error {
diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_settings.go
index ad1c8ff9..dd2aabf8 100644
--- a/pkg/yqlib/delete_navigation_settings.go
+++ b/pkg/yqlib/delete_navigation_settings.go
@@ -2,7 +2,6 @@ package yqlib
import (
"strconv"
- "strings"
yaml "gopkg.in/yaml.v3"
)
@@ -16,15 +15,6 @@ func DeleteNavigationSettings(lastBit string) NavigationSettings {
autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
return true
},
- shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- var prefixMatch = strings.TrimSuffix(head, "*")
- if prefixMatch != head {
- log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
- return strings.HasPrefix(node.Value, prefixMatch)
- }
- log.Debug("equals match, %v", node.Value == head)
- return node.Value == head
- },
visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
log.Debug("need to find %v in here", lastBit)
DebugNode(node)
diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go
index 5ee1d6dc..72d1ae3d 100644
--- a/pkg/yqlib/navigation_settings.go
+++ b/pkg/yqlib/navigation_settings.go
@@ -1,6 +1,7 @@
package yqlib
import (
+ "fmt"
"strings"
yaml "gopkg.in/yaml.v3"
@@ -16,15 +17,14 @@ type VisitedNode struct {
type NavigationSettings interface {
FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
- ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
+ ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
GetVisitedNodes() []*VisitedNode
}
type NavigationSettingsImpl struct {
followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
- shouldVisit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
visit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
visitedNodes []*VisitedNode
}
@@ -51,26 +51,88 @@ func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, ta
return ns.autoCreateMap(node, head, tail, pathStack)
}
-func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
- ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack})
- log.Debug("adding to visited nodes")
- return ns.visit(node, head, tail, pathStack)
-}
-
-func (ns *NavigationSettingsImpl) ShouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- if !ns.alreadyVisited(node) {
- return ns.shouldVisit(node, head, tail, pathStack)
- } else {
- log.Debug("Skipping over %v as we have seen it already", node.Value)
+func (ns *NavigationSettingsImpl) matchesNextPath(path string, candidate string) bool {
+ var prefixMatch = strings.TrimSuffix(path, "*")
+ if prefixMatch != path {
+ log.Debug("prefix match, %v", strings.HasPrefix(candidate, prefixMatch))
+ return strings.HasPrefix(candidate, prefixMatch)
}
- return false
+ return candidate == path
}
-func (ns *NavigationSettingsImpl) alreadyVisited(node *yaml.Node) bool {
+func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ // we should traverse aliases (if enabled), but not visit them :/
+ if len(pathStack) == 0 {
+ return true
+ }
+
+ if ns.alreadyVisited(pathStack) {
+ return false
+ }
+
+ lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
+
+ return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" && ns.matchesNextPath(head, lastBit))
+}
+
+func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ // we should traverse aliases (if enabled), but not visit them :/
+ if len(pathStack) == 0 {
+ return true
+ }
+
+ if ns.alreadyVisited(pathStack) {
+ return false
+ }
+
+ lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
+ // only visit aliases if its an exact match
+ return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && ns.matchesNextPath(head, lastBit))
+
+}
+
+func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
+ if ns.shouldVisit(node, head, tail, pathStack) {
+ ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack})
+ log.Debug("adding to visited nodes, %v", head)
+ return ns.visit(node, head, tail, pathStack)
+ }
+ return nil
+}
+
+func (ns *NavigationSettingsImpl) alreadyVisited(pathStack []interface{}) bool {
+ log.Debug("looking for pathStack")
+ for _, val := range pathStack {
+ log.Debug("\t %v", val)
+ }
for _, candidate := range ns.visitedNodes {
- if candidate.Node.Value == node.Value {
+ candidatePathStack := candidate.PathStack
+ if patchStacksMatch(candidatePathStack, pathStack) {
+ log.Debug("paths match, already seen it")
return true
}
+
}
+ log.Debug("never seen it before!")
return false
}
+
+func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool {
+ log.Debug("checking against path")
+ for _, val := range path1 {
+ log.Debug("\t %v", val)
+ }
+
+ if len(path1) != len(path2) {
+ return false
+ }
+ for index, p1Value := range path1 {
+
+ p2Value := path2[index]
+ if p1Value != p2Value {
+ return false
+ }
+ }
+ return true
+
+}
diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go
index 08d12929..7439495f 100644
--- a/pkg/yqlib/read_navigation_strategy.go
+++ b/pkg/yqlib/read_navigation_strategy.go
@@ -1,8 +1,6 @@
package yqlib
import (
- "strings"
-
yaml "gopkg.in/yaml.v3"
)
@@ -15,21 +13,6 @@ func ReadNavigationSettings() NavigationSettings {
autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
return false
},
- shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- log.Debug("shouldVisit h: %v, actual: %v", head, node.Value)
- if node.Value == "<<" {
- log.Debug("its an alias, skip it")
- // dont match alias keys, as we'll follow them instead
- return false
- }
- var prefixMatch = strings.TrimSuffix(head, "*")
- if prefixMatch != head {
- log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
- return strings.HasPrefix(node.Value, prefixMatch)
- }
- log.Debug("equals match, %v", node.Value == head)
- return node.Value == head
- },
visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
return nil
},
diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go
index 021b78ce..2fb122d7 100644
--- a/pkg/yqlib/update_navigation_strategy.go
+++ b/pkg/yqlib/update_navigation_strategy.go
@@ -1,8 +1,6 @@
package yqlib
import (
- "strings"
-
yaml "gopkg.in/yaml.v3"
)
@@ -15,15 +13,6 @@ func UpdateNavigationSettings(changesToApply *yaml.Node) NavigationSettings {
autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
return true
},
- shouldVisit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- var prefixMatch = strings.TrimSuffix(head, "*")
- if prefixMatch != head {
- log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
- return strings.HasPrefix(node.Value, prefixMatch)
- }
- log.Debug("equals match, %v", node.Value == head)
- return node.Value == head
- },
visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
log.Debug("going to update")
DebugNode(node)
diff --git a/test.yml b/test.yml
index 804fbe36..6a6683f1 100644
--- a/test.yml
+++ b/test.yml
@@ -1,11 +1,3 @@
-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
\ No newline at end of file
+ c: thing
+ d: another thing
\ No newline at end of file
From 707ad09ba51e8f6b590fb4122d53851b1089f6c0 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Fri, 27 Dec 2019 19:06:58 +1100
Subject: [PATCH 32/68] Refactor wip
---
pkg/yqlib/data_navigator.go | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 23734111..84bac231 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -82,11 +82,13 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
log.Debug("should I traverse? %v", head)
DebugNode(value)
- if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value)) == true {
+ newPath := append(pathStack, contents[indexInMap].Value)
+
+ if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPath) == true {
log.Debug("yep!")
traversedEntry = true
contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind))
- return n.doTraverse(contents[indexInMap+1], head, tail, append(pathStack, contents[indexInMap].Value))
+ return n.doTraverse(contents[indexInMap+1], head, tail, newPath)
} else {
log.Debug("nope not traversing")
}
From df52383ffb8848a787ad40fa1dc98a66f6714952 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 28 Dec 2019 10:51:54 +1300
Subject: [PATCH 33/68] Delete works! needs refactor
---
commands_test.go | 2 +-
pkg/yqlib/data_navigator.go | 7 ++---
pkg/yqlib/delete_navigation_settings.go | 41 +++++++++++++------------
pkg/yqlib/navigation_settings.go | 34 ++++++--------------
pkg/yqlib/path_parser.go | 23 ++++++++++++++
5 files changed, 58 insertions(+), 49 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 33aeb4a8..9c07897e 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1077,7 +1077,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
-func TestDeleteYaml(t *testing.T) {
+func TestDeleteYamlCmd(t *testing.T) {
content := `a: 2
b:
c: things
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 84bac231..364985ed 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -82,13 +82,12 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
log.Debug("should I traverse? %v", head)
DebugNode(value)
- newPath := append(pathStack, contents[indexInMap].Value)
-
- if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPath) == true {
+ newPathStack := append(pathStack, contents[indexInMap].Value)
+ if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPathStack, contents[indexInMap].Value) == true {
log.Debug("yep!")
traversedEntry = true
contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind))
- return n.doTraverse(contents[indexInMap+1], head, tail, newPath)
+ return n.doTraverse(contents[indexInMap+1], head, tail, newPathStack)
} else {
log.Debug("nope not traversing")
}
diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_settings.go
index dd2aabf8..b4392b4f 100644
--- a/pkg/yqlib/delete_navigation_settings.go
+++ b/pkg/yqlib/delete_navigation_settings.go
@@ -7,6 +7,7 @@ import (
)
func DeleteNavigationSettings(lastBit string) NavigationSettings {
+ parser := NewPathParser()
return &NavigationSettingsImpl{
visitedNodes: []*VisitedNode{},
followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
@@ -16,7 +17,7 @@ func DeleteNavigationSettings(lastBit string) NavigationSettings {
return true
},
visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
- log.Debug("need to find %v in here", lastBit)
+ log.Debug("need to find and delete %v in here", lastBit)
DebugNode(node)
if node.Kind == yaml.SequenceNode {
newContent, errorDeleting := deleteFromArray(node.Content, lastBit)
@@ -25,31 +26,33 @@ func DeleteNavigationSettings(lastBit string) NavigationSettings {
}
node.Content = newContent
} else if node.Kind == yaml.MappingNode {
- // need to delete in reverse - otherwise the matching indexes
- // become incorrect.
- // matchingIndices := make([]int, 0)
- // _, errorVisiting := n.visitMatchingEntries(node, lastBit, []string{}, pathStack, func(matchingNode []*yaml.Node, indexInMap int) error {
- // matchingIndices = append(matchingIndices, indexInMap)
- // log.Debug("matchingIndices %v", indexInMap)
- // return nil
- // })
- // log.Debug("delete matching indices now")
- // log.Debug("%v", matchingIndices)
- // if errorVisiting != nil {
- // return errorVisiting
- // }
- // for i := len(matchingIndices) - 1; i >= 0; i-- {
- // indexToDelete := matchingIndices[i]
- // log.Debug("deleting index %v, %v", indexToDelete, node.Content[indexToDelete].Value)
- // node.Content = append(node.Content[:indexToDelete], node.Content[indexToDelete+2:]...)
- // }
+ node.Content = deleteFromMap(parser, node.Content, pathStack, lastBit)
}
return nil
},
}
}
+func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, lastBit string) []*yaml.Node {
+ newContents := make([]*yaml.Node, 0)
+ for index := 0; index < len(contents); index = index + 2 {
+ keyNode := contents[index]
+ valueNode := contents[index+1]
+ if pathParser.MatchesNextPathElement(keyNode, lastBit, []string{}, pathStack, keyNode.Value) == false {
+ log.Debug("adding node %v", keyNode.Value)
+ newContents = append(newContents, keyNode, valueNode)
+ } else {
+ log.Debug("skipping node %v", keyNode.Value)
+ }
+ }
+ return newContents
+}
func deleteFromArray(content []*yaml.Node, lastBit string) ([]*yaml.Node, error) {
+
+ if lastBit == "*" {
+ return make([]*yaml.Node, 0), nil
+ }
+
var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
if err != nil {
return content, err
diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go
index 72d1ae3d..f2da767f 100644
--- a/pkg/yqlib/navigation_settings.go
+++ b/pkg/yqlib/navigation_settings.go
@@ -2,7 +2,6 @@ package yqlib
import (
"fmt"
- "strings"
yaml "gopkg.in/yaml.v3"
)
@@ -18,7 +17,7 @@ type NavigationSettings interface {
FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
- ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
+ ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool
GetVisitedNodes() []*VisitedNode
}
@@ -29,16 +28,6 @@ type NavigationSettingsImpl struct {
visitedNodes []*VisitedNode
}
-func matches(node *yaml.Node, head string) bool {
- var prefixMatch = strings.TrimSuffix(head, "*")
- if prefixMatch != head {
- log.Debug("prefix match, %v", strings.HasPrefix(node.Value, prefixMatch))
- return strings.HasPrefix(node.Value, prefixMatch)
- }
- log.Debug("equals match, %v", node.Value == head)
- return node.Value == head
-}
-
func (ns *NavigationSettingsImpl) GetVisitedNodes() []*VisitedNode {
return ns.visitedNodes
}
@@ -51,16 +40,7 @@ func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, ta
return ns.autoCreateMap(node, head, tail, pathStack)
}
-func (ns *NavigationSettingsImpl) matchesNextPath(path string, candidate string) bool {
- var prefixMatch = strings.TrimSuffix(path, "*")
- if prefixMatch != path {
- log.Debug("prefix match, %v", strings.HasPrefix(candidate, prefixMatch))
- return strings.HasPrefix(candidate, prefixMatch)
- }
- return candidate == path
-}
-
-func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool {
// we should traverse aliases (if enabled), but not visit them :/
if len(pathStack) == 0 {
return true
@@ -70,9 +50,10 @@ func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, t
return false
}
- lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
+ parser := NewPathParser()
- return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" && ns.matchesNextPath(head, lastBit))
+ return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" &&
+ parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit))
}
func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
@@ -86,8 +67,11 @@ func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail
}
lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
+ parser := NewPathParser()
+
// only visit aliases if its an exact match
- return (lastBit == "<<" && head == "<<") || (lastBit != "<<" && ns.matchesNextPath(head, lastBit))
+ return (lastBit == "<<" && head == "<<") || (lastBit != "<<" &&
+ parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit))
}
diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go
index 83157054..8ae73cc0 100644
--- a/pkg/yqlib/path_parser.go
+++ b/pkg/yqlib/path_parser.go
@@ -1,7 +1,14 @@
package yqlib
+import (
+ "strings"
+
+ yaml "gopkg.in/yaml.v3"
+)
+
type PathParser interface {
ParsePath(path string) []string
+ MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool
}
type pathParser struct{}
@@ -10,6 +17,22 @@ func NewPathParser() PathParser {
return &pathParser{}
}
+/**
+ * node: node that we may traverse/visit
+ * head: path element expression to match against
+ * tail: remaining path element expressions
+ * pathStack: stack of actual paths we've matched to get to node
+ * lastBit: actual value of this nodes 'key' or index.
+ */
+func (p *pathParser) MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool {
+ var prefixMatch = strings.TrimSuffix(head, "*")
+ if prefixMatch != head {
+ log.Debug("prefix match, %v", strings.HasPrefix(lastBit, prefixMatch))
+ return strings.HasPrefix(lastBit, prefixMatch)
+ }
+ return lastBit == head
+}
+
func (p *pathParser) ParsePath(path string) []string {
if path == "" {
return []string{}
From 0652f67a91b5c9aa3759cf4b71c1e183fb495b09 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 28 Dec 2019 20:19:37 +1300
Subject: [PATCH 34/68] Refactored!
---
pkg/yqlib/data_navigator.go | 16 +--
...tings.go => delete_navigation_strategy.go} | 29 ++--
pkg/yqlib/lib.go | 14 +-
pkg/yqlib/navigation_settings.go | 122 -----------------
pkg/yqlib/navigation_strategy.go | 124 ++++++++++++++++++
pkg/yqlib/path_parser.go | 15 +--
pkg/yqlib/read_navigation_strategy.go | 16 +--
pkg/yqlib/update_navigation_strategy.go | 13 +-
yq.go | 6 +-
9 files changed, 177 insertions(+), 178 deletions(-)
rename pkg/yqlib/{delete_navigation_settings.go => delete_navigation_strategy.go} (51%)
delete mode 100644 pkg/yqlib/navigation_settings.go
create mode 100644 pkg/yqlib/navigation_strategy.go
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 364985ed..d47e8011 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -12,12 +12,12 @@ type DataNavigator interface {
}
type navigator struct {
- navigationSettings NavigationSettings
+ navigationStrategy NavigationStrategy
}
-func NewDataNavigator(navigationSettings NavigationSettings) DataNavigator {
+func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
return &navigator{
- navigationSettings: navigationSettings,
+ navigationStrategy: NavigationStrategy,
}
}
@@ -38,7 +38,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pat
return n.recurse(value, path[0], path[1:], pathStack)
}
log.Debug("should I visit?")
- return n.navigationSettings.Visit(value, head, path, pathStack)
+ return n.navigationStrategy.Visit(NodeContext{value, head, path, pathStack})
}
func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
@@ -66,7 +66,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt
case yaml.AliasNode:
log.Debug("its an alias!")
DebugNode(value.Alias)
- if n.navigationSettings.FollowAlias(value, head, tail, pathStack) == true {
+ if n.navigationStrategy.FollowAlias(NodeContext{value, head, tail, pathStack}) == true {
log.Debug("following the alias")
return n.recurse(value.Alias, head, tail, pathStack)
}
@@ -83,7 +83,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
log.Debug("should I traverse? %v", head)
DebugNode(value)
newPathStack := append(pathStack, contents[indexInMap].Value)
- if n.navigationSettings.ShouldTraverse(contents[indexInMap+1], head, tail, newPathStack, contents[indexInMap].Value) == true {
+ if n.navigationStrategy.ShouldTraverse(NodeContext{contents[indexInMap+1], head, tail, newPathStack}, contents[indexInMap].Value) == true {
log.Debug("yep!")
traversedEntry = true
contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(tail, contents[indexInMap+1].Kind))
@@ -98,7 +98,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
return errorVisiting
}
- if traversedEntry == true || head == "*" || n.navigationSettings.AutoCreateMap(value, head, tail, pathStack) == false {
+ if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NodeContext{value, head, tail, pathStack}) == false {
return nil
}
@@ -136,7 +136,7 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st
// if we don't find a match directly on this node first.
errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
- if errorVisitedDirectEntries != nil || n.navigationSettings.FollowAlias(node, head, tail, pathStack) == false {
+ if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NodeContext{node, head, tail, pathStack}) == false {
return errorVisitedDirectEntries
}
return n.visitAliases(contents, head, tail, pathStack, visit)
diff --git a/pkg/yqlib/delete_navigation_settings.go b/pkg/yqlib/delete_navigation_strategy.go
similarity index 51%
rename from pkg/yqlib/delete_navigation_settings.go
rename to pkg/yqlib/delete_navigation_strategy.go
index b4392b4f..8d221c4a 100644
--- a/pkg/yqlib/delete_navigation_settings.go
+++ b/pkg/yqlib/delete_navigation_strategy.go
@@ -6,38 +6,39 @@ import (
yaml "gopkg.in/yaml.v3"
)
-func DeleteNavigationSettings(lastBit string) NavigationSettings {
+func DeleteNavigationStrategy(pathElementToDelete string) NavigationStrategy {
parser := NewPathParser()
- return &NavigationSettingsImpl{
- visitedNodes: []*VisitedNode{},
- followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ return &NavigationStrategyImpl{
+ visitedNodes: []*NodeContext{},
+ followAlias: func(nodeContext NodeContext) bool {
return false
},
- autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ autoCreateMap: func(nodeContext NodeContext) bool {
return true
},
- visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
- log.Debug("need to find and delete %v in here", lastBit)
+ visit: func(nodeContext NodeContext) error {
+ node := nodeContext.Node
+ log.Debug("need to find and delete %v in here", pathElementToDelete)
DebugNode(node)
if node.Kind == yaml.SequenceNode {
- newContent, errorDeleting := deleteFromArray(node.Content, lastBit)
+ newContent, errorDeleting := deleteFromArray(node.Content, pathElementToDelete)
if errorDeleting != nil {
return errorDeleting
}
node.Content = newContent
} else if node.Kind == yaml.MappingNode {
- node.Content = deleteFromMap(parser, node.Content, pathStack, lastBit)
+ node.Content = deleteFromMap(parser, node.Content, nodeContext.PathStack, pathElementToDelete)
}
return nil
},
}
}
-func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, lastBit string) []*yaml.Node {
+func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, pathElementToDelete string) []*yaml.Node {
newContents := make([]*yaml.Node, 0)
for index := 0; index < len(contents); index = index + 2 {
keyNode := contents[index]
valueNode := contents[index+1]
- if pathParser.MatchesNextPathElement(keyNode, lastBit, []string{}, pathStack, keyNode.Value) == false {
+ if pathParser.MatchesNextPathElement(NodeContext{keyNode, pathElementToDelete, []string{}, pathStack}, keyNode.Value) == false {
log.Debug("adding node %v", keyNode.Value)
newContents = append(newContents, keyNode, valueNode)
} else {
@@ -47,13 +48,13 @@ func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []int
return newContents
}
-func deleteFromArray(content []*yaml.Node, lastBit string) ([]*yaml.Node, error) {
+func deleteFromArray(content []*yaml.Node, pathElementToDelete string) ([]*yaml.Node, error) {
- if lastBit == "*" {
+ if pathElementToDelete == "*" {
return make([]*yaml.Node, 0), nil
}
- var index, err = strconv.ParseInt(lastBit, 10, 64) // nolint
+ var index, err = strconv.ParseInt(pathElementToDelete, 10, 64) // nolint
if err != nil {
return content, err
}
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index c11773d1..74ed5517 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -57,7 +57,7 @@ func guessKind(tail []string, guess yaml.Kind) yaml.Kind {
}
type YqLib interface {
- Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error)
+ Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
New(path string) yaml.Node
}
@@ -73,12 +73,12 @@ func NewYqLib(l *logging.Logger) YqLib {
}
}
-func (l *lib) Get(rootNode *yaml.Node, path string) ([]*VisitedNode, error) {
+func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
var paths = l.parser.ParsePath(path)
- navigationSettings := ReadNavigationSettings()
- navigator := NewDataNavigator(navigationSettings)
+ NavigationStrategy := ReadNavigationStrategy()
+ navigator := NewDataNavigator(NavigationStrategy)
error := navigator.Traverse(rootNode, paths)
- return navigationSettings.GetVisitedNodes(), error
+ return NavigationStrategy.GetVisitedNodes(), error
}
@@ -93,12 +93,12 @@ func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
switch updateCommand.Command {
case "update":
var paths = l.parser.ParsePath(updateCommand.Path)
- navigator := NewDataNavigator(UpdateNavigationSettings(updateCommand.Value))
+ navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand.Value))
return navigator.Traverse(rootNode, paths)
case "delete":
var paths = l.parser.ParsePath(updateCommand.Path)
lastBit, newTail := paths[len(paths)-1], paths[:len(paths)-1]
- navigator := NewDataNavigator(DeleteNavigationSettings(lastBit))
+ navigator := NewDataNavigator(DeleteNavigationStrategy(lastBit))
return navigator.Traverse(rootNode, newTail)
default:
return fmt.Errorf("Unknown command %v", updateCommand.Command)
diff --git a/pkg/yqlib/navigation_settings.go b/pkg/yqlib/navigation_settings.go
deleted file mode 100644
index f2da767f..00000000
--- a/pkg/yqlib/navigation_settings.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package yqlib
-
-import (
- "fmt"
-
- yaml "gopkg.in/yaml.v3"
-)
-
-type VisitedNode struct {
- Node *yaml.Node
- Head string
- Tail []string
- PathStack []interface{}
-}
-
-type NavigationSettings interface {
- FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
- AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
- Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
- ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool
- GetVisitedNodes() []*VisitedNode
-}
-
-type NavigationSettingsImpl struct {
- followAlias func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
- autoCreateMap func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool
- visit func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error
- visitedNodes []*VisitedNode
-}
-
-func (ns *NavigationSettingsImpl) GetVisitedNodes() []*VisitedNode {
- return ns.visitedNodes
-}
-
-func (ns *NavigationSettingsImpl) FollowAlias(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- return ns.followAlias(node, head, tail, pathStack)
-}
-
-func (ns *NavigationSettingsImpl) AutoCreateMap(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- return ns.autoCreateMap(node, head, tail, pathStack)
-}
-
-func (ns *NavigationSettingsImpl) ShouldTraverse(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool {
- // we should traverse aliases (if enabled), but not visit them :/
- if len(pathStack) == 0 {
- return true
- }
-
- if ns.alreadyVisited(pathStack) {
- return false
- }
-
- parser := NewPathParser()
-
- return (lastBit == "<<" && ns.FollowAlias(node, head, tail, pathStack)) || (lastBit != "<<" &&
- parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit))
-}
-
-func (ns *NavigationSettingsImpl) shouldVisit(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
- // we should traverse aliases (if enabled), but not visit them :/
- if len(pathStack) == 0 {
- return true
- }
-
- if ns.alreadyVisited(pathStack) {
- return false
- }
-
- lastBit := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
- parser := NewPathParser()
-
- // only visit aliases if its an exact match
- return (lastBit == "<<" && head == "<<") || (lastBit != "<<" &&
- parser.MatchesNextPathElement(node, head, tail, pathStack, lastBit))
-
-}
-
-func (ns *NavigationSettingsImpl) Visit(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
- if ns.shouldVisit(node, head, tail, pathStack) {
- ns.visitedNodes = append(ns.visitedNodes, &VisitedNode{node, head, tail, pathStack})
- log.Debug("adding to visited nodes, %v", head)
- return ns.visit(node, head, tail, pathStack)
- }
- return nil
-}
-
-func (ns *NavigationSettingsImpl) alreadyVisited(pathStack []interface{}) bool {
- log.Debug("looking for pathStack")
- for _, val := range pathStack {
- log.Debug("\t %v", val)
- }
- for _, candidate := range ns.visitedNodes {
- candidatePathStack := candidate.PathStack
- if patchStacksMatch(candidatePathStack, pathStack) {
- log.Debug("paths match, already seen it")
- return true
- }
-
- }
- log.Debug("never seen it before!")
- return false
-}
-
-func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool {
- log.Debug("checking against path")
- for _, val := range path1 {
- log.Debug("\t %v", val)
- }
-
- if len(path1) != len(path2) {
- return false
- }
- for index, p1Value := range path1 {
-
- p2Value := path2[index]
- if p1Value != p2Value {
- return false
- }
- }
- return true
-
-}
diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go
new file mode 100644
index 00000000..55f4acb7
--- /dev/null
+++ b/pkg/yqlib/navigation_strategy.go
@@ -0,0 +1,124 @@
+package yqlib
+
+import (
+ "fmt"
+
+ yaml "gopkg.in/yaml.v3"
+)
+
+type NodeContext struct {
+ Node *yaml.Node
+ Head string
+ Tail []string
+ PathStack []interface{}
+}
+
+type NavigationStrategy interface {
+ FollowAlias(nodeContext NodeContext) bool
+ AutoCreateMap(nodeContext NodeContext) bool
+ Visit(nodeContext NodeContext) error
+ // node key is the string value of the last element in the path stack
+ // we use it to match against the pathExpression in head.
+ ShouldTraverse(nodeContext NodeContext, nodeKey string) bool
+ GetVisitedNodes() []*NodeContext
+}
+
+type NavigationStrategyImpl struct {
+ followAlias func(nodeContext NodeContext) bool
+ autoCreateMap func(nodeContext NodeContext) bool
+ visit func(nodeContext NodeContext) error
+ visitedNodes []*NodeContext
+}
+
+func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext {
+ return ns.visitedNodes
+}
+
+func (ns *NavigationStrategyImpl) FollowAlias(nodeContext NodeContext) bool {
+ return ns.followAlias(nodeContext)
+}
+
+func (ns *NavigationStrategyImpl) AutoCreateMap(nodeContext NodeContext) bool {
+ return ns.autoCreateMap(nodeContext)
+}
+
+func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool {
+ // we should traverse aliases (if enabled), but not visit them :/
+ if len(nodeContext.PathStack) == 0 {
+ return true
+ }
+
+ if ns.alreadyVisited(nodeContext.PathStack) {
+ return false
+ }
+
+ parser := NewPathParser()
+
+ return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" &&
+ parser.MatchesNextPathElement(nodeContext, nodeKey))
+}
+
+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) {
+ return false
+ }
+
+ nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
+ parser := NewPathParser()
+
+ // only visit aliases if its an exact match
+ return (nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
+ parser.MatchesNextPathElement(nodeContext, nodeKey))
+}
+
+func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
+ if ns.shouldVisit(nodeContext) {
+ ns.visitedNodes = append(ns.visitedNodes, &nodeContext)
+ log.Debug("adding to visited nodes, %v", nodeContext.Head)
+ return ns.visit(nodeContext)
+ }
+ return nil
+}
+
+func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
+ log.Debug("looking for pathStack")
+ for _, val := range pathStack {
+ log.Debug("\t %v", val)
+ }
+ for _, candidate := range ns.visitedNodes {
+ candidatePathStack := candidate.PathStack
+ if patchStacksMatch(candidatePathStack, pathStack) {
+ log.Debug("paths match, already seen it")
+ return true
+ }
+
+ }
+ log.Debug("never seen it before!")
+ return false
+}
+
+func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool {
+ log.Debug("checking against path")
+ for _, val := range path1 {
+ log.Debug("\t %v", val)
+ }
+
+ if len(path1) != len(path2) {
+ return false
+ }
+ for index, p1Value := range path1 {
+
+ p2Value := path2[index]
+ if p1Value != p2Value {
+ return false
+ }
+ }
+ return true
+
+}
diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go
index 8ae73cc0..e4665ddb 100644
--- a/pkg/yqlib/path_parser.go
+++ b/pkg/yqlib/path_parser.go
@@ -2,13 +2,11 @@ package yqlib
import (
"strings"
-
- yaml "gopkg.in/yaml.v3"
)
type PathParser interface {
ParsePath(path string) []string
- MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool
+ MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
}
type pathParser struct{}
@@ -22,15 +20,16 @@ func NewPathParser() PathParser {
* head: path element expression to match against
* tail: remaining path element expressions
* pathStack: stack of actual paths we've matched to get to node
- * lastBit: actual value of this nodes 'key' or index.
+ * nodeKey: actual value of this nodes 'key' or index.
*/
-func (p *pathParser) MatchesNextPathElement(node *yaml.Node, head string, tail []string, pathStack []interface{}, lastBit string) bool {
+func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool {
+ head := nodeContext.Head
var prefixMatch = strings.TrimSuffix(head, "*")
if prefixMatch != head {
- log.Debug("prefix match, %v", strings.HasPrefix(lastBit, prefixMatch))
- return strings.HasPrefix(lastBit, prefixMatch)
+ log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch))
+ return strings.HasPrefix(nodeKey, prefixMatch)
}
- return lastBit == head
+ return nodeKey == head
}
func (p *pathParser) ParsePath(path string) []string {
diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go
index 7439495f..70ecfb7d 100644
--- a/pkg/yqlib/read_navigation_strategy.go
+++ b/pkg/yqlib/read_navigation_strategy.go
@@ -1,19 +1,15 @@
package yqlib
-import (
- yaml "gopkg.in/yaml.v3"
-)
-
-func ReadNavigationSettings() NavigationSettings {
- return &NavigationSettingsImpl{
- visitedNodes: []*VisitedNode{},
- followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+func ReadNavigationStrategy() NavigationStrategy {
+ return &NavigationStrategyImpl{
+ visitedNodes: []*NodeContext{},
+ followAlias: func(nodeContext NodeContext) bool {
return true
},
- autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ autoCreateMap: func(nodeContext NodeContext) bool {
return false
},
- visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
+ visit: func(nodeContext NodeContext) error {
return nil
},
}
diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go
index 2fb122d7..281a4033 100644
--- a/pkg/yqlib/update_navigation_strategy.go
+++ b/pkg/yqlib/update_navigation_strategy.go
@@ -4,16 +4,17 @@ import (
yaml "gopkg.in/yaml.v3"
)
-func UpdateNavigationSettings(changesToApply *yaml.Node) NavigationSettings {
- return &NavigationSettingsImpl{
- visitedNodes: []*VisitedNode{},
- followAlias: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+func UpdateNavigationStrategy(changesToApply *yaml.Node) NavigationStrategy {
+ return &NavigationStrategyImpl{
+ visitedNodes: []*NodeContext{},
+ followAlias: func(nodeContext NodeContext) bool {
return false
},
- autoCreateMap: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) bool {
+ autoCreateMap: func(nodeContext NodeContext) bool {
return true
},
- visit: func(node *yaml.Node, head string, tail []string, pathStack []interface{}) error {
+ visit: func(nodeContext NodeContext) error {
+ node := nodeContext.Node
log.Debug("going to update")
DebugNode(node)
log.Debug("with")
diff --git a/yq.go b/yq.go
index 64614d09..7d1c72b9 100644
--- a/yq.go
+++ b/yq.go
@@ -257,7 +257,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
return errorParsingDocIndex
}
- var matchingNodes []*yqlib.VisitedNode
+ var matchingNodes []*yqlib.NodeContext
var currentIndex = 0
var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
@@ -292,7 +292,7 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
return nil
}
-func appendDocument(originalMatchingNodes []*yqlib.VisitedNode, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.VisitedNode, error) {
+func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) {
log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
yqlib.DebugNode(&dataBucket)
if !updateAll && currentIndex != docIndexInt {
@@ -337,7 +337,7 @@ func printValue(node *yaml.Node, cmd *cobra.Command) error {
return nil
}
-func printResults(matchingNodes []*yqlib.VisitedNode, cmd *cobra.Command) error {
+func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error {
if len(matchingNodes) == 0 {
log.Debug("no matching results, nothing to print")
return nil
From 8a6af1720d5e189e7853fd63d7173fbd9e689f30 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 30 Dec 2019 11:21:21 +1300
Subject: [PATCH 35/68] Fixed modify array issue!
---
commands_test.go | 9 ++++++
pkg/yqlib/data_navigator.go | 32 +++++++++++++-------
pkg/yqlib/delete_navigation_strategy.go | 2 +-
pkg/yqlib/lib.go | 18 +++++++++++
pkg/yqlib/navigation_strategy.go | 40 +++++++++++++++++++------
yq.go | 22 ++------------
6 files changed, 82 insertions(+), 41 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 9c07897e..a7c9f84f 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -103,6 +103,15 @@ func TestReadWithKeyAndValueCmd(t *testing.T) {
test.AssertResult(t, "b.c: 2\n", result.Output)
}
+func TestReadArrayCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.e.1.name")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "b.e.1.name: sam\n", 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 d47e8011..e92c4c2a 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -37,8 +37,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, path []string, pat
DebugNode(value)
return n.recurse(value, path[0], path[1:], pathStack)
}
- log.Debug("should I visit?")
- return n.navigationStrategy.Visit(NodeContext{value, head, path, pathStack})
+ return n.navigationStrategy.Visit(NewNodeContext(value, head, path, pathStack))
}
func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
@@ -66,7 +65,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt
case yaml.AliasNode:
log.Debug("its an alias!")
DebugNode(value.Alias)
- if n.navigationStrategy.FollowAlias(NodeContext{value, head, tail, pathStack}) == true {
+ if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) == true {
log.Debug("following the alias")
return n.recurse(value.Alias, head, tail, pathStack)
}
@@ -79,15 +78,21 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
traversedEntry := false
errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error {
-
- log.Debug("should I traverse? %v", head)
- DebugNode(value)
+ log.Debug("recurseMap: visitMatchingEntries")
+ n.navigationStrategy.DebugVisitedNodes()
newPathStack := append(pathStack, contents[indexInMap].Value)
- if n.navigationStrategy.ShouldTraverse(NodeContext{contents[indexInMap+1], head, tail, newPathStack}, contents[indexInMap].Value) == true {
- log.Debug("yep!")
+ log.Debug("appended %v", contents[indexInMap].Value)
+ n.navigationStrategy.DebugVisitedNodes()
+ log.Debug("should I traverse? %v, %v", head, PathStackToString(newPathStack))
+ DebugNode(value)
+ 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))
- return n.doTraverse(contents[indexInMap+1], head, tail, newPathStack)
+ errorTraversing := n.doTraverse(contents[indexInMap+1], head, tail, newPathStack)
+ log.Debug("recurseMap: Finished traversing")
+ n.navigationStrategy.DebugVisitedNodes()
+ return errorTraversing
} else {
log.Debug("nope not traversing")
}
@@ -98,7 +103,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
return errorVisiting
}
- if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NodeContext{value, head, tail, pathStack}) == false {
+ if traversedEntry == true || head == "*" || n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) == false {
return nil
}
@@ -117,7 +122,9 @@ func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tai
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
+
log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
+ n.navigationStrategy.DebugVisitedNodes()
errorVisiting := visit(contents, index)
if errorVisiting != nil {
return errorVisiting
@@ -136,7 +143,7 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st
// if we don't find a match directly on this node first.
errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
- if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NodeContext{node, head, tail, pathStack}) == false {
+ if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) == false {
return errorVisitedDirectEntries
}
return n.visitAliases(contents, head, tail, pathStack, visit)
@@ -220,5 +227,8 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, p
return nil
}
value.Content[index] = n.getOrReplace(value.Content[index], guessKind(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/delete_navigation_strategy.go b/pkg/yqlib/delete_navigation_strategy.go
index 8d221c4a..b4f95c3a 100644
--- a/pkg/yqlib/delete_navigation_strategy.go
+++ b/pkg/yqlib/delete_navigation_strategy.go
@@ -38,7 +38,7 @@ func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []int
for index := 0; index < len(contents); index = index + 2 {
keyNode := contents[index]
valueNode := contents[index+1]
- if pathParser.MatchesNextPathElement(NodeContext{keyNode, pathElementToDelete, []string{}, pathStack}, keyNode.Value) == false {
+ if pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, []string{}, pathStack), keyNode.Value) == false {
log.Debug("adding node %v", keyNode.Value)
newContents = append(newContents, keyNode, valueNode)
} else {
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 74ed5517..2217a1b5 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"strconv"
+ "strings"
logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
@@ -30,6 +31,23 @@ func DebugNode(value *yaml.Node) {
}
}
+func PathStackToString(pathStack []interface{}) string {
+ var sb strings.Builder
+ for index, path := range pathStack {
+ switch path.(type) {
+ case int:
+ sb.WriteString(fmt.Sprintf("[%v]", path))
+ default:
+ sb.WriteString(fmt.Sprintf("%v", path))
+ }
+
+ if index < len(pathStack)-1 {
+ sb.WriteString(".")
+ }
+ }
+ return sb.String()
+}
+
func guessKind(tail []string, guess yaml.Kind) yaml.Kind {
log.Debug("tail %v", tail)
if len(tail) == 0 && guess == 0 {
diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go
index 55f4acb7..85dd5b21 100644
--- a/pkg/yqlib/navigation_strategy.go
+++ b/pkg/yqlib/navigation_strategy.go
@@ -13,6 +13,20 @@ type NodeContext struct {
PathStack []interface{}
}
+func NewNodeContext(node *yaml.Node, head string, tail []string, pathStack []interface{}) NodeContext {
+ newTail := make([]string, len(tail))
+ copy(newTail, tail)
+
+ newPathStack := make([]interface{}, len(pathStack))
+ copy(newPathStack, pathStack)
+ return NodeContext{
+ Node: node,
+ Head: head,
+ Tail: newTail,
+ PathStack: newPathStack,
+ }
+}
+
type NavigationStrategy interface {
FollowAlias(nodeContext NodeContext) bool
AutoCreateMap(nodeContext NodeContext) bool
@@ -21,6 +35,7 @@ type NavigationStrategy interface {
// we use it to match against the pathExpression in head.
ShouldTraverse(nodeContext NodeContext, nodeKey string) bool
GetVisitedNodes() []*NodeContext
+ DebugVisitedNodes()
}
type NavigationStrategyImpl struct {
@@ -78,19 +93,29 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
}
func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
+ log.Debug("Visit?, %v, %v", nodeContext.Head, PathStackToString(nodeContext.PathStack))
+ DebugNode(nodeContext.Node)
if ns.shouldVisit(nodeContext) {
+ log.Debug("yep, visiting")
+ // pathStack array must be
+ // copied, as append() may sometimes reuse and modify the array
ns.visitedNodes = append(ns.visitedNodes, &nodeContext)
- log.Debug("adding to visited nodes, %v", nodeContext.Head)
+ ns.DebugVisitedNodes()
return ns.visit(nodeContext)
}
+ log.Debug("nope, skip it")
return nil
}
-func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
- log.Debug("looking for pathStack")
- for _, val := range pathStack {
- log.Debug("\t %v", val)
+func (ns *NavigationStrategyImpl) DebugVisitedNodes() {
+ log.Debug("%v", ns.visitedNodes)
+ for _, candidate := range ns.visitedNodes {
+ log.Debug(" - %v", PathStackToString(candidate.PathStack))
}
+}
+
+func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
+ log.Debug("checking already visited pathStack: %v", PathStackToString(pathStack))
for _, candidate := range ns.visitedNodes {
candidatePathStack := candidate.PathStack
if patchStacksMatch(candidatePathStack, pathStack) {
@@ -104,10 +129,7 @@ func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
}
func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool {
- log.Debug("checking against path")
- for _, val := range path1 {
- log.Debug("\t %v", val)
- }
+ log.Debug("checking against path: %v", PathStackToString(path1))
if len(path1) != len(path2) {
return false
diff --git a/yq.go b/yq.go
index 7d1c72b9..59bb74a2 100644
--- a/yq.go
+++ b/yq.go
@@ -7,7 +7,6 @@ import (
"io/ioutil"
"os"
"strconv"
- "strings"
"github.com/mikefarah/yq/v3/pkg/yqlib"
@@ -306,23 +305,6 @@ func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.
return append(originalMatchingNodes, matchingNodes...), nil
}
-func pathToString(pathStack []interface{}) string {
- var sb strings.Builder
- for index, path := range pathStack {
- switch path.(type) {
- case int:
- sb.WriteString(fmt.Sprintf("[%v]", path))
- default:
- sb.WriteString(fmt.Sprintf("%v", path))
- }
-
- if index < len(pathStack)-1 {
- sb.WriteString(".")
- }
- }
- return sb.String()
-}
-
func printValue(node *yaml.Node, cmd *cobra.Command) error {
if node.Kind == yaml.ScalarNode {
cmd.Print(node.Value)
@@ -346,7 +328,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error
for index, mappedDoc := range matchingNodes {
switch printMode {
case "k":
- cmd.Print(pathToString(mappedDoc.PathStack))
+ cmd.Print(yqlib.PathStackToString(mappedDoc.PathStack))
if index < len(matchingNodes)-1 {
cmd.Print("\n")
}
@@ -354,7 +336,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error
// put it into a node and print that.
var parentNode = yaml.Node{Kind: yaml.MappingNode}
parentNode.Content = make([]*yaml.Node, 2)
- parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: pathToString(mappedDoc.PathStack)}
+ parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: yqlib.PathStackToString(mappedDoc.PathStack)}
parentNode.Content[1] = mappedDoc.Node
if err := printValue(&parentNode, cmd); err != nil {
return err
From 4dbdd4a805e6bdca53aeafc4b04fa92eebc39643 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 30 Dec 2019 16:51:07 +1300
Subject: [PATCH 36/68] Deep splat!
---
commands_test.go | 18 ++++++++++++++++++
pkg/yqlib/data_navigator.go | 26 ++++++++++++++++----------
pkg/yqlib/lib.go | 2 +-
pkg/yqlib/navigation_strategy.go | 5 ++---
pkg/yqlib/path_parser.go | 3 +++
5 files changed, 40 insertions(+), 14 deletions(-)
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))
From 625cfdac75c6eb7955c9c050ab59d3b805196954 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Tue, 31 Dec 2019 15:21:39 +1300
Subject: [PATCH 37/68] 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 5d0925da..3e897419 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 c17e7e5b..835c924b 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 6d851b91..3f7737a6 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 91f942d1..740d06f9 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 7c1bd4ba..b18ff5d8 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))
From a065a47b37fcdcfd7ba07fe7d50b23306cfcee98 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 5 Jan 2020 16:22:18 +1300
Subject: [PATCH 38/68] Fixed tests
---
commands_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/commands_test.go b/commands_test.go
index 3e897419..dee6722b 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1113,7 +1113,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `b:
- c: {}
+ c: thing
d: another thing
`
test.AssertResult(t, expectedOutput, result.Output)
From 1aa5ec1d40d5f5cb7d5b443ed5e8aecffabcfc5e Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 5 Jan 2020 17:14:14 +1300
Subject: [PATCH 39/68] Merge! wip
---
commands_test.go | 31 ++++++++++-----
examples/data1.yaml | 2 +
examples/sample2.yaml | 10 +----
pkg/yqlib/lib.go | 14 ++++---
pkg/yqlib/update_navigation_strategy.go | 37 +++++++++---------
yq.go | 50 ++++++++++++++++++-------
6 files changed, 88 insertions(+), 56 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index dee6722b..4f73533a 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1297,32 +1297,45 @@ something: else`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
-func xTestMergeCmd(t *testing.T) {
+func TestMergeCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: simple
-b:
-- 1
-- 2
+b: [1, 2]
+c:
+ test: 1
+ toast: leave
+ tell: 1
+ taco: cool
+`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
+func TestMergeNoAutoCreateCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "merge -c=false examples/data1.yaml examples/data2.yaml")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `a: simple
+b: [1, 2]
c:
test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
-func xTestMergeOverwriteCmd(t *testing.T) {
+func TestMergeOverwriteCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "merge --overwrite examples/data1.yaml examples/data2.yaml")
+ result := test.RunCmd(cmd, "merge -c=false --overwrite examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: other
-b:
-- 3
-- 4
+b: [3, 4]
c:
test: 1
`
diff --git a/examples/data1.yaml b/examples/data1.yaml
index c9ad78b4..b0f017b1 100644
--- a/examples/data1.yaml
+++ b/examples/data1.yaml
@@ -1,2 +1,4 @@
a: simple
b: [1, 2]
+c:
+ test: 1
diff --git a/examples/sample2.yaml b/examples/sample2.yaml
index e16e0b69..3df90f8b 100644
--- a/examples/sample2.yaml
+++ b/examples/sample2.yaml
@@ -1,10 +1,2 @@
-a: Easy! as one two three
b:
- c: things
- d: whatever
-things:
- borg: snorg
- thing1:
- cat: 'fred'
- thing2:
- cat: 'sam'
+ c: things
\ No newline at end of file
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 3f7737a6..dbf19183 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -12,10 +12,12 @@ import (
var log = logging.MustGetLogger("yq")
+// TODO: enumerate
type UpdateCommand struct {
- Command string
- Path string
- Value *yaml.Node
+ Command string
+ Path string
+ Value *yaml.Node
+ Overwrite bool
}
func DebugNode(value *yaml.Node) {
@@ -76,7 +78,7 @@ func guessKind(head string, tail []string, guess yaml.Kind) yaml.Kind {
type YqLib interface {
Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
- Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
+ Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
New(path string) yaml.Node
}
@@ -106,12 +108,12 @@ func (l *lib) New(path string) yaml.Node {
return newNode
}
-func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {
+func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error {
log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
switch updateCommand.Command {
case "update":
var paths = l.parser.ParsePath(updateCommand.Path)
- navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand.Value))
+ navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand, autoCreate))
return navigator.Traverse(rootNode, paths)
case "delete":
var paths = l.parser.ParsePath(updateCommand.Path)
diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go
index 281a4033..8eea9069 100644
--- a/pkg/yqlib/update_navigation_strategy.go
+++ b/pkg/yqlib/update_navigation_strategy.go
@@ -1,32 +1,33 @@
package yqlib
-import (
- yaml "gopkg.in/yaml.v3"
-)
-
-func UpdateNavigationStrategy(changesToApply *yaml.Node) NavigationStrategy {
+func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
followAlias: func(nodeContext NodeContext) bool {
return false
},
autoCreateMap: func(nodeContext NodeContext) bool {
- return true
+ return autoCreate
},
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
- log.Debug("going to update")
- DebugNode(node)
- log.Debug("with")
- DebugNode(changesToApply)
- node.Value = changesToApply.Value
- node.Tag = changesToApply.Tag
- node.Kind = changesToApply.Kind
- node.Style = changesToApply.Style
- node.Content = changesToApply.Content
- node.HeadComment = changesToApply.HeadComment
- node.LineComment = changesToApply.LineComment
- node.FootComment = changesToApply.FootComment
+ changesToApply := updateCommand.Value
+ if updateCommand.Overwrite == true || node.Value == "" {
+ log.Debug("going to update")
+ DebugNode(node)
+ log.Debug("with")
+ DebugNode(changesToApply)
+ node.Value = changesToApply.Value
+ node.Tag = changesToApply.Tag
+ node.Kind = changesToApply.Kind
+ node.Style = changesToApply.Style
+ node.Content = changesToApply.Content
+ node.HeadComment = changesToApply.HeadComment
+ node.LineComment = changesToApply.LineComment
+ node.FootComment = changesToApply.FootComment
+ } else {
+ log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite)
+ }
return nil
},
}
diff --git a/yq.go b/yq.go
index 59bb74a2..22cace4c 100644
--- a/yq.go
+++ b/yq.go
@@ -22,6 +22,7 @@ var printMode = "v"
var writeInplace = false
var writeScript = ""
var overwriteFlag = false
+var autoCreateFlag = true
var allowEmptyFlag = false
var appendFlag = false
var verbose = false
@@ -235,7 +236,8 @@ Note that if you set both flags only overwrite will take effect.
RunE: mergeProperties,
}
cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
- // cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
+ cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
+ cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries")
// cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
// cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
@@ -256,10 +258,20 @@ func readProperty(cmd *cobra.Command, args []string) error {
return errorParsingDocIndex
}
+ matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt)
+
+ if errorReadingStream != nil {
+ return errorReadingStream
+ }
+
+ return printResults(matchingNodes, cmd)
+}
+
+func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) {
var matchingNodes []*yqlib.NodeContext
var currentIndex = 0
- var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
+ var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error {
for {
var dataBucket yaml.Node
errorReading := decoder.Decode(&dataBucket)
@@ -275,12 +287,7 @@ func readProperty(cmd *cobra.Command, args []string) error {
currentIndex = currentIndex + 1
}
})
-
- if errorReadingStream != nil {
- return errorReadingStream
- }
-
- return printResults(matchingNodes, cmd)
+ return matchingNodes, errorReadingStream
}
func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
@@ -419,7 +426,21 @@ func writeProperty(cmd *cobra.Command, args []string) error {
func mergeProperties(cmd *cobra.Command, args []string) error {
// first generate update commands from the file
- return nil
+ var filesToMerge = args[1:]
+ var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
+
+ for _, fileToMerge := range filesToMerge {
+ matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0)
+ if errorProcessingFile != nil {
+ return errorProcessingFile
+ }
+ for _, matchingNode := range matchingNodes {
+ mergePath := yqlib.PathStackToString(matchingNode.PathStack)
+ updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag})
+ }
+ }
+
+ return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
}
func newProperty(cmd *cobra.Command, args []string) error {
@@ -431,7 +452,7 @@ func newProperty(cmd *cobra.Command, args []string) error {
for _, updateCommand := range updateCommands {
- errorUpdating := lib.Update(&newNode, updateCommand)
+ errorUpdating := lib.Update(&newNode, updateCommand, true)
if errorUpdating != nil {
return errorUpdating
@@ -474,7 +495,7 @@ func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucke
newNode := lib.New(updateCommand.Path)
dataBucket.Content[0] = &newNode
- errorUpdating := lib.Update(dataBucket, updateCommand)
+ errorUpdating := lib.Update(dataBucket, updateCommand, true)
if errorUpdating != nil {
return errorUpdating
}
@@ -502,7 +523,8 @@ func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io
if updateAll || currentIndex == docIndexInt {
log.Debugf("Updating doc %v", currentIndex)
for _, updateCommand := range updateCommands {
- errorUpdating := lib.Update(dataBucket, updateCommand)
+ log.Debugf("Processing update to Path %v", updateCommand.Path)
+ errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag)
if errorUpdating != nil {
return errorUpdating
}
@@ -605,7 +627,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
log.Debugf("Read write commands file '%v'", parsedCommands)
for index := range parsedCommands {
parsedCommand := parsedCommands[index]
- updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value}
+ updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true}
updateCommands = append(updateCommands, updateCommand)
}
@@ -617,7 +639,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
log.Debug("args %v", args)
log.Debug("path %v", args[expectedArgs-2])
log.Debug("Value %v", args[expectedArgs-1])
- updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag)}
+ updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag), Overwrite: true}
}
return updateCommands, nil
}
From 1f7f1b0defeb8e10fafb44657387cc556025d9e9 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sun, 5 Jan 2020 17:28:24 +1300
Subject: [PATCH 40/68] Merge arrays!
---
Upgrade Notes | 10 ++++++++++
commands_test.go | 18 +++++-------------
examples/sample_array_2.yaml | 3 ++-
pkg/yqlib/lib.go | 11 ++++++++++-
yq.go | 4 ++--
5 files changed, 29 insertions(+), 17 deletions(-)
create mode 100644 Upgrade Notes
diff --git a/Upgrade Notes b/Upgrade Notes
new file mode 100644
index 00000000..153ebf50
--- /dev/null
+++ b/Upgrade Notes
@@ -0,0 +1,10 @@
+# New Features
+ - Keeps comments and formatting (e.g. inline arrays)!
+ - Handles anchors!
+ - Can specify yaml tags (e.g. !!int)
+
+# Update scripts file format has changed
+
+# Merge command
+- autocreates missing entries in target by default, new flag to turn that off.
+
diff --git a/commands_test.go b/commands_test.go
index 4f73533a..91b9cffe 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1342,34 +1342,26 @@ c:
test.AssertResult(t, expectedOutput, result.Output)
}
-func xTestMergeAppendCmd(t *testing.T) {
+func TestMergeAppendCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "merge --append examples/data1.yaml examples/data2.yaml")
+ result := test.RunCmd(cmd, "merge --autocreate=false --append examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: simple
-b:
-- 1
-- 2
-- 3
-- 4
+b: [1, 2, 3, 4]
c:
test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
-func xTestMergeArraysCmd(t *testing.T) {
+func TestMergeArraysCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `- 1
-- 2
-- 3
-- 4
-- 5
+ expectedOutput := `[1, 2, 3, 4, 5]
`
test.AssertResult(t, expectedOutput, result.Output)
}
diff --git a/examples/sample_array_2.yaml b/examples/sample_array_2.yaml
index b32037f9..e83ee7a1 100644
--- a/examples/sample_array_2.yaml
+++ b/examples/sample_array_2.yaml
@@ -1 +1,2 @@
-[4,5]
\ No newline at end of file
+- 4
+- 5
\ No newline at end of file
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index dbf19183..18689815 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -34,11 +34,20 @@ func DebugNode(value *yaml.Node) {
}
func PathStackToString(pathStack []interface{}) string {
+ return MergePathStackToString(pathStack, false)
+}
+
+func MergePathStackToString(pathStack []interface{}, appendArrays bool) string {
var sb strings.Builder
for index, path := range pathStack {
switch path.(type) {
case int:
- sb.WriteString(fmt.Sprintf("[%v]", path))
+ if appendArrays {
+ sb.WriteString("[+]")
+ } else {
+ sb.WriteString(fmt.Sprintf("[%v]", path))
+ }
+
default:
sb.WriteString(fmt.Sprintf("%v", path))
}
diff --git a/yq.go b/yq.go
index 22cace4c..a1333791 100644
--- a/yq.go
+++ b/yq.go
@@ -238,7 +238,7 @@ Note that if you set both flags only overwrite will take effect.
cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries")
- // cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
+ cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
// cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdMerge
@@ -435,7 +435,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
return errorProcessingFile
}
for _, matchingNode := range matchingNodes {
- mergePath := yqlib.PathStackToString(matchingNode.PathStack)
+ mergePath := yqlib.MergePathStackToString(matchingNode.PathStack, appendFlag)
updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag})
}
}
From 690da9ee7487e35bdf6a39305319558b00a0a2da Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 6 Jan 2020 10:12:30 +1300
Subject: [PATCH 41/68] Fixed merge new array
---
Upgrade Notes | 5 +++++
commands_test.go | 31 ++++++++++++++++++++++---------
examples/data1.yaml | 2 +-
examples/data2.yaml | 2 +-
pkg/yqlib/data_navigator.go | 6 ++++++
5 files changed, 35 insertions(+), 11 deletions(-)
diff --git a/Upgrade Notes b/Upgrade Notes
index 153ebf50..8567039b 100644
--- a/Upgrade Notes
+++ b/Upgrade Notes
@@ -1,3 +1,8 @@
+# Update doco / notes
+ - --autocreate=false to turn off default flags
+ - add comments to test yaml to ensure they are kept on updating.
+ - update built in command notes to includes quotes around path args.
+
# New Features
- Keeps comments and formatting (e.g. inline arrays)!
- Handles anchors!
diff --git a/commands_test.go b/commands_test.go
index 91b9cffe..6e4c5271 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -807,6 +807,18 @@ func TestNewCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
+func TestNewArrayCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "new b[0] 3")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `b:
+- 3
+`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
func TestNewCmd_Error(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c")
@@ -1334,7 +1346,7 @@ func TestMergeOverwriteCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `a: other
+ expectedOutput := `a: other # better than the original
b: [3, 4]
c:
test: 1
@@ -1348,7 +1360,7 @@ func TestMergeAppendCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `a: simple
+ expectedOutput := `a: simple # just the best
b: [1, 2, 3, 4]
c:
test: 1
@@ -1366,26 +1378,27 @@ func TestMergeArraysCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
-func xTestMergeCmd_Multi(t *testing.T) {
+func TestMergeCmd_Multi(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml")
+ result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data1.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: Easy! as one two three
---
-a: other
another:
document: here
+a: simple # just the best
b:
-- 3
-- 4
+- 1
+- 2
c:
test: 1
---
- 1
-- 2`
- test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
+- 2
+`
+ test.AssertResult(t, expectedOutput, result.Output)
}
func xTestMergeYamlMultiAllCmd(t *testing.T) {
diff --git a/examples/data1.yaml b/examples/data1.yaml
index b0f017b1..6b87c47f 100644
--- a/examples/data1.yaml
+++ b/examples/data1.yaml
@@ -1,4 +1,4 @@
-a: simple
+a: simple # just the best
b: [1, 2]
c:
test: 1
diff --git a/examples/data2.yaml b/examples/data2.yaml
index 93764e94..07088cc1 100644
--- a/examples/data2.yaml
+++ b/examples/data2.yaml
@@ -1,4 +1,4 @@
-a: other
+a: other # better than the original
b: [3, 4]
c:
toast: leave
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 835c924b..86a89a21 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -234,7 +234,13 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, p
if err != nil {
return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, PathStackToString(pathStack))
}
+
+ for int64(len(value.Content)) <= index {
+ value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)})
+ }
+
if index >= int64(len(value.Content)) {
+ log.Debug("index longer than array length, aborting!")
return nil
}
value.Content[index] = n.getOrReplace(value.Content[index], guessKind(head, tail, value.Content[index].Kind))
From e3f4eedd5183b08ab39e24d6b26e90bc7c1de289 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 6 Jan 2020 10:12:38 +1300
Subject: [PATCH 42/68] Fixed merge new array
---
pkg/yqlib/data_navigator.go | 4 ----
1 file changed, 4 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 86a89a21..2a0ec80c 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -239,10 +239,6 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, p
value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)})
}
- if index >= int64(len(value.Content)) {
- log.Debug("index longer than array length, aborting!")
- return nil
- }
value.Content[index] = n.getOrReplace(value.Content[index], guessKind(head, tail, value.Content[index].Kind))
return n.doTraverse(value.Content[index], head, tail, append(pathStack, index))
From d8c29b26c1b5d6cf82e5e9634a00b04f8b733ef5 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 6 Jan 2020 16:22:24 +1300
Subject: [PATCH 43/68] Merge can allow empty merges!
---
commands_test.go | 119 +++++++++++++++++------------------------------
yq.go | 52 +++------------------
2 files changed, 49 insertions(+), 122 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 6e4c5271..c310d59e 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -243,7 +243,7 @@ func TestReadBadDocumentIndexCmd(t *testing.T) {
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
- expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
+ expectedOutput := `Could not process document index 1 as there are only 1 document(s)`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
@@ -829,18 +829,6 @@ func TestNewCmd_Error(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error())
}
-func TestNewCmd_Verbose(t *testing.T) {
- cmd := getRootCommand()
- result := test.RunCmd(cmd, "-v new b.c 3")
- if result.Error != nil {
- t.Error(result.Error)
- }
- expectedOutput := `b:
- c: 3
-`
- test.AssertResult(t, expectedOutput, result.Output)
-}
-
func TestWriteCmd(t *testing.T) {
content := `b:
c: 3
@@ -998,24 +986,6 @@ func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error())
}
-func TestWriteCmd_Verbose(t *testing.T) {
- content := `b:
- c: 3
-`
- filename := test.WriteTempYamlFile(content)
- defer test.RemoveTempYamlFile(filename)
-
- cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("-v write %s b.c 7", filename))
- if result.Error != nil {
- t.Error(result.Error)
- }
- expectedOutput := `b:
- c: 7
-`
- test.AssertResult(t, expectedOutput, result.Output)
-}
-
func TestWriteCmd_Inplace(t *testing.T) {
content := `b:
c: 3
@@ -1193,7 +1163,7 @@ b:
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.hi[*].thing", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1224,7 +1194,7 @@ b:
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.there*.c", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1315,7 +1285,7 @@ func TestMergeCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `a: simple
+ expectedOutput := `a: simple # just the best
b: [1, 2]
c:
test: 1
@@ -1332,7 +1302,7 @@ func TestMergeNoAutoCreateCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `a: simple
+ expectedOutput := `a: simple # just the best
b: [1, 2]
c:
test: 1
@@ -1401,7 +1371,7 @@ c:
test.AssertResult(t, expectedOutput, result.Output)
}
-func xTestMergeYamlMultiAllCmd(t *testing.T) {
+func TestMergeYamlMultiAllCmd(t *testing.T) {
content := `b:
c: 3
apples: green
@@ -1420,17 +1390,18 @@ something: good`
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `apples: green
-b:
+ expectedOutput := `b:
c: 3
+apples: green
something: good
---
+something: else
apples: red
-something: else`
- test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
+`
+ test.AssertResult(t, expectedOutput, result.Output)
}
-func xTestMergeYamlMultiAllOverwriteCmd(t *testing.T) {
+func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) {
content := `b:
c: 3
apples: green
@@ -1449,17 +1420,18 @@ something: good`
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `apples: red
-b:
+ expectedOutput := `b:
c: 3
+apples: red
something: good
---
+something: good
apples: red
-something: good`
- test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
+`
+ test.AssertResult(t, expectedOutput, result.Output)
}
-func xTestMergeCmd_Error(t *testing.T) {
+func TestMergeCmd_Error(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge examples/data1.yaml")
if result.Error == nil {
@@ -1469,7 +1441,7 @@ func xTestMergeCmd_Error(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error())
}
-func xTestMergeCmd_ErrorUnreadableFile(t *testing.T) {
+func TestMergeCmd_ErrorUnreadableFile(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge examples/data1.yaml fake-unknown")
if result.Error == nil {
@@ -1477,30 +1449,14 @@ func xTestMergeCmd_ErrorUnreadableFile(t *testing.T) {
}
var expectedOutput string
if runtime.GOOS == "windows" {
- expectedOutput = `Error updating document at index 0: open fake-unknown: The system cannot find the file specified.`
+ expectedOutput = `open fake-unknown: The system cannot find the file specified.`
} else {
- expectedOutput = `Error updating document at index 0: open fake-unknown: no such file or directory`
+ expectedOutput = `open fake-unknown: no such file or directory`
}
test.AssertResult(t, expectedOutput, result.Error.Error())
}
-func xTestMergeCmd_Verbose(t *testing.T) {
- cmd := getRootCommand()
- result := test.RunCmd(cmd, "-v merge examples/data1.yaml examples/data2.yaml")
- if result.Error != nil {
- t.Error(result.Error)
- }
- expectedOutput := `a: simple
-b:
-- 1
-- 2
-c:
- test: 1
-`
- test.AssertResult(t, expectedOutput, result.Output)
-}
-
-func xTestMergeCmd_Inplace(t *testing.T) {
+func TestMergeCmd_Inplace(t *testing.T) {
filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml"))
err := os.Chmod(filename, os.FileMode(int(0666)))
if err != nil {
@@ -1515,26 +1471,35 @@ func xTestMergeCmd_Inplace(t *testing.T) {
}
info, _ := os.Stat(filename)
gotOutput := test.ReadTempYamlFile(filename)
- expectedOutput := `a: simple
-b:
-- 1
-- 2
+ expectedOutput := `a: simple # just the best
+b: [1, 2]
c:
- test: 1`
- test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
+ test: 1
+ toast: leave
+ tell: 1
+ taco: cool
+`
+ test.AssertResult(t, expectedOutput, gotOutput)
test.AssertResult(t, os.FileMode(int(0666)), info.Mode())
}
-func xTestMergeAllowEmptyCmd(t *testing.T) {
+func TestMergeAllowEmptyCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --allow-empty examples/data1.yaml examples/empty.yaml")
if result.Error != nil {
t.Error(result.Error)
}
- expectedOutput := `a: simple
-b:
-- 1
-- 2
+ expectedOutput := `a: simple # just the best
+b: [1, 2]
+c:
+ test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
+
+func TestMergeDontAllowEmptyCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "merge examples/data1.yaml examples/empty.yaml")
+ expectedOutput := `Could not process document index 0 as there are only 0 document(s)`
+ test.AssertResult(t, expectedOutput, result.Error.Error())
+}
diff --git a/yq.go b/yq.go
index a1333791..adfd05f6 100644
--- a/yq.go
+++ b/yq.go
@@ -7,6 +7,7 @@ import (
"io/ioutil"
"os"
"strconv"
+ "strings"
"github.com/mikefarah/yq/v3/pkg/yqlib"
@@ -239,7 +240,7 @@ Note that if you set both flags only overwrite will take effect.
cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries")
cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
- // cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
+ cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdMerge
}
@@ -293,7 +294,7 @@ func readYamlFile(filename string, path string, updateAll bool, docIndexInt int)
func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
log.Debugf("done %v / %v", currentIndex, docIndexInt)
if !updateAll && currentIndex <= docIndexInt {
- return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex)
+ return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex)
}
return nil
}
@@ -425,13 +426,16 @@ func writeProperty(cmd *cobra.Command, args []string) error {
}
func mergeProperties(cmd *cobra.Command, args []string) error {
+ if len(args) < 2 {
+ return errors.New("Must provide at least 2 yaml files")
+ }
// first generate update commands from the file
var filesToMerge = args[1:]
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
for _, fileToMerge := range filesToMerge {
matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0)
- if errorProcessingFile != nil {
+ if errorProcessingFile != nil && (allowEmptyFlag == false || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) {
return errorProcessingFile
}
for _, matchingNode := range matchingNodes {
@@ -569,48 +573,6 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
return readStream(inputFile, mapYamlDecoder(updateData, encoder))
}
-// func mergeProperties(cmd *cobra.Command, args []string) error {
-// if len(args) < 2 {
-// return errors.New("Must provide at least 2 yaml files")
-// }
-// var input = args[0]
-// var filesToMerge = args[1:]
-// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
-// if errorParsingDocIndex != nil {
-// return errorParsingDocIndex
-// }
-
-// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
-// if updateAll || currentIndex == docIndexInt {
-// log.Debugf("Merging doc %v", currentIndex)
-// var mergedData map[interface{}]interface{}
-// // merge only works for maps, so put everything in a temporary
-// // map
-// var mapDataBucket = make(map[interface{}]interface{})
-// mapDataBucket["root"] = dataBucket
-// if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
-// return nil, err
-// }
-// for _, f := range filesToMerge {
-// var fileToMerge interface{}
-// if err := readData(f, 0, &fileToMerge); err != nil {
-// if allowEmptyFlag && err == io.EOF {
-// continue
-// }
-// return nil, err
-// }
-// mapDataBucket["root"] = fileToMerge
-// if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
-// return nil, err
-// }
-// }
-// return mergedData["root"], nil
-// }
-// return dataBucket, nil
-// }
-// return readAndUpdate(cmd.OutOrStdout(), input, updateData)
-// }
-
type updateCommandParsed struct {
Command string
Path string
From 38d35185bc09d0046cf440ab380511cda79d8b77 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 6 Jan 2020 16:27:00 +1300
Subject: [PATCH 44/68] Can overwrite and append with merge
---
commands_test.go | 15 +++++++++++++++
yq.go | 2 --
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index c310d59e..9f600f38 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1337,6 +1337,21 @@ c:
`
test.AssertResult(t, expectedOutput, result.Output)
}
+
+func TestMergeOverwriteAndAppendCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite examples/data1.yaml examples/data2.yaml")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `a: other # better than the original
+b: [1, 2, 3, 4]
+c:
+ test: 1
+`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
func TestMergeArraysCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml")
diff --git a/yq.go b/yq.go
index adfd05f6..053b4a98 100644
--- a/yq.go
+++ b/yq.go
@@ -231,8 +231,6 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd
If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
-
-Note that if you set both flags only overwrite will take effect.
`,
RunE: mergeProperties,
}
From 52eef67e37323eabe36801632df56d6c4b6c8d7e Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Thu, 9 Jan 2020 08:17:56 +1100
Subject: [PATCH 45/68] more tests, some refactoring
---
.gitignore | 1 +
Upgrade Notes | 2 +-
pkg/yqlib/data_navigator_test.go | 396 -------------------------------
pkg/yqlib/lib.go | 2 +-
pkg/yqlib/lib_test.go | 264 ++++++++++-----------
pkg/yqlib/path_parser_test.go | 53 ++++-
pkg/yqlib/value_parser.go | 12 +-
pkg/yqlib/value_parser_test.go | 29 ++-
scripts/coverage.sh | 4 +-
scripts/test.sh | 2 +-
yq.go | 5 +-
11 files changed, 215 insertions(+), 555 deletions(-)
diff --git a/.gitignore b/.gitignore
index 69118014..c9064038 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ _cgo_export.*
_testmain.go
coverage.out
+coverage.html
*.exe
*.test
*.prof
diff --git a/Upgrade Notes b/Upgrade Notes
index 8567039b..1b7bb513 100644
--- a/Upgrade Notes
+++ b/Upgrade Notes
@@ -6,7 +6,7 @@
# New Features
- Keeps comments and formatting (e.g. inline arrays)!
- Handles anchors!
- - Can specify yaml tags (e.g. !!int)
+ - Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead.
# Update scripts file format has changed
diff --git a/pkg/yqlib/data_navigator_test.go b/pkg/yqlib/data_navigator_test.go
index d537d665..88c44e97 100644
--- a/pkg/yqlib/data_navigator_test.go
+++ b/pkg/yqlib/data_navigator_test.go
@@ -1,397 +1 @@
package yqlib
-
-// import (
-// "fmt"
-// "sort"
-// "testing"
-
-// "github.com/mikefarah/yq/v2/test"
-// logging "gopkg.in/op/go-logging.v1"
-// )
-
-// func TestDataNavigator(t *testing.T) {
-// var log = logging.MustGetLogger("yq")
-// subject := NewDataNavigator(log)
-
-// t.Run("TestReadMap_simple", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// b:
-// c: 2
-// `)
-// got, _ := subject.ReadChildValue(data, []string{"b", "c"})
-// test.AssertResult(t, 2, got)
-// })
-
-// t.Run("TestReadMap_numberKey", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// 200: things
-// `)
-// got, _ := subject.ReadChildValue(data, []string{"200"})
-// test.AssertResult(t, "things", got)
-// })
-
-// t.Run("TestReadMap_splat", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// mapSplat:
-// item1: things
-// item2: whatever
-// otherThing: cat
-// `)
-// res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"})
-// test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res))
-// })
-
-// t.Run("TestReadMap_prefixSplat", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// mapSplat:
-// item1: things
-// item2: whatever
-// otherThing: cat
-// `)
-// res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"})
-// test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res))
-// })
-
-// t.Run("TestReadMap_deep_splat", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// mapSplatDeep:
-// item1:
-// cats: bananas
-// item2:
-// cats: apples
-// `)
-
-// res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"})
-// result := res.([]interface{})
-// var actual = []string{result[0].(string), result[1].(string)}
-// sort.Strings(actual)
-// test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual))
-// })
-
-// t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// b:
-// c: 2
-// `)
-// got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"})
-// test.AssertResult(t, nil, got)
-// })
-
-// t.Run("TestReadMap_recurse_against_string", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// a: cat
-// `)
-// got, _ := subject.ReadChildValue(data, []string{"a", "b"})
-// test.AssertResult(t, nil, got)
-// })
-
-// t.Run("TestReadMap_with_array", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// b:
-// d:
-// - 3
-// - 4
-// `)
-// got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"})
-// test.AssertResult(t, 4, got)
-// })
-
-// t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// b:
-// d:
-// - 3
-// - 4
-// `)
-// _, err := subject.ReadChildValue(data, []string{"b", "d", "x"})
-// if err == nil {
-// t.Fatal("Expected error due to invalid path")
-// }
-// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
-// test.AssertResult(t, expectedOutput, err.Error())
-// })
-
-// t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// b:
-// d:
-// e:
-// - 3
-// - 4
-// f:
-// - 1
-// - 2
-// `)
-// _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"})
-// if err == nil {
-// t.Fatal("Expected error due to invalid path")
-// }
-// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
-// test.AssertResult(t, expectedOutput, err.Error())
-// })
-
-// t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// b:
-// d:
-// - names:
-// - fred
-// - smith
-// - names:
-// - sam
-// - bo
-// `)
-// _, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"})
-// if err == nil {
-// t.Fatal("Expected error due to invalid path")
-// }
-// expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
-// test.AssertResult(t, expectedOutput, err.Error())
-// })
-
-// t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// b:
-// d:
-// - 3
-// - 4
-// `)
-// got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"})
-// test.AssertResult(t, nil, got)
-// })
-
-// t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) {
-// var data = test.ParseData(`
-// ---
-// b:
-// d:
-// - 3
-// - 4
-// `)
-// got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"})
-// test.AssertResult(t, nil, got)
-// })
-
-// t.Run("TestReadMap_with_array_splat", func(t *testing.T) {
-// var data = test.ParseData(`
-// e:
-// -
-// name: Fred
-// thing: cat
-// -
-// name: Sam
-// thing: dog
-// `)
-// got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"})
-// test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got))
-// })
-
-// t.Run("TestWrite_really_simple", func(t *testing.T) {
-// var data = test.ParseData(`
-// b: 2
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b"}, "4")
-// test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_simple", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// c: 2
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4")
-// test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_new", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// c: 2
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4")
-// test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_new_deep", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// c: 2
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4")
-// test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_array", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// - aa
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "bb")
-
-// test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_new_array", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// c: 2
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4")
-// test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_new_array_deep", func(t *testing.T) {
-// var data = test.ParseData(`
-// a: apple
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4")
-// test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_new_map_array_deep", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// c: 2
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4")
-// test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_add_to_array", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// - aa
-// `)
-
-// updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb")
-// test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWrite_with_no_tail", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// c: 2
-// `)
-// updated := subject.UpdatedChildValue(data, []string{"b"}, "4")
-
-// test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated))
-// })
-
-// t.Run("TestWriteMap_no_paths", func(t *testing.T) {
-// var data = test.ParseData(`
-// b: 5
-// `)
-// var new = test.ParseData(`
-// c: 4
-// `)
-// result := subject.UpdatedChildValue(data, []string{}, new)
-// test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
-// })
-
-// t.Run("TestWriteArray_no_paths", func(t *testing.T) {
-// var data = make([]interface{}, 1)
-// data[0] = "mike"
-// var new = test.ParseData(`
-// c: 4
-// `)
-// result := subject.UpdatedChildValue(data, []string{}, new)
-// test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
-// })
-
-// t.Run("TestDelete_MapItem", func(t *testing.T) {
-// var data = test.ParseData(`
-// a: 123
-// b: 456
-// `)
-// var expected = test.ParseData(`
-// b: 456
-// `)
-
-// result, _ := subject.DeleteChildValue(data, []string{"a"})
-// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
-// })
-
-// // Ensure deleting an index into a string does nothing
-// t.Run("TestDelete_index_to_string", func(t *testing.T) {
-// var data = test.ParseData(`
-// a: mystring
-// `)
-// result, _ := subject.DeleteChildValue(data, []string{"a", "0"})
-// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
-// })
-
-// t.Run("TestDelete_list_index", func(t *testing.T) {
-// var data = test.ParseData(`
-// a: [3, 4]
-// `)
-// var expected = test.ParseData(`
-// a: [3]
-// `)
-// result, _ := subject.DeleteChildValue(data, []string{"a", "1"})
-// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
-// })
-
-// t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) {
-// var data = test.ParseData(`
-// a: [3, 4]
-// `)
-// result, _ := subject.DeleteChildValue(data, []string{"a", "5"})
-// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
-// })
-
-// t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) {
-// var data = test.ParseData(`
-// a: [3, 4]
-// `)
-// result, _ := subject.DeleteChildValue(data, []string{"a", "2"})
-// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
-// })
-
-// t.Run("TestDelete_no_paths", func(t *testing.T) {
-// var data = test.ParseData(`
-// a: [3, 4]
-// b:
-// - name: test
-// `)
-// result, _ := subject.DeleteChildValue(data, []string{})
-// test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
-// })
-
-// t.Run("TestDelete_array_map_item", func(t *testing.T) {
-// var data = test.ParseData(`
-// b:
-// - name: fred
-// value: blah
-// - name: john
-// value: test
-// `)
-// var expected = test.ParseData(`
-// b:
-// - value: blah
-// - name: john
-// value: test
-// `)
-// result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"})
-// test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
-// })
-// }
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 18689815..1675e60f 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -96,7 +96,7 @@ type lib struct {
parser PathParser
}
-func NewYqLib(l *logging.Logger) YqLib {
+func NewYqLib() YqLib {
return &lib{
parser: NewPathParser(),
}
diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go
index b9c3e4de..77411470 100644
--- a/pkg/yqlib/lib_test.go
+++ b/pkg/yqlib/lib_test.go
@@ -1,166 +1,162 @@
package yqlib
import (
- "fmt"
"testing"
-
- "github.com/mikefarah/yq/v3/test"
- logging "gopkg.in/op/go-logging.v1"
)
func TestLib(t *testing.T) {
- var log = logging.MustGetLogger("yq")
- subject := NewYqLib(log)
+ // var log = logging.MustGetLogger("yq")
+ // subject := NewYqLib(log)
- t.Run("TestReadPath", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- 2: c
-`)
+ // t.Run("TestReadPath", func(t *testing.T) {
+ // var data = test.ParseData(`
+ // ---
+ // b:
+ // 2: c
+ // `)
- got, _ := subject.ReadPath(data, "b.2")
- test.AssertResult(t, `c`, got)
- })
+ // got, _ := subject.ReadPath(data, "b.2")
+ // test.AssertResult(t, `c`, got)
+ // })
- t.Run("TestReadPath_WithError", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- - c
-`)
+ // t.Run("TestReadPath_WithError", func(t *testing.T) {
+ // var data = test.ParseData(`
+ // ---
+ // b:
+ // - c
+ // `)
- _, err := subject.ReadPath(data, "b.[a]")
- if err == nil {
- t.Fatal("Expected error due to invalid path")
- }
- })
+ // _, err := subject.ReadPath(data, "b.[a]")
+ // if err == nil {
+ // t.Fatal("Expected error due to invalid path")
+ // }
+ // })
- t.Run("TestWritePath", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- 2: c
-`)
+ // t.Run("TestWritePath", func(t *testing.T) {
+ // var data = test.ParseData(`
+ // ---
+ // b:
+ // 2: c
+ // `)
- got := subject.WritePath(data, "b.3", "a")
- test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got))
- })
+ // got := subject.WritePath(data, "b.3", "a")
+ // test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got))
+ // })
- t.Run("TestPrefixPath", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- 2: c
-`)
+ // t.Run("TestPrefixPath", func(t *testing.T) {
+ // var data = test.ParseData(`
+ // ---
+ // b:
+ // 2: c
+ // `)
- got := subject.PrefixPath(data, "a.d")
- test.AssertResult(t, `[{a [{d [{b [{2 c}]}]}]}]`, fmt.Sprintf("%v", got))
- })
+ // got := subject.PrefixPath(data, "a.d")
+ // test.AssertResult(t, `[{a [{d [{b [{2 c}]}]}]}]`, fmt.Sprintf("%v", got))
+ // })
- t.Run("TestDeletePath", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- 2: c
- 3: a
-`)
+ // t.Run("TestDeletePath", func(t *testing.T) {
+ // var data = test.ParseData(`
+ // ---
+ // b:
+ // 2: c
+ // 3: a
+ // `)
- got, _ := subject.DeletePath(data, "b.2")
- test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got))
- })
+ // got, _ := subject.DeletePath(data, "b.2")
+ // test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got))
+ // })
- t.Run("TestDeletePath_WithError", func(t *testing.T) {
- var data = test.ParseData(`
----
-b:
- - c
-`)
+ // t.Run("TestDeletePath_WithError", func(t *testing.T) {
+ // var data = test.ParseData(`
+ // ---
+ // b:
+ // - c
+ // `)
- _, err := subject.DeletePath(data, "b.[a]")
- if err == nil {
- t.Fatal("Expected error due to invalid path")
- }
- })
+ // _, err := subject.DeletePath(data, "b.[a]")
+ // if err == nil {
+ // t.Fatal("Expected error due to invalid path")
+ // }
+ // })
- t.Run("TestMerge", func(t *testing.T) {
- var dst = test.ParseData(`
----
-a: b
-c: d
-`)
- var src = test.ParseData(`
----
-a: 1
-b: 2
-`)
+ // t.Run("TestMerge", func(t *testing.T) {
+ // var dst = test.ParseData(`
+ // ---
+ // a: b
+ // c: d
+ // `)
+ // var src = test.ParseData(`
+ // ---
+ // a: 1
+ // b: 2
+ // `)
- var mergedData = make(map[interface{}]interface{})
- mergedData["root"] = dst
- var mapDataBucket = make(map[interface{}]interface{})
- mapDataBucket["root"] = src
+ // var mergedData = make(map[interface{}]interface{})
+ // mergedData["root"] = dst
+ // var mapDataBucket = make(map[interface{}]interface{})
+ // mapDataBucket["root"] = src
- err := subject.Merge(&mergedData, mapDataBucket, false, false)
- if err != nil {
- t.Fatal("Unexpected error")
- }
- test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"]))
- })
+ // err := subject.Merge(&mergedData, mapDataBucket, false, false)
+ // if err != nil {
+ // t.Fatal("Unexpected error")
+ // }
+ // test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"]))
+ // })
- t.Run("TestMerge_WithOverwrite", func(t *testing.T) {
- var dst = test.ParseData(`
----
-a: b
-c: d
-`)
- var src = test.ParseData(`
----
-a: 1
-b: 2
-`)
+ // t.Run("TestMerge_WithOverwrite", func(t *testing.T) {
+ // var dst = test.ParseData(`
+ // ---
+ // a: b
+ // c: d
+ // `)
+ // var src = test.ParseData(`
+ // ---
+ // a: 1
+ // b: 2
+ // `)
- var mergedData = make(map[interface{}]interface{})
- mergedData["root"] = dst
- var mapDataBucket = make(map[interface{}]interface{})
- mapDataBucket["root"] = src
+ // var mergedData = make(map[interface{}]interface{})
+ // mergedData["root"] = dst
+ // var mapDataBucket = make(map[interface{}]interface{})
+ // mapDataBucket["root"] = src
- err := subject.Merge(&mergedData, mapDataBucket, true, false)
- if err != nil {
- t.Fatal("Unexpected error")
- }
- test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
- })
+ // err := subject.Merge(&mergedData, mapDataBucket, true, false)
+ // if err != nil {
+ // t.Fatal("Unexpected error")
+ // }
+ // test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
+ // })
- t.Run("TestMerge_WithAppend", func(t *testing.T) {
- var dst = test.ParseData(`
----
-a: b
-c: d
-`)
- var src = test.ParseData(`
----
-a: 1
-b: 2
-`)
+ // t.Run("TestMerge_WithAppend", func(t *testing.T) {
+ // var dst = test.ParseData(`
+ // ---
+ // a: b
+ // c: d
+ // `)
+ // var src = test.ParseData(`
+ // ---
+ // a: 1
+ // b: 2
+ // `)
- var mergedData = make(map[interface{}]interface{})
- mergedData["root"] = dst
- var mapDataBucket = make(map[interface{}]interface{})
- mapDataBucket["root"] = src
+ // var mergedData = make(map[interface{}]interface{})
+ // mergedData["root"] = dst
+ // var mapDataBucket = make(map[interface{}]interface{})
+ // mapDataBucket["root"] = src
- err := subject.Merge(&mergedData, mapDataBucket, false, true)
- if err != nil {
- t.Fatal("Unexpected error")
- }
- test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
- })
+ // err := subject.Merge(&mergedData, mapDataBucket, false, true)
+ // if err != nil {
+ // t.Fatal("Unexpected error")
+ // }
+ // test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
+ // })
- t.Run("TestMerge_WithError", func(t *testing.T) {
- err := subject.Merge(nil, nil, false, false)
- if err == nil {
- t.Fatal("Expected error due to nil")
- }
- })
+ // t.Run("TestMerge_WithError", func(t *testing.T) {
+ // err := subject.Merge(nil, nil, false, false)
+ // if err == nil {
+ // t.Fatal("Expected error due to nil")
+ // }
+ // })
}
diff --git a/pkg/yqlib/path_parser_test.go b/pkg/yqlib/path_parser_test.go
index 460d3fe9..f4212275 100644
--- a/pkg/yqlib/path_parser_test.go
+++ b/pkg/yqlib/path_parser_test.go
@@ -6,11 +6,15 @@ import (
"github.com/mikefarah/yq/v3/test"
)
+var parser = NewPathParser()
+
var parsePathsTests = []struct {
path string
expectedPaths []string
}{
{"a.b", []string{"a", "b"}},
+ {"a.b.**", []string{"a", "b", "**"}},
+ {"a.b.*", []string{"a", "b", "*"}},
{"a.b[0]", []string{"a", "b", "0"}},
{"a.b.d[+]", []string{"a", "b", "d", "+"}},
{"a", []string{"a"}},
@@ -22,8 +26,53 @@ var parsePathsTests = []struct {
{"[0]", []string{"0"}},
}
-func TestParsePath(t *testing.T) {
+func TestPathParserParsePath(t *testing.T) {
for _, tt := range parsePathsTests {
- test.AssertResultComplex(t, tt.expectedPaths, NewPathParser().ParsePath(tt.path))
+ test.AssertResultComplex(t, tt.expectedPaths, parser.ParsePath(tt.path))
}
}
+
+func TestPathParserMatchesNextPathElementSplat(t *testing.T) {
+ var node = NodeContext{Head: "*"}
+ test.AssertResult(t, true, parser.MatchesNextPathElement(node, ""))
+}
+
+func TestPathParserMatchesNextPathElementDeepSplat(t *testing.T) {
+ var node = NodeContext{Head: "**"}
+ test.AssertResult(t, true, parser.MatchesNextPathElement(node, ""))
+}
+
+func TestPathParserMatchesNextPathElementAppendArrayValid(t *testing.T) {
+ var node = NodeContext{Head: "+"}
+ test.AssertResult(t, true, parser.MatchesNextPathElement(node, "3"))
+}
+
+func TestPathParserMatchesNextPathElementAppendArrayInvalid(t *testing.T) {
+ var node = NodeContext{Head: "+"}
+ test.AssertResult(t, false, parser.MatchesNextPathElement(node, "cat"))
+}
+
+func TestPathParserMatchesNextPathElementPrefixMatchesWhole(t *testing.T) {
+ var node = NodeContext{Head: "cat*"}
+ test.AssertResult(t, true, parser.MatchesNextPathElement(node, "cat"))
+}
+
+func TestPathParserMatchesNextPathElementPrefixMatchesStart(t *testing.T) {
+ var node = NodeContext{Head: "cat*"}
+ test.AssertResult(t, true, parser.MatchesNextPathElement(node, "caterpillar"))
+}
+
+func TestPathParserMatchesNextPathElementPrefixMismatch(t *testing.T) {
+ var node = NodeContext{Head: "cat*"}
+ test.AssertResult(t, false, parser.MatchesNextPathElement(node, "dog"))
+}
+
+func TestPathParserMatchesNextPathElementExactMatch(t *testing.T) {
+ var node = NodeContext{Head: "farahtek"}
+ test.AssertResult(t, true, parser.MatchesNextPathElement(node, "farahtek"))
+}
+
+func TestPathParserMatchesNextPathElementExactMismatch(t *testing.T) {
+ var node = NodeContext{Head: "farahtek"}
+ test.AssertResult(t, false, parser.MatchesNextPathElement(node, "othertek"))
+}
diff --git a/pkg/yqlib/value_parser.go b/pkg/yqlib/value_parser.go
index 7a51d7c9..6f145a75 100644
--- a/pkg/yqlib/value_parser.go
+++ b/pkg/yqlib/value_parser.go
@@ -3,7 +3,6 @@ package yqlib
import (
"strconv"
- logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
)
@@ -12,20 +11,17 @@ type ValueParser interface {
}
type valueParser struct {
- log *logging.Logger
}
-func NewValueParser(l *logging.Logger) ValueParser {
- return &valueParser{log: l}
+func NewValueParser() ValueParser {
+ return &valueParser{}
}
func (v *valueParser) Parse(argument string, customTag string) *yaml.Node {
var err interface{}
var tag = customTag
- var inQuotes = len(argument) > 0 && argument[0] == '"'
- if tag == "" && !inQuotes {
-
+ if tag == "" {
_, err = strconv.ParseBool(argument)
if err == nil {
tag = "!!bool"
@@ -46,6 +42,6 @@ func (v *valueParser) Parse(argument string, customTag string) *yaml.Node {
return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
}
}
- v.log.Debugf("parsed value '%v', tag: '%v'", argument, tag)
+ log.Debugf("parsed value '%v', tag: '%v'", argument, tag)
return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
}
diff --git a/pkg/yqlib/value_parser_test.go b/pkg/yqlib/value_parser_test.go
index 24468e37..e4930974 100644
--- a/pkg/yqlib/value_parser_test.go
+++ b/pkg/yqlib/value_parser_test.go
@@ -4,22 +4,35 @@ import (
"testing"
"github.com/mikefarah/yq/v3/test"
+ yaml "gopkg.in/yaml.v3"
)
var parseValueTests = []struct {
argument string
- expectedResult interface{}
+ customTag string
+ expectedTag string
testDescription string
}{
- {"true", true, "boolean"},
- {"\"true\"", "true", "boolean as string"},
- {"3.4", 3.4, "number"},
- {"\"3.4\"", "3.4", "number as string"},
- {"", "", "empty string"},
+ {"true", "", "!!bool", "boolean"},
+ {"true", "!!string", "!!string", "boolean forced as string"},
+ {"3.4", "", "!!float", "float"},
+ {"1212121", "", "!!int", "big number"},
+ {"1212121.1", "", "!!float", "big float number"},
+ {"3", "", "!!int", "int"},
+ {"null", "", "!!null", "null"},
}
-func TestParseValue(t *testing.T) {
+func TestValueParserParse(t *testing.T) {
for _, tt := range parseValueTests {
- test.AssertResultWithContext(t, tt.expectedResult, NewValueParser().ParseValue(tt.argument), tt.testDescription)
+ actual := NewValueParser().Parse(tt.argument, tt.customTag)
+ test.AssertResultWithContext(t, tt.argument, actual.Value, tt.testDescription)
+ test.AssertResultWithContext(t, tt.expectedTag, actual.Tag, tt.testDescription)
+ test.AssertResult(t, yaml.ScalarNode, actual.Kind)
}
}
+
+func TestValueParserParseEmptyArray(t *testing.T) {
+ actual := NewValueParser().Parse("[]", "")
+ test.AssertResult(t, "!!seq", actual.Tag)
+ test.AssertResult(t, yaml.SequenceNode, actual.Kind)
+}
diff --git a/scripts/coverage.sh b/scripts/coverage.sh
index 1f806696..88b4b974 100755
--- a/scripts/coverage.sh
+++ b/scripts/coverage.sh
@@ -2,5 +2,5 @@
set -e
-go test -coverprofile=coverage.out ./...
-go tool cover -html=coverage.out -o cover/coverage.html
+go test -coverprofile=coverage.out -v $(go list ./... | grep -v -E 'examples' | grep -v -E 'test')
+go tool cover -html=coverage.out -o coverage.html
diff --git a/scripts/test.sh b/scripts/test.sh
index fcba05e2..2ea6262d 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -1,3 +1,3 @@
#!/bin/bash
-go test -v $(go list ./... | grep -v -E 'examples')
+go test -v $(go list ./... | grep -v -E 'examples' | grep -v -E 'test')
diff --git a/yq.go b/yq.go
index 053b4a98..a5da09b7 100644
--- a/yq.go
+++ b/yq.go
@@ -30,8 +30,8 @@ var verbose = false
var version = false
var docIndex = "0"
var log = logging.MustGetLogger("yq")
-var lib = yqlib.NewYqLib(log)
-var valueParser = yqlib.NewValueParser(log)
+var lib = yqlib.NewYqLib()
+var valueParser = yqlib.NewValueParser()
func main() {
cmd := newCommandCLI()
@@ -210,6 +210,7 @@ Note that you can give a create script to perform more sophisticated yaml. This
RunE: newProperty,
}
cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
+ cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
return cmdNew
}
From 924eb6c4622d99702409732fae900dfc1639d3cd Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Thu, 9 Jan 2020 21:18:24 +1100
Subject: [PATCH 46/68] Added missing functions to interface
---
pkg/yqlib/data_navigator.go | 4 ++--
pkg/yqlib/lib.go | 18 ++++++++++++++----
pkg/yqlib/lib_test.go | 3 +++
pkg/yqlib/navigation_strategy.go | 8 ++++----
yq.go | 6 +++---
5 files changed, 26 insertions(+), 13 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 2a0ec80c..cfbfece8 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -96,7 +96,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
newPathStack := append(pathStack, contents[indexInMap].Value)
log.Debug("appended %v", contents[indexInMap].Value)
n.navigationStrategy.DebugVisitedNodes()
- log.Debug("should I traverse? %v, %v", head, PathStackToString(newPathStack))
+ log.Debug("should I traverse? %v, %v", head, pathStackToString(newPathStack))
DebugNode(value)
if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) == true {
log.Debug("recurseMap: Going to traverse")
@@ -232,7 +232,7 @@ func (n *navigator) appendArray(value *yaml.Node, head string, tail []string, pa
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 errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, PathStackToString(pathStack))
+ return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, pathStackToString(pathStack))
}
for int64(len(value.Content)) <= index {
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 1675e60f..8429a126 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -12,7 +12,6 @@ import (
var log = logging.MustGetLogger("yq")
-// TODO: enumerate
type UpdateCommand struct {
Command string
Path string
@@ -33,11 +32,11 @@ func DebugNode(value *yaml.Node) {
}
}
-func PathStackToString(pathStack []interface{}) string {
- return MergePathStackToString(pathStack, false)
+func pathStackToString(pathStack []interface{}) string {
+ return mergePathStackToString(pathStack, false)
}
-func MergePathStackToString(pathStack []interface{}, appendArrays bool) string {
+func mergePathStackToString(pathStack []interface{}, appendArrays bool) string {
var sb strings.Builder
for index, path := range pathStack {
switch path.(type) {
@@ -89,6 +88,9 @@ type YqLib interface {
Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
New(path string) yaml.Node
+
+ PathStackToString(pathStack []interface{}) string
+ MergePathStackToString(pathStack []interface{}, appendArrays bool) string
}
type lib struct {
@@ -111,6 +113,14 @@ func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
}
+func (l *lib) PathStackToString(pathStack []interface{}) string {
+ return pathStackToString(pathStack)
+}
+
+func (l *lib) MergePathStackToString(pathStack []interface{}, appendArrays bool) string {
+ return mergePathStackToString(pathStack, appendArrays)
+}
+
func (l *lib) New(path string) yaml.Node {
var paths = l.parser.ParsePath(path)
newNode := yaml.Node{Kind: guessKind("", paths, 0)}
diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go
index 77411470..23f4d85f 100644
--- a/pkg/yqlib/lib_test.go
+++ b/pkg/yqlib/lib_test.go
@@ -6,6 +6,9 @@ import (
func TestLib(t *testing.T) {
+ // PathStackToString
+ // MergePathStackToString (with true)
+
// var log = logging.MustGetLogger("yq")
// subject := NewYqLib(log)
diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go
index 740d06f9..d1d72ca0 100644
--- a/pkg/yqlib/navigation_strategy.go
+++ b/pkg/yqlib/navigation_strategy.go
@@ -95,7 +95,7 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
}
func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
- log.Debug("Visit?, %v, %v", nodeContext.Head, PathStackToString(nodeContext.PathStack))
+ log.Debug("Visit?, %v, %v", nodeContext.Head, pathStackToString(nodeContext.PathStack))
DebugNode(nodeContext.Node)
if ns.shouldVisit(nodeContext) {
log.Debug("yep, visiting")
@@ -112,12 +112,12 @@ func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
func (ns *NavigationStrategyImpl) DebugVisitedNodes() {
log.Debug("Visited Nodes:")
for _, candidate := range ns.visitedNodes {
- log.Debug(" - %v", PathStackToString(candidate.PathStack))
+ log.Debug(" - %v", pathStackToString(candidate.PathStack))
}
}
func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
- log.Debug("checking already visited pathStack: %v", PathStackToString(pathStack))
+ log.Debug("checking already visited pathStack: %v", pathStackToString(pathStack))
for _, candidate := range ns.visitedNodes {
candidatePathStack := candidate.PathStack
if patchStacksMatch(candidatePathStack, pathStack) {
@@ -131,7 +131,7 @@ func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
}
func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool {
- log.Debug("checking against path: %v", PathStackToString(path1))
+ log.Debug("checking against path: %v", pathStackToString(path1))
if len(path1) != len(path2) {
return false
diff --git a/yq.go b/yq.go
index a5da09b7..021784cf 100644
--- a/yq.go
+++ b/yq.go
@@ -335,7 +335,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error
for index, mappedDoc := range matchingNodes {
switch printMode {
case "k":
- cmd.Print(yqlib.PathStackToString(mappedDoc.PathStack))
+ cmd.Print(lib.PathStackToString(mappedDoc.PathStack))
if index < len(matchingNodes)-1 {
cmd.Print("\n")
}
@@ -343,7 +343,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error
// put it into a node and print that.
var parentNode = yaml.Node{Kind: yaml.MappingNode}
parentNode.Content = make([]*yaml.Node, 2)
- parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: yqlib.PathStackToString(mappedDoc.PathStack)}
+ parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)}
parentNode.Content[1] = mappedDoc.Node
if err := printValue(&parentNode, cmd); err != nil {
return err
@@ -438,7 +438,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
return errorProcessingFile
}
for _, matchingNode := range matchingNodes {
- mergePath := yqlib.MergePathStackToString(matchingNode.PathStack, appendFlag)
+ mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag)
updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag})
}
}
From 0621307391baec9dd012c4ec8dad69bc11ce4306 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Thu, 9 Jan 2020 21:27:52 +1100
Subject: [PATCH 47/68] Fixed linting errors
---
pkg/yqlib/data_navigator.go | 10 +++++-----
pkg/yqlib/delete_navigation_strategy.go | 2 +-
pkg/yqlib/lib.go | 8 +++++---
pkg/yqlib/update_navigation_strategy.go | 2 +-
yq.go | 6 +++---
5 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index cfbfece8..440331ce 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -40,7 +40,7 @@ func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pat
// 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)
+ _ = n.recurse(value, tail[0], tail[1:], pathStack)
}
return errorDeepSplatting
}
@@ -78,7 +78,7 @@ func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathSt
case yaml.AliasNode:
log.Debug("its an alias!")
DebugNode(value.Alias)
- if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) == true {
+ if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) {
log.Debug("following the alias")
return n.recurse(value.Alias, head, tail, pathStack)
}
@@ -98,7 +98,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
n.navigationStrategy.DebugVisitedNodes()
log.Debug("should I traverse? %v, %v", head, pathStackToString(newPathStack))
DebugNode(value)
- 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) {
log.Debug("recurseMap: Going to traverse")
traversedEntry = true
// contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(head, tail, contents[indexInMap+1].Kind))
@@ -116,7 +116,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
return errorVisiting
}
- if traversedEntry == true || head == "*" || head == "**" || n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) == false {
+ if traversedEntry || head == "*" || head == "**" || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) {
return nil
}
@@ -156,7 +156,7 @@ func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []st
// if we don't find a match directly on this node first.
errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
- if errorVisitedDirectEntries != nil || n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) == false {
+ if errorVisitedDirectEntries != nil || !n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) {
return errorVisitedDirectEntries
}
return n.visitAliases(contents, head, tail, pathStack, visit)
diff --git a/pkg/yqlib/delete_navigation_strategy.go b/pkg/yqlib/delete_navigation_strategy.go
index b4f95c3a..fc83c9e2 100644
--- a/pkg/yqlib/delete_navigation_strategy.go
+++ b/pkg/yqlib/delete_navigation_strategy.go
@@ -38,7 +38,7 @@ func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []int
for index := 0; index < len(contents); index = index + 2 {
keyNode := contents[index]
valueNode := contents[index+1]
- if pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, []string{}, pathStack), keyNode.Value) == false {
+ if !pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, []string{}, pathStack), keyNode.Value) {
log.Debug("adding node %v", keyNode.Value)
newContents = append(newContents, keyNode, valueNode)
} else {
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 8429a126..e455d7ff 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -25,7 +25,10 @@ func DebugNode(value *yaml.Node) {
} else if log.IsEnabledFor(logging.DEBUG) {
buf := new(bytes.Buffer)
encoder := yaml.NewEncoder(buf)
- encoder.Encode(value)
+ errorEncoding := encoder.Encode(value)
+ if errorEncoding != nil {
+ log.Error("Error debugging node, %v", errorEncoding.Error())
+ }
encoder.Close()
log.Debug("Tag: %v", value.Tag)
log.Debug("%v", buf.String())
@@ -94,8 +97,7 @@ type YqLib interface {
}
type lib struct {
- navigator DataNavigator
- parser PathParser
+ parser PathParser
}
func NewYqLib() YqLib {
diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go
index 8eea9069..e6be8b71 100644
--- a/pkg/yqlib/update_navigation_strategy.go
+++ b/pkg/yqlib/update_navigation_strategy.go
@@ -12,7 +12,7 @@ func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navi
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
changesToApply := updateCommand.Value
- if updateCommand.Overwrite == true || node.Value == "" {
+ if updateCommand.Overwrite || node.Value == "" {
log.Debug("going to update")
DebugNode(node)
log.Debug("with")
diff --git a/yq.go b/yq.go
index 021784cf..c36ba59d 100644
--- a/yq.go
+++ b/yq.go
@@ -434,7 +434,7 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
for _, fileToMerge := range filesToMerge {
matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0)
- if errorProcessingFile != nil && (allowEmptyFlag == false || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) {
+ if errorProcessingFile != nil && (!allowEmptyFlag || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) {
return errorProcessingFile
}
for _, matchingNode := range matchingNodes {
@@ -464,9 +464,9 @@ func newProperty(cmd *cobra.Command, args []string) error {
var encoder = yaml.NewEncoder(cmd.OutOrStdout())
encoder.SetIndent(2)
- encoder.Encode(&newNode)
+ errorEncoding := encoder.Encode(&newNode)
encoder.Close()
- return nil
+ return errorEncoding
}
func prefixProperty(cmd *cobra.Command, args []string) error {
From feba7b04faf86354a36d324e11eb97279d3ec753 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Thu, 9 Jan 2020 21:36:05 +1100
Subject: [PATCH 48/68] Added path stack to string test
---
pkg/yqlib/lib_test.go | 37 ++++++++++++++++++++++++-------------
1 file changed, 24 insertions(+), 13 deletions(-)
diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go
index 23f4d85f..4adf7df5 100644
--- a/pkg/yqlib/lib_test.go
+++ b/pkg/yqlib/lib_test.go
@@ -2,26 +2,37 @@ package yqlib
import (
"testing"
+
+ "github.com/mikefarah/yq/v3/test"
)
func TestLib(t *testing.T) {
- // PathStackToString
- // MergePathStackToString (with true)
+ subject := NewYqLib()
- // var log = logging.MustGetLogger("yq")
- // subject := NewYqLib(log)
+ t.Run("PathStackToString_Empty", func(t *testing.T) {
+ emptyArray := make([]interface{}, 0)
+ got := subject.PathStackToString(emptyArray)
+ test.AssertResult(t, ``, got)
+ })
- // t.Run("TestReadPath", func(t *testing.T) {
- // var data = test.ParseData(`
- // ---
- // b:
- // 2: c
- // `)
+ t.Run("PathStackToString", func(t *testing.T) {
+ array := make([]interface{}, 3)
+ array[0] = "a"
+ array[1] = 0
+ array[2] = "b"
+ got := subject.PathStackToString(array)
+ test.AssertResult(t, `a.[0].b`, got)
+ })
- // got, _ := subject.ReadPath(data, "b.2")
- // test.AssertResult(t, `c`, got)
- // })
+ t.Run("MergePathStackToString", func(t *testing.T) {
+ array := make([]interface{}, 3)
+ array[0] = "a"
+ array[1] = 0
+ array[2] = "b"
+ got := subject.MergePathStackToString(array, true)
+ test.AssertResult(t, `a.[+].b`, got)
+ })
// t.Run("TestReadPath_WithError", func(t *testing.T) {
// var data = test.ParseData(`
From 854f5f0fc9041fa618de9800ffbd0aead1f57a88 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Fri, 10 Jan 2020 22:01:59 +1100
Subject: [PATCH 49/68] wip json encoding
---
Upgrade Notes | 9 ++---
commands_test.go | 2 +-
examples/instruction_sample.yaml | 5 +--
examples/sample.yaml | 2 +-
pkg/yqlib/encoder.go | 44 +++++++++++++++++++++
pkg/yqlib/value_parser_test.go | 2 +-
yq.go | 65 +++++++++++++++++++++-----------
7 files changed, 94 insertions(+), 35 deletions(-)
create mode 100644 pkg/yqlib/encoder.go
diff --git a/Upgrade Notes b/Upgrade Notes
index 1b7bb513..38f97368 100644
--- a/Upgrade Notes
+++ b/Upgrade Notes
@@ -1,15 +1,12 @@
-# Update doco / notes
- - --autocreate=false to turn off default flags
- - add comments to test yaml to ensure they are kept on updating.
- - update built in command notes to includes quotes around path args.
# New Features
- Keeps comments and formatting (e.g. inline arrays)!
- Handles anchors!
- Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead.
+ - JSON output works for all commands! Yaml files with multiple documents are printed out as one JSON document per line.
-# Update scripts file format has changed
+# Update scripts file format has changed to be more powerful. Comments can be added, and delete commands have been introduced.
# Merge command
-- autocreates missing entries in target by default, new flag to turn that off.
+- New flag 'autocreates' missing entries in target by default, new flag to turn that off.
diff --git a/commands_test.go b/commands_test.go
index 9f600f38..bf0b438f 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -494,7 +494,7 @@ func TestReadCmd_Verbose(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "2", result.Output)
+ test.AssertResult(t, "2\n", result.Output)
}
// func TestReadCmd_ToJson(t *testing.T) {
diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml
index 24445bca..d531a302 100644
--- a/examples/instruction_sample.yaml
+++ b/examples/instruction_sample.yaml
@@ -6,6 +6,5 @@
- command: update
path: b.e[+].name
value: Mike Farah
-- command: update
- path: d.a
- value: Cow
\ No newline at end of file
+- command: delete
+ path: b.d
\ No newline at end of file
diff --git a/examples/sample.yaml b/examples/sample.yaml
index a08d1d1f..1fb93352 100644
--- a/examples/sample.yaml
+++ b/examples/sample.yaml
@@ -1,4 +1,4 @@
-a: Easy! as one two three
+a: true
b:
c: 2
d: [3, 4, 5]
diff --git a/pkg/yqlib/encoder.go b/pkg/yqlib/encoder.go
new file mode 100644
index 00000000..affd213e
--- /dev/null
+++ b/pkg/yqlib/encoder.go
@@ -0,0 +1,44 @@
+package yqlib
+
+import (
+ "encoding/json"
+ "io"
+
+ yaml "gopkg.in/yaml.v3"
+)
+
+type Encoder interface {
+ Encode(node *yaml.Node) error
+}
+
+type yamlEncoder struct {
+ encoder *yaml.Encoder
+}
+
+func NewYamlEncoder(destination io.Writer) Encoder {
+ var encoder = yaml.NewEncoder(destination)
+ encoder.SetIndent(2)
+ return &yamlEncoder{encoder}
+}
+
+func (ye *yamlEncoder) Encode(node *yaml.Node) error {
+ return ye.encoder.Encode(node)
+}
+
+type jsonEncoder struct {
+ encoder *json.Encoder
+}
+
+func NewJsonEncoder(destination io.Writer) Encoder {
+ var encoder = json.NewEncoder(destination)
+ return &jsonEncoder{encoder}
+}
+
+func (je *jsonEncoder) Encode(node *yaml.Node) error {
+ var dataBucket interface{}
+ errorDecoding := node.Decode(&dataBucket)
+ if errorDecoding != nil {
+ return errorDecoding
+ }
+ return je.encoder.Encode(dataBucket)
+}
diff --git a/pkg/yqlib/value_parser_test.go b/pkg/yqlib/value_parser_test.go
index e4930974..fb439d69 100644
--- a/pkg/yqlib/value_parser_test.go
+++ b/pkg/yqlib/value_parser_test.go
@@ -14,7 +14,7 @@ var parseValueTests = []struct {
testDescription string
}{
{"true", "", "!!bool", "boolean"},
- {"true", "!!string", "!!string", "boolean forced as string"},
+ {"true", "!!str", "!!str", "boolean forced as string"},
{"3.4", "", "!!float", "float"},
{"1212121", "", "!!int", "big number"},
{"1212121.1", "", "!!float", "big float number"},
diff --git a/yq.go b/yq.go
index c36ba59d..5df65ffe 100644
--- a/yq.go
+++ b/yq.go
@@ -22,6 +22,7 @@ var customTag = ""
var printMode = "v"
var writeInplace = false
var writeScript = ""
+var outputToJSON = false
var overwriteFlag = false
var autoCreateFlag = true
var allowEmptyFlag = false
@@ -73,6 +74,7 @@ func newCommandCLI() *cobra.Command {
}
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
+ rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.AddCommand(
@@ -97,9 +99,9 @@ func createReadCmd() *cobra.Command {
yq read things.yaml a.b.c
yq r - a.b.c (reads from stdin)
yq r things.yaml a.*.c
-yq r -d1 things.yaml a.array[0].blah
-yq r things.yaml a.array[*].blah
-yq r -- things.yaml --key-starting-with-dashes
+yq r -d1 things.yaml 'a.array[0].blah'
+yq r things.yaml 'a.array[*].blah'
+yq r -- things.yaml --key-starting-with-dashes.blah
`,
Long: "Outputs the value of the given path in the yaml file to STDOUT",
RunE: readProperty,
@@ -115,13 +117,15 @@ func createWriteCmd() *cobra.Command {
Aliases: []string{"w"},
Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue",
Example: `
-yq write things.yaml a.b.c cat
+yq write things.yaml a.b.c true
+yq write things.yaml a.b.c --tag '!!str' true
+yq write things.yaml a.b.c --tag '!!float' 3
yq write --inplace -- things.yaml a.b.c --cat
yq w -i things.yaml a.b.c cat
yq w --script update_script.yaml things.yaml
yq w -i -s update_script.yaml things.yaml
-yq w --doc 2 things.yaml a.b.d[+] foo
-yq w -d2 things.yaml a.b.d[+] foo
+yq w --doc 2 things.yaml 'a.b.d[+]' foo
+yq w -d2 things.yaml 'a.b.d[+]' foo
`,
Long: `Updates the yaml file w.r.t the given path and value.
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
@@ -129,12 +133,16 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd
Append value to array adds the value to the end of array.
Update Scripts:
-Note that you can give an update script to perform more sophisticated updated. Update script
-format is a yaml map where the key is the path and the value is..well the value. e.g.:
+Note that you can give an update script to perform more sophisticated update. Update script
+format is list of update commands (update or delete) like so:
---
-a.b.c: true,
-a.b.e:
- - name: bob
+- command: update
+ path: b.c
+ value:
+ #great
+ things: frog # wow!
+- command: delete
+ path: b.d
`,
RunE: writeProperty,
}
@@ -198,6 +206,7 @@ func createNewCmd() *cobra.Command {
Example: `
yq new a.b.c cat
yq n a.b.c cat
+yq n a.b[+] --tag '!!str' true
yq n -- --key-starting-with-dash cat
yq n --script create_script.yaml
`,
@@ -226,6 +235,7 @@ yq m -i things.yaml other.yaml
yq m --overwrite things.yaml other.yaml
yq m -i -x things.yaml other.yaml
yq m -i -a things.yaml other.yaml
+yq m -i --autocreate=false things.yaml other.yaml
`,
Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
@@ -313,16 +323,18 @@ func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.
}
func printValue(node *yaml.Node, cmd *cobra.Command) error {
- if node.Kind == yaml.ScalarNode {
- cmd.Print(node.Value)
- return nil
+ bufferedWriter := bufio.NewWriter(cmd.OutOrStdout())
+ defer safelyFlush(bufferedWriter)
+
+ var encoder yqlib.Encoder
+ if outputToJSON {
+ encoder = yqlib.NewJsonEncoder(bufferedWriter)
+ } else {
+ encoder = yqlib.NewYamlEncoder(bufferedWriter)
}
- var encoder = yaml.NewEncoder(cmd.OutOrStdout())
- encoder.SetIndent(2)
if err := encoder.Encode(node); err != nil {
return err
}
- encoder.Close()
return nil
}
@@ -376,7 +388,7 @@ func parseDocumentIndex() (bool, int, error) {
type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error
-func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn {
+func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn {
return func(decoder *yaml.Decoder) error {
var dataBucket yaml.Node
var errorReading error
@@ -561,14 +573,21 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
safelyRenameFile(tempFile.Name(), inputFile)
}()
} else {
- var writer = bufio.NewWriter(stdOut)
- destination = writer
+ destination = stdOut
destinationName = "Stdout"
- defer safelyFlush(writer)
}
- var encoder = yaml.NewEncoder(destination)
- encoder.SetIndent(2)
+
log.Debugf("Writing to %v from %v", destinationName, inputFile)
+
+ bufferedWriter := bufio.NewWriter(destination)
+ defer safelyFlush(bufferedWriter)
+
+ var encoder yqlib.Encoder
+ if outputToJSON {
+ encoder = yqlib.NewJsonEncoder(bufferedWriter)
+ } else {
+ encoder = yqlib.NewYamlEncoder(bufferedWriter)
+ }
return readStream(inputFile, mapYamlDecoder(updateData, encoder))
}
From 728cbe991a661e836780d620504e4b54b6b1da64 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 09:07:39 +1100
Subject: [PATCH 50/68] Print path is more accurate than keys (i think)
---
Upgrade Notes | 50 +++++++++++++++++++++++++++++++++++++++++++++---
commands_test.go | 50 ++++++++++++++++++++++++++++++++++--------------
yq.go | 19 +++++++++++++-----
3 files changed, 97 insertions(+), 22 deletions(-)
diff --git a/Upgrade Notes b/Upgrade Notes
index 38f97368..9f2eecdf 100644
--- a/Upgrade Notes
+++ b/Upgrade Notes
@@ -1,11 +1,55 @@
# New Features
- - Keeps comments and formatting (e.g. inline arrays)!
- - Handles anchors!
+ - Keeps yaml comments and formatting (string blocks are saved, number formatting is preserved, so it won't drop off trailing 0s for values like 0.10, which is important when that's a version entry )
+
+ - Handles anchors! (doc link)
- Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead.
+ - Can print out matching paths and values when splatting (doc link)
- JSON output works for all commands! Yaml files with multiple documents are printed out as one JSON document per line.
+ - Deep splat (**) to match arbitrary paths, (doc link)
-# Update scripts file format has changed to be more powerful. Comments can be added, and delete commands have been introduced.
+
+# Breaking changes
+
+## Update scripts file format has changed to be more powerful.
+Comments can be added, and delete commands have been introduced.
+
+## Reading and splatting, matching results are printed once per line.
+ e.g:
+
+```json
+parent:
+ childA:
+ no: matches here
+ childB:
+ there: matches
+ hi: no match
+ there2: also matches
+```
+
+yq r sample.yaml 'parent.*.there*'
+
+old
+```yaml
+- null
+- - matches
+ - also matches
+```
+
+new
+```yaml
+matches
+also matches
+```
+
+and you can print the matching paths:
+
+yq r --printMode pv sample.yaml 'parent.*.there*'
+
+```yaml
+parent.childB.there: matches
+parent.childB.there2: also matches
+```
# Merge command
- New flag 'autocreates' missing entries in target by default, new flag to turn that off.
diff --git a/commands_test.go b/commands_test.go
index bf0b438f..487bb7fd 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -96,7 +96,7 @@ func TestReadCmd(t *testing.T) {
func TestReadWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.c")
+ result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
@@ -105,7 +105,7 @@ func TestReadWithKeyAndValueCmd(t *testing.T) {
func TestReadArrayCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.e.1.name")
+ result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.e.1.name")
if result.Error != nil {
t.Error(result.Error)
}
@@ -114,7 +114,7 @@ func TestReadArrayCmd(t *testing.T) {
func TestReadDeepSplatCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.**")
+ result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**")
if result.Error != nil {
t.Error(result.Error)
}
@@ -132,7 +132,7 @@ b.e.[1].value: 4
func TestReadDeepSplatWithSuffixCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p kv examples/sample.yaml b.**.name")
+ result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**.name")
if result.Error != nil {
t.Error(result.Error)
}
@@ -144,7 +144,7 @@ b.e.[1].name: sam
func TestReadWithKeyCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p k examples/sample.yaml b.c")
+ result := test.RunCmd(cmd, "read -p p examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
@@ -162,7 +162,7 @@ func TestReadAnchorsCmd(t *testing.T) {
func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p kv examples/simple-anchor.yaml foobar.a")
+ result := test.RunCmd(cmd, "read -p pv examples/simple-anchor.yaml foobar.a")
if result.Error != nil {
t.Error(result.Error)
}
@@ -189,7 +189,7 @@ func TestReadMergeAnchorsOverrideCmd(t *testing.T) {
func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "r -p kv examples/merge-anchor.yaml foobar.th*")
+ result := test.RunCmd(cmd, "r -p pv examples/merge-anchor.yaml foobar.th*")
if result.Error != nil {
t.Error(result.Error)
}
@@ -271,7 +271,7 @@ func TestReadMultiCmd(t *testing.T) {
func TestReadMultiWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p vk -d 1 examples/multiple_docs.yaml another.document")
+ result := test.RunCmd(cmd, "read -p vp -d 1 examples/multiple_docs.yaml another.document")
if result.Error != nil {
t.Error(result.Error)
}
@@ -292,7 +292,7 @@ third document`, result.Output)
func TestReadMultiAllWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p kv -d* examples/multiple_docs.yaml commonKey")
+ result := test.RunCmd(cmd, "read -p pv -d* examples/multiple_docs.yaml commonKey")
if result.Error != nil {
t.Error(result.Error)
}
@@ -372,7 +372,7 @@ gather_facts: true
func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p kv examples/array.yaml [*]")
+ result := test.RunCmd(cmd, "read -p pv examples/array.yaml [*]")
if result.Error != nil {
t.Error(result.Error)
}
@@ -394,7 +394,7 @@ func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) {
func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -p k examples/array.yaml [*]")
+ result := test.RunCmd(cmd, "read -p p examples/array.yaml [*]")
if result.Error != nil {
t.Error(result.Error)
}
@@ -494,7 +494,7 @@ func TestReadCmd_Verbose(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
- test.AssertResult(t, "2\n", result.Output)
+ test.AssertResult(t, "2", result.Output)
}
// func TestReadCmd_ToJson(t *testing.T) {
@@ -559,7 +559,7 @@ b:
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("read -p kv %s b.there*.c", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("read -p pv %s b.there*.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -587,7 +587,7 @@ b:
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
- result := test.RunCmd(cmd, fmt.Sprintf("read -p k %s b.there*.c", filename))
+ result := test.RunCmd(cmd, fmt.Sprintf("read -p p %s b.there*.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -871,6 +871,28 @@ func TestWriteCmdScript(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
+func TestWriteCmdEmptyScript(t *testing.T) {
+ content := `b:
+ c: 3
+`
+ filename := test.WriteTempYamlFile(content)
+ defer test.RemoveTempYamlFile(filename)
+
+ updateScript := ``
+ scriptFilename := test.WriteTempYamlFile(updateScript)
+ defer test.RemoveTempYamlFile(scriptFilename)
+
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `b:
+ c: 3
+`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
func TestWriteMultiCmd(t *testing.T) {
content := `b:
c: 3
diff --git a/yq.go b/yq.go
index 5df65ffe..59d26480 100644
--- a/yq.go
+++ b/yq.go
@@ -107,7 +107,7 @@ yq r -- things.yaml --key-starting-with-dashes.blah
RunE: readProperty,
}
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
- cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), k (keys), kv (key and value pairs)")
+ cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
return cmdRead
}
@@ -218,7 +218,7 @@ Note that you can give a create script to perform more sophisticated yaml. This
`,
RunE: newProperty,
}
- cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
+ cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml")
cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
return cmdNew
}
@@ -323,6 +323,11 @@ func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.
}
func printValue(node *yaml.Node, cmd *cobra.Command) error {
+ if node.Kind == yaml.ScalarNode {
+ cmd.Print(node.Value)
+ return nil
+ }
+
bufferedWriter := bufio.NewWriter(cmd.OutOrStdout())
defer safelyFlush(bufferedWriter)
@@ -346,12 +351,12 @@ func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error
for index, mappedDoc := range matchingNodes {
switch printMode {
- case "k":
+ case "p":
cmd.Print(lib.PathStackToString(mappedDoc.PathStack))
if index < len(matchingNodes)-1 {
cmd.Print("\n")
}
- case "kv", "vk":
+ case "pv", "vp":
// put it into a node and print that.
var parentNode = yaml.Node{Kind: yaml.MappingNode}
parentNode.Content = make([]*yaml.Node, 2)
@@ -601,9 +606,13 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
if writeScript != "" {
var parsedCommands = make([]updateCommandParsed, 0)
- if err := readData(writeScript, 0, &parsedCommands); err != nil {
+
+ err := readData(writeScript, 0, &parsedCommands)
+
+ if err != nil && err != io.EOF {
return nil, err
}
+
log.Debugf("Read write commands file '%v'", parsedCommands)
for index := range parsedCommands {
parsedCommand := parsedCommands[index]
From 24dcb56466081b28019d99b92fb3547829742748 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 09:13:42 +1100
Subject: [PATCH 51/68] Inc version - fix help text
---
snap/snapcraft.yaml | 2 +-
version.go | 2 +-
yq.go | 5 +++++
3 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index ad171bd3..05a0545f 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,5 +1,5 @@
name: yq
-version: '2.4.1'
+version: '3.0.0'
summary: A lightweight and portable command-line YAML processor
description: |
The aim of the project is to be the jq or sed of yaml files.
diff --git a/version.go b/version.go
index eca405f0..4d0226b0 100644
--- a/version.go
+++ b/version.go
@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
- Version = "2.4.1"
+ Version = "3.0.0"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
diff --git a/yq.go b/yq.go
index 59d26480..931be12a 100644
--- a/yq.go
+++ b/yq.go
@@ -99,6 +99,7 @@ func createReadCmd() *cobra.Command {
yq read things.yaml a.b.c
yq r - a.b.c (reads from stdin)
yq r things.yaml a.*.c
+yq r things.yaml a.**.c
yq r -d1 things.yaml 'a.array[0].blah'
yq r things.yaml 'a.array[*].blah'
yq r -- things.yaml --key-starting-with-dashes.blah
@@ -118,6 +119,8 @@ func createWriteCmd() *cobra.Command {
Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue",
Example: `
yq write things.yaml a.b.c true
+yq write things.yaml 'a.*.c' true
+yq write things.yaml 'a.**' true
yq write things.yaml a.b.c --tag '!!str' true
yq write things.yaml a.b.c --tag '!!float' 3
yq write --inplace -- things.yaml a.b.c --cat
@@ -183,6 +186,8 @@ func createDeleteCmd() *cobra.Command {
Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
Example: `
yq delete things.yaml a.b.c
+yq delete things.yaml a.*.c
+yq delete things.yaml a.**
yq delete --inplace things.yaml a.b.c
yq delete --inplace -- things.yaml --key-starting-with-dash
yq d -i things.yaml a.b.c
From 9361b8b3e9e59e60a1d07a1b2fbfc8a259f67555 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 09:14:32 +1100
Subject: [PATCH 52/68] Beta
---
snap/snapcraft.yaml | 2 +-
version.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 05a0545f..1f96b906 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,5 +1,5 @@
name: yq
-version: '3.0.0'
+version: '3.0.0-beta'
summary: A lightweight and portable command-line YAML processor
description: |
The aim of the project is to be the jq or sed of yaml files.
diff --git a/version.go b/version.go
index 4d0226b0..588f6f73 100644
--- a/version.go
+++ b/version.go
@@ -16,7 +16,7 @@ var (
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
- VersionPrerelease = ""
+ VersionPrerelease = "beta"
)
// ProductName is the name of the product
From 96955ffa9c670f1d5d448a1761f34629d91cc999 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 09:55:24 +1100
Subject: [PATCH 53/68] release notes
---
Upgrade Notes | 40 +++++++++++++++++++++++---------
examples/instruction_sample.yaml | 3 ---
scripts/publish.sh | 2 +-
scripts/xcompile.sh | 4 ++--
4 files changed, 32 insertions(+), 17 deletions(-)
diff --git a/Upgrade Notes b/Upgrade Notes
index 9f2eecdf..bdd8f99a 100644
--- a/Upgrade Notes
+++ b/Upgrade Notes
@@ -1,12 +1,14 @@
+Major release! Upgraded underlying yaml parser, re-written majority of yq. This has brought on a number of features that have been in demand for a while (see below).
+
+This is in beta and needs some community feedback and testing :)
+
# New Features
- - Keeps yaml comments and formatting (string blocks are saved, number formatting is preserved, so it won't drop off trailing 0s for values like 0.10, which is important when that's a version entry )
-
- - Handles anchors! (doc link)
- - Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead.
- - Can print out matching paths and values when splatting (doc link)
+ - Keeps yaml comments and formatting, can specify yaml tags when updating. https://github.com/mikefarah/yq/issues/19, https://github.com/mikefarah/yq/issues/169, https://github.com/mikefarah/yq/issues/107, https://github.com/mikefarah/yq/issues/171, https://github.com/mikefarah/yq/issues/245, https://github.com/mikefarah/yq/issues/303,https://github.com/mikefarah/yq/issues/308,https://github.com/mikefarah/yq/issues/314
+ - Handles anchors! https://github.com/mikefarah/yq/issues/310, https://github.com/mikefarah/yq/issues/178
+ - Can print out matching paths and values when splatting https://github.com/mikefarah/yq/issues/20
- JSON output works for all commands! Yaml files with multiple documents are printed out as one JSON document per line.
- - Deep splat (**) to match arbitrary paths, (doc link)
+ - Deep splat (**) to match arbitrary paths
# Breaking changes
@@ -14,6 +16,24 @@
## Update scripts file format has changed to be more powerful.
Comments can be added, and delete commands have been introduced.
+Before:
+```yaml
+b.e[+].name: Mike Farah
+```
+
+After:
+```yaml
+- command: update
+ path: b.e[+].thing
+ value:
+ #great
+ things: frog # wow!
+- command: delete
+ path: b.d
+```
+
+https://github.com/mikefarah/yq/issues/305
+
## Reading and splatting, matching results are printed once per line.
e.g:
@@ -27,7 +47,9 @@ parent:
there2: also matches
```
+```bash
yq r sample.yaml 'parent.*.there*'
+```
old
```yaml
@@ -49,8 +71,4 @@ yq r --printMode pv sample.yaml 'parent.*.there*'
```yaml
parent.childB.there: matches
parent.childB.there2: also matches
-```
-
-# Merge command
-- New flag 'autocreates' missing entries in target by default, new flag to turn that off.
-
+```
\ No newline at end of file
diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml
index d531a302..652e0228 100644
--- a/examples/instruction_sample.yaml
+++ b/examples/instruction_sample.yaml
@@ -3,8 +3,5 @@
value:
#great
things: frog # wow!
-- command: update
- path: b.e[+].name
- value: Mike Farah
- command: delete
path: b.d
\ No newline at end of file
diff --git a/scripts/publish.sh b/scripts/publish.sh
index c878481e..40b290f6 100755
--- a/scripts/publish.sh
+++ b/scripts/publish.sh
@@ -32,5 +32,5 @@ upload() {
done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
}
-release
+# release
upload
diff --git a/scripts/xcompile.sh b/scripts/xcompile.sh
index 29d30f2a..1ecc2bfb 100755
--- a/scripts/xcompile.sh
+++ b/scripts/xcompile.sh
@@ -3,7 +3,7 @@
# This assumes that gonative and gox is installed as per the 'one time setup' instructions
# at https://github.com/inconshreveable/gonative
-gox -ldflags "${LDFLAGS}" -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}"
+gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}"
# include non-default linux builds too
-gox -ldflags "${LDFLAGS}" -os=linux -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}"
+gox -ldflags "${LDFLAGS}" -os=linux -output="build/yq_{{.OS}}_{{.Arch}}"
From 74c7a4e0274e390a41fd30c9cabf87a5a5f582c9 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 18:52:15 +1100
Subject: [PATCH 54/68] it works! wip
---
commands_test.go | 9 +++++++
examples/sample.yaml | 13 +++-------
pkg/yqlib/data_navigator.go | 15 +++++++----
...ilter_matching_node_navigation_strategy.go | 20 +++++++++++++++
pkg/yqlib/lib.go | 6 ++---
pkg/yqlib/navigation_strategy.go | 13 +++++-----
pkg/yqlib/path_parser.go | 25 ++++++++++++++++++-
7 files changed, 77 insertions(+), 24 deletions(-)
create mode 100644 pkg/yqlib/filter_matching_node_navigation_strategy.go
diff --git a/commands_test.go b/commands_test.go
index 487bb7fd..370a0a97 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -94,6 +94,15 @@ func TestReadCmd(t *testing.T) {
test.AssertResult(t, "2", result.Output)
}
+func TestReadWithAdvancedFilterCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read examples/sample.yaml b.e(name == sam).value")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ test.AssertResult(t, "4", result.Output)
+}
+
func TestReadWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c")
diff --git a/examples/sample.yaml b/examples/sample.yaml
index 1fb93352..603dc54d 100644
--- a/examples/sample.yaml
+++ b/examples/sample.yaml
@@ -1,9 +1,4 @@
-a: true
-b:
- c: 2
- d: [3, 4, 5]
- e:
- - name: fred
- value: 3
- - name: sam
- value: 4
\ No newline at end of file
+- name: fred
+ value: 3
+- name: sam
+ value: 4
\ No newline at end of file
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 440331ce..8dedf9a8 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -2,6 +2,7 @@ package yqlib
import (
"strconv"
+ "strings"
errors "github.com/pkg/errors"
yaml "gopkg.in/yaml.v3"
@@ -69,7 +70,7 @@ 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!", len(value.Content))
- if head == "*" || head == "**" {
+ if head == "*" || head == "**" || strings.Contains(head, "==") {
return n.splatArray(value, head, tail, pathStack)
} else if head == "+" {
return n.appendArray(value, head, tail, pathStack)
@@ -96,7 +97,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
newPathStack := append(pathStack, contents[indexInMap].Value)
log.Debug("appended %v", contents[indexInMap].Value)
n.navigationStrategy.DebugVisitedNodes()
- log.Debug("should I traverse? %v, %v", head, pathStackToString(newPathStack))
+ log.Debug("should I traverse? head: %v, path: %v", head, pathStackToString(newPathStack))
DebugNode(value)
if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) {
log.Debug("recurseMap: Going to traverse")
@@ -214,9 +215,13 @@ func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pat
log.Debug("processing")
DebugNode(childValue)
childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind))
- var err = n.doTraverse(childValue, head, tail, append(pathStack, index))
- if err != nil {
- return err
+
+ newPathStack := append(pathStack, index)
+ if n.navigationStrategy.ShouldTraverse(NewNodeContext(childValue, head, tail, newPathStack), childValue.Value) {
+ var err = n.doTraverse(childValue, head, tail, newPathStack)
+ if err != nil {
+ return err
+ }
}
}
return nil
diff --git a/pkg/yqlib/filter_matching_node_navigation_strategy.go b/pkg/yqlib/filter_matching_node_navigation_strategy.go
new file mode 100644
index 00000000..dde3b8a3
--- /dev/null
+++ b/pkg/yqlib/filter_matching_node_navigation_strategy.go
@@ -0,0 +1,20 @@
+package yqlib
+
+func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy {
+ return &NavigationStrategyImpl{
+ visitedNodes: []*NodeContext{},
+ followAlias: func(nodeContext NodeContext) bool {
+ return true
+ },
+ autoCreateMap: func(nodeContext NodeContext) bool {
+ return false
+ },
+ visit: func(nodeContext NodeContext) error {
+ return nil
+ },
+ shouldVisitExtraFn: func(nodeContext NodeContext) bool {
+ log.Debug("does %v match %v ? %v", nodeContext.Node.Value, value, nodeContext.Node.Value == value)
+ return nodeContext.Node.Value == value
+ },
+ }
+}
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index e455d7ff..108ef3ac 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -108,10 +108,10 @@ func NewYqLib() YqLib {
func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
var paths = l.parser.ParsePath(path)
- NavigationStrategy := ReadNavigationStrategy()
- navigator := NewDataNavigator(NavigationStrategy)
+ navigationStrategy := ReadNavigationStrategy()
+ navigator := NewDataNavigator(navigationStrategy)
error := navigator.Traverse(rootNode, paths)
- return NavigationStrategy.GetVisitedNodes(), error
+ return navigationStrategy.GetVisitedNodes(), error
}
diff --git a/pkg/yqlib/navigation_strategy.go b/pkg/yqlib/navigation_strategy.go
index d1d72ca0..a5ae1f1f 100644
--- a/pkg/yqlib/navigation_strategy.go
+++ b/pkg/yqlib/navigation_strategy.go
@@ -39,10 +39,11 @@ type NavigationStrategy interface {
}
type NavigationStrategyImpl struct {
- followAlias func(nodeContext NodeContext) bool
- autoCreateMap func(nodeContext NodeContext) bool
- visit func(nodeContext NodeContext) error
- visitedNodes []*NodeContext
+ followAlias func(nodeContext NodeContext) bool
+ autoCreateMap func(nodeContext NodeContext) bool
+ visit func(nodeContext NodeContext) error
+ shouldVisitExtraFn func(nodeContext NodeContext) bool
+ visitedNodes []*NodeContext
}
func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext {
@@ -90,8 +91,8 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
parser := NewPathParser()
// only visit aliases if its an exact match
- return (nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
- parser.MatchesNextPathElement(nodeContext, nodeKey))
+ return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
+ parser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext))
}
func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go
index b18ff5d8..112d7912 100644
--- a/pkg/yqlib/path_parser.go
+++ b/pkg/yqlib/path_parser.go
@@ -28,6 +28,26 @@ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey str
if head == "**" || head == "*" {
return true
}
+ if strings.Contains(head, "==") {
+ log.Debug("ooh deep recursion time")
+ result := strings.SplitN(head, "==", 2)
+ path := strings.TrimSpace(result[0])
+ value := strings.TrimSpace(result[1])
+ log.Debug("path %v", path)
+ log.Debug("value %v", value)
+ DebugNode(nodeContext.Node)
+ navigationStrategy := FilterMatchingNodesNavigationStrategy(value)
+
+ navigator := NewDataNavigator(navigationStrategy)
+ err := navigator.Traverse(nodeContext.Node, p.ParsePath(path))
+ if err != nil {
+ log.Error(err.Error())
+ }
+ //crap handle error
+ log.Debug("done deep recursing, found %v matches", len(navigationStrategy.GetVisitedNodes()))
+ return len(navigationStrategy.GetVisitedNodes()) > 0
+ }
+
if head == "+" {
log.Debug("head is +, nodeKey is %v", nodeKey)
var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint
@@ -66,9 +86,12 @@ func (p *pathParser) nextYamlPath(path string) (pathElement string, remaining st
case '"':
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
return p.search(path[1:], []uint8{'"'}, true)
+ case '(':
+ // e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
+ return p.search(path[1:], []uint8{')'}, true)
default:
// e.g "a.blah.cat" -> return "a" and "blah.cat"
- return p.search(path[0:], []uint8{'.', '['}, false)
+ return p.search(path[0:], []uint8{'.', '[', '"', '('}, false)
}
}
From 2d237e7e8e5dd749b9399c7f98e3319cd5204523 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 19:13:52 +1100
Subject: [PATCH 55/68] it works! wip
---
commands_test.go | 2 +-
examples/sample.yaml | 13 +++++++++----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index 370a0a97..d7cebecb 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -96,7 +96,7 @@ func TestReadCmd(t *testing.T) {
func TestReadWithAdvancedFilterCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read examples/sample.yaml b.e(name == sam).value")
+ result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e(name==sam).value")
if result.Error != nil {
t.Error(result.Error)
}
diff --git a/examples/sample.yaml b/examples/sample.yaml
index 603dc54d..1fb93352 100644
--- a/examples/sample.yaml
+++ b/examples/sample.yaml
@@ -1,4 +1,9 @@
-- name: fred
- value: 3
-- name: sam
- value: 4
\ No newline at end of file
+a: true
+b:
+ c: 2
+ d: [3, 4, 5]
+ e:
+ - name: fred
+ value: 3
+ - name: sam
+ value: 4
\ No newline at end of file
From 35fd5b7ae42cad5e35d9cebdc0eb84ff1b1ec802 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 19:30:27 +1100
Subject: [PATCH 56/68] Extracted out is path expression checking logic
---
pkg/yqlib/data_navigator.go | 5 ++---
pkg/yqlib/delete_navigation_strategy.go | 1 +
.../filter_matching_node_navigation_strategy.go | 1 +
pkg/yqlib/lib.go | 3 ++-
pkg/yqlib/navigation_strategy.go | 13 ++++++++-----
pkg/yqlib/path_parser.go | 5 +++++
pkg/yqlib/read_navigation_strategy.go | 1 +
pkg/yqlib/update_navigation_strategy.go | 1 +
8 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go
index 8dedf9a8..b3932163 100644
--- a/pkg/yqlib/data_navigator.go
+++ b/pkg/yqlib/data_navigator.go
@@ -2,7 +2,6 @@ package yqlib
import (
"strconv"
- "strings"
errors "github.com/pkg/errors"
yaml "gopkg.in/yaml.v3"
@@ -70,7 +69,7 @@ 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!", len(value.Content))
- if head == "*" || head == "**" || strings.Contains(head, "==") {
+ if n.navigationStrategy.GetPathParser().IsPathExpression(head) {
return n.splatArray(value, head, tail, pathStack)
} else if head == "+" {
return n.appendArray(value, head, tail, pathStack)
@@ -117,7 +116,7 @@ func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pat
return errorVisiting
}
- if traversedEntry || head == "*" || head == "**" || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) {
+ if traversedEntry || n.navigationStrategy.GetPathParser().IsPathExpression(head) || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) {
return nil
}
diff --git a/pkg/yqlib/delete_navigation_strategy.go b/pkg/yqlib/delete_navigation_strategy.go
index fc83c9e2..becad835 100644
--- a/pkg/yqlib/delete_navigation_strategy.go
+++ b/pkg/yqlib/delete_navigation_strategy.go
@@ -10,6 +10,7 @@ func DeleteNavigationStrategy(pathElementToDelete string) NavigationStrategy {
parser := NewPathParser()
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
+ pathParser: parser,
followAlias: func(nodeContext NodeContext) bool {
return false
},
diff --git a/pkg/yqlib/filter_matching_node_navigation_strategy.go b/pkg/yqlib/filter_matching_node_navigation_strategy.go
index dde3b8a3..ad4ede0c 100644
--- a/pkg/yqlib/filter_matching_node_navigation_strategy.go
+++ b/pkg/yqlib/filter_matching_node_navigation_strategy.go
@@ -3,6 +3,7 @@ package yqlib
func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
+ pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return true
},
diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go
index 108ef3ac..196e3ddf 100644
--- a/pkg/yqlib/lib.go
+++ b/pkg/yqlib/lib.go
@@ -74,7 +74,8 @@ func guessKind(head string, tail []string, guess yaml.Kind) yaml.Kind {
if tail[0] == "+" || errorParsingInt == nil {
return yaml.SequenceNode
}
- if (tail[0] == "*" || tail[0] == "**" || head == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) {
+ pathParser := NewPathParser()
+ if (pathParser.IsPathExpression(tail[0]) || head == "**") && (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 a5ae1f1f..892988a7 100644
--- a/pkg/yqlib/navigation_strategy.go
+++ b/pkg/yqlib/navigation_strategy.go
@@ -36,6 +36,7 @@ type NavigationStrategy interface {
ShouldTraverse(nodeContext NodeContext, nodeKey string) bool
GetVisitedNodes() []*NodeContext
DebugVisitedNodes()
+ GetPathParser() PathParser
}
type NavigationStrategyImpl struct {
@@ -44,6 +45,11 @@ type NavigationStrategyImpl struct {
visit func(nodeContext NodeContext) error
shouldVisitExtraFn func(nodeContext NodeContext) bool
visitedNodes []*NodeContext
+ pathParser PathParser
+}
+
+func (ns *NavigationStrategyImpl) GetPathParser() PathParser {
+ return ns.pathParser
}
func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext {
@@ -68,10 +74,8 @@ func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKe
return false
}
- parser := NewPathParser()
-
return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" &&
- parser.MatchesNextPathElement(nodeContext, nodeKey))
+ ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))
}
func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
@@ -88,11 +92,10 @@ func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
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
return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
- parser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext))
+ ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext))
}
func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go
index 112d7912..38464522 100644
--- a/pkg/yqlib/path_parser.go
+++ b/pkg/yqlib/path_parser.go
@@ -8,6 +8,7 @@ import (
type PathParser interface {
ParsePath(path string) []string
MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
+ IsPathExpression(pathElement string) bool
}
type pathParser struct{}
@@ -16,6 +17,10 @@ func NewPathParser() PathParser {
return &pathParser{}
}
+func (p *pathParser) IsPathExpression(pathElement string) bool {
+ return pathElement == "*" || pathElement == "**" || strings.Contains(pathElement, "==")
+}
+
/**
* node: node that we may traverse/visit
* head: path element expression to match against
diff --git a/pkg/yqlib/read_navigation_strategy.go b/pkg/yqlib/read_navigation_strategy.go
index 70ecfb7d..4040e506 100644
--- a/pkg/yqlib/read_navigation_strategy.go
+++ b/pkg/yqlib/read_navigation_strategy.go
@@ -3,6 +3,7 @@ package yqlib
func ReadNavigationStrategy() NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
+ pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return true
},
diff --git a/pkg/yqlib/update_navigation_strategy.go b/pkg/yqlib/update_navigation_strategy.go
index e6be8b71..9cb4ac92 100644
--- a/pkg/yqlib/update_navigation_strategy.go
+++ b/pkg/yqlib/update_navigation_strategy.go
@@ -3,6 +3,7 @@ package yqlib
func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
+ pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return false
},
From a3f8f9df10f46076dc5e02f556183c9ff1dc92b3 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 19:38:16 +1100
Subject: [PATCH 57/68] more bits
---
commands_test.go | 12 ++++++++++++
yq.go | 3 +++
2 files changed, 15 insertions(+)
diff --git a/commands_test.go b/commands_test.go
index d7cebecb..bacc4960 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -103,6 +103,18 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) {
test.AssertResult(t, "4", result.Output)
}
+func TestReadWithAdvancedFilterMapCmd(t *testing.T) {
+ cmd := getRootCommand()
+ result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e[name==fred]")
+ if result.Error != nil {
+ t.Error(result.Error)
+ }
+ expectedOutput := `name: fred
+value: 3
+`
+ test.AssertResult(t, expectedOutput, result.Output)
+}
+
func TestReadWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c")
diff --git a/yq.go b/yq.go
index 931be12a..2a821756 100644
--- a/yq.go
+++ b/yq.go
@@ -100,6 +100,7 @@ yq read things.yaml a.b.c
yq r - a.b.c (reads from stdin)
yq r things.yaml a.*.c
yq r things.yaml a.**.c
+yq r things.yaml a.(child.subchild==cool).c
yq r -d1 things.yaml 'a.array[0].blah'
yq r things.yaml 'a.array[*].blah'
yq r -- things.yaml --key-starting-with-dashes.blah
@@ -121,6 +122,7 @@ func createWriteCmd() *cobra.Command {
yq write things.yaml a.b.c true
yq write things.yaml 'a.*.c' true
yq write things.yaml 'a.**' true
+yq write things.yaml a.(child.subchild==cool).c true
yq write things.yaml a.b.c --tag '!!str' true
yq write things.yaml a.b.c --tag '!!float' 3
yq write --inplace -- things.yaml a.b.c --cat
@@ -187,6 +189,7 @@ func createDeleteCmd() *cobra.Command {
Example: `
yq delete things.yaml a.b.c
yq delete things.yaml a.*.c
+yq delete things.yaml a.(child.subchild==cool).c
yq delete things.yaml a.**
yq delete --inplace things.yaml a.b.c
yq delete --inplace -- things.yaml --key-starting-with-dash
From 350a8343e9cc6e165afc208922505c8250cfe156 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Sat, 11 Jan 2020 19:52:33 +1100
Subject: [PATCH 58/68] adv search with prefix!
---
commands_test.go | 2 +-
.../filter_matching_node_navigation_strategy.go | 2 +-
pkg/yqlib/path_parser.go | 17 +++++++++++------
yq.go | 6 +++---
4 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/commands_test.go b/commands_test.go
index bacc4960..9a514d66 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -105,7 +105,7 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) {
func TestReadWithAdvancedFilterMapCmd(t *testing.T) {
cmd := getRootCommand()
- result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e[name==fred]")
+ result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e[name==fr*]")
if result.Error != nil {
t.Error(result.Error)
}
diff --git a/pkg/yqlib/filter_matching_node_navigation_strategy.go b/pkg/yqlib/filter_matching_node_navigation_strategy.go
index ad4ede0c..ac49e1c4 100644
--- a/pkg/yqlib/filter_matching_node_navigation_strategy.go
+++ b/pkg/yqlib/filter_matching_node_navigation_strategy.go
@@ -15,7 +15,7 @@ func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy {
},
shouldVisitExtraFn: func(nodeContext NodeContext) bool {
log.Debug("does %v match %v ? %v", nodeContext.Node.Value, value, nodeContext.Node.Value == value)
- return nodeContext.Node.Value == value
+ return matchesString(value, nodeContext.Node.Value)
},
}
}
diff --git a/pkg/yqlib/path_parser.go b/pkg/yqlib/path_parser.go
index 38464522..a06ff048 100644
--- a/pkg/yqlib/path_parser.go
+++ b/pkg/yqlib/path_parser.go
@@ -17,6 +17,15 @@ func NewPathParser() PathParser {
return &pathParser{}
}
+func matchesString(expression string, value string) bool {
+ var prefixMatch = strings.TrimSuffix(expression, "*")
+ if prefixMatch != expression {
+ log.Debug("prefix match, %v", strings.HasPrefix(value, prefixMatch))
+ return strings.HasPrefix(value, prefixMatch)
+ }
+ return value == expression
+}
+
func (p *pathParser) IsPathExpression(pathElement string) bool {
return pathElement == "*" || pathElement == "**" || strings.Contains(pathElement, "==")
}
@@ -60,12 +69,8 @@ func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey str
return true
}
}
- var prefixMatch = strings.TrimSuffix(head, "*")
- if prefixMatch != head {
- log.Debug("prefix match, %v", strings.HasPrefix(nodeKey, prefixMatch))
- return strings.HasPrefix(nodeKey, prefixMatch)
- }
- return nodeKey == head
+
+ return matchesString(head, nodeKey)
}
func (p *pathParser) ParsePath(path string) []string {
diff --git a/yq.go b/yq.go
index 2a821756..fc160538 100644
--- a/yq.go
+++ b/yq.go
@@ -100,7 +100,7 @@ yq read things.yaml a.b.c
yq r - a.b.c (reads from stdin)
yq r things.yaml a.*.c
yq r things.yaml a.**.c
-yq r things.yaml a.(child.subchild==cool).c
+yq r things.yaml a.(child.subchild==co*).c
yq r -d1 things.yaml 'a.array[0].blah'
yq r things.yaml 'a.array[*].blah'
yq r -- things.yaml --key-starting-with-dashes.blah
@@ -122,7 +122,7 @@ func createWriteCmd() *cobra.Command {
yq write things.yaml a.b.c true
yq write things.yaml 'a.*.c' true
yq write things.yaml 'a.**' true
-yq write things.yaml a.(child.subchild==cool).c true
+yq write things.yaml a.(child.subchild==co*).c true
yq write things.yaml a.b.c --tag '!!str' true
yq write things.yaml a.b.c --tag '!!float' 3
yq write --inplace -- things.yaml a.b.c --cat
@@ -189,7 +189,7 @@ func createDeleteCmd() *cobra.Command {
Example: `
yq delete things.yaml a.b.c
yq delete things.yaml a.*.c
-yq delete things.yaml a.(child.subchild==cool).c
+yq delete things.yaml a.(child.subchild==co*).c
yq delete things.yaml a.**
yq delete --inplace things.yaml a.b.c
yq delete --inplace -- things.yaml --key-starting-with-dash
From 2d7be26ad53c22d78eb05c1add71eaca9f045033 Mon Sep 17 00:00:00 2001
From: Mike Farah
Date: Mon, 13 Jan 2020 16:58:11 +1100
Subject: [PATCH 59/68] wip update docs
---
README.md | 2 +-
docs/404.html | 12 +
docs/convert/index.html | 33 +-
docs/create/index.html | 82 +--
docs/delete/index.html | 261 +------
docs/index.html | 14 +-
docs/merge/index.html | 13 +-
docs/path_expressions/index.html | 862 +++++++++++++++++++++++
docs/prefix/index.html | 18 +-
docs/read/index.html | 72 +-
docs/search/search_index.json | 2 +-
docs/sitemap.xml | 21 +-
docs/sitemap.xml.gz | Bin 201 -> 201 bytes
docs/snippets/niche/index.html | 447 ------------
docs/snippets/works_with_json/index.html | 385 ----------
docs/write/index.html | 72 +-
examples/sample.yaml | 22 +-
mkdocs.yml | 1 +
mkdocs/convert.md | 28 +-
mkdocs/create.md | 29 +-
mkdocs/delete.md | 141 +---
mkdocs/index.md | 2 +-
mkdocs/merge.md | 29 +-
mkdocs/path_expressions.md | 208 ++++++
mkdocs/prefix.md | 39 +-
mkdocs/read.md | 105 +--
mkdocs/snippets/niche.md | 35 -
mkdocs/snippets/works_with_json.md | 1 -
mkdocs/write.md | 137 +---
yq.go | 77 +-
30 files changed, 1401 insertions(+), 1749 deletions(-)
create mode 100644 docs/path_expressions/index.html
delete mode 100644 docs/snippets/niche/index.html
delete mode 100644 docs/snippets/works_with_json/index.html
create mode 100644 mkdocs/path_expressions.md
delete mode 100644 mkdocs/snippets/niche.md
delete mode 100644 mkdocs/snippets/works_with_json.md
diff --git a/README.md b/README.md
index 69f3c5df..c64d83e8 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ sudo apt install yq -y
```
### or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively:
```
-GO111MODULE=on go get github.com/mikefarah/yq/v2
+GO111MODULE=on go get github.com/mikefarah/yq/v3
```
## Run with Docker
diff --git a/docs/404.html b/docs/404.html
index d9cfab02..a6337614 100644
--- a/docs/404.html
+++ b/docs/404.html
@@ -248,6 +248,18 @@
+
+
+ Path Expressions
+
+
+
+
+
+
+
+
+
Write/Update
diff --git a/docs/convert/index.html b/docs/convert/index.html
index cc2f4922..7ed7ef07 100644
--- a/docs/convert/index.html
+++ b/docs/convert/index.html
@@ -252,6 +252,18 @@
+
+
+ Path Expressions
+
+
+
+
+
+
+
+
+
Write/Update
@@ -414,20 +426,37 @@
Convert
Yaml to Json
-To convert output to json, use the --tojson (or -j) flag. This can only be used with the read command.
+To convert output to json, use the --tojson (or -j) flag. This is supported by all commands.
+Each matching yaml node will be converted to json and printed out on a separate line.
Given a sample.yaml file of:
b:
c: 2
then
-yq r -j sample.yaml b.c
+yq r -j sample.yaml
will output
{"b":{"c":2}}
+Given a sample.yaml file of:
+bob:
+ c: 2
+bab:
+ c: 5
+
+
+then
+yq r -j sample.yaml b*
+
+
+will output
+{"c":2}
+{"c":5}
+
+
Json to Yaml
To read in json, just pass in a json file instead of yaml, it will just work :)
e.g given a json file
diff --git a/docs/create/index.html b/docs/create/index.html
index 643060d3..7114b530 100644
--- a/docs/create/index.html
+++ b/docs/create/index.html
@@ -252,6 +252,18 @@
+
+
+ Path Expressions
+
+
+
+
+
+
+
+
+
Write/Update
@@ -325,20 +337,6 @@
-
-
- Keys with dots
-
-
-
-
-
-
- Keys (and values) with leading dashes
-
-
-
-
@@ -406,20 +404,6 @@
-
-
- Keys with dots
-
-
-
-
-
-
- Keys (and values) with leading dashes
-
-
-
-
@@ -441,10 +425,11 @@
Create
- Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.
-yq n <path> <new value>
+ yq n <path_expression> <new value>
+Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.
+See docs for path expression
Creating a simple yaml file
yq n b.c cat
@@ -457,8 +442,11 @@
Creating using a create script
Create scripts follow the same format as the update scripts.
Given a script create_instructions.yaml of:
-b.c: 3
-b.e[+].name: Howdy Partner
+- command: update
+ path: b.c
+ value:
+ #great
+ things: frog # wow!
then
@@ -467,38 +455,14 @@ b.e[+].name: Howdy Partner
will output:
b:
- c: 3
- e:
- - name: Howdy Partner
+ c:
+ #great
+ things: frog # wow!
You can also pipe the instructions in:
cat create_instructions.yaml | yq n -s -
-
-Keys with dots
-When specifying a key that has a dot use key lookup indicator.
-b:
- foo.bar: 7
-
-
-yaml r sample.yaml 'b[foo.bar]'
-
-
-yaml w sample.yaml 'b[foo.bar]' 9
-
-
-Any valid yaml key can be specified as part of a key lookup.
-Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
-Keys (and values) with leading dashes
-If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).
-To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:
-yq n -t -- --key --value
-
-
-Will result in
-`
---key: --value
diff --git a/docs/delete/index.html b/docs/delete/index.html
index e23d0abc..42b4d97f 100644
--- a/docs/delete/index.html
+++ b/docs/delete/index.html
@@ -94,7 +94,7 @@
-
+
Skip to content
@@ -252,6 +252,18 @@
+
+
+ Path Expressions
+
+
+
+
+
+
+
+
+
Write/Update
@@ -299,25 +311,11 @@
Table of contents
-
-
-
-
- Splat
-
-
-
-
-
-
- Prefix Splat
-
-
-
-
-
-
- Array Splat
-
-
@@ -362,20 +339,6 @@
-
-
- Keys with dots
-
-
-
-
-
-
- Keys (and values) with leading dashes
-
-
-
-
@@ -441,25 +404,11 @@
Table of contents
-
-
-
-
- Splat
-
-
-
-
-
-
- Prefix Splat
-
-
-
-
-
-
- Array Splat
-
-
@@ -504,20 +432,6 @@
-
-
- Keys with dots
-
-
-
-
-
-
- Keys (and values) with leading dashes
-
-
-
-
@@ -539,49 +453,16 @@
Delete
- yq d <yaml_file> <path_to_delete>
-
-
-To Stdout
-Given a sample.yaml file of:
-b:
- c: 2
- apples: green
-
-
-then
-yq d sample.yaml b.c
-
-
-will output:
-b:
- apples: green
+ yq delete <yaml_file|-> <path_expression>
+The delete command will delete all the matching nodes for the path expression in the given yaml input.
+See docs for path expression for more details.
From STDIN
+Use "-" (without quotes) inplace of a file name if you wish to pipe in input from STDIN.
cat sample.yaml | yq d - b.c
-Deleting array elements
-Given a sample.yaml file of:
-b:
- c:
- - 1
- - 2
- - 3
-
-
-then
-yq d sample.yaml 'b.c[1]'
-
-
-will output:
-b:
- c:
- - 1
- - 3
-
-
Deleting nodes in-place
Given a sample.yaml file of:
b:
@@ -594,91 +475,6 @@
will update the sample.yaml file so that the 'c' node is deleted
-Splat
-Given a sample.yaml file of:
----
-bob:
- item1:
- cats: bananas
- dogs: woof
- item2:
- cats: apples
- dogs: woof2
- thing:
- cats: oranges
- dogs: woof3
-
-
-then
-yq d sample.yaml bob.*.cats
-
-
-will output:
----
-bob:
- item1:
- dogs: woof
- item2:
- dogs: woof2
- thing:
- dogs: woof3
-
-
-Prefix Splat
-Given a sample.yaml file of:
----
-bob:
- item1:
- cats: bananas
- dogs: woof
- item2:
- cats: apples
- dogs: woof2
- thing:
- cats: oranges
- dogs: woof3
-
-
-then
-yq d sample.yaml bob.item*.cats
-
-
-will output:
----
-bob:
- item1:
- dogs: woof
- item2:
- dogs: woof2
- thing:
- cats: oranges
- dogs: woof3
-
-
-Array Splat
-Given a sample.yaml file of:
----
-bob:
-- cats: bananas
- dogs: woof
-- cats: apples
- dogs: woof2
-- cats: oranges
- dogs: woof3
-
-
-then
-yq d sample.yaml bob.[*].cats
-
-
-will output:
----
-bob:
-- dogs: woof
-- dogs: woof2
-- dogs: woof3
-
-
Multiple Documents - delete from single document
Given a sample.yaml file of:
something: else
@@ -723,29 +519,6 @@ b:
Note that '*' is in quotes to avoid being interpreted by your shell.
-Keys with dots
-When specifying a key that has a dot use key lookup indicator.
-b:
- foo.bar: 7
-
-
-yaml r sample.yaml 'b[foo.bar]'
-
-
-yaml w sample.yaml 'b[foo.bar]' 9
-
-
-Any valid yaml key can be specified as part of a key lookup.
-Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
-Keys (and values) with leading dashes
-If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).
-To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:
-yq n -t -- --key --value
-
-
-Will result in
-`
---key: --value
diff --git a/docs/index.html b/docs/index.html
index 4c1b3d95..3528e93d 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -290,6 +290,18 @@
+
+
+ Path Expressions
+
+
+
+
+
+
+
+
+
Write/Update
@@ -422,7 +434,7 @@ sudo apt install yq -y
or, Download latest binary or alternatively:
-go get gopkg.in/mikefarah/yq.v2
+GO111MODULE=on go get github.com/mikefarah/yq/v3
View on GitHub
diff --git a/docs/merge/index.html b/docs/merge/index.html
index ce5fa44a..0d30aee9 100644
--- a/docs/merge/index.html
+++ b/docs/merge/index.html
@@ -252,6 +252,18 @@
+
+
+ Path Expressions
+
+
+
+
+
+
+
+
+
Write/Update
@@ -608,7 +620,6 @@ d: hi
Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a).
-Append cannot be used with overwrite, if both flags are given then append is ignored.
Multiple Documents - merge into single document
Currently yq only has multi-document support for the first document being merged into. The remaining yaml files will have their first document selected.
Given a data1.yaml file of:
diff --git a/docs/path_expressions/index.html b/docs/path_expressions/index.html
new file mode 100644
index 00000000..1b36cb77
--- /dev/null
+++ b/docs/path_expressions/index.html
@@ -0,0 +1,862 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Path Expressions - Yq
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Skip to content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Path Expressions
+
+ Path expressions are used to deeply navigate and match particular yaml nodes.
+As a general rule, you should wrap paths in quotes in the CLI to prevent your interpreter from processing '*, []' and other special characters.
+Simple expressions
+Maps
+a.b.c
+a:
+ b:
+ c: thing # MATCHES
+
+
+Arrays
+a.b[1].c
+a:
+ b:
+ - c: thing0
+ - c: thing1 # MATCHES
+ - c: thing2
+
+
+Appending to arrays
+(e.g. when using the write command)
+a.b[+].c
+a:
+ b:
+ - c: thing0
+
+
+Will add a new entry:
+a:
+ b:
+ - c: thing0
+ - c: thing1 # NEW entry from [+] on B array.
+
+
+Splat
+Maps
+a.*.c
+a:
+ b1:
+ c: thing # MATCHES
+ b2:
+ c: thing # MATCHES
+
+
+Arrays
+a.b[*].c
+a:
+ b:
+ - c: thing0 # MATCHES
+ - c: thing1 # MATCHES
+ - c: thing2 # MATCHES
+
+
+Deep Splat
+'**' will match arbitrary nodes for both maps and arrays:
+a.**.c
+a:
+ b1:
+ c: thing1 # MATCHES
+ b2:
+ c: thing2 # MATCHES
+ b3:
+ d:
+ - f:
+ c: thing3 # MATCHES
+ - f:
+ g:
+ c: thing4 # MATCHES
+
+
+Finding parents with particular children nodes
+a.(b.d==cat).b.c
+a:
+ - b:
+ c: thing0
+ d: leopard
+ ba: fast
+ - b:
+ c: thing1 # MATCHES
+ d: cat
+ ba: meowy
+ - b:
+ c: thing2
+ d: caterpillar
+ ba: icky
+ - b:
+ c: thing3 # MATCHES
+ d: cat
+ ba: also meowy
+
+
+With prefixes
+a.(b.d==cat*).c
+a:
+ - b:
+ c: thing0
+ d: leopard
+ ba: fast
+ - b:
+ c: thing1 # MATCHES
+ d: cat
+ ba: meowy
+ - b:
+ c: thing2 # MATCHES
+ d: caterpillar
+ ba: icky
+ - b:
+ c: thing3 # MATCHES
+ d: cat
+ ba: also meowy
+
+
+Special Characters
+Keys with dots
+When specifying a key that has a dot use key lookup indicator.
+b:
+ foo.bar: 7
+
+
+yaml r sample.yaml 'b[foo.bar]'
+
+
+yaml w sample.yaml 'b[foo.bar]' 9
+
+
+Any valid yaml key can be specified as part of a key lookup.
+Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
+Keys (and values) with leading dashes
+If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).
+To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:
+yq n -t -- --key --value
+
+
+Will result in
+--key: --value
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/prefix/index.html b/docs/prefix/index.html
index 44c83045..e83b0179 100644
--- a/docs/prefix/index.html
+++ b/docs/prefix/index.html
@@ -252,6 +252,18 @@
+
+
+ Path Expressions
+
+
+
+
+
+
+
+
+
Write/Update
@@ -455,11 +467,11 @@
Prefix
- Paths can be prefixed using the 'prefix' command.
-The complete yaml content will be nested inside the new prefix path.
-yq p <yaml_file> <path>
+ yq p <yaml_file> <path>
+Prefixes a yaml document with the given path expression. The complete yaml content will be nested inside the new prefix path.
+See docs for path expression for more details.
To Stdout
Given a data1.yaml file of:
a: simple
diff --git a/docs/read/index.html b/docs/read/index.html
index 504b7296..9383334b 100644
--- a/docs/read/index.html
+++ b/docs/read/index.html
@@ -319,20 +319,6 @@
-
-
- Keys with dots
-
-
-
-
-
-
- Keys (and values) with leading dashes
-
-
-
-
@@ -349,6 +335,18 @@
+
+
+ Path Expressions
+
+
+
+
+
+
+
+
+
Write/Update
@@ -490,20 +488,6 @@
-
-
- Keys with dots
-
-
-
-
-
-
- Keys (and values) with leading dashes
-
-
-
-
@@ -525,10 +509,11 @@
Read
- yq r <yaml_file|json_file> <path>
+ yq r <yaml_file|json_file> <path_expression>
-This command can take a json file as input too, and will output yaml unless specified to export as json (-j)
+Returns the matching nodes of the path expression for the given yaml file (or STDIN).
+See docs for path expression for more details.
Basic
Given a sample.yaml file of:
b:
@@ -662,29 +647,6 @@ e.g.: given a sample file of
Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
-Keys with dots
-When specifying a key that has a dot use key lookup indicator.
-b:
- foo.bar: 7
-
-
-yaml r sample.yaml 'b[foo.bar]'
-
-
-yaml w sample.yaml 'b[foo.bar]' 9
-
-
-Any valid yaml key can be specified as part of a key lookup.
-Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
-Keys (and values) with leading dashes
-If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).
-To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:
-yq n -t -- --key --value
-
-
-Will result in
-`
---key: --value
@@ -720,13 +682,13 @@ e.g.: given a sample file of
-