Skip to content

Commit

Permalink
Added shuffle command #1503
Browse files Browse the repository at this point in the history
  • Loading branch information
mikefarah committed Feb 10, 2023
1 parent a1698b7 commit d17fd94
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 0 deletions.
4 changes: 4 additions & 0 deletions pkg/yqlib/doc/operators/headers/shuffle.md
@@ -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.

51 changes: 51 additions & 0 deletions pkg/yqlib/doc/operators/shuffle.md
@@ -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
```

1 change: 1 addition & 0 deletions pkg/yqlib/lexer_participle.go
Expand Up @@ -49,6 +49,7 @@ var participleYqRules = []*participleYqRule{
simpleOp("to_?unix", toUnixOpType),
simpleOp("with_dtf", withDtFormatOpType),
simpleOp("error", errorOpType),
simpleOp("shuffle", shuffleOpType),
simpleOp("sortKeys", sortKeysOpType),
simpleOp("sort_?keys", sortKeysOpType),

Expand Down
1 change: 1 addition & 0 deletions pkg/yqlib/lib.go
Expand Up @@ -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 reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 50, Handler: reverseOperator}
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}

Expand Down
37 changes: 37 additions & 0 deletions pkg/yqlib/operator_shuffle.go
@@ -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
}
30 changes: 30 additions & 0 deletions pkg/yqlib/operator_shuffle_test.go
@@ -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)
}

0 comments on commit d17fd94

Please sign in to comment.