1
0
mirror of https://github.com/taigrr/yq synced 2025-01-18 04:53:17 -08:00

read command

This commit is contained in:
Mike Farah
2020-10-13 12:51:37 +11:00
parent 6a0a4efa7b
commit d19e9f6917
37 changed files with 2308 additions and 3604 deletions

5
pkg/yqlib/constants.go Normal file
View File

@@ -0,0 +1,5 @@
package yqlib
import "gopkg.in/op/go-logging.v1"
var log = logging.MustGetLogger("yq-lib")

View File

@@ -1,295 +0,0 @@
package yqlib
import (
"fmt"
"strconv"
yaml "gopkg.in/yaml.v3"
)
type DataNavigator interface {
Traverse(value *yaml.Node, path []interface{}) error
}
type navigator struct {
navigationStrategy NavigationStrategy
}
func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
return &navigator{
navigationStrategy: NavigationStrategy,
}
}
func (n *navigator) Traverse(value *yaml.Node, path []interface{}) error {
emptyArray := make([]interface{}, 0)
log.Debugf("Traversing path %v", pathStackToString(path))
return n.doTraverse(value, "", path, emptyArray)
}
func (n *navigator) doTraverse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
log.Debug("head %v", head)
DebugNode(value)
var nodeContext = NewNodeContext(value, head, tail, pathStack)
var errorDeepSplatting error
// no need to deeply traverse the DocumentNode, as it's already covered by its first child.
if head == "**" && value.Kind != yaml.DocumentNode && value.Kind != yaml.ScalarNode && n.navigationStrategy.ShouldDeeplyTraverse(nodeContext) {
if len(pathStack) == 0 || pathStack[len(pathStack)-1] != "<<" {
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 value.Kind == yaml.DocumentNode {
log.Debugf("its a document, diving into %v", head)
DebugNode(value)
return n.recurse(value, head, tail, pathStack)
} else if len(tail) > 0 && value.Kind != yaml.ScalarNode {
log.Debugf("diving into %v", tail[0])
DebugNode(value)
return n.recurse(value, tail[0], tail[1:], pathStack)
}
return n.navigationStrategy.Visit(nodeContext)
}
func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
if original.Kind != expectedKind {
log.Debug("wanted %v but it was %v, overriding", KindString(expectedKind), KindString(original.Kind))
return &yaml.Node{Kind: expectedKind}
}
return original
}
func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
log.Debug("recursing, processing %v, pathStack %v", head, pathStackToString(pathStack))
nodeContext := NewNodeContext(value, head, tail, pathStack)
if head == "**" && !n.navigationStrategy.ShouldOnlyDeeplyVisitLeaves(nodeContext) {
nodeContext.IsMiddleNode = true
errorVisitingDeeply := n.navigationStrategy.Visit(nodeContext)
if errorVisitingDeeply != nil {
return errorVisitingDeeply
}
}
switch value.Kind {
case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2)
headString := fmt.Sprintf("%v", head)
return n.recurseMap(value, headString, tail, pathStack)
case yaml.SequenceNode:
log.Debug("its a sequence of %v things!", len(value.Content))
switch head := head.(type) {
case int64:
return n.recurseArray(value, head, head, tail, pathStack)
default:
if head == "+" {
return n.appendArray(value, head, tail, pathStack)
} else if len(value.Content) == 0 && head == "**" {
return n.navigationStrategy.Visit(nodeContext)
}
return n.splatArray(value, head, tail, pathStack)
}
case yaml.AliasNode:
log.Debug("its an alias!")
DebugNode(value.Alias)
if n.navigationStrategy.FollowAlias(nodeContext) {
log.Debug("following the alias")
return n.recurse(value.Alias, head, tail, pathStack)
}
return nil
case yaml.DocumentNode:
return n.doTraverse(value.Content[0], head, tail, pathStack)
default:
return n.navigationStrategy.Visit(nodeContext)
}
}
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []interface{}, pathStack []interface{}) error {
traversedEntry := false
errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error {
log.Debug("recurseMap: visitMatchingEntries for %v", contents[indexInMap].Value)
n.navigationStrategy.DebugVisitedNodes()
newPathStack := append(pathStack, contents[indexInMap].Value)
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")
traversedEntry = true
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()
return errorTraversing
} else {
log.Debug("nope not traversing")
}
return nil
})
if errorVisiting != nil {
return errorVisiting
}
if len(value.Content) == 0 && head == "**" {
return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack))
} else if traversedEntry || n.navigationStrategy.GetPathParser().IsPathExpression(head) || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) {
return nil
}
_, errorParsingInt := strconv.ParseInt(head, 10, 64)
mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
if errorParsingInt == nil {
// fixes a json encoding problem where keys that look like numbers
// get treated as numbers and cannot be used in a json map
mapEntryKey.Style = yaml.LiteralStyle
}
value.Content = append(value.Content, &mapEntryKey)
mapEntryValue := yaml.Node{Kind: guessKind(head, tail, 0)}
value.Content = append(value.Content, &mapEntryValue)
log.Debug("adding a new node %v - def a string", 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 []interface{}, pathStack []interface{}, visit mapVisitorFn) error {
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
}
}
return nil
}
func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error {
var contents = node.Content
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
// 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)) {
return errorVisitedDirectEntries
}
return n.visitAliases(contents, head, tail, pathStack, visit)
}
func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []interface{}, 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.
// a node can either be
// an alias to one other node (e.g. <<: *blah)
// or a sequence of aliases (e.g. <<: [*blah, *foo])
log.Debug("checking for aliases, head: %v, pathstack: %v", head, pathStackToString(pathStack))
for index := len(contents) - 2; index >= 0; index = index - 2 {
if contents[index+1].Kind == yaml.AliasNode && contents[index].Value == "<<" {
valueNode := contents[index+1]
log.Debug("found an alias")
DebugNode(contents[index])
DebugNode(valueNode)
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...
errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit)
if errorVisitingAliasSeq != nil {
return errorVisitingAliasSeq
}
}
}
return nil
}
func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []interface{}, 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)
errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit)
if errorInAlias != nil {
return errorInAlias
}
}
}
return nil
}
func (n *navigator) splatArray(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
for index, childValue := range value.Content {
log.Debug("processing")
DebugNode(childValue)
childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind))
newPathStack := append(pathStack, index)
if n.navigationStrategy.ShouldTraverse(NewNodeContext(childValue, head, tail, newPathStack), childValue.Value) {
// here we should not deeply traverse the array if we are appending..not sure how to do that.
// need to visit instead...
// easiest way is to pop off the head and pass the rest of the tail in.
var err = n.doTraverse(childValue, head, tail, newPathStack)
if err != nil {
return err
}
}
}
return nil
}
func (n *navigator) appendArray(value *yaml.Node, head interface{}, tail []interface{}, 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)
return n.doTraverse(&newNode, head, tail, append(pathStack, len(value.Content)-1))
}
func (n *navigator) recurseArray(value *yaml.Node, index int64, head interface{}, tail []interface{}, pathStack []interface{}) error {
var contentLength = int64(len(value.Content))
for contentLength <= index {
value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)})
contentLength = int64(len(value.Content))
}
var indexToUse = index
if indexToUse < 0 {
indexToUse = contentLength + indexToUse
}
if indexToUse < 0 {
return fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
}
value.Content[indexToUse] = n.getOrReplace(value.Content[indexToUse], guessKind(head, tail, value.Content[indexToUse].Kind))
return n.doTraverse(value.Content[indexToUse], head, tail, append(pathStack, index))
}

