mirror of
https://github.com/mikefarah/yq.git
synced 2025-01-27 00:47:56 +00:00
wip
This commit is contained in:
parent
055e441686
commit
2f05f7390f
@ -1 +1,15 @@
|
|||||||
["a a", "b thing", 1, true]
|
strings:
|
||||||
|
- a: banana
|
||||||
|
- a: cat
|
||||||
|
- a: apple
|
||||||
|
|
||||||
|
numbers:
|
||||||
|
- a: 12
|
||||||
|
- a: 13
|
||||||
|
- a: 120
|
||||||
|
|
||||||
|
|
||||||
|
obj:
|
||||||
|
- a: {cat: "adog"}
|
||||||
|
- a: {cat: "doga"}
|
||||||
|
- a: apple
|
||||||
|
19
pkg/yqlib/doc/Sort.md
Normal file
19
pkg/yqlib/doc/Sort.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
## Sort by string field
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: banana
|
||||||
|
- a: cat
|
||||||
|
- a: apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'sort_by(.a)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- a: apple
|
||||||
|
- a: banana
|
||||||
|
- a: cat
|
||||||
|
```
|
||||||
|
|
@ -377,6 +377,8 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
lexer.Add([]byte(`capture`), opToken(captureOpType))
|
lexer.Add([]byte(`capture`), opToken(captureOpType))
|
||||||
lexer.Add([]byte(`test`), opToken(testOpType))
|
lexer.Add([]byte(`test`), opToken(testOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`sort_by`), opToken(sortByOpType))
|
||||||
|
|
||||||
lexer.Add([]byte(`any`), opToken(anyOpType))
|
lexer.Add([]byte(`any`), opToken(anyOpType))
|
||||||
lexer.Add([]byte(`any_c`), opToken(anyConditionOpType))
|
lexer.Add([]byte(`any_c`), opToken(anyConditionOpType))
|
||||||
lexer.Add([]byte(`all`), opToken(allOpType))
|
lexer.Add([]byte(`all`), opToken(allOpType))
|
||||||
|
@ -98,6 +98,7 @@ var getFileIndexOpType = &operationType{Type: "GET_FILE_INDEX", NumArgs: 0, Prec
|
|||||||
var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: getPathOperator}
|
var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: getPathOperator}
|
||||||
|
|
||||||
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
||||||
|
var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 50, Handler: sortByOperator}
|
||||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||||
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
||||||
var subStringOpType = &operationType{Type: "SUBSTR", NumArgs: 1, Precedence: 50, Handler: substituteStringOperator}
|
var subStringOpType = &operationType{Type: "SUBSTR", NumArgs: 1, Precedence: 50, Handler: substituteStringOperator}
|
||||||
|
@ -51,7 +51,7 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
|
|||||||
|
|
||||||
switch lhsNode.Kind {
|
switch lhsNode.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
return nil, fmt.Errorf("Maps not yet supported for addition")
|
return nil, fmt.Errorf("maps not yet supported for addition")
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
target.Node.Kind = yaml.SequenceNode
|
target.Node.Kind = yaml.SequenceNode
|
||||||
target.Node.Style = lhsNode.Style
|
target.Node.Style = lhsNode.Style
|
||||||
|
101
pkg/yqlib/operator_sort.go
Normal file
101
pkg/yqlib/operator_sort.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// context represents the current matching nodes in the expression pipeline
|
||||||
|
//expressionNode is your current expression (sort_by)
|
||||||
|
func sortByOperator(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("%v is not an array", candidate.GetKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
sortableArray := make(sortableNodeArray, len(candidateNode.Content))
|
||||||
|
|
||||||
|
for i, originalNode := range candidateNode.Content {
|
||||||
|
|
||||||
|
childCandidate := candidate.CreateChildInArray(i, originalNode)
|
||||||
|
compareContext, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(childCandidate), expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return Context{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeToCompare := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!null"}
|
||||||
|
if compareContext.MatchingNodes.Len() > 0 {
|
||||||
|
nodeToCompare = compareContext.MatchingNodes.Front().Value.(*CandidateNode).Node
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("going to compare %v by %v", NodeToString(candidate.CreateReplacement(originalNode)), NodeToString(candidate.CreateReplacement(nodeToCompare)))
|
||||||
|
|
||||||
|
sortableArray[i] = sortableNode{Node: originalNode, NodeToCompare: nodeToCompare}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sortableArray)
|
||||||
|
|
||||||
|
sortedList := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Style: candidateNode.Style}
|
||||||
|
sortedList.Content = make([]*yaml.Node, len(candidateNode.Content))
|
||||||
|
|
||||||
|
for i, sortedNode := range sortableArray {
|
||||||
|
sortedList.Content[i] = sortedNode.Node
|
||||||
|
}
|
||||||
|
results.PushBack(candidate.CreateReplacement(sortedList))
|
||||||
|
}
|
||||||
|
return context.ChildContext(results), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableNode struct {
|
||||||
|
Node *yaml.Node
|
||||||
|
NodeToCompare *yaml.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableNodeArray []sortableNode
|
||||||
|
|
||||||
|
func (a sortableNodeArray) Len() int { return len(a) }
|
||||||
|
func (a sortableNodeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
|
||||||
|
func (a sortableNodeArray) Less(i, j int) bool {
|
||||||
|
lhs := a[i].NodeToCompare
|
||||||
|
rhs := a[j].NodeToCompare
|
||||||
|
|
||||||
|
if lhs.Tag != rhs.Tag || lhs.Tag == "!!str" {
|
||||||
|
return strings.Compare(lhs.Value, rhs.Value) < 0
|
||||||
|
} else if lhs.Tag == "!!int" && rhs.Tag == "!!int" {
|
||||||
|
_, lhsNum, err := parseInt(lhs.Value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, rhsNum, err := parseInt(rhs.Value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return lhsNum < rhsNum
|
||||||
|
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") {
|
||||||
|
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return lhsNum < rhsNum
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
29
pkg/yqlib/operator_sort_test.go
Normal file
29
pkg/yqlib/operator_sort_test.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var sortByOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Sort by string field",
|
||||||
|
document: "[{a: banana},{a: cat},{a: apple}]",
|
||||||
|
expression: `sort_by(.a)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!bool)::true\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// description: "Sort, nulls come first",
|
||||||
|
// document: "[8,3,null,6]",
|
||||||
|
// expression: `sort`,
|
||||||
|
// expected: []string{
|
||||||
|
// "D0, P[], (!!bool)::[null,3,6,8]\n",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortByOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range sortByOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "Sort", sortByOperatorScenarios)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user