From 8142e94349d0d231cb73ace767bfc01aaef43ec1 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Tue, 22 Feb 2022 14:15:31 +1100 Subject: [PATCH] Added reverse op --- pkg/yqlib/doc/operators/headers/reverse.md | 3 ++ pkg/yqlib/doc/operators/headers/sort.md | 3 ++ pkg/yqlib/doc/operators/reverse.md | 48 +++++++++++++++++++ pkg/yqlib/doc/operators/sort.md | 23 +++++++++ pkg/yqlib/expression_tokeniser.go | 1 + pkg/yqlib/lib.go | 1 + pkg/yqlib/operator_reverse.go | 34 ++++++++++++++ pkg/yqlib/operator_reverse_test.go | 54 ++++++++++++++++++++++ pkg/yqlib/operator_sort_test.go | 9 ++++ 9 files changed, 176 insertions(+) create mode 100644 pkg/yqlib/doc/operators/headers/reverse.md create mode 100644 pkg/yqlib/doc/operators/reverse.md create mode 100644 pkg/yqlib/operator_reverse.go create mode 100644 pkg/yqlib/operator_reverse_test.go diff --git a/pkg/yqlib/doc/operators/headers/reverse.md b/pkg/yqlib/doc/operators/headers/reverse.md new file mode 100644 index 00000000..50602b3e --- /dev/null +++ b/pkg/yqlib/doc/operators/headers/reverse.md @@ -0,0 +1,3 @@ +# Reverse + +Reverses the order of the items in an array diff --git a/pkg/yqlib/doc/operators/headers/sort.md b/pkg/yqlib/doc/operators/headers/sort.md index 509aca0d..1022b80a 100644 --- a/pkg/yqlib/doc/operators/headers/sort.md +++ b/pkg/yqlib/doc/operators/headers/sort.md @@ -2,4 +2,7 @@ Sorts an array. Use `sort` to sort an array as is, or `sort_by(exp)` to sort by a particular expression (e.g. subfield). +To sort by descending order, pipe the results through the `reverse` operator after sorting. + Note that at this stage, `yq` only sorts scalar fields. + diff --git a/pkg/yqlib/doc/operators/reverse.md b/pkg/yqlib/doc/operators/reverse.md new file mode 100644 index 00000000..eb9cafc3 --- /dev/null +++ b/pkg/yqlib/doc/operators/reverse.md @@ -0,0 +1,48 @@ +# Reverse + +Reverses the order of the items in an array + +{% hint style="warning" %} +Note that versions prior to 4.18 require the 'eval/e' command to be specified. + +`yq e ` +{% endhint %} + +## Reverse +Given a sample.yml file of: +```yaml +- 1 +- 2 +- 3 +``` +then +```bash +yq 'reverse' sample.yml +``` +will output +```yaml +- 3 +- 2 +- 1 +``` + +## Sort descending by string field +Use sort with reverse to sort in descending order. + +Given a sample.yml file of: +```yaml +- a: banana +- a: cat +- a: apple +``` +then +```bash +yq 'sort_by(.a) | reverse' sample.yml +``` +will output +```yaml +- a: cat +- a: banana +- a: apple +``` + diff --git a/pkg/yqlib/doc/operators/sort.md b/pkg/yqlib/doc/operators/sort.md index 9885d092..b7b810d3 100644 --- a/pkg/yqlib/doc/operators/sort.md +++ b/pkg/yqlib/doc/operators/sort.md @@ -2,8 +2,11 @@ Sorts an array. Use `sort` to sort an array as is, or `sort_by(exp)` to sort by a particular expression (e.g. subfield). +To sort by descending order, pipe the results through the `reverse` operator after sorting. + Note that at this stage, `yq` only sorts scalar fields. + {% hint style="warning" %} Note that versions prior to 4.18 require the 'eval/e' command to be specified. @@ -28,6 +31,26 @@ will output - a: cat ``` +## Sort descending by string field +Use sort with reverse to sort in descending order. + +Given a sample.yml file of: +```yaml +- a: banana +- a: cat +- a: apple +``` +then +```bash +yq 'sort_by(.a) | reverse' sample.yml +``` +will output +```yaml +- a: cat +- a: banana +- a: apple +``` + ## Sort array in place Given a sample.yml file of: ```yaml diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 8e942640..ddd582d6 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -416,6 +416,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`sort`), opToken(sortOpType)) lexer.Add([]byte(`sort_by`), opToken(sortByOpType)) + lexer.Add([]byte(`reverse`), opToken(reverseOpType)) lexer.Add([]byte(`any`), opToken(anyOpType)) lexer.Add([]byte(`any_c`), opToken(anyConditionOpType)) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 3175ed30..ed2d9c6c 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -123,6 +123,7 @@ var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator} var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 50, Handler: sortByOperator} +var reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 50, Handler: reverseOperator} var sortOpType = &operationType{Type: "SORT", NumArgs: 0, Precedence: 50, Handler: sortOperator} var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator} var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator} diff --git a/pkg/yqlib/operator_reverse.go b/pkg/yqlib/operator_reverse.go new file mode 100644 index 00000000..2721fba9 --- /dev/null +++ b/pkg/yqlib/operator_reverse.go @@ -0,0 +1,34 @@ +package yqlib + +import ( + "container/list" + "fmt" + + yaml "gopkg.in/yaml.v3" +) + +func reverseOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { + 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("node at path [%v] is not an array (it's a %v)", candidate.GetNicePath(), candidate.GetNiceTag()) + } + + reverseList := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Style: candidateNode.Style} + reverseList.Content = make([]*yaml.Node, len(candidateNode.Content)) + + for i, originalNode := range candidateNode.Content { + reverseList.Content[len(candidateNode.Content)-i-1] = originalNode + } + results.PushBack(candidate.CreateReplacement(reverseList)) + + } + + return context.ChildContext(results), nil + +} diff --git a/pkg/yqlib/operator_reverse_test.go b/pkg/yqlib/operator_reverse_test.go new file mode 100644 index 00000000..76fee73a --- /dev/null +++ b/pkg/yqlib/operator_reverse_test.go @@ -0,0 +1,54 @@ +package yqlib + +import "testing" + +var reverseOperatorScenarios = []expressionScenario{ + { + description: "Reverse", + document: "[1, 2, 3]", + expression: `reverse`, + expected: []string{ + "D0, P[], (!!seq)::[3, 2, 1]\n", + }, + }, + { + skipDoc: true, + document: "[]", + expression: `reverse`, + expected: []string{ + "D0, P[], (!!seq)::[]\n", + }, + }, + { + skipDoc: true, + document: "[1]", + expression: `reverse`, + expected: []string{ + "D0, P[], (!!seq)::[1]\n", + }, + }, + { + skipDoc: true, + document: "[1,2]", + expression: `reverse`, + expected: []string{ + "D0, P[], (!!seq)::[2, 1]\n", + }, + }, + { + description: "Sort descending by string field", + subdescription: "Use sort with reverse to sort in descending order.", + document: "[{a: banana},{a: cat},{a: apple}]", + expression: `sort_by(.a) | reverse`, + expected: []string{ + "D0, P[], (!!seq)::[{a: cat}, {a: banana}, {a: apple}]\n", + }, + }, +} + +func TestReverseOperatorScenarios(t *testing.T) { + for _, tt := range reverseOperatorScenarios { + testScenario(t, &tt) + } + documentOperatorScenarios(t, "reverse", reverseOperatorScenarios) +} diff --git a/pkg/yqlib/operator_sort_test.go b/pkg/yqlib/operator_sort_test.go index 7c4128d6..075e81ac 100644 --- a/pkg/yqlib/operator_sort_test.go +++ b/pkg/yqlib/operator_sort_test.go @@ -11,6 +11,15 @@ var sortByOperatorScenarios = []expressionScenario{ "D0, P[], (!!seq)::[{a: apple}, {a: banana}, {a: cat}]\n", }, }, + { + description: "Sort descending by string field", + subdescription: "Use sort with reverse to sort in descending order.", + document: "[{a: banana},{a: cat},{a: apple}]", + expression: `sort_by(.a) | reverse`, + expected: []string{ + "D0, P[], (!!seq)::[{a: cat}, {a: banana}, {a: apple}]\n", + }, + }, { description: "Sort array in place", document: "cool: [{a: banana},{a: cat},{a: apple}]",