View File

@@ -1 +0,0 @@
package yqlib

View File

@@ -1,73 +0,0 @@
package yqlib
import (
yaml "gopkg.in/yaml.v3"
)
func DeleteNavigationStrategy(pathElementToDelete interface{}) NavigationStrategy {
parser := NewPathParser()
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: parser,
followAlias: func(nodeContext NodeContext) bool {
return false
},
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
return false
},
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
log.Debug("need to find and delete %v in here (%v)", pathElementToDelete, pathStackToString(nodeContext.PathStack))
DebugNode(node)
if node.Kind == yaml.SequenceNode {
newContent := deleteFromArray(parser, node.Content, nodeContext.PathStack, pathElementToDelete)
node.Content = newContent
} else if node.Kind == yaml.MappingNode {
node.Content = deleteFromMap(parser, node.Content, nodeContext.PathStack, pathElementToDelete)
}
return nil
},
}
}
func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, pathElementToDelete interface{}) []*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(NewNodeContext(keyNode, pathElementToDelete, make([]interface{}, 0), pathStack), keyNode.Value) {
log.Debug("adding node %v", keyNode.Value)
newContents = append(newContents, keyNode, valueNode)
} else {
log.Debug("skipping node %v", keyNode.Value)
}
}
return newContents
}
func deleteFromArray(pathParser PathParser, content []*yaml.Node, pathStack []interface{}, pathElementToDelete interface{}) []*yaml.Node {
switch pathElementToDelete := pathElementToDelete.(type) {
case int64:
return deleteIndexInArray(content, pathElementToDelete)
default:
log.Debug("%v is not a numeric index, finding matching patterns", pathElementToDelete)
var newArray = make([]*yaml.Node, 0)
for _, childValue := range content {
if !pathParser.MatchesNextPathElement(NewNodeContext(childValue, pathElementToDelete, make([]interface{}, 0), pathStack), childValue.Value) {
newArray = append(newArray, childValue)
}
}
return newArray
}
}
func deleteIndexInArray(content []*yaml.Node, index int64) []*yaml.Node {
log.Debug("deleting index %v in array", index)
if index >= int64(len(content)) {
log.Debug("index %v is greater than content length %v", index, len(content))
return content
}
return append(content[:index], content[index+1:]...)
}

