Added shuffle command #1503

This commit is contained in:
Mike Farah 2023-02-11 05:08:20 +11:00
parent a1698b740a
commit d17fd9424e
6 changed files with 124 additions and 0 deletions

View File

@ -0,0 +1,4 @@
# Shuffle
Shuffles an array. Note that this command does _not_ use a cryptographically secure random number generator to randomise the array order.

View File

@ -0,0 +1,51 @@
# Shuffle
Shuffles an array. Note that this command does _not_ use a cryptographically secure random number generator to randomise the array order.
## Shuffle array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
- 4
- 5
```
then
```bash
yq 'shuffle' sample.yml
```
will output
```yaml
- 5
- 2
- 4
- 1
- 3
```
## Shuffle array in place
Given a sample.yml file of:
```yaml
cool:
- 1
- 2
- 3
- 4
- 5
```
then
```bash
yq '.cool |= shuffle' sample.yml
```
will output
```yaml
cool:
- 5
- 2
- 4
- 1
- 3
```

View File

@ -49,6 +49,7 @@ var participleYqRules = []*participleYqRule{
simpleOp("to_?unix", toUnixOpType), simpleOp("to_?unix", toUnixOpType),
simpleOp("with_dtf", withDtFormatOpType), simpleOp("with_dtf", withDtFormatOpType),
simpleOp("error", errorOpType), simpleOp("error", errorOpType),
simpleOp("shuffle", shuffleOpType),
simpleOp("sortKeys", sortKeysOpType), simpleOp("sortKeys", sortKeysOpType),
simpleOp("sort_?keys", sortKeysOpType), simpleOp("sort_?keys", sortKeysOpType),

View File

@ -135,6 +135,7 @@ var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50,
var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 50, Handler: sortByOperator} var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 50, Handler: sortByOperator}
var reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 50, Handler: reverseOperator} var reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 50, Handler: reverseOperator}
var sortOpType = &operationType{Type: "SORT", NumArgs: 0, Precedence: 50, Handler: sortOperator} var sortOpType = &operationType{Type: "SORT", NumArgs: 0, Precedence: 50, Handler: sortOperator}
var shuffleOpType = &operationType{Type: "SHUFFLE", NumArgs: 0, Precedence: 50, Handler: shuffleOperator}
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator} var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}

View File

@ -0,0 +1,37 @@
package yqlib
import (
"container/list"
"fmt"
"math/rand"
yaml "gopkg.in/yaml.v3"
)
func shuffleOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
// ignore CWE-338 gosec issue of not using crypto/rand
// this is just to shuffle an array rather generating a
// secret or something that needs proper rand.
myRand := rand.New(rand.NewSource(Now().UnixNano())) // #nosec
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())
}
result := deepClone(candidateNode)
a := result.Content
myRand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] })
results.PushBack(candidate.CreateReplacement(result))
}
return context.ChildContext(results), nil
}

View File

@ -0,0 +1,30 @@
package yqlib
import "testing"
var shuffleOperatorScenarios = []expressionScenario{
{
description: "Shuffle array",
document: "[1, 2, 3, 4, 5]",
expression: `shuffle`,
expected: []string{
"D0, P[], (!!seq)::[5, 2, 4, 1, 3]\n",
},
},
{
description: "Shuffle array in place",
document: "cool: [1, 2, 3, 4, 5]",
expression: `.cool |= shuffle`,
expected: []string{
"D0, P[], (doc)::cool: [5, 2, 4, 1, 3]\n",
},
},
}
func TestShuffleByOperatorScenarios(t *testing.T) {
for _, tt := range shuffleOperatorScenarios {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "shuffle", shuffleOperatorScenarios)
}