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
```
## 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
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")
//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")
result = append(result, &Operation{OperationType: shortPipeOpType})
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", "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", "]"),
@ -72,6 +77,11 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
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.[]`,
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", "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]`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),

View File

@ -29,8 +29,9 @@ const (
type token struct {
TokenType tokenType
Operation *Operation
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
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 {
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(`\[`), 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(closeCollectObject, true))
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, err := d.GetMatchingNodes(context, expressionNode.Rhs)
if err != nil {
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
//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 {
return Context{}, err
}

View File

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