Added any and all operators

This commit is contained in:
Mike Farah 2021-05-14 14:29:55 +10:00
parent 8627441705
commit 8e14b3b393
6 changed files with 208 additions and 6 deletions

View File

@ -1,4 +1,10 @@
The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes. The `or` and `and` operators take two parameters and return a boolean result.
`not` flips a boolean from true to false, or vice versa.
`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.
## OR example ## OR example
Running Running
```bash ```bash
@ -41,6 +47,79 @@ will output
b: fly b: fly
``` ```
## ANY returns true if any boolean in a given array is true
Given a sample.yml file of:
```yaml
- false
- true
```
then
```bash
yq eval 'any' sample.yml
```
will output
```yaml
true
```
## ANY returns true if any boolean in a given array is true
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:
```yaml
[]
```
then
```bash
yq eval 'any' sample.yml
```
will output
```yaml
false
```
## ALL returns true if all booleans in a given array are true
Given a sample.yml file of:
```yaml
- true
- true
```
then
```bash
yq eval 'all' sample.yml
```
will output
```yaml
true
```
## ANY returns true for an empty array
Given a sample.yml file of:
```yaml
[]
```
then
```bash
yq eval 'all' sample.yml
```
will output
```yaml
true
```
## Not true is false ## Not true is false
Running Running
```bash ```bash

View File

@ -1 +1,7 @@
The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes. The `or` and `and` operators take two parameters and return a boolean result.
`not` flips a boolean from true to false, or vice versa.
`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.

View File

@ -276,6 +276,9 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`join`), opToken(joinStringOpType)) lexer.Add([]byte(`join`), opToken(joinStringOpType))
lexer.Add([]byte(`sub`), opToken(subStringOpType)) lexer.Add([]byte(`sub`), opToken(subStringOpType))
lexer.Add([]byte(`any`), opToken(anyOpType))
lexer.Add([]byte(`all`), opToken(allOpType))
lexer.Add([]byte(`split`), opToken(splitStringOpType)) lexer.Add([]byte(`split`), opToken(splitStringOpType))
lexer.Add([]byte(`keys`), opToken(keysOpType)) lexer.Add([]byte(`keys`), opToken(keysOpType))

View File

@ -61,6 +61,9 @@ var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence:
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator} var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator} var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
var anyOpType = &operationType{Type: "ANY", NumArgs: 0, Precedence: 50, Handler: anyOperator}
var allOpType = &operationType{Type: "ALL", NumArgs: 0, 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}
var withEntriesOpType = &operationType{Type: "WITH_ENTRIES", NumArgs: 1, Precedence: 50, Handler: withEntriesOperator} var withEntriesOpType = &operationType{Type: "WITH_ENTRIES", NumArgs: 1, Precedence: 50, Handler: withEntriesOperator}

View File

@ -2,14 +2,12 @@ package yqlib
import ( import (
"container/list" "container/list"
"fmt"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func isTruthy(c *CandidateNode) (bool, error) { func isTruthyNode(node * yaml.Node) (bool, error) {
node := unwrapDoc(c.Node)
value := true value := true
if node.Tag == "!!null" { if node.Tag == "!!null" {
return false, nil return false, nil
} }
@ -23,6 +21,11 @@ func isTruthy(c *CandidateNode) (bool, error) {
return value, nil return value, nil
} }
func isTruthy(c *CandidateNode) (bool, error) {
node := unwrapDoc(c.Node)
return isTruthyNode(node)
}
type boolOp func(bool, bool) bool type boolOp func(bool, bool) bool
func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
@ -61,6 +64,58 @@ func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *C
} }
} }
func findBoolean(wantBool bool, sequenceNode * yaml.Node) (bool, error) {
for _, node := range sequenceNode.Content {
truthy, err := isTruthyNode(node)
if err != nil {
return false, err
}
if truthy == wantBool {
return true, nil
}
}
return false, nil
}
func allOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
candidateNode := unwrapDoc(candidate.Node)
if candidateNode.Kind != yaml.SequenceNode {
return Context{}, fmt.Errorf("any only supports arrays, was %v", candidateNode.Tag)
}
booleanResult, err := findBoolean(false, candidateNode)
if err != nil {
return Context{}, err
}
result := createBooleanCandidate(candidate, !booleanResult)
results.PushBack(result)
}
return context.ChildContext(results), nil
}
func anyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
candidateNode := unwrapDoc(candidate.Node)
if candidateNode.Kind != yaml.SequenceNode {
return Context{}, fmt.Errorf("any only supports arrays, was %v", candidateNode.Tag)
}
booleanResult, err := findBoolean(true, candidateNode)
if err != nil {
return Context{}, err
}
result := createBooleanCandidate(candidate, booleanResult)
results.PushBack(result)
}
return context.ChildContext(results), nil
}
func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- orOp") log.Debugf("-- orOp")
return crossFunction(d, context, expressionNode, performBoolOp( return crossFunction(d, context, expressionNode, performBoolOp(

View File

@ -43,6 +43,62 @@ var booleanOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::- {a: bird, b: dog}\n- {a: cat, b: fly}\n", "D0, P[], (!!seq)::- {a: bird, b: dog}\n- {a: cat, b: fly}\n",
}, },
}, },
{
description: "ANY returns true if any boolean in a given array is true",
document: `[false, true]`,
expression: "any",
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{
description: "ANY returns true if any boolean in a given array is true",
document: `[false, true]`,
expression: "any",
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{
description: "ANY returns false for an empty array",
document: `[]`,
expression: "any",
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: `[false, false]`,
expression: "any",
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{
"D0, P[], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: `[false, true]`,
expression: "all",
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "ANY returns true for an empty array",
document: `[]`,
expression: "all",
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{ {
skipDoc: true, skipDoc: true,
expression: `false or false`, expression: `false or false`,