View File

@@ -1,15 +0,0 @@
package yqlib
func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
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 matchesString(value, nodeContext.Node.Value)
},
}
}

View File

@@ -1,208 +0,0 @@
package yqlib
import (
"bytes"
"fmt"
"strconv"
"strings"
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
Overwrite bool
DontUpdateNodeValue bool
DontUpdateNodeContent bool
CommentsMergeStrategy CommentsMergeStrategy
}
func KindString(kind yaml.Kind) string {
switch kind {
case yaml.ScalarNode:
return "ScalarNode"
case yaml.SequenceNode:
return "SequenceNode"
case yaml.MappingNode:
return "MappingNode"
case yaml.DocumentNode:
return "DocumentNode"
case yaml.AliasNode:
return "AliasNode"
default:
return "unknown!"
}
}
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)
errorEncoding := encoder.Encode(value)
if errorEncoding != nil {
log.Error("Error debugging node, %v", errorEncoding.Error())
}
encoder.Close()
log.Debug("Tag: %v, Kind: %v, Anchor: %v", value.Tag, KindString(value.Kind), value.Anchor)
log.Debug("Head Comment: %v", value.HeadComment)
log.Debug("Line Comment: %v", value.LineComment)
log.Debug("FootComment Comment: %v", value.FootComment)
log.Debug("\n%v", buf.String())
}
}
func pathStackToString(pathStack []interface{}) string {
return mergePathStackToString(pathStack, UpdateArrayMergeStrategy)
}
func mergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string {
var sb strings.Builder
for index, path := range pathStack {
switch path.(type) {
case int, int64:
if arrayMergeStrategy == AppendArrayMergeStrategy {
sb.WriteString("[+]")
} else {
sb.WriteString(fmt.Sprintf("[%v]", path))
}
default:
s := fmt.Sprintf("%v", path)
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
hasDoubleQuotes := strings.Contains(s, "\"")
wrappingCharacterStart := "\""
wrappingCharacterEnd := "\""
if hasDoubleQuotes {
wrappingCharacterStart = "("
wrappingCharacterEnd = ")"
}
if hasSpecial || errParsingInt == nil {
sb.WriteString(wrappingCharacterStart)
}
sb.WriteString(s)
if hasSpecial || errParsingInt == nil {
sb.WriteString(wrappingCharacterEnd)
}
}
if index < len(pathStack)-1 {
sb.WriteString(".")
}
}
return sb.String()
}
func guessKind(head interface{}, tail []interface{}, guess yaml.Kind) yaml.Kind {
log.Debug("guessKind: 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 next = tail[0]
switch next.(type) {
case int64:
return yaml.SequenceNode
default:
var nextString = fmt.Sprintf("%v", next)
if nextString == "+" {
return yaml.SequenceNode
}
pathParser := NewPathParser()
if pathParser.IsPathExpression(nextString) && (guess == yaml.SequenceNode || guess == yaml.MappingNode) {
return guess
} else if guess == yaml.AliasNode {
log.Debug("guess was an alias, okey doke.")
return guess
} else if head == "**" {
log.Debug("deep wildcard, go with the guess")
return guess
}
log.Debug("forcing a mapping node")
log.Debug("yaml.SequenceNode %v", guess == yaml.SequenceNode)
log.Debug("yaml.ScalarNode %v", guess == yaml.ScalarNode)
return yaml.MappingNode
}
}
type YqLib interface {
Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
New(path string) yaml.Node
PathStackToString(pathStack []interface{}) string
MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
}
type lib struct {
parser PathParser
}
func NewYqLib() YqLib {
return &lib{
parser: NewPathParser(),
}
}
func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
var paths = l.parser.ParsePath(path)
navigationStrategy := ReadNavigationStrategy()
navigator := NewDataNavigator(navigationStrategy)
error := navigator.Traverse(rootNode, paths)
return navigationStrategy.GetVisitedNodes(), error
}
func (l *lib) GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) {
var paths = l.parser.ParsePath(path)
navigationStrategy := ReadForMergeNavigationStrategy(arrayMergeStrategy)
navigator := NewDataNavigator(navigationStrategy)
error := navigator.Traverse(rootNode, paths)
return navigationStrategy.GetVisitedNodes(), error
}
func (l *lib) PathStackToString(pathStack []interface{}) string {
return pathStackToString(pathStack)
}
func (l *lib) MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string {
return mergePathStackToString(pathStack, arrayMergeStrategy)
}
func (l *lib) New(path string) yaml.Node {
var paths = l.parser.ParsePath(path)
newNode := yaml.Node{Kind: guessKind("", paths, 0)}
return newNode
}
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, autoCreate))
return navigator.Traverse(rootNode, paths)
case "merge":
var paths = l.parser.ParsePath(updateCommand.Path)
navigator := NewDataNavigator(MergeNavigationStrategy(updateCommand, autoCreate))
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(DeleteNavigationStrategy(lastBit))
return navigator.Traverse(rootNode, newTail)
default:
return fmt.Errorf("Unknown command %v", updateCommand.Command)
}
}

