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
)
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-12-26 10:37:08 +00:00
return traverseNodesWithArrayIndices ( matches , make ( [ ] * yaml . Node , 0 ) , false )
2020-10-21 02:54:51 +00:00
}
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-08 23:59:03 +00:00
2020-10-20 02:53:26 +00:00
for el := matchMap . Front ( ) ; el != nil ; el = el . Next ( ) {
2020-12-26 22:55:08 +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
}
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 )
2020-12-26 10:37:08 +00:00
followAlias := true
if operation . Preferences != nil {
followAlias = ! operation . Preferences . ( * TraversePreferences ) . DontFollowAlias
}
return traverseMap ( matchingNode , operation . StringValue , followAlias , 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 , & CandidateNode {
2020-12-26 10:37:08 +00:00
Node : matchingNode . Node . Content [ 0 ] ,
Filename : matchingNode . Filename ,
FileIndex : matchingNode . FileIndex ,
Document : matchingNode . Document } , 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
}
2020-12-26 10:37:08 +00:00
func TraverseArrayOperator ( d * dataTreeNavigator , matchingNodes * list . List , pathNode * PathTreeNode ) ( * list . List , error ) {
// rhs is a collect expression that will yield indexes to retreive of the arrays
rhs , err := d . GetMatchingNodes ( matchingNodes , pathNode . Rhs )
if err != nil {
return nil , err
}
var indicesToTraverse = rhs . Front ( ) . Value . ( * CandidateNode ) . Node . Content
return traverseNodesWithArrayIndices ( matchingNodes , indicesToTraverse , true )
}
func traverseNodesWithArrayIndices ( matchingNodes * list . List , indicesToTraverse [ ] * yaml . Node , followAlias bool ) ( * list . List , error ) {
var matchingNodeMap = list . New ( )
for el := matchingNodes . Front ( ) ; el != nil ; el = el . Next ( ) {
candidate := el . Value . ( * CandidateNode )
newNodes , err := traverseArrayIndices ( candidate , indicesToTraverse , followAlias )
if err != nil {
return nil , err
}
matchingNodeMap . PushBackList ( newNodes )
}
return matchingNodeMap , nil
}
func traverseArrayIndices ( matchingNode * CandidateNode , indicesToTraverse [ ] * yaml . Node , followAlias bool ) ( * list . List , error ) { // call this if doc / alias like the other traverse
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
return traverseArrayIndices ( matchingNode , indicesToTraverse , followAlias )
} else if node . Kind == yaml . SequenceNode {
return traverseArrayWithIndices ( matchingNode , indicesToTraverse )
} else if node . Kind == yaml . MappingNode {
return traverseMapWithIndices ( matchingNode , indicesToTraverse , followAlias )
} else if node . Kind == yaml . DocumentNode {
return traverseArrayIndices ( & CandidateNode {
Node : matchingNode . Node . Content [ 0 ] ,
Filename : matchingNode . Filename ,
FileIndex : matchingNode . FileIndex ,
Document : matchingNode . Document } , indicesToTraverse , followAlias )
}
log . Debugf ( "OperatorArrayTraverse skipping %v as its a %v" , matchingNode , node . Tag )
return list . New ( ) , nil
}
func traverseMapWithIndices ( candidate * CandidateNode , indices [ ] * yaml . Node , followAlias bool ) ( * list . List , error ) {
if len ( indices ) == 0 {
return traverseMap ( candidate , "" , followAlias , true )
}
var matchingNodeMap = list . New ( )
for _ , indexNode := range indices {
log . Debug ( "traverseMapWithIndices: %v" , indexNode . Value )
newNodes , err := traverseMap ( candidate , indexNode . Value , followAlias , false )
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 ( )
node := UnwrapDoc ( candidate . Node )
if len ( indices ) == 0 {
log . Debug ( "splatting" )
var index int64
for index = 0 ; index < int64 ( len ( node . Content ) ) ; index = index + 1 {
newMatches . PushBack ( & CandidateNode {
Document : candidate . Document ,
Path : candidate . CreateChildPath ( index ) ,
Node : node . Content [ index ] ,
} )
}
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 ( & CandidateNode {
Node : node . Content [ indexToUse ] ,
Document : candidate . Document ,
Path : candidate . CreateChildPath ( index ) ,
} )
}
return newMatches , nil
}
func keyMatches ( key * yaml . Node , wantedKey string ) bool {
return Match ( key . Value , wantedKey )
2020-10-08 23:59:03 +00:00
}
2020-12-26 10:37:08 +00:00
func traverseMap ( matchingNode * CandidateNode , key string , followAlias bool , splat bool ) ( * list . List , error ) {
2020-12-26 22:51:34 +00:00
var newMatches = orderedmap . NewOrderedMap ( )
2020-12-26 10:37:08 +00:00
err := doTraverseMap ( newMatches , matchingNode , key , followAlias , 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 )
2020-12-26 22:51:34 +00:00
candidateNode := & CandidateNode {
Node : valueNode ,
2020-12-26 10:37:08 +00:00
Path : append ( matchingNode . Path , key ) ,
2020-12-26 22:51:34 +00:00
Document : matchingNode . Document ,
}
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
}
2020-12-26 10:37:08 +00:00
func doTraverseMap ( newMatches * orderedmap . OrderedMap , candidate * CandidateNode , wantedKey string , followAlias bool , 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-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-12-26 10:37:08 +00:00
err := traverseMergeAnchor ( newMatches , candidate , value , wantedKey , 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-10-30 01:00:48 +00:00
candidateNode := & CandidateNode {
2020-10-08 23:59:03 +00:00
Node : value ,
2020-12-25 01:46:08 +00:00
Path : candidate . CreateChildPath ( key . Value ) ,
2020-10-08 23:59:03 +00:00
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-12-26 10:37:08 +00:00
func traverseMergeAnchor ( newMatches * orderedmap . OrderedMap , originalCandidate * CandidateNode , value * yaml . Node , wantedKey string , splat bool ) 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-12-26 10:37:08 +00:00
return doTraverseMap ( newMatches , candidateNode , wantedKey , true , splat )
2020-10-30 01:00:48 +00:00
case yaml . SequenceNode :
for _ , childValue := range value . Content {
2020-12-26 10:37:08 +00:00
err := traverseMergeAnchor ( newMatches , originalCandidate , childValue , wantedKey , 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
}