mirror of
https://github.com/mikefarah/yq.git
synced 2025-01-26 08:25:38 +00:00
wip
This commit is contained in:
parent
f7f8bed955
commit
7b54bead5e
@ -2,8 +2,8 @@ This is the simplest (and perhaps most used) operator, it is used to navigate de
|
||||
## Simple map navigation
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: {b: apple}
|
||||
'': null
|
||||
a:
|
||||
b: apple
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -11,7 +11,7 @@ yq eval '.a' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
{b: apple}
|
||||
b: apple
|
||||
```
|
||||
|
||||
## Splat
|
||||
@ -20,9 +20,7 @@ Often used to pipe children into other operators
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- b: apple
|
||||
'': null
|
||||
- c: banana
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -31,9 +29,7 @@ yq eval '.[]' sample.yml
|
||||
will output
|
||||
```yaml
|
||||
b: apple
|
||||
'': null
|
||||
c: banana
|
||||
'': null
|
||||
```
|
||||
|
||||
## Special characters
|
||||
@ -42,7 +38,6 @@ Use quotes around path elements with special characters
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
"{}": frog
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -59,7 +54,6 @@ Nodes are added dynamically while traversing
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
c: banana
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -73,8 +67,9 @@ null
|
||||
## Wildcard matching
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: {cat: apple, mad: things}
|
||||
'': null
|
||||
a:
|
||||
cat: apple
|
||||
mad: things
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -89,9 +84,9 @@ things
|
||||
## Aliases
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat {c: frog}
|
||||
a: &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -105,9 +100,9 @@ will output
|
||||
## Traversing aliases with splat
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat {c: frog}
|
||||
a: &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -121,9 +116,9 @@ frog
|
||||
## Traversing aliases explicitly
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat {c: frog}
|
||||
a: &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -154,7 +149,6 @@ will output
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
2: cat
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -162,13 +156,13 @@ yq eval '.[2]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
```
|
||||
|
||||
## Maps with non existing numeric keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: b
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -176,6 +170,7 @@ yq eval '.[0]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
null
|
||||
```
|
||||
|
||||
## Traversing merge anchors
|
||||
@ -191,13 +186,14 @@ bar: &bar
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<: [*foo, *bar]
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -221,13 +217,14 @@ bar: &bar
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<: [*foo, *bar]
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -251,13 +248,14 @@ bar: &bar
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<: [*foo, *bar]
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -281,13 +279,14 @@ bar: &bar
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<: [*foo, *bar]
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -315,13 +314,14 @@ bar: &bar
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<: [*foo, *bar]
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -345,13 +345,14 @@ bar: &bar
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<: [*foo, *bar]
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
'': null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@ -360,7 +361,26 @@ yq eval '.foobarList.[]' sample.yml
|
||||
will output
|
||||
```yaml
|
||||
foobarList_b
|
||||
[*foo, *bar]
|
||||
- *foo
|
||||
- *bar
|
||||
foobarList_c
|
||||
```
|
||||
|
||||
## Select multiple indices
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a[0, 2]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a
|
||||
c
|
||||
```
|
||||
|
||||
|
@ -1,139 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TraverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
// lhs is an expression that will yield a bunch of arrays
|
||||
// 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
|
||||
|
||||
var matchingNodeMap = list.New()
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := UnwrapDoc(candidate.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
|
||||
} else if node.Kind == yaml.AliasNode {
|
||||
candidate.Node = node.Alias
|
||||
node = node.Alias
|
||||
}
|
||||
|
||||
if node.Kind == yaml.SequenceNode {
|
||||
newNodes, err := traverseArrayWithIndices(candidate, indicesToTraverse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchingNodeMap.PushBackList(newNodes)
|
||||
} else if node.Kind == yaml.MappingNode && len(indicesToTraverse) == 0 {
|
||||
// splat the map
|
||||
newNodes, err := traverseMapWithIndices(candidate, indicesToTraverse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchingNodeMap.PushBackList(newNodes)
|
||||
} else {
|
||||
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", candidate, node.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) {
|
||||
//REWRITE TO USE TRAVERSE MAP
|
||||
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
var contents = node.Content
|
||||
var matchingNodeMap = list.New()
|
||||
if len(indices) == 0 {
|
||||
for index := 0; index < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
matchingNodeMap.PushBack(&CandidateNode{
|
||||
Node: value,
|
||||
Path: candidate.CreateChildPath(key.Value),
|
||||
Document: candidate.Document,
|
||||
})
|
||||
}
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
for index := 0; index < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
for _, indexNode := range indices {
|
||||
if key.Value == indexNode.Value {
|
||||
matchingNodeMap.PushBack(&CandidateNode{
|
||||
Node: value,
|
||||
Path: candidate.CreateChildPath(key.Value),
|
||||
Document: candidate.Document,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
index, err := strconv.ParseInt(indexNode.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var traverseArrayOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `[a,b,c]`,
|
||||
expression: `.[]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!str)::a\n",
|
||||
"D0, P[1], (!!str)::b\n",
|
||||
"D0, P[2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[a,b,c]`,
|
||||
expression: `[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[0]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[0, 2]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[0, 2]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[0]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[-1]`,
|
||||
expected: []string{
|
||||
"D0, P[a -1], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[-1]`,
|
||||
expected: []string{
|
||||
"D0, P[a -1], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[-2]`,
|
||||
expected: []string{
|
||||
"D0, P[a -2], (!!str)::b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[-2]`,
|
||||
expected: []string{
|
||||
"D0, P[a -2], (!!str)::b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a | .[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestTraverseArrayOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range traverseArrayOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"container/list"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/elliotchance/orderedmap"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
@ -14,10 +14,7 @@ type TraversePreferences struct {
|
||||
}
|
||||
|
||||
func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) {
|
||||
preferences := &TraversePreferences{DontFollowAlias: true}
|
||||
splatOperation := &Operation{OperationType: TraversePath, Value: "[]", Preferences: preferences}
|
||||
splatTreeNode := &PathTreeNode{Operation: splatOperation}
|
||||
return TraversePathOperator(d, matches, splatTreeNode)
|
||||
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), false)
|
||||
}
|
||||
|
||||
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
@ -56,7 +53,12 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
|
||||
switch value.Kind {
|
||||
case yaml.MappingNode:
|
||||
log.Debug("its a map with %v entries", len(value.Content)/2)
|
||||
return traverseMap(matchingNode, operation)
|
||||
followAlias := true
|
||||
|
||||
if operation.Preferences != nil {
|
||||
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
|
||||
}
|
||||
return traverseMap(matchingNode, operation.StringValue, followAlias)
|
||||
|
||||
case yaml.SequenceNode:
|
||||
log.Debug("its a sequence of %v things!", len(value.Content))
|
||||
@ -69,20 +71,155 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
|
||||
case yaml.DocumentNode:
|
||||
log.Debug("digging into doc node")
|
||||
return traverse(d, &CandidateNode{
|
||||
Node: matchingNode.Node.Content[0],
|
||||
Document: matchingNode.Document}, operation)
|
||||
Node: matchingNode.Node.Content[0],
|
||||
Filename: matchingNode.Filename,
|
||||
FileIndex: matchingNode.FileIndex,
|
||||
Document: matchingNode.Document}, operation)
|
||||
default:
|
||||
return list.New(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func keyMatches(key *yaml.Node, pathNode *Operation) bool {
|
||||
return Match(key.Value, pathNode.StringValue)
|
||||
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 traverseMap(matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
|
||||
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) {
|
||||
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
var contents = node.Content
|
||||
var matchingNodeMap = list.New()
|
||||
if len(indices) == 0 {
|
||||
for index := 0; index < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
matchingNodeMap.PushBack(&CandidateNode{
|
||||
Node: value,
|
||||
Path: candidate.CreateChildPath(key.Value),
|
||||
Document: candidate.Document,
|
||||
})
|
||||
}
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
for _, indexNode := range indices {
|
||||
log.Debug("traverseMapWithIndices: %v", indexNode.Value)
|
||||
newNodes, err := traverseMap(candidate, indexNode.Value, followAlias)
|
||||
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) (*list.List, error) {
|
||||
var newMatches = orderedmap.NewOrderedMap()
|
||||
err := doTraverseMap(newMatches, matchingNode, operation)
|
||||
err := doTraverseMap(newMatches, matchingNode, key, followAlias)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -92,10 +229,10 @@ func traverseMap(matchingNode *CandidateNode, operation *Operation) (*list.List,
|
||||
//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: operation.StringValue}, valueNode)
|
||||
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode)
|
||||
candidateNode := &CandidateNode{
|
||||
Node: valueNode,
|
||||
Path: append(matchingNode.Path, operation.StringValue),
|
||||
Path: append(matchingNode.Path, key),
|
||||
Document: matchingNode.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
@ -111,21 +248,14 @@ func traverseMap(matchingNode *CandidateNode, operation *Operation) (*list.List,
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, operation *Operation) error {
|
||||
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, followAlias 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.
|
||||
//TODO ALIASES, auto creation?
|
||||
|
||||
node := candidate.Node
|
||||
|
||||
followAlias := true
|
||||
|
||||
if operation.Preferences != nil {
|
||||
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
|
||||
}
|
||||
|
||||
var contents = node.Content
|
||||
for index := 0; index < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
@ -135,11 +265,11 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
|
||||
//skip the 'merge' tag, find a direct match first
|
||||
if key.Tag == "!!merge" && followAlias {
|
||||
log.Debug("Merge anchor")
|
||||
err := traverseMergeAnchor(newMatches, candidate, value, operation)
|
||||
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if keyMatches(key, operation) {
|
||||
} else if keyMatches(key, wantedKey) {
|
||||
log.Debug("MATCHED")
|
||||
candidateNode := &CandidateNode{
|
||||
Node: value,
|
||||
@ -153,7 +283,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) error {
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string) error {
|
||||
switch value.Kind {
|
||||
case yaml.AliasNode:
|
||||
candidateNode := &CandidateNode{
|
||||
@ -161,10 +291,10 @@ func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *C
|
||||
Path: originalCandidate.Path,
|
||||
Document: originalCandidate.Document,
|
||||
}
|
||||
return doTraverseMap(newMatches, candidateNode, operation)
|
||||
return doTraverseMap(newMatches, candidateNode, wantedKey, true)
|
||||
case yaml.SequenceNode:
|
||||
for _, childValue := range value.Content {
|
||||
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, operation)
|
||||
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -175,49 +305,6 @@ func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *C
|
||||
|
||||
func traverseArray(candidate *CandidateNode, operation *Operation) (*list.List, error) {
|
||||
log.Debug("operation Value %v", operation.Value)
|
||||
if operation.Value == "[]" {
|
||||
|
||||
var contents = candidate.Node.Content
|
||||
var newMatches = list.New()
|
||||
var index int64
|
||||
for index = 0; index < int64(len(contents)); index = index + 1 {
|
||||
|
||||
newMatches.PushBack(&CandidateNode{
|
||||
Document: candidate.Document,
|
||||
Path: candidate.CreateChildPath(index),
|
||||
Node: contents[index],
|
||||
})
|
||||
}
|
||||
return newMatches, nil
|
||||
|
||||
}
|
||||
|
||||
switch operation.Value.(type) {
|
||||
case int64:
|
||||
index := operation.Value.(int64)
|
||||
indexToUse := index
|
||||
contentLength := int64(len(candidate.Node.Content))
|
||||
for contentLength <= index {
|
||||
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
|
||||
contentLength = int64(len(candidate.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)
|
||||
}
|
||||
|
||||
return nodeToMap(&CandidateNode{
|
||||
Node: candidate.Node.Content[indexToUse],
|
||||
Document: candidate.Document,
|
||||
Path: candidate.CreateChildPath(index),
|
||||
}), nil
|
||||
default:
|
||||
log.Debug("argument not an int (%v), no array matches", operation.Value)
|
||||
return list.New(), nil
|
||||
}
|
||||
|
||||
indices := []*yaml.Node{&yaml.Node{Value: operation.StringValue}}
|
||||
return traverseArrayWithIndices(candidate, indices)
|
||||
}
|
||||
|
@ -150,12 +150,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[b c], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[1,2,3]`,
|
||||
expression: `.b`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "Traversing arrays by index",
|
||||
document: `[1,2,3]`,
|
||||
@ -274,6 +268,120 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[a,b,c]`,
|
||||
expression: `.[]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!str)::a\n",
|
||||
"D0, P[1], (!!str)::b\n",
|
||||
"D0, P[2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[a,b,c]`,
|
||||
expression: `[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[0]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Select multiple indices",
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[0, 2]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[0, 2]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[0]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[-1]`,
|
||||
expected: []string{
|
||||
"D0, P[a -1], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[-1]`,
|
||||
expected: []string{
|
||||
"D0, P[a -1], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[-2]`,
|
||||
expected: []string{
|
||||
"D0, P[a -2], (!!str)::b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[-2]`,
|
||||
expected: []string{
|
||||
"D0, P[a -2], (!!str)::b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a | .[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestTraversePathOperatorScenarios(t *testing.T) {
|
||||
|
@ -38,7 +38,7 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
||||
if s.document != "" {
|
||||
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
|
||||
if err != nil {
|
||||
t.Error(err, s.document)
|
||||
t.Error(err, s.document, s.expression)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@ -55,7 +55,7 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
||||
results, err = treeNavigator.GetMatchingNodes(inputs, node)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(fmt.Errorf("%v: %v", err, s.expression))
|
||||
return
|
||||
}
|
||||
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
|
||||
@ -167,17 +167,17 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
|
||||
if s.document != "" {
|
||||
node, err := treeCreator.ParsePath(s.expression)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(err, s.expression)
|
||||
}
|
||||
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(err, s.expression)
|
||||
}
|
||||
} else {
|
||||
err = streamEvaluator.EvaluateNew(s.expression, printer)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(err, s.expression)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user