View File

@@ -1,176 +0,0 @@
package yqlib
import (
"testing"
"github.com/mikefarah/yq/v3/test"
)
func TestLib(t *testing.T) {
subject := NewYqLib()
t.Run("PathStackToString_Empty", func(t *testing.T) {
emptyArray := make([]interface{}, 0)
got := subject.PathStackToString(emptyArray)
test.AssertResult(t, ``, got)
})
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)
})
t.Run("MergePathStackToString", func(t *testing.T) {
array := make([]interface{}, 3)
array[0] = "a"
array[1] = 0
array[2] = "b"
got := subject.MergePathStackToString(array, AppendArrayMergeStrategy)
test.AssertResult(t, `a.[+].b`, got)
})
// 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")
// }
// })
// 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))
// })
// 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))
// })
// 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))
// })
// 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")
// }
// })
// 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
// 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
// `)
// 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"]))
// })
// 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
// 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")
// }
// })
}

View File

@@ -1,103 +0,0 @@
package yqlib
import "gopkg.in/yaml.v3"
type ArrayMergeStrategy uint32
const (
UpdateArrayMergeStrategy ArrayMergeStrategy = 1 << iota
OverwriteArrayMergeStrategy
AppendArrayMergeStrategy
)
type CommentsMergeStrategy uint32
const (
SetWhenBlankCommentsMergeStrategy CommentsMergeStrategy = 1 << iota
IgnoreCommentsMergeStrategy
OverwriteCommentsMergeStrategy
AppendCommentsMergeStrategy
)
func MergeNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return false
},
autoCreateMap: func(nodeContext NodeContext) bool {
return autoCreate
},
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
changesToApply := updateCommand.Value
if node.Kind == yaml.DocumentNode && changesToApply.Kind != yaml.DocumentNode {
// when the path is empty, it matches both the top level pseudo document node
// and the actual top level node (e.g. map/sequence/whatever)
// so when we are updating with no path, make sure we update the right node.
node = node.Content[0]
}
log.Debug("going to update")
DebugNode(node)
log.Debug("with")
DebugNode(changesToApply)
if updateCommand.Overwrite || node.Value == "" {
node.Value = changesToApply.Value
node.Tag = changesToApply.Tag
node.Kind = changesToApply.Kind
node.Style = changesToApply.Style
node.Anchor = changesToApply.Anchor
node.Alias = changesToApply.Alias
if !updateCommand.DontUpdateNodeContent {
node.Content = changesToApply.Content
}
} else {
log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite)
}
switch updateCommand.CommentsMergeStrategy {
case OverwriteCommentsMergeStrategy:
node.HeadComment = changesToApply.HeadComment
node.LineComment = changesToApply.LineComment
node.FootComment = changesToApply.FootComment
case SetWhenBlankCommentsMergeStrategy:
if node.HeadComment == "" {
node.HeadComment = changesToApply.HeadComment
}
if node.LineComment == "" {
node.LineComment = changesToApply.LineComment
}
if node.FootComment == "" {
node.FootComment = changesToApply.FootComment
}
case AppendCommentsMergeStrategy:
if node.HeadComment == "" {
node.HeadComment = changesToApply.HeadComment
} else {
node.HeadComment = node.HeadComment + "\n" + changesToApply.HeadComment
}
if node.LineComment == "" {
node.LineComment = changesToApply.LineComment
} else {
node.LineComment = node.LineComment + " " + changesToApply.LineComment
}
if node.FootComment == "" {
node.FootComment = changesToApply.FootComment
} else {
node.FootComment = node.FootComment + "\n" + changesToApply.FootComment
}
default:
}
log.Debug("result")
DebugNode(node)
return nil
},
}
}

