Fixed empty array op

This commit is contained in:
Mike Farah 2020-11-22 13:50:32 +11:00
parent aed598c736
commit 3f04a1b52e
15 changed files with 45 additions and 82 deletions

View File

@ -100,7 +100,7 @@ a:
``` ```
then then
```bash ```bash
yq eval '.a[] | select(. == "apple") |= "frog"' sample.yml yq eval '.a.[] | select(. == "apple") |= "frog"' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@ -10,6 +10,7 @@ yq eval --null-input '[]'
``` ```
will output will output
```yaml ```yaml
[]
``` ```
## Collect single ## Collect single

View File

@ -34,7 +34,7 @@ pets:
``` ```
then then
```bash ```bash
yq eval '{.name: .pets[]}' sample.yml yq eval '{.name: .pets.[]}' sample.yml
``` ```
will output will output
```yaml ```yaml
@ -57,7 +57,7 @@ pets:
``` ```
then then
```bash ```bash
yq eval '{.name: .pets[]}' sample.yml yq eval '{.name: .pets.[]}' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@ -1,53 +0,0 @@
## Retrieve a document index
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval '.a | documentIndex' sample.yml
```
will output
```yaml
0
---
1
```
## Filter by document index
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval 'select(. | documentIndex == 1)' sample.yml
```
will output
```yaml
a: frog
```
## Print Document Index with matches
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml
```
will output
```yaml
match: cat
doc: 0
match: frog
doc: 1
```

View File

@ -26,7 +26,7 @@ a:
``` ```
then then
```bash ```bash
yq eval '(.a[] | select(. == "*at")) |= "rabbit"' sample.yml yq eval '(.a.[] | select(. == "*at")) |= "rabbit"' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@ -73,7 +73,7 @@ var assignOperatorScenarios = []expressionScenario{
{ {
description: "Update selected results", description: "Update selected results",
document: `{a: {b: apple, c: cactus}}`, document: `{a: {b: apple, c: cactus}}`,
expression: `.a[] | select(. == "apple") |= "frog"`, expression: `.a.[] | select(. == "apple") |= "frog"`,
expected: []string{ expected: []string{
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n", "D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
}, },

View File

@ -3,12 +3,18 @@ package yqlib
import ( import (
"container/list" "container/list"
"gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- collectOperation") log.Debugf("-- collectOperation")
if matchMap.Len() == 0 {
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
candidate := &CandidateNode{Node: node}
return nodeToMap(candidate), nil
}
var results = list.New() var results = list.New()
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}

View File

@ -41,7 +41,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
{ {
description: `Using splat to create multiple objects`, description: `Using splat to create multiple objects`,
document: `{name: Mike, pets: [cat, dog]}`, document: `{name: Mike, pets: [cat, dog]}`,
expression: `{.name: .pets[]}`, expression: `{.name: .pets.[]}`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::Mike: cat\n", "D0, P[], (!!map)::Mike: cat\n",
"D0, P[], (!!map)::Mike: dog\n", "D0, P[], (!!map)::Mike: dog\n",
@ -51,7 +51,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
description: `Working with multiple documents`, description: `Working with multiple documents`,
dontFormatInputForDoc: false, dontFormatInputForDoc: false,
document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}", document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}",
expression: `{.name: .pets[]}`, expression: `{.name: .pets.[]}`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::Mike: cat\n", "D0, P[], (!!map)::Mike: cat\n",
"D0, P[], (!!map)::Mike: dog\n", "D0, P[], (!!map)::Mike: dog\n",
@ -62,7 +62,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
{ {
skipDoc: true, skipDoc: true,
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
expression: `{.name: .pets[], "f":.food[]}`, expression: `{.name: .pets.[], "f":.food.[]}`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::Mike: cat\nf: hotdog\n", "D0, P[], (!!map)::Mike: cat\nf: hotdog\n",
"D0, P[], (!!map)::Mike: cat\nf: burger\n", "D0, P[], (!!map)::Mike: cat\nf: burger\n",

View File

@ -9,7 +9,9 @@ var collectOperatorScenarios = []expressionScenario{
description: "Collect empty", description: "Collect empty",
document: ``, document: ``,
expression: `[]`, expression: `[]`,
expected: []string{}, expected: []string{
"D0, P[], (!!seq)::[]\n",
},
}, },
{ {
description: "Collect single", description: "Collect single",
@ -52,7 +54,7 @@ var collectOperatorScenarios = []expressionScenario{
}, },
{ {
document: `a: {b: [1,2,3]}`, document: `a: {b: [1,2,3]}`,
expression: `[.a.b[]]`, expression: `[.a.b.[]]`,
skipDoc: true, skipDoc: true,
expected: []string{ expected: []string{
"D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n", "D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n",

View File

@ -21,14 +21,14 @@ var createMapOperatorScenarios = []expressionScenario{
}, },
{ {
document: `{name: Mike, pets: [cat, dog]}`, document: `{name: Mike, pets: [cat, dog]}`,
expression: `.name: .pets[]`, expression: `.name: .pets.[]`,
expected: []string{ expected: []string{
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n", "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
}, },
}, },
{ {
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`, document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
expression: `.name: .pets[], "f":.food[]`, expression: `.name: .pets.[], "f":.food.[]`,
expected: []string{ expected: []string{
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n", "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n", "D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n",
@ -36,7 +36,7 @@ var createMapOperatorScenarios = []expressionScenario{
}, },
{ {
document: "{name: Mike, pets: [cat, dog], food: [hotdog, burger]}\n---\n{name: Fred, pets: [mouse], food: [pizza, onion, apple]}", document: "{name: Mike, pets: [cat, dog], food: [hotdog, burger]}\n---\n{name: Fred, pets: [mouse], food: [pizza, onion, apple]}",
expression: `.name: .pets[], "f":.food[]`, expression: `.name: .pets.[], "f":.food.[]`,
expected: []string{ expected: []string{
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n", "D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n",
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n", "D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n",

View File

@ -23,7 +23,7 @@ var selectOperatorScenarios = []expressionScenario{
{ {
skipDoc: true, skipDoc: true,
document: `a: [cat,goat,dog]`, document: `a: [cat,goat,dog]`,
expression: `.a[] | select(. == "*at")`, expression: `.a.[] | select(. == "*at")`,
expected: []string{ expected: []string{
"D0, P[a 0], (!!str)::cat\n", "D0, P[a 0], (!!str)::cat\n",
"D0, P[a 1], (!!str)::goat\n"}, "D0, P[a 1], (!!str)::goat\n"},
@ -31,7 +31,7 @@ var selectOperatorScenarios = []expressionScenario{
{ {
description: "Select and update matching values in map", description: "Select and update matching values in map",
document: `a: { things: cat, bob: goat, horse: dog }`, document: `a: { things: cat, bob: goat, horse: dog }`,
expression: `(.a[] | select(. == "*at")) |= "rabbit"`, expression: `(.a.[] | select(. == "*at")) |= "rabbit"`,
expected: []string{ expected: []string{
"D0, P[], (doc)::a: {things: rabbit, bob: rabbit, horse: dog}\n", "D0, P[], (doc)::a: {things: rabbit, bob: rabbit, horse: dog}\n",
}, },
@ -39,7 +39,7 @@ var selectOperatorScenarios = []expressionScenario{
{ {
skipDoc: true, skipDoc: true,
document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`, document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`,
expression: `.a[] | select(.include)`, expression: `.a.[] | select(.include)`,
expected: []string{ expected: []string{
"D0, P[a things], (!!map)::{include: true}\n", "D0, P[a things], (!!map)::{include: true}\n",
"D0, P[a andMe], (!!map)::{include: fold}\n", "D0, P[a andMe], (!!map)::{include: fold}\n",

View File

@ -29,7 +29,7 @@ func testScenario(t *testing.T, s *expressionScenario) {
node, err := treeCreator.ParsePath(s.expression) node, err := treeCreator.ParsePath(s.expression)
if err != nil { if err != nil {
t.Error(err) t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
return return
} }
inputs := list.New() inputs := list.New()

View File

@ -37,8 +37,18 @@ var pathTests = []struct {
// {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")}, // {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")},
// {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")}, // {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")},
// {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")}, // {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")},
// {`.a[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")}, // {`.a.[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")},
// {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")}, // {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")},
// {
// `["cat"]`,
// append(make([]interface{}, 0), "[", "cat (string)", "]"),
// append(make([]interface{}, 0), "cat (string)", "COLLECT", "PIPE"),
// },
{
`[]`,
append(make([]interface{}, 0), "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"),
},
{ {
`d0.a`, `d0.a`,
append(make([]interface{}, 0), "d0", "PIPE", "a"), append(make([]interface{}, 0), "d0", "PIPE", "a"),
@ -85,7 +95,7 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"), append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"),
}, },
{ {
`{.a: .c, .b[]: .f.g[]}`, `{.a: .c, .b.[]: .f.g.[]}`,
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"), append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"),
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"), append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"),
}, },

View File

@ -3,7 +3,7 @@ package yqlib
import ( import (
"errors" "errors"
"gopkg.in/op/go-logging.v1" logging "gopkg.in/op/go-logging.v1"
) )
type PathPostFixer interface { type PathPostFixer interface {

View File

@ -33,6 +33,7 @@ type Token struct {
func (t *Token) toString() string { func (t *Token) toString() string {
if t.TokenType == OperationToken { if t.TokenType == OperationToken {
log.Debug("toString, its an op")
return t.Operation.toString() return t.Operation.toString()
} else if t.TokenType == OpenBracket { } else if t.TokenType == OpenBracket {
return "(" return "("
@ -58,13 +59,7 @@ func pathToken(wrapped bool) lex.Action {
if wrapped { if wrapped {
value = unwrap(value) value = unwrap(value)
} }
op := &Operation{OperationType: TraversePath, Value: value, StringValue: value} log.Debug("PathToken %v", value)
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
}
}
func literalPathToken(value string) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
op := &Operation{OperationType: TraversePath, Value: value, StringValue: value} op := &Operation{OperationType: TraversePath, Value: value, StringValue: value}
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
} }
@ -78,6 +73,7 @@ func documentToken() lex.Action {
if errParsingInt != nil { if errParsingInt != nil {
return nil, errParsingInt return nil, errParsingInt
} }
log.Debug("documentToken %v", string(m.Bytes))
op := &Operation{OperationType: DocumentFilter, Value: number, StringValue: numberString} op := &Operation{OperationType: DocumentFilter, Value: number, StringValue: numberString}
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
} }
@ -93,6 +89,7 @@ func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.A
func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action { func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
value := string(m.Bytes) value := string(m.Bytes)
op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences} op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences}
var assign *Operation var assign *Operation
@ -187,7 +184,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\(`), literalToken(OpenBracket, false)) lexer.Add([]byte(`\(`), literalToken(OpenBracket, false))
lexer.Add([]byte(`\)`), literalToken(CloseBracket, true)) lexer.Add([]byte(`\)`), literalToken(CloseBracket, true))
lexer.Add([]byte(`\.?\[\]`), literalPathToken("[]")) lexer.Add([]byte(`\.\[\]`), pathToken(false))
lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent)) lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent))
lexer.Add([]byte(`,`), opToken(Union)) lexer.Add([]byte(`,`), opToken(Union))