mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Traverse Array Operator
This commit is contained in:
parent
ea231006ed
commit
6a13c8b78f
@ -65,6 +65,7 @@ var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Han
|
|||||||
|
|
||||||
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
||||||
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||||
|
var TraverseArray = &OperationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: TraverseArrayOperator}
|
||||||
|
|
||||||
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||||
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
|
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
|
||||||
|
91
pkg/yqlib/operator_array_traverse.go
Normal file
91
pkg/yqlib/operator_array_traverse.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 := lhs.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
|
if candidate.Node.Kind == yaml.SequenceNode {
|
||||||
|
newNodes, err := traverseArrayWithIndices(candidate, indicesToTraverse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
matchingNodeMap.PushBackList(newNodes)
|
||||||
|
} else {
|
||||||
|
log.Debugf("OperatorArray Traverse skipping %v as its a %v", candidate, candidate.Node.Tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchingNodeMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) {
|
||||||
|
log.Debug("traverseArrayWithIndices")
|
||||||
|
var newMatches = list.New()
|
||||||
|
|
||||||
|
if len(indices) == 0 {
|
||||||
|
var contents = candidate.Node.Content
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, indexNode := range indices {
|
||||||
|
index, err := strconv.ParseInt(indexNode.Value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
newMatches.PushBack(&CandidateNode{
|
||||||
|
Node: candidate.Node.Content[indexToUse],
|
||||||
|
Document: candidate.Document,
|
||||||
|
Path: candidate.CreateChildPath(index),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return newMatches, nil
|
||||||
|
}
|
91
pkg/yqlib/operator_array_traverse_test.go
Normal file
91
pkg/yqlib/operator_array_traverse_test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var traverseArrayOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
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]`,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -19,8 +19,18 @@ var pathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
`.a[]`,
|
`.a[]`,
|
||||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"),
|
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "]"),
|
||||||
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.a[0]`,
|
||||||
|
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||||
|
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.a.[0]`,
|
||||||
|
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||||
|
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`.a.[]`,
|
`.a.[]`,
|
||||||
@ -29,8 +39,8 @@ var pathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
`.a[].c`,
|
`.a[].c`,
|
||||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]", "SHORT_PIPE", "c"),
|
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "c"),
|
||||||
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE", "c", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "c", "SHORT_PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`[3]`,
|
`[3]`,
|
||||||
|
@ -22,7 +22,6 @@ const (
|
|||||||
CloseCollect
|
CloseCollect
|
||||||
OpenCollectObject
|
OpenCollectObject
|
||||||
CloseCollectObject
|
CloseCollectObject
|
||||||
SplatOrEmptyCollect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
@ -49,8 +48,6 @@ func (t *Token) toString() string {
|
|||||||
return "{"
|
return "{"
|
||||||
} else if t.TokenType == CloseCollectObject {
|
} else if t.TokenType == CloseCollectObject {
|
||||||
return "}"
|
return "}"
|
||||||
} else if t.TokenType == SplatOrEmptyCollect {
|
|
||||||
return "[]?"
|
|
||||||
} else {
|
} else {
|
||||||
return "NFI"
|
return "NFI"
|
||||||
}
|
}
|
||||||
@ -254,8 +251,6 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
|
|
||||||
lexer.Add([]byte(`"[^"]*"`), stringValue(true))
|
lexer.Add([]byte(`"[^"]*"`), stringValue(true))
|
||||||
|
|
||||||
lexer.Add([]byte(`\[\]`), literalToken(SplatOrEmptyCollect, true))
|
|
||||||
|
|
||||||
lexer.Add([]byte(`\[`), literalToken(OpenCollect, false))
|
lexer.Add([]byte(`\[`), literalToken(OpenCollect, false))
|
||||||
lexer.Add([]byte(`\]`), literalToken(CloseCollect, true))
|
lexer.Add([]byte(`\]`), literalToken(CloseCollect, true))
|
||||||
lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false))
|
lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false))
|
||||||
@ -324,25 +319,6 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
|
|||||||
func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTokens []*Token) (tokensAccum []*Token, skipNextToken bool) {
|
func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTokens []*Token) (tokensAccum []*Token, skipNextToken bool) {
|
||||||
skipNextToken = false
|
skipNextToken = false
|
||||||
token := tokens[index]
|
token := tokens[index]
|
||||||
if token.TokenType == SplatOrEmptyCollect {
|
|
||||||
if index > 0 && tokens[index-1].TokenType == OperationToken &&
|
|
||||||
tokens[index-1].Operation.OperationType == TraversePath {
|
|
||||||
// must be a splat without a preceding dot , e.g. .a[]
|
|
||||||
// lets put a pipe in front of it, and convert it to a traverse "[]" token
|
|
||||||
pipeOp := &Operation{OperationType: ShortPipe, Value: "PIPE"}
|
|
||||||
|
|
||||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: pipeOp})
|
|
||||||
|
|
||||||
traverseOp := &Operation{OperationType: TraversePath, Value: "[]", StringValue: "[]"}
|
|
||||||
token = &Token{TokenType: OperationToken, Operation: traverseOp, CheckForPostTraverse: true}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// gotta be a collect empty array, we need to split this into two tokens
|
|
||||||
// one OpenCollect, the other CloseCollect
|
|
||||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OpenCollect})
|
|
||||||
token = &Token{TokenType: CloseCollect, CheckForPostTraverse: true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if index != len(tokens)-1 && token.AssignOperation != nil &&
|
if index != len(tokens)-1 && token.AssignOperation != nil &&
|
||||||
tokens[index+1].TokenType == OperationToken &&
|
tokens[index+1].TokenType == OperationToken &&
|
||||||
@ -359,5 +335,10 @@ func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTok
|
|||||||
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
|
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
|
||||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
||||||
}
|
}
|
||||||
|
if index != len(tokens)-1 && token.CheckForPostTraverse &&
|
||||||
|
tokens[index+1].TokenType == OpenCollect {
|
||||||
|
op := &Operation{OperationType: TraverseArray}
|
||||||
|
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
||||||
|
}
|
||||||
return postProcessedTokens, skipNextToken
|
return postProcessedTokens, skipNextToken
|
||||||
}
|
}
|
||||||
|
60
yq_test.go
60
yq_test.go
@ -1,60 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "fmt"
|
|
||||||
// "runtime"
|
|
||||||
// "testing"
|
|
||||||
|
|
||||||
// "github.com/mikefarah/yq/v2/pkg/marshal"
|
|
||||||
// "github.com/mikefarah/yq/v2/test"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// func TestMultilineString(t *testing.T) {
|
|
||||||
// testString := `
|
|
||||||
// abcd
|
|
||||||
// efg`
|
|
||||||
// formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
|
|
||||||
// test.AssertResult(t, testString, formattedResult)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestNewYaml(t *testing.T) {
|
|
||||||
// result, _ := newYaml([]string{"b.c", "3"})
|
|
||||||
// formattedResult := fmt.Sprintf("%v", result)
|
|
||||||
// test.AssertResult(t,
|
|
||||||
// "[{b [{c 3}]}]",
|
|
||||||
// formattedResult)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestNewYamlArray(t *testing.T) {
|
|
||||||
// result, _ := newYaml([]string{"[0].cat", "meow"})
|
|
||||||
// formattedResult := fmt.Sprintf("%v", result)
|
|
||||||
// test.AssertResult(t,
|
|
||||||
// "[[{cat meow}]]",
|
|
||||||
// formattedResult)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestNewYaml_WithScript(t *testing.T) {
|
|
||||||
// writeScript = "examples/instruction_sample.yaml"
|
|
||||||
// expectedResult := `b:
|
|
||||||
// c: cat
|
|
||||||
// e:
|
|
||||||
// - name: Mike Farah`
|
|
||||||
// result, _ := newYaml([]string{""})
|
|
||||||
// actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
|
|
||||||
// test.AssertResult(t, expectedResult, actualResult)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestNewYaml_WithUnknownScript(t *testing.T) {
|
|
||||||
// writeScript = "fake-unknown"
|
|
||||||
// _, err := newYaml([]string{""})
|
|
||||||
// if err == nil {
|
|
||||||
// t.Error("Expected error due to unknown file")
|
|
||||||
// }
|
|
||||||
// var expectedOutput string
|
|
||||||
// if runtime.GOOS == "windows" {
|
|
||||||
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
|
|
||||||
// } else {
|
|
||||||
// expectedOutput = `open fake-unknown: no such file or directory`
|
|
||||||
// }
|
|
||||||
// test.AssertResult(t, expectedOutput, err.Error())
|
|
||||||
// }
|
|
Loading…
x
Reference in New Issue
Block a user