diff --git a/pkg/yqlib/doc/Traverse.md b/pkg/yqlib/doc/Traverse.md index 3e4239a0..44dfdbdd 100644 --- a/pkg/yqlib/doc/Traverse.md +++ b/pkg/yqlib/doc/Traverse.md @@ -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 +``` + diff --git a/pkg/yqlib/operator_array_traverse.go b/pkg/yqlib/operator_array_traverse.go deleted file mode 100644 index 52495e16..00000000 --- a/pkg/yqlib/operator_array_traverse.go +++ /dev/null @@ -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 -} diff --git a/pkg/yqlib/operator_array_traverse_test.go b/pkg/yqlib/operator_array_traverse_test.go deleted file mode 100644 index 5756e782..00000000 --- a/pkg/yqlib/operator_array_traverse_test.go +++ /dev/null @@ -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) - } -} diff --git a/pkg/yqlib/operator_traverse_path.go b/pkg/yqlib/operator_traverse_path.go index 225929c1..b4f81f64 100644 --- a/pkg/yqlib/operator_traverse_path.go +++ b/pkg/yqlib/operator_traverse_path.go @@ -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) } diff --git a/pkg/yqlib/operator_traverse_path_test.go b/pkg/yqlib/operator_traverse_path_test.go index 1a9ea912..daaa4178 100644 --- a/pkg/yqlib/operator_traverse_path_test.go +++ b/pkg/yqlib/operator_traverse_path_test.go @@ -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) { diff --git a/pkg/yqlib/operators_test.go b/pkg/yqlib/operators_test.go index 610c8873..95047a05 100644 --- a/pkg/yqlib/operators_test.go +++ b/pkg/yqlib/operators_test.go @@ -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) } }