Skip to content

Commit 4532346

Browse files
committed
Adding first operator
1 parent ff40a02 commit 4532346

File tree

6 files changed

+588
-0
lines changed

6 files changed

+588
-0
lines changed

pkg/yqlib/doc/operators/first.md

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
2+
## First matching element from array
3+
Given a sample.yml file of:
4+
```yaml
5+
- a: banana
6+
- a: cat
7+
- a: apple
8+
```
9+
then
10+
```bash
11+
yq 'first(.a == "cat")' sample.yml
12+
```
13+
will output
14+
```yaml
15+
a: cat
16+
```
17+
18+
## First matching element from array with multiple matches
19+
Given a sample.yml file of:
20+
```yaml
21+
- a: banana
22+
- a: cat
23+
- a: apple
24+
- a: cat
25+
```
26+
then
27+
```bash
28+
yq 'first(.a == "cat")' sample.yml
29+
```
30+
will output
31+
```yaml
32+
a: cat
33+
```
34+
35+
## First matching element from array with numeric condition
36+
Given a sample.yml file of:
37+
```yaml
38+
- a: 10
39+
- a: 100
40+
- a: 1
41+
```
42+
then
43+
```bash
44+
yq 'first(.a > 50)' sample.yml
45+
```
46+
will output
47+
```yaml
48+
a: 100
49+
```
50+
51+
## First matching element from array with boolean condition
52+
Given a sample.yml file of:
53+
```yaml
54+
- a: false
55+
- a: true
56+
- a: false
57+
```
58+
then
59+
```bash
60+
yq 'first(.a == true)' sample.yml
61+
```
62+
will output
63+
```yaml
64+
a: true
65+
```
66+
67+
## First matching element from array with null values
68+
Given a sample.yml file of:
69+
```yaml
70+
- a: null
71+
- a: cat
72+
- a: apple
73+
```
74+
then
75+
```bash
76+
yq 'first(.a != null)' sample.yml
77+
```
78+
will output
79+
```yaml
80+
a: cat
81+
```
82+
83+
## First matching element from array with complex condition
84+
Given a sample.yml file of:
85+
```yaml
86+
- a: dog
87+
b: 5
88+
- a: cat
89+
b: 3
90+
- a: apple
91+
b: 7
92+
```
93+
then
94+
```bash
95+
yq 'first(.b > 4)' sample.yml
96+
```
97+
will output
98+
```yaml
99+
a: dog
100+
b: 5
101+
```
102+
103+
## First matching element from map
104+
Given a sample.yml file of:
105+
```yaml
106+
x:
107+
a: banana
108+
y:
109+
a: cat
110+
z:
111+
a: apple
112+
```
113+
then
114+
```bash
115+
yq 'first(.a == "cat")' sample.yml
116+
```
117+
will output
118+
```yaml
119+
a: cat
120+
```
121+
122+
## First matching element from map with numeric condition
123+
Given a sample.yml file of:
124+
```yaml
125+
x:
126+
a: 10
127+
y:
128+
a: 100
129+
z:
130+
a: 1
131+
```
132+
then
133+
```bash
134+
yq 'first(.a > 50)' sample.yml
135+
```
136+
will output
137+
```yaml
138+
a: 100
139+
```
140+
141+
## First matching element from nested structure
142+
Given a sample.yml file of:
143+
```yaml
144+
items:
145+
- a: banana
146+
- a: cat
147+
- a: apple
148+
```
149+
then
150+
```bash
151+
yq '.items | first(.a == "cat")' sample.yml
152+
```
153+
will output
154+
```yaml
155+
a: cat
156+
```
157+
158+
## First matching element with no matches
159+
Given a sample.yml file of:
160+
```yaml
161+
- a: banana
162+
- a: cat
163+
- a: apple
164+
```
165+
then
166+
```bash
167+
yq 'first(.a == "dog")' sample.yml
168+
```
169+
will output
170+
```yaml
171+
```
172+
173+
## First matching element from empty array
174+
Given a sample.yml file of:
175+
```yaml
176+
[]
177+
```
178+
then
179+
```bash
180+
yq 'first(.a == "cat")' sample.yml
181+
```
182+
will output
183+
```yaml
184+
```
185+
186+
## First matching element from scalar node
187+
Given a sample.yml file of:
188+
```yaml
189+
hello
190+
```
191+
then
192+
```bash
193+
yq 'first(. == "hello")' sample.yml
194+
```
195+
will output
196+
```yaml
197+
```
198+
199+
## First matching element from null node
200+
Given a sample.yml file of:
201+
```yaml
202+
null
203+
```
204+
then
205+
```bash
206+
yq 'first(. == "hello")' sample.yml
207+
```
208+
will output
209+
```yaml
210+
```
211+
212+
## First matching element with string condition
213+
Given a sample.yml file of:
214+
```yaml
215+
- a: banana
216+
- a: cat
217+
- a: apple
218+
```
219+
then
220+
```bash
221+
yq 'first(.a | test("^c"))' sample.yml
222+
```
223+
will output
224+
```yaml
225+
a: cat
226+
```
227+
228+
## First matching element with length condition
229+
Given a sample.yml file of:
230+
```yaml
231+
- a: hi
232+
- a: hello
233+
- a: world
234+
```
235+
then
236+
```bash
237+
yq 'first(.a | length > 4)' sample.yml
238+
```
239+
will output
240+
```yaml
241+
a: hello
242+
```
243+
244+
## First matching element from array of strings
245+
Given a sample.yml file of:
246+
```yaml
247+
- banana
248+
- cat
249+
- apple
250+
```
251+
then
252+
```bash
253+
yq 'first(. == "cat")' sample.yml
254+
```
255+
will output
256+
```yaml
257+
cat
258+
```
259+
260+
## First matching element from array of numbers
261+
Given a sample.yml file of:
262+
```yaml
263+
- 10
264+
- 100
265+
- 1
266+
```
267+
then
268+
```bash
269+
yq 'first(. > 50)' sample.yml
270+
```
271+
will output
272+
```yaml
273+
100
274+
```
275+
276+
## First element with no RHS from array
277+
Given a sample.yml file of:
278+
```yaml
279+
- 10
280+
- 100
281+
- 1
282+
```
283+
then
284+
```bash
285+
yq 'first' sample.yml
286+
```
287+
will output
288+
```yaml
289+
10
290+
```
291+
292+
## First element with no RHS from array of maps
293+
Given a sample.yml file of:
294+
```yaml
295+
- a: 10
296+
- a: 100
297+
```
298+
then
299+
```bash
300+
yq 'first' sample.yml
301+
```
302+
will output
303+
```yaml
304+
a: 10
305+
```
306+
307+
## No RHS on empty array returns nothing
308+
Given a sample.yml file of:
309+
```yaml
310+
[]
311+
```
312+
then
313+
```bash
314+
yq 'first' sample.yml
315+
```
316+
will output
317+
```yaml
318+
```
319+
320+
## No RHS on scalar returns nothing
321+
Given a sample.yml file of:
322+
```yaml
323+
hello
324+
```
325+
then
326+
```bash
327+
yq 'first' sample.yml
328+
```
329+
will output
330+
```yaml
331+
```
332+
333+
## No RHS on null returns nothing
334+
Given a sample.yml file of:
335+
```yaml
336+
null
337+
```
338+
then
339+
```bash
340+
yq 'first' sample.yml
341+
```
342+
will output
343+
```yaml
344+
```
345+

