Skip to content

Commit d99614f

Browse files
authored
Slice array (#1403)
1 parent 880397d commit d99614f

File tree

10 files changed

+368
-18
lines changed

10 files changed

+368
-18
lines changed

examples/array.yaml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,2 @@
1-
---
2-
- become: true
3-
gather_facts: false
4-
hosts: lalaland
5-
name: "Apply smth"
6-
roles:
7-
- lala
8-
- land
9-
serial: 1
10-
- become: false
11-
gather_facts: true
1+
- [cat, dog, frog, cow]
2+
- [apple, banana, grape, mango]
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
2+
## Slicing arrays
3+
Given a sample.yml file of:
4+
```yaml
5+
- cat
6+
- dog
7+
- frog
8+
- cow
9+
```
10+
then
11+
```bash
12+
yq '.[1:3]' sample.yml
13+
```
14+
will output
15+
```yaml
16+
- dog
17+
- frog
18+
```
19+
20+
## Slicing arrays - without the first number
21+
Starts from the start of the array
22+
23+
Given a sample.yml file of:
24+
```yaml
25+
- cat
26+
- dog
27+
- frog
28+
- cow
29+
```
30+
then
31+
```bash
32+
yq '.[:2]' sample.yml
33+
```
34+
will output
35+
```yaml
36+
- cat
37+
- dog
38+
```
39+
40+
## Slicing arrays - without the second number
41+
Finishes at the end of the array
42+
43+
Given a sample.yml file of:
44+
```yaml
45+
- cat
46+
- dog
47+
- frog
48+
- cow
49+
```
50+
then
51+
```bash
52+
yq '.[2:]' sample.yml
53+
```
54+
will output
55+
```yaml
56+
- frog
57+
- cow
58+
```
59+
60+
## Slicing arrays - use negative numbers to count backwards from the end
61+
Given a sample.yml file of:
62+
```yaml
63+
- cat
64+
- dog
65+
- frog
66+
- cow
67+
```
68+
then
69+
```bash
70+
yq '.[1:-1]' sample.yml
71+
```
72+
will output
73+
```yaml
74+
- dog
75+
- frog
76+
```
77+

pkg/yqlib/lexer.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package yqlib
33
import (
44
"fmt"
55
"regexp"
6-
"strconv"
76
)
87

98
type expressionTokeniser interface {
@@ -64,11 +63,11 @@ func unwrap(value string) string {
6463
func extractNumberParameter(value string) (int, error) {
6564
parameterParser := regexp.MustCompile(`.*\(([0-9]+)\)`)
6665
matches := parameterParser.FindStringSubmatch(value)
67-
var indent, errParsingInt = strconv.ParseInt(matches[1], 10, 32)
66+
var indent, errParsingInt = parseInt(matches[1])
6867
if errParsingInt != nil {
6968
return 0, errParsingInt
7069
}
71-
return int(indent), nil
70+
return indent, nil
7271
}
7372

7473
func hasOptionParameter(value string, option string) bool {

pkg/yqlib/lexer_participle.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package yqlib
22

33
import (
4+
"regexp"
45
"strconv"
56
"strings"
67

@@ -12,6 +13,10 @@ var participleYqRules = []*participleYqRule{
1213
{"HEAD_COMMENT", `head_?comment|headComment`, opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{HeadComment: true}), 0},
1314
{"FOOT_COMMENT", `foot_?comment|footComment`, opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{FootComment: true}), 0},
1415

16+
{"SliceArray", `\.\[-?[0-9]+:-?[0-9]+\]`, sliceArrayTwoNumbers(), 0},
17+
{"SliceArraySecond", `\.\[\:-?[0-9]+\]`, sliceArraySecondNumberOnly(), 0},
18+
{"SliceArrayFirst", `\.\[-?[0-9]+\:\]`, sliceArrayFirstNumberOnly(), 0},
19+
1520
{"OpenBracket", `\(`, literalToken(openBracket, false), 0},
1621
{"CloseBracket", `\)`, literalToken(closeBracket, true), 0},
1722
{"OpenTraverseArrayCollect", `\.\[`, literalToken(traverseArrayCollect, false), 0},
@@ -300,6 +305,84 @@ func flattenWithDepth() yqAction {
300305
}
301306
}
302307

308+
func sliceArrayTwoNumbers() yqAction {
309+
return func(rawToken lexer.Token) (*token, error) {
310+
value := rawToken.Value
311+
sliceArrayNumbers := regexp.MustCompile(`\.\[(-?[0-9]+)\:(-?[0-9]+)\]`)
312+
matches := sliceArrayNumbers.FindStringSubmatch(value)
313+
log.Debug("sliceArrayTwoNumbers value: %v", value)
314+
log.Debug("Matches: %v", matches)
315+
316+
firstNumber, err := parseInt(matches[1])
317+
if err != nil {
318+
return nil, err
319+
}
320+
secondNumber, err := parseInt(matches[2])
321+
if err != nil {
322+
return nil, err
323+
}
324+
325+
prefs := sliceArrayPreferences{
326+
firstNumber: firstNumber,
327+
secondNumber: secondNumber,
328+
secondNumberDefined: true,
329+
}
330+
log.Debug("%v", prefs)
331+
332+
op := &Operation{OperationType: sliceArrayOpType, Value: sliceArrayOpType.Type, StringValue: value, Preferences: prefs}
333+
return &token{TokenType: operationToken, Operation: op}, nil
334+
}
335+
}
336+
337+
func sliceArraySecondNumberOnly() yqAction {
338+
return func(rawToken lexer.Token) (*token, error) {
339+
value := rawToken.Value
340+
sliceArrayNumbers := regexp.MustCompile(`\.\[\:(-?[0-9]+)\]`)
341+
matches := sliceArrayNumbers.FindStringSubmatch(value)
342+
log.Debug("sliceArraySecondNumberOnly value: %v", value)
343+
log.Debug("Matches: %v", matches)
344+
345+
secondNumber, err := parseInt(matches[1])
346+
if err != nil {
347+
return nil, err
348+
}
349+
350+
prefs := sliceArrayPreferences{
351+
firstNumber: 0,
352+
secondNumber: secondNumber,
353+
secondNumberDefined: true,
354+
}
355+
log.Debug("%v", prefs)
356+
357+
op := &Operation{OperationType: sliceArrayOpType, Value: sliceArrayOpType.Type, StringValue: value, Preferences: prefs}
358+
return &token{TokenType: operationToken, Operation: op}, nil
359+
}
360+
}
361+
362+
func sliceArrayFirstNumberOnly() yqAction {
363+
return func(rawToken lexer.Token) (*token, error) {
364+
value := rawToken.Value
365+
sliceArrayNumbers := regexp.MustCompile(`\.\[(-?[0-9]+)\:\]`)
366+
matches := sliceArrayNumbers.FindStringSubmatch(value)
367+
log.Debug("sliceArrayFirstNumberOnly value: %v", value)
368+
log.Debug("Matches: %v", matches)
369+
370+
firstNumber, err := parseInt(matches[1])
371+
if err != nil {
372+
return nil, err
373+
}
374+
375+
prefs := sliceArrayPreferences{
376+
firstNumber: firstNumber,
377+
secondNumberDefined: false,
378+
}
379+
log.Debug("%v", prefs)
380+
381+
op := &Operation{OperationType: sliceArrayOpType, Value: sliceArrayOpType.Type, StringValue: value, Preferences: prefs}
382+
return &token{TokenType: operationToken, Operation: op}, nil
383+
}
384+
}
385+
303386
func assignAllCommentsOp(updateAssign bool) yqAction {
304387
return func(rawToken lexer.Token) (*token, error) {
305388
log.Debug("assignAllCommentsOp %v", rawToken.Value)

pkg/yqlib/lexer_participle_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,62 @@ type participleLexerScenario struct {
1414
}
1515

1616
var participleLexerScenarios = []participleLexerScenario{
17+
{
18+
expression: ".[1:3]",
19+
tokens: []*token{
20+
{
21+
TokenType: operationToken,
22+
Operation: &Operation{
23+
OperationType: sliceArrayOpType,
24+
Value: "SLICE",
25+
StringValue: ".[1:3]",
26+
Preferences: sliceArrayPreferences{firstNumber: 1, secondNumber: 3, secondNumberDefined: true},
27+
},
28+
},
29+
},
30+
},
31+
{
32+
expression: ".[:3]",
33+
tokens: []*token{
34+
{
35+
TokenType: operationToken,
36+
Operation: &Operation{
37+
OperationType: sliceArrayOpType,
38+
Value: "SLICE",
39+
StringValue: ".[:3]",
40+
Preferences: sliceArrayPreferences{firstNumber: 0, secondNumber: 3, secondNumberDefined: true},
41+
},
42+
},
43+
},
44+
},
45+
{
46+
expression: ".[1:]",
47+
tokens: []*token{
48+
{
49+
TokenType: operationToken,
50+
Operation: &Operation{
51+
OperationType: sliceArrayOpType,
52+
Value: "SLICE",
53+
StringValue: ".[1:]",
54+
Preferences: sliceArrayPreferences{firstNumber: 1, secondNumber: 0, secondNumberDefined: false},
55+
},
56+
},
57+
},
58+
},
59+
{
60+
expression: ".[-100:-54]",
61+
tokens: []*token{
62+
{
63+
TokenType: operationToken,
64+
Operation: &Operation{
65+
OperationType: sliceArrayOpType,
66+
Value: "SLICE",
67+
StringValue: ".[-100:-54]",
68+
Preferences: sliceArrayPreferences{firstNumber: -100, secondNumber: -54, secondNumberDefined: true},
69+
},
70+
},
71+
},
72+
},
1773
{
1874
expression: ".a",
1975
tokens: []*token{

pkg/yqlib/lib.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ var lineOpType = &operationType{Type: "LINE", NumArgs: 0, Precedence: 50, Handle
8181
var columnOpType = &operationType{Type: "LINE", NumArgs: 0, Precedence: 50, Handler: columnOperator}
8282

8383
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator}
84+
var sliceArrayOpType = &operationType{Type: "SLICE", NumArgs: 0, Precedence: 50, Handler: sliceArrayOperator}
8485
var mapOpType = &operationType{Type: "MAP", NumArgs: 1, Precedence: 50, Handler: mapOperator}
8586
var errorOpType = &operationType{Type: "ERROR", NumArgs: 1, Precedence: 50, Handler: errorOperator}
8687
var pickOpType = &operationType{Type: "PICK", NumArgs: 1, Precedence: 50, Handler: pickOperator}
@@ -352,8 +353,8 @@ func parseInt(numberString string) (int, error) {
352353

353354
if err != nil {
354355
return 0, err
355-
} else if parsed > math.MaxInt {
356-
return 0, fmt.Errorf("%v is too big (larger than %v)", parsed, math.MaxInt)
356+
} else if parsed > math.MaxInt || parsed < math.MinInt {
357+
return 0, fmt.Errorf("%v is not within [%v, %v]", parsed, math.MinInt, math.MaxInt)
357358
}
358359

359360
return int(parsed), err

pkg/yqlib/operator_slice.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package yqlib
2+
3+
import (
4+
"container/list"
5+
6+
yaml "gopkg.in/yaml.v3"
7+
)
8+
9+
type sliceArrayPreferences struct {
10+
firstNumber int
11+
secondNumber int
12+
secondNumberDefined bool
13+
}
14+
15+
func sliceArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
16+
17+
lhs, err := d.GetMatchingNodes(context, expressionNode.LHS)
18+
if err != nil {
19+
return Context{}, err
20+
}
21+
prefs := expressionNode.Operation.Preferences.(sliceArrayPreferences)
22+
firstNumber := prefs.firstNumber
23+
secondNumber := prefs.secondNumber
24+
25+
results := list.New()
26+
27+
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
28+
lhsNode := el.Value.(*CandidateNode)
29+
original := unwrapDoc(lhsNode.Node)
30+
31+
relativeFirstNumber := firstNumber
32+
if relativeFirstNumber < 0 {
33+
relativeFirstNumber = len(original.Content) + firstNumber
34+
}
35+
36+
relativeSecondNumber := len(original.Content)
37+
if prefs.secondNumberDefined {
38+
relativeSecondNumber = secondNumber
39+
if relativeSecondNumber < 0 {
40+
relativeSecondNumber = len(original.Content) + secondNumber
41+
}
42+
}
43+
44+
log.Debug("calculateIndicesToTraverse: slice from %v to %v", relativeFirstNumber, relativeSecondNumber)
45+
46+
var newResults []*yaml.Node
47+
for i := relativeFirstNumber; i < relativeSecondNumber; i++ {
48+
newResults = append(newResults, original.Content[i])
49+
}
50+
51+
slicedArrayNode := &yaml.Node{
52+
Kind: yaml.SequenceNode,
53+
Tag: original.Tag,
54+
Content: newResults,
55+
}
56+
results.PushBack(lhsNode.CreateReplacement(slicedArrayNode))
57+
58+
}
59+
60+
// result is now the context that has the nodes we need to put back into a sequence.
61+
//what about multiple arrays in the context? I think we need to create an array for each one
62+
return context.ChildContext(results), nil
63+
}

0 commit comments

Comments
 (0)