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-01-13 05:54:28 +00:00
DontFollowAlias bool
IncludeMapKeys bool
DontAutoCreate bool // by default, we automatically create entries on the fly.
2020-10-29 23:56:45 +00:00
}
2021-01-13 05:54:28 +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 )
2021-01-13 05:54:28 +00:00
return traverseMap ( 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 ) )
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" )
2021-01-12 08:36:28 +00:00
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
2021-01-13 05:54:28 +00:00
return traverseNodesWithArrayIndices ( matchingNodes , indicesToTraverse , traversePreferences { } )
2020-12-26 10:37:08 +00:00
}
2021-01-13 05:54:28 +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
}
2021-01-13 05:54:28 +00:00
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 {
2021-01-12 08:36:28 +00:00
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
}
2021-01-13 05:54:28 +00:00
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 {
2021-01-12 08:36:28 +00:00
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 )
}
2021-01-12 08:36:28 +00:00
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 {
2021-01-11 06:13:48 +00:00
return matchKey ( key . Value , wantedKey )
2020-10-08 23:59:03 +00:00
}
2021-01-13 05:54:28 +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
}
2021-01-13 05:54:28 +00:00
if ! prefs . 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" }
node := matchingNode . Node
2020-12-26 10:37:08 +00:00
node . Content = append ( node . Content , & yaml . Node { Kind : yaml . ScalarNode , Value : key } , valueNode )
2021-01-12 08:36:28 +00:00
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
}
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-01-12 08:36:28 +00:00
candidateNode := candidate . CreateChild ( key . Value , key )
2020-12-28 00:24:42 +00:00
newMatches . Set ( fmt . Sprintf ( "keyOf-%v" , candidateNode . GetKey ( ) ) , candidateNode )
}
2021-01-12 08:36:28 +00:00
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
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 :
2021-01-12 08:36:28 +00:00
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
}