added path operator!

This commit is contained in:
Mike Farah 2020-11-22 12:19:57 +11:00
parent fc3af441e5
commit 064cff1341
6 changed files with 146 additions and 1 deletions

View File

@ -0,0 +1,59 @@
The path operator can be used to find the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.
## Examples
### Map path
Given a sample.yml file of:
```yaml
a:
b: cat
```
then
```bash
yq eval '.a.b | path' sample.yml
```
will output
```yaml
- a
- b
```
### Array path
Given a sample.yml file of:
```yaml
a:
- cat
- dog
```
then
```bash
yq eval '.a.[] | select(. == "dog") | path' sample.yml
```
will output
```yaml
- a
- 1
```
### Print path and value
Given a sample.yml file of:
```yaml
a:
- cat
- dog
- frog
```
then
```bash
yq eval '.a.[] | select(. == "*og") | [{"path":path, "value":.}]' sample.yml
```
will output
```yaml
- path:
- a
- 1
value: dog
- path:
- a
- 2
value: frog
```

View File

@ -0,0 +1 @@
The path operator can be used to find the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.

View File

@ -17,7 +17,6 @@ type OperationType struct {
}
// operators TODO:
// - get path operator (like doc index)
// - write in place
// - mergeAppend (merges and appends arrays)
// - mergeEmpty (sets only if the document is empty, do I do that now?)
@ -51,6 +50,7 @@ var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50,
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}

View File

@ -0,0 +1,39 @@
package yqlib
import (
"container/list"
"fmt"
yaml "gopkg.in/yaml.v3"
)
func createPathNodeFor(pathElement interface{}) *yaml.Node {
switch pathElement := pathElement.(type) {
case string:
return &yaml.Node{Kind: yaml.ScalarNode, Value: pathElement, Tag: "!!str"}
default:
return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", pathElement), Tag: "!!int"}
}
}
func GetPathOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetPath")
var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
content := make([]*yaml.Node, len(candidate.Path))
for pathIndex := 0; pathIndex < len(candidate.Path); pathIndex++ {
path := candidate.Path[pathIndex]
content[pathIndex] = createPathNodeFor(path)
}
node.Content = content
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}

View File

@ -0,0 +1,45 @@
package yqlib
import (
"testing"
)
var pathOperatorScenarios = []expressionScenario{
{
description: "Map path",
document: `{a: {b: cat}}`,
expression: `.a.b | path`,
expected: []string{
"D0, P[a b], (!!seq)::- a\n- b\n",
},
},
{
description: "Array path",
document: `{a: [cat, dog]}`,
expression: `.a.[] | select(. == "dog") | path`,
expected: []string{
"D0, P[a 1], (!!seq)::- a\n- 1\n",
},
},
{
description: "Print path and value",
document: `{a: [cat, dog, frog]}`,
expression: `.a.[] | select(. == "*og") | [{"path":path, "value":.}]`,
expected: []string{`D0, P[], (!!seq)::- path:
- a
- 1
value: dog
- path:
- a
- 2
value: frog
`},
},
}
func TestPathOperatorsScenarios(t *testing.T) {
for _, tt := range pathOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Path Operators", pathOperatorScenarios)
}

View File

@ -206,6 +206,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag))
lexer.Add([]byte(`filename`), opToken(GetFilename))
lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex))
lexer.Add([]byte(`path`), opToken(GetPath))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true}))