yq/pkg/yqlib/operator_traverse_path.go

224 lines
6.4 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-13 02:17:18 +00:00
"fmt"
2020-10-21 01:54:58 +00:00
"container/list"
2020-10-30 01:00:48 +00:00
"github.com/elliotchance/orderedmap"
2020-10-08 23:59:03 +00:00
"gopkg.in/yaml.v3"
)
2020-10-29 23:56:45 +00:00
type TraversePreferences struct {
DontFollowAlias bool
}
2020-10-21 02:54:51 +00:00
func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) {
2020-10-29 23:56:45 +00:00
preferences := &TraversePreferences{DontFollowAlias: true}
splatOperation := &Operation{OperationType: TraversePath, Value: "[]", Preferences: preferences}
2020-10-21 02:54:51 +00:00
splatTreeNode := &PathTreeNode{Operation: splatOperation}
return TraversePathOperator(d, matches, splatTreeNode)
}
2020-10-21 01:54:58 +00:00
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*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-20 02:53:26 +00:00
var newNodes []*CandidateNode
var err error
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() {
2020-10-20 04:33:20 +00:00
newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
2020-10-20 02:53:26 +00:00
if err != nil {
return nil, err
}
for _, n := range newNodes {
2020-10-21 01:54:58 +00:00
matchingNodeMap.PushBack(n)
2020-10-20 02:53:26 +00:00
}
}
return matchingNodeMap, nil
2020-10-08 23:59:03 +00:00
}
2020-10-29 23:56:45 +00:00
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) ([]*CandidateNode, 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)
2020-10-30 01:00:48 +00:00
var newMatches = orderedmap.NewOrderedMap()
err := traverseMap(newMatches, matchingNode, operation)
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
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: operation.StringValue}, valueNode)
candidateNode := &CandidateNode{
Node: valueNode,
Path: append(matchingNode.Path, operation.StringValue),
Document: matchingNode.Document,
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
arrayMatches := make([]*CandidateNode, newMatches.Len())
i := 0
for el := newMatches.Front(); el != nil; el = el.Next() {
arrayMatches[i] = el.Value.(*CandidateNode)
i++
}
return arrayMatches, nil
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, &CandidateNode{
Node: matchingNode.Node.Content[0],
2020-10-29 23:56:45 +00:00
Document: matchingNode.Document}, operation)
2020-10-20 02:53:26 +00:00
default:
return nil, nil
}
2020-10-08 23:59:03 +00:00
}
2020-10-20 04:33:20 +00:00
func keyMatches(key *yaml.Node, pathNode *Operation) bool {
2020-10-17 11:10:47 +00:00
return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue)
2020-10-08 23:59:03 +00:00
}
2020-10-30 01:00:48 +00:00
func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, operation *Operation) 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.
//TODO ALIASES, auto creation?
node := candidate.Node
2020-10-30 01:40:44 +00:00
followAlias := true
if operation.Preferences != nil {
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
}
2020-10-08 23:59:03 +00:00
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-10-30 01:40:44 +00:00
if key.Tag == "!!merge" && followAlias {
2020-10-30 01:00:48 +00:00
log.Debug("Merge anchor")
2020-11-13 03:07:11 +00:00
err := traverseMergeAnchor(newMatches, candidate, value, operation)
if err != nil {
return err
}
2020-10-30 01:00:48 +00:00
} else if keyMatches(key, operation) {
2020-10-08 23:59:03 +00:00
log.Debug("MATCHED")
2020-10-30 01:00:48 +00:00
candidateNode := &CandidateNode{
2020-10-08 23:59:03 +00:00
Node: value,
Path: append(candidate.Path, key.Value),
Document: candidate.Document,
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
2020-11-13 03:07:11 +00:00
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) error {
2020-10-30 01:00:48 +00:00
switch value.Kind {
case yaml.AliasNode:
candidateNode := &CandidateNode{
Node: value.Alias,
Path: originalCandidate.Path,
Document: originalCandidate.Document,
}
2020-11-13 03:07:11 +00:00
return traverseMap(newMatches, candidateNode, operation)
2020-10-30 01:00:48 +00:00
case yaml.SequenceNode:
for _, childValue := range value.Content {
2020-11-13 03:07:11 +00:00
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, operation)
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-10-30 01:00:48 +00:00
func traverseArray(candidate *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
log.Debug("operation Value %v", operation.Value)
if operation.Value == "[]" {
2020-10-09 05:38:07 +00:00
var contents = candidate.Node.Content
var newMatches = make([]*CandidateNode, len(contents))
2020-10-19 05:36:46 +00:00
var index int64
for index = 0; index < int64(len(contents)); index = index + 1 {
2020-10-09 05:38:07 +00:00
newMatches[index] = &CandidateNode{
Document: candidate.Document,
Path: append(candidate.Path, index),
Node: contents[index],
}
}
return newMatches, nil
}
2020-11-03 23:48:43 +00:00
switch operation.Value.(type) {
case int64:
index := operation.Value.(int64)
indexToUse := index
contentLength := int64(len(candidate.Node.Content))
for contentLength <= index {
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
contentLength = int64(len(candidate.Node.Content))
}
2020-10-09 05:38:07 +00:00
2020-11-03 23:48:43 +00:00
if indexToUse < 0 {
indexToUse = contentLength + indexToUse
}
2020-10-13 02:17:18 +00:00
2020-11-03 23:48:43 +00:00
if indexToUse < 0 {
return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
}
2020-10-13 02:17:18 +00:00
2020-11-03 23:48:43 +00:00
return []*CandidateNode{&CandidateNode{
Node: candidate.Node.Content[indexToUse],
Document: candidate.Document,
Path: append(candidate.Path, index),
}}, nil
default:
log.Debug("argument not an int (%v), no array matches", operation.Value)
return nil, nil
}
2020-10-08 23:59:03 +00:00
}