View File

@@ -1,180 +0,0 @@
package yqlib
import (
"fmt"
yaml "gopkg.in/yaml.v3"
)
type NodeContext struct {
Node *yaml.Node
Head interface{}
Tail []interface{}
PathStack []interface{}
// middle nodes are nodes that match along the original path, but not a
// target match of the path. This is only relevant when ShouldOnlyDeeplyVisitLeaves is false.
IsMiddleNode bool
}
func NewNodeContext(node *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) NodeContext {
newTail := make([]interface{}, 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
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
ShouldDeeplyTraverse(nodeContext NodeContext) bool
// when deeply traversing, should we visit all matching nodes, or just leaves?
ShouldOnlyDeeplyVisitLeaves(NodeContext) bool
GetVisitedNodes() []*NodeContext
DebugVisitedNodes()
GetPathParser() PathParser
}
type NavigationStrategyImpl struct {
followAlias func(nodeContext NodeContext) bool
autoCreateMap func(nodeContext NodeContext) bool
visit func(nodeContext NodeContext) error
shouldVisitExtraFn func(nodeContext NodeContext) bool
shouldDeeplyTraverse func(nodeContext NodeContext) bool
shouldOnlyDeeplyVisitLeaves func(nodeContext NodeContext) bool
visitedNodes []*NodeContext
pathParser PathParser
}
func (ns *NavigationStrategyImpl) GetPathParser() PathParser {
return ns.pathParser
}
func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext {
return ns.visitedNodes
}
func (ns *NavigationStrategyImpl) FollowAlias(nodeContext NodeContext) bool {
if ns.followAlias != nil {
return ns.followAlias(nodeContext)
}
return true
}
func (ns *NavigationStrategyImpl) AutoCreateMap(nodeContext NodeContext) bool {
if ns.autoCreateMap != nil {
return ns.autoCreateMap(nodeContext)
}
return false
}
func (ns *NavigationStrategyImpl) ShouldDeeplyTraverse(nodeContext NodeContext) bool {
if ns.shouldDeeplyTraverse != nil {
return ns.shouldDeeplyTraverse(nodeContext)
}
return true
}
func (ns *NavigationStrategyImpl) ShouldOnlyDeeplyVisitLeaves(nodeContext NodeContext) bool {
if ns.shouldOnlyDeeplyVisitLeaves != nil {
return ns.shouldOnlyDeeplyVisitLeaves(nodeContext)
}
return true
}
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
}
return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" &&
ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))
}
func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
pathStack := nodeContext.PathStack
if len(pathStack) == 0 {
return true
}
log.Debug("tail len %v", len(nodeContext.Tail))
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)
// only visit aliases if its an exact match
return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext))
}
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)
ns.DebugVisitedNodes()
return ns.visit(nodeContext)
}
log.Debug("nope, skip it")
return nil
}
func (ns *NavigationStrategyImpl) DebugVisitedNodes() {
log.Debug("Visited Nodes:")
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) {
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: %v", pathStackToString(path1))
if len(path1) != len(path2) {
return false
}
for index, p1Value := range path1 {
p2Value := path2[index]
if p1Value != p2Value {
return false
}
}
return true
}

View File

