Added optional traverse flag

This commit is contained in:
Mike Farah 2021-05-09 15:36:33 +10:00
parent cc08afc435
commit bb3ffd40b5
6 changed files with 63 additions and 21 deletions

View File

@ -32,6 +32,21 @@ b: apple
c: banana c: banana
``` ```
## Optional Splat
Just like splat, but won't error if you run it against scalars
Given a sample.yml file of:
```yaml
cat
```
then
```bash
yq eval '.[]' sample.yml
```
will output
```yaml
```
## Special characters ## Special characters
Use quotes with brackets around path elements with special characters Use quotes with brackets around path elements with special characters

View File

@ -55,7 +55,15 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
log.Debugf("deleteing open bracket from opstack") log.Debugf("deleteing open bracket from opstack")
//and append a collect to the opStack //and append a collect to the opStack
result = append(result, &Operation{OperationType: collectOperator}) // hack - see if there's the optional traverse flag
// on the close op - move it to the collect op.
// allows for .["cat"]?
prefs := traversePreferences{}
closeTokenMatch := string(currentToken.Match.Bytes)
if closeTokenMatch[len(closeTokenMatch)-1:] == "?" {
prefs.OptionalTraverse = true
}
result = append(result, &Operation{OperationType: collectOperator, Preferences: prefs})
log.Debugf("put collect onto the result") log.Debugf("put collect onto the result")
result = append(result, &Operation{OperationType: shortPipeOpType}) result = append(result, &Operation{OperationType: shortPipeOpType})
log.Debugf("put shortpipe onto the result") log.Debugf("put shortpipe onto the result")

View File

@ -62,6 +62,11 @@ var pathTests = []struct {
append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"), append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"),
append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
}, },
{
`.b[.a]?`,
append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"),
append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{ {
`.[]`, `.[]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"), append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
@ -72,6 +77,11 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
}, },
{
`.a[]?`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{ {
`.a.[]`, `.a.[]`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
@ -82,6 +92,11 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), 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.[0]`, `.a.[0]`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),

View File

@ -29,8 +29,9 @@ const (
type token struct { type token struct {
TokenType tokenType TokenType tokenType
Operation *Operation Operation *Operation
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
Match *machines.Match // match that created this token
} }
@ -145,7 +146,7 @@ func assignAllCommentsOp(updateAssign bool) lex.Action {
func literalToken(pType tokenType, checkForPost bool) lex.Action { func literalToken(pType tokenType, checkForPost bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
return &token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil return &token{TokenType: pType, CheckForPostTraverse: checkForPost, Match: m}, nil
} }
} }
@ -331,7 +332,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`env\([^\)]+\)`), envOp(false)) lexer.Add([]byte(`env\([^\)]+\)`), envOp(false))
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))
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true)) lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
lexer.Add([]byte(`\*[\+|\?d]*`), multiplyWithPrefs()) lexer.Add([]byte(`\*[\+|\?d]*`), multiplyWithPrefs())

View File

@ -90,14 +90,19 @@ func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode
// rhs is a collect expression that will yield indexes to retreive of the arrays // rhs is a collect expression that will yield indexes to retreive of the arrays
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }
prefs := traversePreferences{}
if expressionNode.Rhs.Rhs != nil && expressionNode.Rhs.Rhs.Operation.Preferences != nil {
prefs = expressionNode.Rhs.Rhs.Operation.Preferences.(traversePreferences)
}
var indicesToTraverse = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Content var indicesToTraverse = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Content
//now we traverse the result of the lhs against the indices we found //now we traverse the result of the lhs against the indices we found
result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, traversePreferences{}) result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, prefs)
if err != nil { if err != nil {
return Context{}, err return Context{}, err
} }

View File

@ -45,14 +45,13 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[1], (!!map)::{c: banana}\n", "D0, P[1], (!!map)::{c: banana}\n",
}, },
}, },
// { {
// description: "Optional Splat", description: "Optional Splat",
// subdescription: "Just like splat, but won't error if you run it against scalars", subdescription: "Just like splat, but won't error if you run it against scalars",
// document: `"cat"`, document: `"cat"`,
// expression: `.[]?`, expression: `.[]`,
// expected: []string{ expected: []string{},
// }, },
// },
{ {
description: "Special characters", description: "Special characters",
subdescription: "Use quotes with brackets around path elements with special characters", subdescription: "Use quotes with brackets around path elements with special characters",
@ -112,13 +111,12 @@ var traversePathOperatorScenarios = []expressionScenario{
expression: `.a?`, expression: `.a?`,
expected: []string{}, expected: []string{},
}, },
// { {
// skipDoc: true, skipDoc: true,
// document: `[1,2,3]`, document: `[[1,2,3], {a: frog}]`,
// expression: `.["a"]?`, expression: `.[] | .["a"]?`,
// expected: []string{ expected: []string{"D0, P[1 a], (!!str)::frog\n"},
// }, },
// },
{ {
skipDoc: true, skipDoc: true,
document: ``, document: ``,