mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-13 22:38:04 +00:00
300 lines
9.2 KiB
Go
300 lines
9.2 KiB
Go
package yqlib
|
|
|
|
import (
|
|
"container/list"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/elliotchance/orderedmap"
|
|
yaml "gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type TraversePreferences struct {
|
|
DontFollowAlias bool
|
|
}
|
|
|
|
func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) {
|
|
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), false)
|
|
}
|
|
|
|
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
|
log.Debugf("-- Traversing")
|
|
var matchingNodeMap = list.New()
|
|
|
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
|
newNodes, err := traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
matchingNodeMap.PushBackList(newNodes)
|
|
}
|
|
|
|
return matchingNodeMap, nil
|
|
}
|
|
|
|
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
|
|
log.Debug("Traversing %v", NodeToString(matchingNode))
|
|
value := matchingNode.Node
|
|
|
|
if value.Tag == "!!null" && operation.Value != "[]" {
|
|
log.Debugf("Guessing kind")
|
|
// we must ahve added this automatically, lets guess what it should be now
|
|
switch operation.Value.(type) {
|
|
case int, int64:
|
|
log.Debugf("probably an array")
|
|
value.Kind = yaml.SequenceNode
|
|
default:
|
|
log.Debugf("probably a map")
|
|
value.Kind = yaml.MappingNode
|
|
}
|
|
value.Tag = ""
|
|
}
|
|
|
|
switch value.Kind {
|
|
case yaml.MappingNode:
|
|
log.Debug("its a map with %v entries", len(value.Content)/2)
|
|
followAlias := true
|
|
|
|
if operation.Preferences != nil {
|
|
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
|
|
}
|
|
return traverseMap(matchingNode, operation.StringValue, followAlias, false)
|
|
|
|
case yaml.SequenceNode:
|
|
log.Debug("its a sequence of %v things!", len(value.Content))
|
|
return traverseArray(matchingNode, operation)
|
|
|
|
case yaml.AliasNode:
|
|
log.Debug("its an alias!")
|
|
matchingNode.Node = matchingNode.Node.Alias
|
|
return traverse(d, matchingNode, operation)
|
|
case yaml.DocumentNode:
|
|
log.Debug("digging into doc node")
|
|
return traverse(d, &CandidateNode{
|
|
Node: matchingNode.Node.Content[0],
|
|
Filename: matchingNode.Filename,
|
|
FileIndex: matchingNode.FileIndex,
|
|
Document: matchingNode.Document}, operation)
|
|
default:
|
|
return list.New(), nil
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func traverseMap(matchingNode *CandidateNode, key string, followAlias bool, splat bool) (*list.List, error) {
|
|
var newMatches = orderedmap.NewOrderedMap()
|
|
err := doTraverseMap(newMatches, matchingNode, key, followAlias, splat)
|
|
|
|
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: key}, valueNode)
|
|
candidateNode := &CandidateNode{
|
|
Node: valueNode,
|
|
Path: append(matchingNode.Path, key),
|
|
Document: matchingNode.Document,
|
|
}
|
|
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
|
|
|
}
|
|
|
|
results := list.New()
|
|
i := 0
|
|
for el := newMatches.Front(); el != nil; el = el.Next() {
|
|
results.PushBack(el.Value)
|
|
i++
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, followAlias bool, splat bool) error {
|
|
// 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)
|
|
//skip the 'merge' tag, find a direct match first
|
|
if key.Tag == "!!merge" && followAlias {
|
|
log.Debug("Merge anchor")
|
|
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, splat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if splat || keyMatches(key, wantedKey) {
|
|
log.Debug("MATCHED")
|
|
candidateNode := &CandidateNode{
|
|
Node: value,
|
|
Path: candidate.CreateChildPath(key.Value),
|
|
Document: candidate.Document,
|
|
}
|
|
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, splat bool) error {
|
|
switch value.Kind {
|
|
case yaml.AliasNode:
|
|
candidateNode := &CandidateNode{
|
|
Node: value.Alias,
|
|
Path: originalCandidate.Path,
|
|
Document: originalCandidate.Document,
|
|
}
|
|
return doTraverseMap(newMatches, candidateNode, wantedKey, true, splat)
|
|
case yaml.SequenceNode:
|
|
for _, childValue := range value.Content {
|
|
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey, splat)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func traverseArray(candidate *CandidateNode, operation *Operation) (*list.List, error) {
|
|
log.Debug("operation Value %v", operation.Value)
|
|
indices := []*yaml.Node{&yaml.Node{Value: operation.StringValue}}
|
|
return traverseArrayWithIndices(candidate, indices)
|
|
}
|