mirror of
https://github.com/mikefarah/yq.git
synced 2025-01-23 14:16:10 +00:00
Added any_c and all_c operators
This commit is contained in:
parent
8e14b3b393
commit
f4392f8658
@ -4,8 +4,11 @@ The `or` and `and` operators take two parameters and return a boolean result.
|
|||||||
|
|
||||||
`any` will return `true` if there are any `true` values in a array sequence, and `all` will return true if _all_ elements in an array are true.
|
`any` will return `true` if there are any `true` values in a array sequence, and `all` will return true if _all_ elements in an array are true.
|
||||||
|
|
||||||
|
`any_c(condition)` and `all_c(condition)` are like `any` and `all` but they take a condition expression that is used against each element to determine if it's `true`. Note: in `jq` you can simply pass a condition to `any` or `all` and it simply works - `yq` isn't that clever..yet
|
||||||
|
|
||||||
These are most commonly used with the `select` operator to filter particular nodes.
|
These are most commonly used with the `select` operator to filter particular nodes.
|
||||||
## OR example
|
|
||||||
|
## `or` example
|
||||||
Running
|
Running
|
||||||
```bash
|
```bash
|
||||||
yq eval --null-input 'true or false'
|
yq eval --null-input 'true or false'
|
||||||
@ -15,7 +18,7 @@ will output
|
|||||||
true
|
true
|
||||||
```
|
```
|
||||||
|
|
||||||
## AND example
|
## `and` example
|
||||||
Running
|
Running
|
||||||
```bash
|
```bash
|
||||||
yq eval --null-input 'true and false'
|
yq eval --null-input 'true and false'
|
||||||
@ -47,7 +50,7 @@ will output
|
|||||||
b: fly
|
b: fly
|
||||||
```
|
```
|
||||||
|
|
||||||
## ANY returns true if any boolean in a given array is true
|
## `any` returns true if any boolean in a given array is true
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
- false
|
- false
|
||||||
@ -62,22 +65,7 @@ will output
|
|||||||
true
|
true
|
||||||
```
|
```
|
||||||
|
|
||||||
## ANY returns true if any boolean in a given array is true
|
## `any` returns false for an empty array
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- false
|
|
||||||
- true
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval 'any' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
true
|
|
||||||
```
|
|
||||||
|
|
||||||
## ANY returns false for an empty array
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
[]
|
[]
|
||||||
@ -91,7 +79,27 @@ will output
|
|||||||
false
|
false
|
||||||
```
|
```
|
||||||
|
|
||||||
## ALL returns true if all booleans in a given array are true
|
## `any_c` returns true if any element in the array is true for the given condition.
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
- rad
|
||||||
|
- awesome
|
||||||
|
b:
|
||||||
|
- meh
|
||||||
|
- whatever
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.[] |= any_c(. == "awesome")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: true
|
||||||
|
b: false
|
||||||
|
```
|
||||||
|
|
||||||
|
## `all` returns true if all booleans in a given array are true
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
- true
|
- true
|
||||||
@ -106,7 +114,7 @@ will output
|
|||||||
true
|
true
|
||||||
```
|
```
|
||||||
|
|
||||||
## ANY returns true for an empty array
|
## `all` returns true for an empty array
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
[]
|
[]
|
||||||
@ -120,6 +128,26 @@ will output
|
|||||||
true
|
true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `all_c` returns true if all elements in the array are true for the given condition.
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
- rad
|
||||||
|
- awesome
|
||||||
|
b:
|
||||||
|
- meh
|
||||||
|
- 12
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.[] |= all_c(tag == "!!str")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: true
|
||||||
|
b: false
|
||||||
|
```
|
||||||
|
|
||||||
## Not true is false
|
## Not true is false
|
||||||
Running
|
Running
|
||||||
```bash
|
```bash
|
||||||
|
@ -4,4 +4,6 @@ The `or` and `and` operators take two parameters and return a boolean result.
|
|||||||
|
|
||||||
`any` will return `true` if there are any `true` values in a array sequence, and `all` will return true if _all_ elements in an array are true.
|
`any` will return `true` if there are any `true` values in a array sequence, and `all` will return true if _all_ elements in an array are true.
|
||||||
|
|
||||||
These are most commonly used with the `select` operator to filter particular nodes.
|
`any_c(condition)` and `all_c(condition)` are like `any` and `all` but they take a condition expression that is used against each element to determine if it's `true`. Note: in `jq` you can simply pass a condition to `any` or `all` and it simply works - `yq` isn't that clever..yet
|
||||||
|
|
||||||
|
These are most commonly used with the `select` operator to filter particular nodes.
|
||||||
|
@ -277,7 +277,9 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
lexer.Add([]byte(`sub`), opToken(subStringOpType))
|
lexer.Add([]byte(`sub`), opToken(subStringOpType))
|
||||||
|
|
||||||
lexer.Add([]byte(`any`), opToken(anyOpType))
|
lexer.Add([]byte(`any`), opToken(anyOpType))
|
||||||
|
lexer.Add([]byte(`any_c`), opToken(anyConditionOpType))
|
||||||
lexer.Add([]byte(`all`), opToken(allOpType))
|
lexer.Add([]byte(`all`), opToken(allOpType))
|
||||||
|
lexer.Add([]byte(`all_c`), opToken(allConditionOpType))
|
||||||
|
|
||||||
lexer.Add([]byte(`split`), opToken(splitStringOpType))
|
lexer.Add([]byte(`split`), opToken(splitStringOpType))
|
||||||
lexer.Add([]byte(`keys`), opToken(keysOpType))
|
lexer.Add([]byte(`keys`), opToken(keysOpType))
|
||||||
|
@ -63,6 +63,8 @@ var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50,
|
|||||||
|
|
||||||
var anyOpType = &operationType{Type: "ANY", NumArgs: 0, Precedence: 50, Handler: anyOperator}
|
var anyOpType = &operationType{Type: "ANY", NumArgs: 0, Precedence: 50, Handler: anyOperator}
|
||||||
var allOpType = &operationType{Type: "ALL", NumArgs: 0, Precedence: 50, Handler: allOperator}
|
var allOpType = &operationType{Type: "ALL", NumArgs: 0, Precedence: 50, Handler: allOperator}
|
||||||
|
var anyConditionOpType = &operationType{Type: "ANY_CONDITION", NumArgs: 1, Precedence: 50, Handler: anyOperator}
|
||||||
|
var allConditionOpType = &operationType{Type: "ALL_CONDITION", NumArgs: 1, Precedence: 50, Handler: allOperator}
|
||||||
|
|
||||||
var toEntriesOpType = &operationType{Type: "TO_ENTRIES", NumArgs: 0, Precedence: 50, Handler: toEntriesOperator}
|
var toEntriesOpType = &operationType{Type: "TO_ENTRIES", NumArgs: 0, Precedence: 50, Handler: toEntriesOperator}
|
||||||
var fromEntriesOpType = &operationType{Type: "FROM_ENTRIES", NumArgs: 0, Precedence: 50, Handler: fromEntriesOperator}
|
var fromEntriesOpType = &operationType{Type: "FROM_ENTRIES", NumArgs: 0, Precedence: 50, Handler: fromEntriesOperator}
|
||||||
|
@ -3,10 +3,11 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isTruthyNode(node * yaml.Node) (bool, error) {
|
func isTruthyNode(node *yaml.Node) (bool, error) {
|
||||||
value := true
|
value := true
|
||||||
if node.Tag == "!!null" {
|
if node.Tag == "!!null" {
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -64,9 +65,24 @@ func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func findBoolean(wantBool bool, sequenceNode * yaml.Node) (bool, error) {
|
func findBoolean(wantBool bool, d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, sequenceNode *yaml.Node) (bool, error) {
|
||||||
for _, node := range sequenceNode.Content {
|
for _, node := range sequenceNode.Content {
|
||||||
|
|
||||||
|
if expressionNode != nil {
|
||||||
|
//need to evaluate the expression against the node
|
||||||
|
candidate := &CandidateNode{Node: node}
|
||||||
|
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if rhs.MatchingNodes.Len() > 0 {
|
||||||
|
node = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node
|
||||||
|
} else {
|
||||||
|
// no results found, ignore this entry
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
truthy, err := isTruthyNode(node)
|
truthy, err := isTruthyNode(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -87,7 +103,7 @@ func allOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
|
|||||||
if candidateNode.Kind != yaml.SequenceNode {
|
if candidateNode.Kind != yaml.SequenceNode {
|
||||||
return Context{}, fmt.Errorf("any only supports arrays, was %v", candidateNode.Tag)
|
return Context{}, fmt.Errorf("any only supports arrays, was %v", candidateNode.Tag)
|
||||||
}
|
}
|
||||||
booleanResult, err := findBoolean(false, candidateNode)
|
booleanResult, err := findBoolean(false, d, context, expressionNode.Rhs, candidateNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return Context{}, err
|
||||||
}
|
}
|
||||||
@ -106,7 +122,7 @@ func anyOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
|
|||||||
if candidateNode.Kind != yaml.SequenceNode {
|
if candidateNode.Kind != yaml.SequenceNode {
|
||||||
return Context{}, fmt.Errorf("any only supports arrays, was %v", candidateNode.Tag)
|
return Context{}, fmt.Errorf("any only supports arrays, was %v", candidateNode.Tag)
|
||||||
}
|
}
|
||||||
booleanResult, err := findBoolean(true, candidateNode)
|
booleanResult, err := findBoolean(true, d, context, expressionNode.Rhs, candidateNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return Context{}, err
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
var booleanOperatorScenarios = []expressionScenario{
|
var booleanOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
description: "OR example",
|
description: "`or` example",
|
||||||
expression: `true or false`,
|
expression: `true or false`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::true\n",
|
"D0, P[], (!!bool)::true\n",
|
||||||
@ -29,7 +29,7 @@ var booleanOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "AND example",
|
description: "`and` example",
|
||||||
expression: `true and false`,
|
expression: `true and false`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::false\n",
|
"D0, P[], (!!bool)::false\n",
|
||||||
@ -44,61 +44,69 @@ var booleanOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "ANY returns true if any boolean in a given array is true",
|
description: "`any` returns true if any boolean in a given array is true",
|
||||||
document: `[false, true]`,
|
document: `[false, true]`,
|
||||||
expression: "any",
|
expression: "any",
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::true\n",
|
"D0, P[], (!!bool)::true\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "ANY returns true if any boolean in a given array is true",
|
description: "`any` returns false for an empty array",
|
||||||
document: `[false, true]`,
|
document: `[]`,
|
||||||
expression: "any",
|
expression: "any",
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::true\n",
|
"D0, P[], (!!bool)::false\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "ANY returns false for an empty array",
|
description: "`any_c` returns true if any element in the array is true for the given condition.",
|
||||||
document: `[]`,
|
document: "a: [rad, awesome]\nb: [meh, whatever]",
|
||||||
|
expression: `.[] |= any_c(. == "awesome")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::a: true\nb: false\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `[false, false]`,
|
||||||
expression: "any",
|
expression: "any",
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::false\n",
|
"D0, P[], (!!bool)::false\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
description: "`all` returns true if all booleans in a given array are true",
|
||||||
document: `[false, false]`,
|
document: `[true, true]`,
|
||||||
expression: "any",
|
expression: "all",
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (!!bool)::false\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "ALL returns true if all booleans in a given array are true",
|
|
||||||
document: `[true, true]`,
|
|
||||||
expression: "all",
|
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::true\n",
|
"D0, P[], (!!bool)::true\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: `[false, true]`,
|
document: `[false, true]`,
|
||||||
expression: "all",
|
expression: "all",
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::false\n",
|
"D0, P[], (!!bool)::false\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "ANY returns true for an empty array",
|
description: "`all` returns true for an empty array",
|
||||||
document: `[]`,
|
document: `[]`,
|
||||||
expression: "all",
|
expression: "all",
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::true\n",
|
"D0, P[], (!!bool)::true\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "`all_c` returns true if all elements in the array are true for the given condition.",
|
||||||
|
document: "a: [rad, awesome]\nb: [meh, 12]",
|
||||||
|
expression: `.[] |= all_c(tag == "!!str")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::a: true\nb: false\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
expression: `false or false`,
|
expression: `false or false`,
|
||||||
|
@ -25,8 +25,7 @@ var entriesOperatorScenarios = []expressionScenario{
|
|||||||
description: "to_entries null",
|
description: "to_entries null",
|
||||||
document: `null`,
|
document: `null`,
|
||||||
expression: `to_entries`,
|
expression: `to_entries`,
|
||||||
expected: []string{
|
expected: []string{},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "from_entries map",
|
description: "from_entries map",
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/elliotchance/orderedmap"
|
|
||||||
"container/list"
|
"container/list"
|
||||||
yaml "gopkg.in/yaml.v3"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/elliotchance/orderedmap"
|
||||||
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func unique(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func unique(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||||
@ -19,7 +20,6 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN
|
|||||||
log.Debugf("-- uniqueBy Operator")
|
log.Debugf("-- uniqueBy Operator")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
|
|
||||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
candidateNode := unwrapDoc(candidate.Node)
|
candidateNode := unwrapDoc(candidate.Node)
|
||||||
@ -27,7 +27,7 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN
|
|||||||
if candidateNode.Kind != yaml.SequenceNode {
|
if candidateNode.Kind != yaml.SequenceNode {
|
||||||
return Context{}, fmt.Errorf("Only arrays are supported for unique")
|
return Context{}, fmt.Errorf("Only arrays are supported for unique")
|
||||||
}
|
}
|
||||||
|
|
||||||
var newMatches = orderedmap.NewOrderedMap()
|
var newMatches = orderedmap.NewOrderedMap()
|
||||||
for _, node := range candidateNode.Content {
|
for _, node := range candidateNode.Content {
|
||||||
child := &CandidateNode{Node: node}
|
child := &CandidateNode{Node: node}
|
||||||
@ -56,4 +56,4 @@ func uniqueBy(d *dataTreeNavigator, context Context, expressionNode *ExpressionN
|
|||||||
|
|
||||||
return context.ChildContext(results), nil
|
return context.ChildContext(results), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
|
|
||||||
var uniqueOperatorScenarios = []expressionScenario{
|
var uniqueOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
description: "Unique array of scalars (string/numbers)",
|
description: "Unique array of scalars (string/numbers)",
|
||||||
document: `[1,2,3,2]`,
|
document: `[1,2,3,2]`,
|
||||||
expression: `unique`,
|
expression: `unique`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- 1\n- 2\n- 3\n",
|
"D0, P[], (!!seq)::- 1\n- 2\n- 3\n",
|
||||||
},
|
},
|
||||||
@ -16,8 +16,8 @@ var uniqueOperatorScenarios = []expressionScenario{
|
|||||||
{
|
{
|
||||||
description: "Unique nulls",
|
description: "Unique nulls",
|
||||||
subdescription: "Unique works on the node value, so it considers different representations of nulls to be different",
|
subdescription: "Unique works on the node value, so it considers different representations of nulls to be different",
|
||||||
document: `[~,null, ~, null]`,
|
document: `[~,null, ~, null]`,
|
||||||
expression: `unique`,
|
expression: `unique`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- ~\n- null\n",
|
"D0, P[], (!!seq)::- ~\n- null\n",
|
||||||
},
|
},
|
||||||
@ -25,21 +25,20 @@ var uniqueOperatorScenarios = []expressionScenario{
|
|||||||
{
|
{
|
||||||
description: "Unique all nulls",
|
description: "Unique all nulls",
|
||||||
subdescription: "Run against the node tag to unique all the nulls",
|
subdescription: "Run against the node tag to unique all the nulls",
|
||||||
document: `[~,null, ~, null]`,
|
document: `[~,null, ~, null]`,
|
||||||
expression: `unique_by(tag)`,
|
expression: `unique_by(tag)`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- ~\n",
|
"D0, P[], (!!seq)::- ~\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Unique array object fields",
|
description: "Unique array object fields",
|
||||||
document: `[{name: harry, pet: cat}, {name: billy, pet: dog}, {name: harry, pet: dog}]`,
|
document: `[{name: harry, pet: cat}, {name: billy, pet: dog}, {name: harry, pet: dog}]`,
|
||||||
expression: `unique_by(.name)`,
|
expression: `unique_by(.name)`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- {name: harry, pet: cat}\n- {name: billy, pet: dog}\n",
|
"D0, P[], (!!seq)::- {name: harry, pet: cat}\n- {name: billy, pet: dog}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUniqueOperatorScenarios(t *testing.T) {
|
func TestUniqueOperatorScenarios(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user