@@ -1,153 +0,0 @@
package yqlib
import (
"fmt"
"strconv"
"strings"
yaml "gopkg.in/yaml.v3"
)
type PathParser interface {
ParsePath(path string) []interface{}
MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
IsPathExpression(pathElement string) bool
}
type pathParser struct{}
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, "==")
}
/**
* 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
* nodeKey: actual value of this nodes 'key' or index.
*/
func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool {
head := nodeContext.Head
if head == "**" || head == "*" {
return true
}
var headString = fmt.Sprintf("%v", head)
if strings.Contains(headString, "==") && nodeContext.Node.Kind != yaml.ScalarNode {
log.Debug("ooh deep recursion time")
result := strings.SplitN(headString, "==", 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("Error deep recursing - ignoring")
log.Error(err.Error())
}
log.Debug("done deep recursing, found %v matches", len(navigationStrategy.GetVisitedNodes()))
return len(navigationStrategy.GetVisitedNodes()) > 0
} else if strings.Contains(headString, "==") && nodeContext.Node.Kind == yaml.ScalarNode {
result := strings.SplitN(headString, "==", 2)
path := strings.TrimSpace(result[0])
value := strings.TrimSpace(result[1])
if path == "." {
log.Debug("need to match scalar")
return matchesString(value, nodeContext.Node.Value)
}
}
if head == "+" {
log.Debug("head is +, nodeKey is %v", nodeKey)
var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint
if err == nil {
return true
}
}
return matchesString(headString, nodeKey)
}
func (p *pathParser) ParsePath(path string) []interface{} {
var paths = make([]interface{}, 0)
if path == "" {
return paths
}
return p.parsePathAccum(paths, path)
}
func (p *pathParser) parsePathAccum(paths []interface{}, remaining string) []interface{} {
head, tail := p.nextYamlPath(remaining)
if tail == "" {
return append(paths, head)
}
return p.parsePathAccum(append(paths, head), tail)
}
func (p *pathParser) nextYamlPath(path string) (pathElement interface{}, remaining string) {
switch path[0] {
case '[':
// e.g [0].blah.cat -> we need to return "0" and "blah.cat"
var value, remainingBit = p.search(path[1:], []uint8{']'}, true)
var number, errParsingInt = strconv.ParseInt(value, 10, 64) // nolint
if errParsingInt == nil {
return number, remainingBit
}
return value, remainingBit
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)
}
}
func (p *pathParser) search(path string, matchingChars []uint8, skipNext bool) (pathElement string, remaining string) {
for i := 0; i < len(path); i++ {
var char = path[i]
if p.contains(matchingChars, char) {
var remainingStart = i + 1
if skipNext {
remainingStart = remainingStart + 1
} else if !skipNext && char != '.' {
remainingStart = i
}
if remainingStart > len(path) {
remainingStart = len(path)
}
return path[0:i], path[remainingStart:]
}
}
return path, ""
}
func (p *pathParser) contains(matchingChars []uint8, candidate uint8) bool {
for _, a := range matchingChars {
if a == candidate {
return true
}
}
return false
}

View File

@@ -1,79 +0,0 @@
package yqlib
import (
"testing"
"github.com/mikefarah/yq/v3/test"
)
var parser = NewPathParser()
var parsePathsTests = []struct {
path string
expectedPaths []interface{}
}{
{"a.b", append(make([]interface{}, 0), "a", "b")},
{"a.b.**", append(make([]interface{}, 0), "a", "b", "**")},
{"a.b.*", append(make([]interface{}, 0), "a", "b", "*")},
{"a.b[0]", append(make([]interface{}, 0), "a", "b", int64(0))},
{"a.b.0", append(make([]interface{}, 0), "a", "b", "0")},
{"a.b.d[+]", append(make([]interface{}, 0), "a", "b", "d", "+")},
{"a", append(make([]interface{}, 0), "a")},
{"a.b.c", append(make([]interface{}, 0), "a", "b", "c")},
{"\"a.b\".c", append(make([]interface{}, 0), "a.b", "c")},
{"a.\"b.c\".d", append(make([]interface{}, 0), "a", "b.c", "d")},
{"[1].a.d", append(make([]interface{}, 0), int64(1), "a", "d")},
{"a[0].c", append(make([]interface{}, 0), "a", int64(0), "c")},
{"[0]", append(make([]interface{}, 0), int64(0))},
}
func TestPathParserParsePath(t *testing.T) {
for _, tt := range parsePathsTests {
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"))
}

View File

@@ -1,37 +0,0 @@
package yqlib
import "gopkg.in/yaml.v3"
func ReadForMergeNavigationStrategy(arrayMergeStrategy ArrayMergeStrategy) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return false
},
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
return false
},
visit: func(nodeContext NodeContext) error {
return nil
},
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
if nodeContext.Node.Kind == yaml.SequenceNode && arrayMergeStrategy == OverwriteArrayMergeStrategy {
nodeContext.IsMiddleNode = false
return false
}
var isInArray = false
if len(nodeContext.PathStack) > 0 {
var lastElement = nodeContext.PathStack[len(nodeContext.PathStack)-1]
switch lastElement.(type) {
case int:
isInArray = true
default:
isInArray = false
}
}
return arrayMergeStrategy == UpdateArrayMergeStrategy || !isInArray
},
}
}

