Skip to content

Commit

Permalink
Add filter operation (#1588)
Browse files Browse the repository at this point in the history
* add filter operation

* add tests

* add tests

* revert debug

* simplify filter

* fix tests

* remove logs
  • Loading branch information
rbren committed Mar 8, 2023
1 parent d30941b commit 9539877
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 0 deletions.
18 changes: 18 additions & 0 deletions pkg/yqlib/doc/operators/filter.md
@@ -0,0 +1,18 @@

## Filter array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq 'filter(. < 3)' sample.yml
```
will output
```yaml
- 1
- 2
```

1 change: 1 addition & 0 deletions pkg/yqlib/lexer_participle.go
Expand Up @@ -37,6 +37,7 @@ var participleYqRules = []*participleYqRule{

{"MapValues", `map_?values`, opToken(mapValuesOpType), 0},
simpleOp("map", mapOpType),
simpleOp("filter", filterOpType),
simpleOp("pick", pickOpType),

{"FlattenWithDepth", `flatten\([0-9]+\)`, flattenWithDepth(), 0},
Expand Down
1 change: 1 addition & 0 deletions pkg/yqlib/lib.go
Expand Up @@ -84,6 +84,7 @@ var expressionOpType = &operationType{Type: "EXP", NumArgs: 0, Precedence: 50, H

var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator}
var mapOpType = &operationType{Type: "MAP", NumArgs: 1, Precedence: 50, Handler: mapOperator}
var filterOpType = &operationType{Type: "FILTER", NumArgs: 1, Precedence: 50, Handler: filterOperator}
var errorOpType = &operationType{Type: "ERROR", NumArgs: 1, Precedence: 50, Handler: errorOperator}
var pickOpType = &operationType{Type: "PICK", NumArgs: 1, Precedence: 50, Handler: pickOperator}
var evalOpType = &operationType{Type: "EVAL", NumArgs: 1, Precedence: 50, Handler: evalOperator}
Expand Down
33 changes: 33 additions & 0 deletions pkg/yqlib/operator_filter.go
@@ -0,0 +1,33 @@
package yqlib

import (
"container/list"
)

func filterOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- filterOperation")
var results = list.New()

for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
children := context.SingleChildContext(candidate)
splatted, err := splat(children, traversePreferences{})
if err != nil {
return Context{}, err
}
filtered, err := selectOperator(d, splatted, expressionNode)
if err != nil {
return Context{}, err
}

selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
collected, err := collectTogether(d, filtered, selfExpression)
if err != nil {
return Context{}, err
}
collected.Node.Style = unwrapDoc(candidate.Node).Style
results.PushBack(collected)
}
return context.ChildContext(results), nil
}

49 changes: 49 additions & 0 deletions pkg/yqlib/operator_filter_test.go
@@ -0,0 +1,49 @@
package yqlib

import (
"testing"
)

var filterOperatorScenarios = []expressionScenario{
{
description: "Filter array",
document: `[1,2,3]`,
expression: `filter(. < 3)`,
expected: []string{
"D0, P[], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
expression: `filter(. > 1)`,
expected: []string{
"D0, P[], (!!seq)::[2, 3]\n",
},
},
{
skipDoc: true,
description: "Filter array to empty",
document: `[1,2,3]`,
expression: `filter(. > 4)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
description: "Filter empty array",
document: `[]`,
expression: `filter(. > 1)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
}

func TestFilterOperatorScenarios(t *testing.T) {
for _, tt := range filterOperatorScenarios {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "filter", filterOperatorScenarios)
}

0 comments on commit 9539877

Please sign in to comment.