yq/pkg/yqlib/operator_traverse_path.go

317 lines
10 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"
2020-10-21 01:54:58 +00:00
2020-10-30 01:00:48 +00:00
"github.com/elliotchance/orderedmap"
2020-10-08 23:59:03 +00:00
)
type traversePreferences struct {
2021-02-08 02:58:46 +00:00
DontFollowAlias bool
IncludeMapKeys bool
DontAutoCreate bool // by default, we automatically create entries on the fly.
DontIncludeMapValues bool
2021-05-09 05:12:50 +00:00
OptionalTraverse bool // e.g. .adf?
2020-10-29 23:56:45 +00:00
}
func splat(context Context, prefs traversePreferences) (Context, error) {
2023-04-03 07:27:41 +00:00
return traverseNodesWithArrayIndices(context, make([]*CandidateNode, 0), prefs)
2020-10-21 02:54:51 +00:00
}
func traversePathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
2021-02-03 04:51:26 +00:00
log.Debugf("-- traversePathOperator")
var matches = list.New()
2020-10-08 23:59:03 +00:00
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
newNodes, err := traverse(context, el.Value.(*CandidateNode), expressionNode.Operation)
2020-10-20 02:53:26 +00:00
if err != nil {
return Context{}, err
2020-10-20 02:53:26 +00:00
}
matches.PushBackList(newNodes)
2020-10-20 02:53:26 +00:00
}
return context.ChildContext(matches), nil
2020-10-08 23:59:03 +00:00
}
func traverse(context Context, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
2020-10-20 02:53:26 +00:00
log.Debug("Traversing %v", NodeToString(matchingNode))
2023-04-03 07:27:41 +00:00
if matchingNode.Tag == "!!null" && operation.Value != "[]" && !context.DontAutoCreate {
2020-10-20 02:53:26 +00:00
log.Debugf("Guessing kind")
2021-11-25 09:24:51 +00:00
// we must have 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")
2023-04-03 07:27:41 +00:00
matchingNode.Kind = SequenceNode
2020-10-20 02:53:26 +00:00
default:
2020-10-28 00:34:01 +00:00
log.Debugf("probably a map")
2023-04-03 07:27:41 +00:00
matchingNode.Kind = MappingNode
2020-10-20 02:53:26 +00:00
}
2023-04-03 07:27:41 +00:00
matchingNode.Tag = ""
2020-10-20 02:53:26 +00:00
}
2023-04-03 07:27:41 +00:00
switch matchingNode.Kind {
case MappingNode:
log.Debug("its a map with %v entries", len(matchingNode.Content)/2)
return traverseMap(context, matchingNode, createStringScalarNode(operation.StringValue), operation.Preferences.(traversePreferences), false)
2020-10-20 02:53:26 +00:00
2023-04-03 07:27:41 +00:00
case SequenceNode:
log.Debug("its a sequence of %v things!", len(matchingNode.Content))
2021-05-09 05:12:50 +00:00
return traverseArray(matchingNode, operation, operation.Preferences.(traversePreferences))
2020-10-29 23:56:45 +00:00
2023-04-03 07:27:41 +00:00
case AliasNode:
2020-10-29 23:56:45 +00:00
log.Debug("its an alias!")
2023-04-03 07:27:41 +00:00
matchingNode = matchingNode.Alias
return traverse(context, matchingNode, 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
}
func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
2021-02-03 04:51:26 +00:00
//lhs may update the variable context, we should pass that into the RHS
// BUT we still return the original context back (see jq)
// https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...
log.Debugf("--traverseArrayOperator")
2022-11-10 07:03:18 +00:00
if expressionNode.RHS != nil && expressionNode.RHS.RHS != nil && expressionNode.RHS.RHS.Operation.OperationType == createMapOpType {
return sliceArrayOperator(d, context, expressionNode.RHS.RHS)
}
lhs, err := d.GetMatchingNodes(context, expressionNode.LHS)
2021-02-03 04:51:26 +00:00
if err != nil {
return Context{}, err
}
// rhs is a collect expression that will yield indices to retrieve of the arrays
2020-12-26 10:37:08 +00:00
rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.RHS)
2021-05-09 05:36:33 +00:00
2020-12-26 10:37:08 +00:00
if err != nil {
return Context{}, err
2020-12-26 10:37:08 +00:00
}
2021-05-09 05:36:33 +00:00
prefs := traversePreferences{}
2020-12-26 10:37:08 +00:00
if expressionNode.Operation.Preferences != nil {
prefs = expressionNode.Operation.Preferences.(traversePreferences)
2021-05-09 05:36:33 +00:00
}
2023-04-03 07:27:41 +00:00
var indicesToTraverse = rhs.MatchingNodes.Front().Value.(*CandidateNode).Content
2021-02-03 04:51:26 +00:00
log.Debugf("indicesToTraverse %v", len(indicesToTraverse))
2021-02-03 04:51:26 +00:00
//now we traverse the result of the lhs against the indices we found
2021-05-09 05:36:33 +00:00
result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, prefs)
2021-02-03 04:51:26 +00:00
if err != nil {
return Context{}, err
}
return context.ChildContext(result.MatchingNodes), nil
2020-12-26 10:37:08 +00:00
}
2023-04-03 07:27:41 +00:00
func traverseNodesWithArrayIndices(context Context, indicesToTraverse []*CandidateNode, prefs traversePreferences) (Context, error) {
2020-12-26 10:37:08 +00:00
var matchingNodeMap = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
2020-12-26 10:37:08 +00:00
candidate := el.Value.(*CandidateNode)
2021-02-03 00:54:10 +00:00
newNodes, err := traverseArrayIndices(context, candidate, indicesToTraverse, prefs)
2020-12-26 10:37:08 +00:00
if err != nil {
return Context{}, err
2020-12-26 10:37:08 +00:00
}
matchingNodeMap.PushBackList(newNodes)
}
return context.ChildContext(matchingNodeMap), nil
2020-12-26 10:37:08 +00:00
}
2023-04-03 07:27:41 +00:00
func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesToTraverse []*CandidateNode, prefs traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
if matchingNode.Tag == "!!null" {
2020-12-26 10:37:08 +00:00
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
// auto vivification
2023-04-03 07:27:41 +00:00
matchingNode.Tag = ""
matchingNode.Kind = SequenceNode
//check that the indices are numeric, if not, then we should create an object
if len(indicesToTraverse) != 0 && indicesToTraverse[0].Tag != "!!int" {
2023-04-03 07:27:41 +00:00
matchingNode.Kind = MappingNode
}
2020-12-26 10:37:08 +00:00
}
2023-04-03 07:27:41 +00:00
if matchingNode.Kind == AliasNode {
matchingNode = matchingNode.Alias
2021-02-03 00:54:10 +00:00
return traverseArrayIndices(context, matchingNode, indicesToTraverse, prefs)
2023-04-03 07:27:41 +00:00
} else if matchingNode.Kind == SequenceNode {
2021-05-09 05:12:50 +00:00
return traverseArrayWithIndices(matchingNode, indicesToTraverse, prefs)
2023-04-03 07:27:41 +00:00
} else if matchingNode.Kind == MappingNode {
2021-02-03 00:54:10 +00:00
return traverseMapWithIndices(context, matchingNode, indicesToTraverse, prefs)
2020-12-26 10:37:08 +00:00
}
2023-04-03 07:27:41 +00:00
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, matchingNode.Tag)
2020-12-26 10:37:08 +00:00
return list.New(), nil
}
2023-04-03 07:27:41 +00:00
func traverseMapWithIndices(context Context, candidate *CandidateNode, indices []*CandidateNode, prefs traversePreferences) (*list.List, error) {
2020-12-26 10:37:08 +00:00
if len(indices) == 0 {
return traverseMap(context, candidate, createStringScalarNode(""), prefs, true)
2020-12-26 10:37:08 +00:00
}
var matchingNodeMap = list.New()
for _, indexNode := range indices {
log.Debug("traverseMapWithIndices: %v", indexNode.Value)
newNodes, err := traverseMap(context, candidate, indexNode, prefs, false)
2020-12-26 10:37:08 +00:00
if err != nil {
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
}
return matchingNodeMap, nil
}
2023-06-07 17:45:42 +00:00
func traverseArrayWithIndices(node *CandidateNode, indices []*CandidateNode, prefs traversePreferences) (*list.List, error) {
2020-12-26 10:37:08 +00:00
log.Debug("traverseArrayWithIndices")
var newMatches = list.New()
if len(indices) == 0 {
log.Debug("splatting")
2021-11-23 22:57:35 +00:00
var index int
for index = 0; index < len(node.Content); index = index + 1 {
2023-04-03 07:27:41 +00:00
newMatches.PushBack(node.Content[index])
2020-12-26 10:37:08 +00:00
}
return newMatches, nil
}
for _, indexNode := range indices {
log.Debug("traverseArrayWithIndices: '%v'", indexNode.Value)
2022-05-22 11:19:59 +00:00
index, err := parseInt(indexNode.Value)
2021-05-09 05:12:50 +00:00
if err != nil && prefs.OptionalTraverse {
continue
}
2020-12-26 10:37:08 +00:00
if err != nil {
2022-05-06 03:46:14 +00:00
return nil, fmt.Errorf("cannot index array with '%v' (%w)", indexNode.Value, err)
2020-12-26 10:37:08 +00:00
}
indexToUse := index
2022-05-06 03:46:14 +00:00
contentLength := len(node.Content)
2020-12-26 10:37:08 +00:00
for contentLength <= index {
if contentLength == 0 {
2023-09-18 23:52:36 +00:00
// default to nice yaml formatting
node.Style = 0
}
2023-05-09 03:51:21 +00:00
valueNode := createScalarNode(nil, "null")
node.AddChild(valueNode)
2022-05-06 03:46:14 +00:00
contentLength = len(node.Content)
2020-12-26 10:37:08 +00:00
}
if indexToUse < 0 {
indexToUse = contentLength + indexToUse
}
if indexToUse < 0 {
return nil, fmt.Errorf("index [%v] out of range, array size is %v", index, contentLength)
2020-12-26 10:37:08 +00:00
}
2023-04-03 07:27:41 +00:00
newMatches.PushBack(node.Content[indexToUse])
2020-12-26 10:37:08 +00:00
}
return newMatches, nil
}
2023-04-03 07:27:41 +00:00
func keyMatches(key *CandidateNode, wantedKey string) bool {
return matchKey(key.Value, wantedKey)
2020-10-08 23:59:03 +00:00
}
2023-04-03 07:27:41 +00:00
func traverseMap(context Context, matchingNode *CandidateNode, keyNode *CandidateNode, prefs traversePreferences, splat bool) (*list.List, error) {
2020-12-26 22:51:34 +00:00
var newMatches = orderedmap.NewOrderedMap()
err := doTraverseMap(newMatches, matchingNode, keyNode.Value, prefs, splat)
2020-12-26 22:51:34 +00:00
if err != nil {
return nil, err
}
if !splat && !prefs.DontAutoCreate && !context.DontAutoCreate && newMatches.Len() == 0 {
2023-05-29 22:46:51 +00:00
log.Debugf("no matches, creating one for %v", NodeToString(keyNode))
2020-12-26 22:51:34 +00:00
//no matches, create one automagically
2023-05-05 04:13:18 +00:00
valueNode := matchingNode.CreateChild()
valueNode.Kind = ScalarNode
valueNode.Tag = "!!null"
valueNode.Value = "null"
2023-04-03 07:27:41 +00:00
if len(matchingNode.Content) == 0 {
matchingNode.Style = 0
}
2023-05-29 22:46:51 +00:00
keyNode, valueNode = matchingNode.AddKeyValueChild(keyNode, valueNode)
2021-02-08 02:58:46 +00:00
if prefs.IncludeMapKeys {
2023-04-03 07:27:41 +00:00
newMatches.Set(keyNode.GetKey(), keyNode)
2021-02-08 02:58:46 +00:00
}
if !prefs.DontIncludeMapValues {
2023-04-03 07:27:41 +00:00
newMatches.Set(valueNode.GetKey(), valueNode)
2021-02-08 02:58:46 +00:00
}
2020-12-26 22:51:34 +00:00
}
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
}
2023-04-03 07:27:41 +00:00
func doTraverseMap(newMatches *orderedmap.OrderedMap, node *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 indices, values in odd.
2020-10-08 23:59:03 +00:00
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
key := contents[index]
value := contents[index+1]
2020-10-30 01:00:48 +00:00
//skip the 'merge' tag, find a direct match first
2021-01-13 05:54:28 +00:00
if key.Tag == "!!merge" && !prefs.DontFollowAlias {
2020-10-30 01:00:48 +00:00
log.Debug("Merge anchor")
2023-05-09 03:51:21 +00:00
err := traverseMergeAnchor(newMatches, 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 {
2021-02-08 02:58:46 +00:00
log.Debug("including key")
2023-04-03 07:27:41 +00:00
newMatches.Set(key.GetKey(), key)
2020-12-28 00:24:42 +00:00
}
2021-02-08 02:58:46 +00:00
if !prefs.DontIncludeMapValues {
log.Debug("including value")
2023-04-03 07:27:41 +00:00
newMatches.Set(value.GetKey(), value)
2021-02-08 02:58:46 +00:00
}
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
2023-05-09 03:51:21 +00:00
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, value *CandidateNode, wantedKey string, prefs traversePreferences, splat bool) error {
2020-10-30 01:00:48 +00:00
switch value.Kind {
2023-04-03 07:27:41 +00:00
case AliasNode:
if value.Alias.Kind != MappingNode {
return fmt.Errorf("can only use merge anchors with maps (!!map), but got %v", value.Alias.Tag)
}
2023-04-03 07:27:41 +00:00
return doTraverseMap(newMatches, value.Alias, wantedKey, prefs, splat)
case SequenceNode:
2020-10-30 01:00:48 +00:00
for _, childValue := range value.Content {
2023-05-09 03:51:21 +00:00
err := traverseMergeAnchor(newMatches, 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
}
2021-05-09 05:12:50 +00:00
func traverseArray(candidate *CandidateNode, operation *Operation, prefs traversePreferences) (*list.List, error) {
2020-10-30 01:00:48 +00:00
log.Debug("operation Value %v", operation.Value)
2023-04-03 07:27:41 +00:00
indices := []*CandidateNode{{Value: operation.StringValue}}
2021-05-09 05:12:50 +00:00
return traverseArrayWithIndices(candidate, indices, prefs)
2020-10-08 23:59:03 +00:00
}