View File

@@ -1,11 +0,0 @@
package yqlib
func ReadNavigationStrategy() NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
visit: func(nodeContext NodeContext) error {
return nil
},
}
}

View File

@@ -0,0 +1,62 @@
package treeops
import (
"fmt"
"strconv"
"strings"
"gopkg.in/yaml.v3"
)
type CandidateNode struct {
Node *yaml.Node // the actual node
Path []interface{} /// the path we took to get to this node
Document uint // the document index of this node
}
func (n *CandidateNode) GetKey() string {
return fmt.Sprintf("%v - %v", n.Document, n.Path)
}
func (n *CandidateNode) PathStackToString() string {
return mergePathStackToString(n.Path)
}
func mergePathStackToString(pathStack []interface{}) string {
var sb strings.Builder
for index, path := range pathStack {
switch path.(type) {
case int, int64:
// if arrayMergeStrategy == AppendArrayMergeStrategy {
// sb.WriteString("[+]")
// } else {
sb.WriteString(fmt.Sprintf("[%v]", path))
// }
default:
s := fmt.Sprintf("%v", path)
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
hasDoubleQuotes := strings.Contains(s, "\"")
wrappingCharacterStart := "\""
wrappingCharacterEnd := "\""
if hasDoubleQuotes {
wrappingCharacterStart = "("
wrappingCharacterEnd = ")"
}
if hasSpecial || errParsingInt == nil {
sb.WriteString(wrappingCharacterStart)
}
sb.WriteString(s)
if hasSpecial || errParsingInt == nil {
sb.WriteString(wrappingCharacterEnd)
}
}
if index < len(pathStack)-1 {
sb.WriteString(".")
}
}
return sb.String()
}

View File

@@ -35,7 +35,7 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *
return nil, err
}
for _, n := range newNodes {
matchingNodeMap.Set(n.getKey(), n)
matchingNodeMap.Set(n.GetKey(), n)
}
}
@@ -46,7 +46,7 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat
var matchingNodeMap = orderedmap.NewOrderedMap()
for _, n := range matchingNodes {
matchingNodeMap.Set(n.getKey(), n)
matchingNodeMap.Set(n.GetKey(), n)
}
matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode)
@@ -63,6 +63,10 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat
}
func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
if pathNode == nil {
log.Debugf("getMatchingNodes - nothing to do")
return matchingNodes, nil
}
log.Debugf("Processing Path: %v", pathNode.PathElement.toString())
if pathNode.PathElement.PathElementType == SelfReference {
return matchingNodes, nil

View File

@@ -17,7 +17,7 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
elMap := orderedmap.NewOrderedMap()
elMap.Set(candidate.getKey(), candidate)
elMap.Set(candidate.GetKey(), candidate)
nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs)
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
if err != nil {
@@ -50,9 +50,9 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa
Document: candidate.Document,
Path: append(candidate.Path, key.Value),
}
_, shouldDelete := nodesToDelete.Get(childCandidate.getKey())
_, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
log.Debugf("shouldDelete %v ? %v", childCandidate.getKey(), shouldDelete)
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
if !shouldDelete {
newContents = append(newContents, key, value)
@@ -76,7 +76,7 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.Ordered
Path: append(candidate.Path, index),
}
_, shouldDelete := nodesToDelete.Get(childCandidate.getKey())
_, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
if !shouldDelete {
newContents = append(newContents, value)
}

View File

