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
)
2021-01-11 06:13:48 +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
}
2021-12-20 22:30:08 +00:00
func splat ( context Context , prefs traversePreferences ) ( Context , error ) {
2021-02-02 07:17:59 +00:00
return traverseNodesWithArrayIndices ( context , make ( [ ] * yaml . Node , 0 ) , prefs )
2020-10-21 02:54:51 +00:00
}
2021-02-02 07:17:59 +00:00
func traversePathOperator ( d * dataTreeNavigator , context Context , expressionNode * ExpressionNode ) ( Context , error ) {
2021-02-03 04:51:26 +00:00
log . Debugf ( "-- traversePathOperator" )
2021-02-02 07:17:59 +00:00
var matches = list . New ( )
2020-10-08 23:59:03 +00:00
2021-02-02 07:17:59 +00:00
for el := context . MatchingNodes . Front ( ) ; el != nil ; el = el . Next ( ) {
2021-12-20 22:30:08 +00:00
newNodes , err := traverse ( context , el . Value . ( * CandidateNode ) , expressionNode . Operation )
2020-10-20 02:53:26 +00:00
if err != nil {
2021-02-02 07:17:59 +00:00
return Context { } , err
2020-10-20 02:53:26 +00:00
}
2021-02-02 07:17:59 +00:00
matches . PushBackList ( newNodes )
2020-10-20 02:53:26 +00:00
}
2021-02-02 07:17:59 +00:00
return context . ChildContext ( matches ) , nil
2020-10-08 23:59:03 +00:00
}
2021-12-20 22:30:08 +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 ) )
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" )
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" )
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 )
2021-02-03 00:54:10 +00:00
return traverseMap ( context , matchingNode , operation . StringValue , operation . Preferences . ( traversePreferences ) , false )
2020-10-20 02:53:26 +00:00
case yaml . SequenceNode :
log . Debug ( "its a sequence of %v things!" , len ( value . Content ) )
2021-05-09 05:12:50 +00:00
return traverseArray ( matchingNode , operation , operation . Preferences . ( traversePreferences ) )
2020-10-29 23:56:45 +00:00
case yaml . AliasNode :
log . Debug ( "its an alias!" )
2020-10-30 01:40:44 +00:00
matchingNode . Node = matchingNode . Node . Alias
2021-12-20 22:30:08 +00:00
return traverse ( context , matchingNode , operation )
2020-10-20 02:53:26 +00:00
case yaml . DocumentNode :
log . Debug ( "digging into doc node" )
2021-01-12 08:36:28 +00:00
2021-12-20 22:30:08 +00:00
return traverse ( context , matchingNode . CreateChildInMap ( 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-02-02 07:17:59 +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|...
2022-02-07 00:55:55 +00:00
lhs , err := d . GetMatchingNodes ( context , expressionNode . LHS )
2021-02-03 04:51:26 +00:00
if err != nil {
return Context { } , err
}
2021-11-25 09:24:51 +00:00
// rhs is a collect expression that will yield indexes to retrieve of the arrays
2020-12-26 10:37:08 +00:00
2022-02-07 00:55:55 +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 {
2021-02-02 07:17:59 +00:00
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
2021-11-30 02:19:30 +00:00
if expressionNode . Operation . Preferences != nil {
prefs = expressionNode . Operation . Preferences . ( traversePreferences )
2021-05-09 05:36:33 +00:00
}
2021-02-02 07:17:59 +00:00
var indicesToTraverse = rhs . MatchingNodes . Front ( ) . Value . ( * CandidateNode ) . Node . Content
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
}
2021-02-02 07:17:59 +00:00
func traverseNodesWithArrayIndices ( context Context , indicesToTraverse [ ] * yaml . Node , prefs traversePreferences ) ( Context , error ) {
2020-12-26 10:37:08 +00:00
var matchingNodeMap = list . New ( )
2021-02-02 07:17:59 +00:00
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 {
2021-02-02 07:17:59 +00:00
return Context { } , err
2020-12-26 10:37:08 +00:00
}
matchingNodeMap . PushBackList ( newNodes )
}
2021-02-02 07:17:59 +00:00
return context . ChildContext ( matchingNodeMap ) , nil
2020-12-26 10:37:08 +00:00
}
2021-02-03 00:54:10 +00:00
func traverseArrayIndices ( context Context , 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" )
2021-06-11 04:27:44 +00:00
// auto vivification
2020-12-26 10:37:08 +00:00
node . Tag = ""
node . Kind = yaml . SequenceNode
2021-06-11 04:27:44 +00:00
//check that the indices are numeric, if not, then we should create an object
if len ( indicesToTraverse ) != 0 {
_ , err := strconv . ParseInt ( indicesToTraverse [ 0 ] . Value , 10 , 64 )
if err != nil {
node . Kind = yaml . MappingNode
}
}
2020-12-26 10:37:08 +00:00
}
if node . Kind == yaml . AliasNode {
matchingNode . Node = node . Alias
2021-02-03 00:54:10 +00:00
return traverseArrayIndices ( context , matchingNode , indicesToTraverse , prefs )
2020-12-26 10:37:08 +00:00
} else if node . Kind == yaml . SequenceNode {
2021-05-09 05:12:50 +00:00
return traverseArrayWithIndices ( matchingNode , indicesToTraverse , prefs )
2020-12-26 10:37:08 +00:00
} else if node . Kind == yaml . MappingNode {
2021-02-03 00:54:10 +00:00
return traverseMapWithIndices ( context , matchingNode , indicesToTraverse , prefs )
2020-12-26 10:37:08 +00:00
} else if node . Kind == yaml . DocumentNode {
2021-11-23 22:57:35 +00:00
return traverseArrayIndices ( context , matchingNode . CreateChildInMap ( 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
}
2021-02-03 00:54:10 +00:00
func traverseMapWithIndices ( context Context , candidate * CandidateNode , indices [ ] * yaml . Node , prefs traversePreferences ) ( * list . List , error ) {
2020-12-26 10:37:08 +00:00
if len ( indices ) == 0 {
2021-02-03 00:54:10 +00:00
return traverseMap ( context , 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 )
2021-02-03 00:54:10 +00:00
newNodes , err := traverseMap ( context , 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
}
2021-05-09 05:12:50 +00:00
func traverseArrayWithIndices ( candidate * CandidateNode , indices [ ] * yaml . Node , prefs traversePreferences ) ( * list . List , error ) {
2020-12-26 10:37:08 +00:00
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" )
2021-11-23 22:57:35 +00:00
var index int
for index = 0 ; index < len ( node . Content ) ; index = index + 1 {
newMatches . PushBack ( candidate . CreateChildInArray ( 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 )
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 {
2022-04-14 22:27:22 +00:00
if contentLength == 0 {
// default to nice yaml formating
node . Style = 0
}
2020-12-26 10:37:08 +00:00
node . Content = append ( node . Content , & yaml . Node { Tag : "!!null" , Kind : yaml . ScalarNode , Value : "null" } )
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 {
2022-04-14 22:27:22 +00:00
return nil , fmt . Errorf ( "index [%v] out of range, array size is %v" , index , contentLength )
2020-12-26 10:37:08 +00:00
}
2022-05-06 03:46:14 +00:00
newMatches . PushBack ( candidate . CreateChildInArray ( index , node . Content [ indexToUse ] ) )
2020-12-26 10:37:08 +00:00
}
return newMatches , nil
}
func keyMatches ( key * yaml . Node , wantedKey string ) bool {
2021-01-11 06:13:48 +00:00
return matchKey ( key . Value , wantedKey )
2020-10-08 23:59:03 +00:00
}
2021-02-03 00:54:10 +00:00
func traverseMap ( context Context , 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
}
2021-02-03 00:54:10 +00:00
if ! prefs . DontAutoCreate && ! context . DontAutoCreate && newMatches . Len ( ) == 0 {
2020-12-26 22:51:34 +00:00
//no matches, create one automagically
valueNode := & yaml . Node { Tag : "!!null" , Kind : yaml . ScalarNode , Value : "null" }
2021-02-08 02:58:46 +00:00
keyNode := & yaml . Node { Kind : yaml . ScalarNode , Value : key }
2020-12-26 22:51:34 +00:00
node := matchingNode . Node
2022-04-14 22:27:22 +00:00
if len ( node . Content ) == 0 {
node . Style = 0
}
2021-02-08 02:58:46 +00:00
node . Content = append ( node . Content , keyNode , valueNode )
if prefs . IncludeMapKeys {
log . Debug ( "including key" )
2021-11-23 22:57:35 +00:00
candidateNode := matchingNode . CreateChildInMap ( keyNode , keyNode )
2021-02-08 02:58:46 +00:00
candidateNode . IsMapKey = true
newMatches . Set ( fmt . Sprintf ( "keyOf-%v" , candidateNode . GetKey ( ) ) , candidateNode )
}
if ! prefs . DontIncludeMapValues {
log . Debug ( "including value" )
2021-11-23 22:57:35 +00:00
candidateNode := matchingNode . CreateChildInMap ( keyNode , valueNode )
2021-02-08 02:58:46 +00:00
newMatches . Set ( candidateNode . GetKey ( ) , candidateNode )
}
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
}
2021-01-13 05:54:28 +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
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" )
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 {
2021-02-08 02:58:46 +00:00
log . Debug ( "including key" )
2021-11-23 22:57:35 +00:00
candidateNode := candidate . CreateChildInMap ( key , key )
2021-02-08 02:58:46 +00:00
candidateNode . IsMapKey = true
2020-12-28 00:24:42 +00:00
newMatches . Set ( fmt . Sprintf ( "keyOf-%v" , candidateNode . GetKey ( ) ) , candidateNode )
}
2021-02-08 02:58:46 +00:00
if ! prefs . DontIncludeMapValues {
log . Debug ( "including value" )
2021-11-23 22:57:35 +00:00
candidateNode := candidate . CreateChildInMap ( key , value )
2021-02-08 02:58:46 +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
2021-01-13 05:54:28 +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 :
2022-04-27 04:46:52 +00:00
if value . Alias . Kind != yaml . MappingNode {
return fmt . Errorf ( "can only use merge anchors with maps (!!map), but got %v" , value . Alias . Tag )
}
2021-11-23 22:57:35 +00:00
candidateNode := originalCandidate . CreateReplacement ( 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
}
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 )
2021-06-01 00:52:14 +00:00
indices := [ ] * yaml . Node { { Value : operation . StringValue } }
2021-05-09 05:12:50 +00:00
return traverseArrayWithIndices ( candidate , indices , prefs )
2020-10-08 23:59:03 +00:00
}