yq/pkg/yqlib/operator_traverse_path.go

273 lines
8.8 KiB
Go
Raw Normal View History

2020-11-03 23:48:43 +00:00
package yqlib
2020-10-08 23:59:03 +00:00
import (
2020-10-21 01:54:58 +00:00
"container/list"
2020-12-26 10:37:08 +00:00
"fmt"
"strconv"
2020-10-21 01:54:58 +00:00
2020-10-30 01:00:48 +00:00
"github.com/elliotchance/orderedmap"
2020-12-25 01:46:08 +00:00
yaml "gopkg.in/yaml.v3"
2020-10-08 23:59:03 +00:00
)
type traversePreferences struct {
2020-12-28 00:24:42 +00:00
FollowAlias bool
IncludeMapKeys bool
2020-10-29 23:56:45 +00:00
}
func splat(d *dataTreeNavigator, matches *list.List, prefs *traversePreferences) (*list.List, error) {
2020-12-28 00:24:42 +00:00
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
2020-10-21 02:54:51 +00:00
}
2021-01-12 23:18:53 +00:00
func traversePathOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
2020-10-20 02:53:26 +00:00
log.Debugf("-- Traversing")
2020-10-21 01:54:58 +00:00
var matchingNodeMap = list.New()
2020-10-08 23:59:03 +00:00
2020-10-20 02:53:26 +00:00
for el := matchMap.Front(); el != nil; el = el.Next() {
2021-01-12 23:18:53 +00:00
newNodes, err := traverse(d, el.Value.(*CandidateNode), expressionNode.Operation)
2020-10-20 02:53:26 +00:00
if err != nil {
return nil, err
}
2020-12-26 22:55:08 +00:00
matchingNodeMap.PushBackList(newNodes)
2020-10-20 02:53:26 +00:00
}
return matchingNodeMap, nil
2020-10-08 23:59:03 +00:00
}
2020-12-26 22:55:08 +00:00
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
2020-10-20 02:53:26 +00:00
log.Debug("Traversing %v", NodeToString(matchingNode))
value := matchingNode.Node
2020-10-29 23:56:45 +00:00
if value.Tag == "!!null" && operation.Value != "[]" {
2020-10-20 02:53:26 +00:00
log.Debugf("Guessing kind")
// we must ahve added this automatically, lets guess what it should be now
2020-10-29 23:56:45 +00:00
switch operation.Value.(type) {
2020-10-20 02:53:26 +00:00
case int, int64:
log.Debugf("probably an array")
value.Kind = yaml.SequenceNode
default:
2020-10-28 00:34:01 +00:00
log.Debugf("probably a map")
2020-10-20 02:53:26 +00:00
value.Kind = yaml.MappingNode
}
2020-10-21 01:54:58 +00:00
value.Tag = ""
2020-10-20 02:53:26 +00:00
}
switch value.Kind {
case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2)
prefs := &traversePreferences{FollowAlias: true}
2020-12-28 00:24:42 +00:00
return traverseMap(matchingNode, operation.StringValue, prefs, false)
2020-10-20 02:53:26 +00:00
case yaml.SequenceNode:
log.Debug("its a sequence of %v things!", len(value.Content))
2020-10-29 23:56:45 +00:00
return traverseArray(matchingNode, operation)
case yaml.AliasNode:
log.Debug("its an alias!")
2020-10-30 01:40:44 +00:00
matchingNode.Node = matchingNode.Node.Alias
return traverse(d, matchingNode, operation)
2020-10-20 02:53:26 +00:00
case yaml.DocumentNode:
log.Debug("digging into doc node")
return traverse(d, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), operation)
2020-10-20 02:53:26 +00:00
default:
2020-12-26 22:55:08 +00:00
return list.New(), nil
2020-10-20 02:53:26 +00:00
}
2020-10-08 23:59:03 +00:00
}
2021-01-12 23:18:53 +00:00
func traverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
2020-12-26 10:37:08 +00:00
// rhs is a collect expression that will yield indexes to retreive of the arrays
2021-01-12 23:18:53 +00:00
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
2020-12-26 10:37:08 +00:00
if err != nil {
return nil, err
}
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
prefs := &traversePreferences{FollowAlias: true}
2020-12-28 00:24:42 +00:00
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs)
2020-12-26 10:37:08 +00:00
}
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *traversePreferences) (*list.List, error) {
2020-12-26 10:37:08 +00:00
var matchingNodeMap = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
2020-12-28 00:24:42 +00:00
newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs)
2020-12-26 10:37:08 +00:00
if err != nil {
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
}
return matchingNodeMap, nil
}
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
2020-12-26 10:37:08 +00:00
node := matchingNode.Node
if node.Tag == "!!null" {
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
// auto vivification, make it into an empty array
node.Tag = ""
node.Kind = yaml.SequenceNode
}
if node.Kind == yaml.AliasNode {
matchingNode.Node = node.Alias
2020-12-28 00:24:42 +00:00
return traverseArrayIndices(matchingNode, indicesToTraverse, prefs)
2020-12-26 10:37:08 +00:00
} else if node.Kind == yaml.SequenceNode {
return traverseArrayWithIndices(matchingNode, indicesToTraverse)
} else if node.Kind == yaml.MappingNode {
2020-12-28 00:24:42 +00:00
return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs)
2020-12-26 10:37:08 +00:00
} else if node.Kind == yaml.DocumentNode {
return traverseArrayIndices(matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs)
2020-12-26 10:37:08 +00:00
}
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
return list.New(), nil
}
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *traversePreferences) (*list.List, error) {
2020-12-26 10:37:08 +00:00
if len(indices) == 0 {
2020-12-28 00:24:42 +00:00
return traverseMap(candidate, "", prefs, true)
2020-12-26 10:37:08 +00:00
}
var matchingNodeMap = list.New()
for _, indexNode := range indices {
log.Debug("traverseMapWithIndices: %v", indexNode.Value)
2020-12-28 00:24:42 +00:00
newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false)
2020-12-26 10:37:08 +00:00
if err != nil {
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
}
return matchingNodeMap, nil
}
func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) {
log.Debug("traverseArrayWithIndices")
var newMatches = list.New()
2021-01-12 23:00:51 +00:00
node := unwrapDoc(candidate.Node)
2020-12-26 10:37:08 +00:00
if len(indices) == 0 {
log.Debug("splatting")
var index int64
for index = 0; index < int64(len(node.Content)); index = index + 1 {
newMatches.PushBack(candidate.CreateChild(index, node.Content[index]))
2020-12-26 10:37:08 +00:00
}
return newMatches, nil
}
for _, indexNode := range indices {
log.Debug("traverseArrayWithIndices: '%v'", indexNode.Value)
index, err := strconv.ParseInt(indexNode.Value, 10, 64)
if err != nil {
return nil, fmt.Errorf("Cannot index array with '%v' (%v)", indexNode.Value, err)
}
indexToUse := index
contentLength := int64(len(node.Content))
for contentLength <= index {
node.Content = append(node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
contentLength = int64(len(node.Content))
}
if indexToUse < 0 {
indexToUse = contentLength + indexToUse
}
if indexToUse < 0 {
return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
}
newMatches.PushBack(candidate.CreateChild(index, node.Content[indexToUse]))
2020-12-26 10:37:08 +00:00
}
return newMatches, nil
}
func keyMatches(key *yaml.Node, wantedKey string) bool {
return matchKey(key.Value, wantedKey)
2020-10-08 23:59:03 +00:00
}
func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePreferences, splat bool) (*list.List, error) {
2020-12-26 22:51:34 +00:00
var newMatches = orderedmap.NewOrderedMap()
2020-12-28 00:24:42 +00:00
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
2020-12-26 22:51:34 +00:00
if err != nil {
return nil, err
}
if newMatches.Len() == 0 {
//no matches, create one automagically
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
node := matchingNode.Node
2020-12-26 10:37:08 +00:00
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode)
candidateNode := matchingNode.CreateChild(key, valueNode)
2020-12-26 22:51:34 +00:00
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
2020-12-26 22:55:08 +00:00
results := list.New()
2020-12-26 22:51:34 +00:00
i := 0
for el := newMatches.Front(); el != nil; el = el.Next() {
2020-12-26 22:55:08 +00:00
results.PushBack(el.Value)
2020-12-26 22:51:34 +00:00
i++
}
2020-12-26 22:55:08 +00:00
return results, nil
2020-12-26 22:51:34 +00:00
}
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs *traversePreferences, splat bool) error {
2020-10-08 23:59:03 +00:00
// 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.
node := candidate.Node
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
key := contents[index]
value := contents[index+1]
log.Debug("checking %v (%v)", key.Value, key.Tag)
2020-10-30 01:00:48 +00:00
//skip the 'merge' tag, find a direct match first
2020-12-28 00:24:42 +00:00
if key.Tag == "!!merge" && prefs.FollowAlias {
2020-10-30 01:00:48 +00:00
log.Debug("Merge anchor")
2020-12-28 00:24:42 +00:00
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
2020-11-13 03:07:11 +00:00
if err != nil {
return err
}
2020-12-26 10:37:08 +00:00
} else if splat || keyMatches(key, wantedKey) {
2020-10-08 23:59:03 +00:00
log.Debug("MATCHED")
2020-12-28 00:24:42 +00:00
if prefs.IncludeMapKeys {
candidateNode := candidate.CreateChild(key.Value, key)
2020-12-28 00:24:42 +00:00
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
}
candidateNode := candidate.CreateChild(key.Value, value)
2020-10-30 01:00:48 +00:00
newMatches.Set(candidateNode.GetKey(), candidateNode)
2020-10-08 23:59:03 +00:00
}
}
2020-10-19 05:14:29 +00:00
2020-10-30 01:00:48 +00:00
return nil
}
2020-10-19 05:14:29 +00:00
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *traversePreferences, splat bool) error {
2020-10-30 01:00:48 +00:00
switch value.Kind {
case yaml.AliasNode:
candidateNode := originalCandidate.CreateChild(nil, value.Alias)
2020-12-28 00:24:42 +00:00
return doTraverseMap(newMatches, candidateNode, wantedKey, prefs, splat)
2020-10-30 01:00:48 +00:00
case yaml.SequenceNode:
for _, childValue := range value.Content {
2020-12-28 00:24:42 +00:00
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey, prefs, splat)
2020-11-13 03:07:11 +00:00
if err != nil {
return err
}
2020-10-30 01:00:48 +00:00
}
}
2020-11-13 03:07:11 +00:00
return nil
2020-10-09 05:38:07 +00:00
}
2020-12-26 22:55:08 +00:00
func traverseArray(candidate *CandidateNode, operation *Operation) (*list.List, error) {
2020-10-30 01:00:48 +00:00
log.Debug("operation Value %v", operation.Value)
2020-12-26 10:37:08 +00:00
indices := []*yaml.Node{&yaml.Node{Value: operation.StringValue}}
return traverseArrayWithIndices(candidate, indices)
2020-10-08 23:59:03 +00:00
}