pkg/yqlib/expression_parser.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*
5454
switch numArgs {
5555
case 1:
5656
if len(stack) < 1 {
57+
// Allow certain unary ops to accept zero args by interpreting missing RHS as nil
58+
// TODO - make this more general on OperationType
59+
if Operation.OperationType == firstOpType {
60+
// no RHS provided; proceed without popping
61+
break
62+
}
5763
return nil, fmt.Errorf("'%v' expects 1 arg but received none", strings.TrimSpace(Operation.StringValue))
5864
}
5965
remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1]

pkg/yqlib/lexer_participle.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ var participleYqRules = []*participleYqRule{
118118

119119
simpleOp("sort_?by", sortByOpType),
120120
simpleOp("sort", sortOpType),
121+
simpleOp("first", firstOpType),
121122

122123
simpleOp("reverse", reverseOpType),
123124

pkg/yqlib/operation.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ var delPathsOpType = &operationType{Type: "DEL_PATHS", NumArgs: 1, Precedence: 5
143143

144144
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 52, Handler: explodeOperator, CheckForPostTraverse: true}
145145
var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 52, Handler: sortByOperator, CheckForPostTraverse: true}
146+
var firstOpType = &operationType{Type: "FIRST", NumArgs: 1, Precedence: 52, Handler: firstOperator, CheckForPostTraverse: true}
146147
var reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 52, Handler: reverseOperator, CheckForPostTraverse: true}
147148
var sortOpType = &operationType{Type: "SORT", NumArgs: 0, Precedence: 52, Handler: sortOperator, CheckForPostTraverse: true}
148149
var shuffleOpType = &operationType{Type: "SHUFFLE", NumArgs: 0, Precedence: 52, Handler: shuffleOperator, CheckForPostTraverse: true}

0 commit comments

Comments
 (0)