@@ -11,16 +11,6 @@ import (
var log = logging.MustGetLogger("yq-treeops")
type CandidateNode struct {
Node *yaml.Node // the actual node
Path []interface{} /// the path we took to get to this node
Document uint // the document index of this node
}
func (n *CandidateNode) getKey() string {
return fmt.Sprintf("%v - %v", n.Document, n.Path)
}
type PathElementType uint32
const (
@@ -76,7 +66,7 @@ func (p *PathElement) toString() string {
}
type YqTreeLib interface {
Get(rootNode *yaml.Node, path string) ([]*CandidateNode, error)
Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error)
// GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error)
// Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
// New(path string) yaml.Node
@@ -85,10 +75,24 @@ type YqTreeLib interface {
// MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
}
func NewYqTreeLib() YqTreeLib {
return &lib{treeCreator: NewPathTreeCreator()}
}
type lib struct {
treeCreator PathTreeCreator
}
func (l *lib) Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error) {
nodes := []*CandidateNode{&CandidateNode{Node: documentNode.Content[0], Document: 0}}
navigator := NewDataTreeNavigator(NavigationPrefs{})
pathNode, errPath := l.treeCreator.ParsePath(path)
if errPath != nil {
return nil, errPath
}
return navigator.GetMatchingNodes(nodes, pathNode)
}
//use for debugging only
func NodesToString(collection *orderedmap.OrderedMap) string {
if !log.IsEnabledFor(logging.DEBUG) {

View File

@@ -24,7 +24,7 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap,
}
for el := lhs.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
log.Debugf("Assiging %v to %v", node.getKey(), pathNode.Rhs.PathElement.StringValue)
log.Debugf("Assiging %v to %v", node.GetKey(), pathNode.Rhs.PathElement.StringValue)
node.Node.Value = pathNode.Rhs.PathElement.StringValue
}
return lhs, nil
@@ -41,7 +41,7 @@ func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p
}
for el := rhs.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
lhs.Set(node.getKey(), node)
lhs.Set(node.GetKey(), node)
}
return lhs, nil
}
@@ -67,7 +67,7 @@ func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordere
func splatNode(d *dataTreeNavigator, candidate *CandidateNode) (*orderedmap.OrderedMap, error) {
elMap := orderedmap.NewOrderedMap()
elMap.Set(candidate.getKey(), candidate)
elMap.Set(candidate.GetKey(), candidate)
//need to splat matching nodes, then search through them
splatter := &PathTreeNode{PathElement: &PathElement{
PathElementType: PathKey,
@@ -112,7 +112,7 @@ func CountOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNo
length := childMatches.Len()
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.Set(candidate.getKey(), lengthCand)
results.Set(candidate.GetKey(), lengthCand)
}
@@ -131,7 +131,7 @@ func findMatchingChildren(d *dataTreeNavigator, results *orderedmap.OrderedMap,
}
} else {
children = orderedmap.NewOrderedMap()
children.Set(candidate.getKey(), candidate)
children.Set(candidate.GetKey(), candidate)
}
for childEl := children.Front(); childEl != nil; childEl = childEl.Next() {

View File

@@ -39,6 +39,10 @@ func (p *pathTreeCreator) ParsePath(path string) (*PathTreeNode, error) {
func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) {
var stack = make([]*PathTreeNode, 0)
if len(postFixPath) == 0 {
return nil, nil
}
for _, pathElement := range postFixPath {
var newNode = PathTreeNode{PathElement: pathElement}
if pathElement.PathElementType == Operation {

View File

@@ -1,43 +0,0 @@
package yqlib
func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return false
},
autoCreateMap: func(nodeContext NodeContext) bool {
return autoCreate
},
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
changesToApply := updateCommand.Value
if updateCommand.Overwrite || node.Value == "" {
log.Debug("going to update")
DebugNode(node)
log.Debug("with")
DebugNode(changesToApply)
if !updateCommand.DontUpdateNodeValue {
node.Value = changesToApply.Value
}
node.Tag = changesToApply.Tag
node.Kind = changesToApply.Kind
node.Style = changesToApply.Style
if !updateCommand.DontUpdateNodeContent {
node.Content = changesToApply.Content
}
node.Anchor = changesToApply.Anchor
node.Alias = changesToApply.Alias
if updateCommand.CommentsMergeStrategy != IgnoreCommentsMergeStrategy {
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
},
}
}