mirror of
https://github.com/mikefarah/yq.git
synced 2026-07-07 14:25:38 +00:00
Compare commits
4 Commits
cf67ff2bde
...
f02692a7a9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f02692a7a9 | ||
|
|
4532346e13 | ||
|
|
02b28073bf | ||
|
|
6957399dc0 |
2
go.mod
2
go.mod
@ -17,7 +17,7 @@ require (
|
|||||||
github.com/spf13/cobra v1.10.1
|
github.com/spf13/cobra v1.10.1
|
||||||
github.com/spf13/pflag v1.0.10
|
github.com/spf13/pflag v1.0.10
|
||||||
github.com/yuin/gopher-lua v1.1.1
|
github.com/yuin/gopher-lua v1.1.1
|
||||||
go.yaml.in/yaml/v3 v3.0.4
|
go.yaml.in/yaml/v4 v4.0.0-rc.2
|
||||||
golang.org/x/net v0.43.0
|
golang.org/x/net v0.43.0
|
||||||
golang.org/x/text v0.28.0
|
golang.org/x/text v0.28.0
|
||||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||||
|
|||||||
5
go.sum
5
go.sum
@ -50,8 +50,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -59,7 +59,6 @@ golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
|||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
||||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
yaml "go.yaml.in/yaml/v3"
|
yaml "go.yaml.in/yaml/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MapYamlStyle(original yaml.Style) Style {
|
func MapYamlStyle(original yaml.Style) Style {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
yaml "go.yaml.in/yaml/v3"
|
yaml "go.yaml.in/yaml/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type yamlDecoder struct {
|
type yamlDecoder struct {
|
||||||
|
|||||||
@ -198,12 +198,6 @@ then
|
|||||||
yq 'explode(.f)' sample.yml
|
yq 'explode(.f)' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
|
||||||
f:
|
|
||||||
a: cat
|
|
||||||
cat: b
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dereference and update a field
|
## Dereference and update a field
|
||||||
Use explode with multiply to dereference an object
|
Use explode with multiply to dereference an object
|
||||||
|
|
||||||
@ -345,139 +339,3 @@ x: 1
|
|||||||
y: 2
|
y: 2
|
||||||
```
|
```
|
||||||
|
|
||||||
## FIXED: Explode with merge anchors
|
|
||||||
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).
|
|
||||||
Observe that foobarList.b property is still foobarList_b.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
foo: &foo
|
|
||||||
a: foo_a
|
|
||||||
thing: foo_thing
|
|
||||||
c: foo_c
|
|
||||||
bar: &bar
|
|
||||||
b: bar_b
|
|
||||||
thing: bar_thing
|
|
||||||
c: bar_c
|
|
||||||
foobarList:
|
|
||||||
b: foobarList_b
|
|
||||||
!!merge <<:
|
|
||||||
- *foo
|
|
||||||
- *bar
|
|
||||||
c: foobarList_c
|
|
||||||
foobar:
|
|
||||||
c: foobar_c
|
|
||||||
!!merge <<: *foo
|
|
||||||
thing: foobar_thing
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq 'explode(.)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
foo:
|
|
||||||
a: foo_a
|
|
||||||
thing: foo_thing
|
|
||||||
c: foo_c
|
|
||||||
bar:
|
|
||||||
b: bar_b
|
|
||||||
thing: bar_thing
|
|
||||||
c: bar_c
|
|
||||||
foobarList:
|
|
||||||
b: foobarList_b
|
|
||||||
a: foo_a
|
|
||||||
thing: foo_thing
|
|
||||||
c: foobarList_c
|
|
||||||
foobar:
|
|
||||||
c: foobar_c
|
|
||||||
a: foo_a
|
|
||||||
thing: foobar_thing
|
|
||||||
```
|
|
||||||
|
|
||||||
## FIXED: Merge multiple maps
|
|
||||||
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).
|
|
||||||
Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the correct key order.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- &CENTER
|
|
||||||
x: 1
|
|
||||||
y: 2
|
|
||||||
- &LEFT
|
|
||||||
x: 0
|
|
||||||
y: 2
|
|
||||||
- &BIG
|
|
||||||
r: 10
|
|
||||||
- &SMALL
|
|
||||||
r: 1
|
|
||||||
- !!merge <<:
|
|
||||||
- *CENTER
|
|
||||||
- *BIG
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.[4] | explode(.)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
x: 1
|
|
||||||
y: 2
|
|
||||||
r: 10
|
|
||||||
```
|
|
||||||
|
|
||||||
## FIXED: Override
|
|
||||||
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).
|
|
||||||
Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the correct key order.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- &CENTER
|
|
||||||
x: 1
|
|
||||||
y: 2
|
|
||||||
- &LEFT
|
|
||||||
x: 0
|
|
||||||
y: 2
|
|
||||||
- &BIG
|
|
||||||
r: 10
|
|
||||||
- &SMALL
|
|
||||||
r: 1
|
|
||||||
- !!merge <<:
|
|
||||||
- *BIG
|
|
||||||
- *LEFT
|
|
||||||
- *SMALL
|
|
||||||
x: 1
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq '.[4] | explode(.)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
r: 10
|
|
||||||
y: 2
|
|
||||||
x: 1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Exploding inline merge anchor
|
|
||||||
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).
|
|
||||||
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a:
|
|
||||||
b: &b 42
|
|
||||||
!!merge <<:
|
|
||||||
c: *b
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq 'explode(.) | sort_keys(.)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a:
|
|
||||||
b: 42
|
|
||||||
c: 42
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
345
pkg/yqlib/doc/operators/first.md
Normal file
345
pkg/yqlib/doc/operators/first.md
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
|
||||||
|
## First matching element from array
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: banana
|
||||||
|
- a: cat
|
||||||
|
- a: apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a == "cat")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from array with multiple matches
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: banana
|
||||||
|
- a: cat
|
||||||
|
- a: apple
|
||||||
|
- a: cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a == "cat")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from array with numeric condition
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: 10
|
||||||
|
- a: 100
|
||||||
|
- a: 1
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a > 50)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 100
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from array with boolean condition
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: false
|
||||||
|
- a: true
|
||||||
|
- a: false
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a == true)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from array with null values
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: null
|
||||||
|
- a: cat
|
||||||
|
- a: apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a != null)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from array with complex condition
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: dog
|
||||||
|
b: 5
|
||||||
|
- a: cat
|
||||||
|
b: 3
|
||||||
|
- a: apple
|
||||||
|
b: 7
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.b > 4)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: dog
|
||||||
|
b: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from map
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
x:
|
||||||
|
a: banana
|
||||||
|
y:
|
||||||
|
a: cat
|
||||||
|
z:
|
||||||
|
a: apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a == "cat")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from map with numeric condition
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
x:
|
||||||
|
a: 10
|
||||||
|
y:
|
||||||
|
a: 100
|
||||||
|
z:
|
||||||
|
a: 1
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a > 50)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 100
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from nested structure
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
items:
|
||||||
|
- a: banana
|
||||||
|
- a: cat
|
||||||
|
- a: apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq '.items | first(.a == "cat")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element with no matches
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: banana
|
||||||
|
- a: cat
|
||||||
|
- a: apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a == "dog")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from empty array
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a == "cat")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from scalar node
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
hello
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(. == "hello")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from null node
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
null
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(. == "hello")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element with string condition
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: banana
|
||||||
|
- a: cat
|
||||||
|
- a: apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a | test("^c"))' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element with length condition
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: hi
|
||||||
|
- a: hello
|
||||||
|
- a: world
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(.a | length > 4)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: hello
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from array of strings
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- banana
|
||||||
|
- cat
|
||||||
|
- apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(. == "cat")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## First matching element from array of numbers
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- 10
|
||||||
|
- 100
|
||||||
|
- 1
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first(. > 50)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
100
|
||||||
|
```
|
||||||
|
|
||||||
|
## First element with no RHS from array
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- 10
|
||||||
|
- 100
|
||||||
|
- 1
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
10
|
||||||
|
```
|
||||||
|
|
||||||
|
## First element with no RHS from array of maps
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: 10
|
||||||
|
- a: 100
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
## No RHS on empty array returns nothing
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## No RHS on scalar returns nothing
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
hello
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## No RHS on null returns nothing
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
null
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'first' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
```
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"go.yaml.in/yaml/v3"
|
"go.yaml.in/yaml/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type yamlEncoder struct {
|
type yamlEncoder struct {
|
||||||
|
|||||||
@ -54,6 +54,12 @@ func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*
|
|||||||
switch numArgs {
|
switch numArgs {
|
||||||
case 1:
|
case 1:
|
||||||
if len(stack) < 1 {
|
if len(stack) < 1 {
|
||||||
|
// Allow certain unary ops to accept zero args by interpreting missing RHS as nil
|
||||||
|
// TODO - make this more general on OperationType
|
||||||
|
if Operation.OperationType == firstOpType {
|
||||||
|
// no RHS provided; proceed without popping
|
||||||
|
break
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("'%v' expects 1 arg but received none", strings.TrimSpace(Operation.StringValue))
|
return nil, fmt.Errorf("'%v' expects 1 arg but received none", strings.TrimSpace(Operation.StringValue))
|
||||||
}
|
}
|
||||||
remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1]
|
remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1]
|
||||||
|
|||||||
@ -118,6 +118,7 @@ var participleYqRules = []*participleYqRule{
|
|||||||
|
|
||||||
simpleOp("sort_?by", sortByOpType),
|
simpleOp("sort_?by", sortByOpType),
|
||||||
simpleOp("sort", sortOpType),
|
simpleOp("sort", sortOpType),
|
||||||
|
simpleOp("first", firstOpType),
|
||||||
|
|
||||||
simpleOp("reverse", reverseOpType),
|
simpleOp("reverse", reverseOpType),
|
||||||
|
|
||||||
|
|||||||
@ -143,6 +143,7 @@ var delPathsOpType = &operationType{Type: "DEL_PATHS", NumArgs: 1, Precedence: 5
|
|||||||
|
|
||||||
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 52, Handler: explodeOperator, CheckForPostTraverse: true}
|
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 52, Handler: explodeOperator, CheckForPostTraverse: true}
|
||||||
var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 52, Handler: sortByOperator, CheckForPostTraverse: true}
|
var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 52, Handler: sortByOperator, CheckForPostTraverse: true}
|
||||||
|
var firstOpType = &operationType{Type: "FIRST", NumArgs: 1, Precedence: 52, Handler: firstOperator, CheckForPostTraverse: true}
|
||||||
var reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 52, Handler: reverseOperator, CheckForPostTraverse: true}
|
var reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 52, Handler: reverseOperator, CheckForPostTraverse: true}
|
||||||
var sortOpType = &operationType{Type: "SORT", NumArgs: 0, Precedence: 52, Handler: sortOperator, CheckForPostTraverse: true}
|
var sortOpType = &operationType{Type: "SORT", NumArgs: 0, Precedence: 52, Handler: sortOperator, CheckForPostTraverse: true}
|
||||||
var shuffleOpType = &operationType{Type: "SHUFFLE", NumArgs: 0, Precedence: 52, Handler: shuffleOperator, CheckForPostTraverse: true}
|
var shuffleOpType = &operationType{Type: "SHUFFLE", NumArgs: 0, Precedence: 52, Handler: shuffleOperator, CheckForPostTraverse: true}
|
||||||
|
|||||||
@ -464,7 +464,7 @@ var anchorOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: `{f : {a: &a cat, b: &b {foo: *a}, *a: *b}}`,
|
document: `{f : {a: &a cat, b: &b {foo: *a}, *a : *b}}`,
|
||||||
expression: `explode(.f)`,
|
expression: `explode(.f)`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::{f: {a: cat, b: {foo: cat}, cat: {foo: cat}}}\n",
|
"D0, P[], (!!map)::{f: {a: cat, b: {foo: cat}, cat: {foo: cat}}}\n",
|
||||||
|
|||||||
51
pkg/yqlib/operator_first.go
Normal file
51
pkg/yqlib/operator_first.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import "container/list"
|
||||||
|
|
||||||
|
func firstOperator(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)
|
||||||
|
|
||||||
|
// If no RHS expression is provided, simply return the first entry in candidate.Content
|
||||||
|
if expressionNode == nil || expressionNode.RHS == nil {
|
||||||
|
if len(candidate.Content) > 0 {
|
||||||
|
results.PushBack(candidate.Content[0])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
splatted, err := splat(context.SingleChildContext(candidate), traversePreferences{})
|
||||||
|
if err != nil {
|
||||||
|
return Context{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||||
|
splatCandidate := splatEl.Value.(*CandidateNode)
|
||||||
|
// Create a new context for this splatted candidate
|
||||||
|
splatContext := context.SingleChildContext(splatCandidate)
|
||||||
|
// Evaluate the RHS expression against this splatted candidate
|
||||||
|
rhs, err := d.GetMatchingNodes(splatContext, expressionNode.RHS)
|
||||||
|
if err != nil {
|
||||||
|
return Context{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
includeResult := false
|
||||||
|
|
||||||
|
for resultEl := rhs.MatchingNodes.Front(); resultEl != nil; resultEl = resultEl.Next() {
|
||||||
|
result := resultEl.Value.(*CandidateNode)
|
||||||
|
includeResult = isTruthyNode(result)
|
||||||
|
if includeResult {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if includeResult {
|
||||||
|
results.PushBack(splatCandidate)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return context.ChildContext(results), nil
|
||||||
|
}
|
||||||
184
pkg/yqlib/operator_first_test.go
Normal file
184
pkg/yqlib/operator_first_test.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var firstOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "First matching element from array",
|
||||||
|
document: "[{a: banana},{a: cat},{a: apple}]",
|
||||||
|
expression: `first(.a == "cat")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!map)::{a: cat}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from array with multiple matches",
|
||||||
|
document: "[{a: banana},{a: cat},{a: apple},{a: cat}]",
|
||||||
|
expression: `first(.a == "cat")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!map)::{a: cat}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from array with numeric condition",
|
||||||
|
document: "[{a: 10},{a: 100},{a: 1}]",
|
||||||
|
expression: `first(.a > 50)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!map)::{a: 100}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from array with boolean condition",
|
||||||
|
document: "[{a: false},{a: true},{a: false}]",
|
||||||
|
expression: `first(.a == true)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!map)::{a: true}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from array with null values",
|
||||||
|
document: "[{a: null},{a: cat},{a: apple}]",
|
||||||
|
expression: `first(.a != null)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!map)::{a: cat}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from array with complex condition",
|
||||||
|
document: "[{a: dog, b: 5},{a: cat, b: 3},{a: apple, b: 7}]",
|
||||||
|
expression: `first(.b > 4)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[0], (!!map)::{a: dog, b: 5}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from map",
|
||||||
|
document: "x: {a: banana}\ny: {a: cat}\nz: {a: apple}",
|
||||||
|
expression: `first(.a == "cat")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[y], (!!map)::{a: cat}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from map with numeric condition",
|
||||||
|
document: "x: {a: 10}\ny: {a: 100}\nz: {a: 1}",
|
||||||
|
expression: `first(.a > 50)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[y], (!!map)::{a: 100}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from nested structure",
|
||||||
|
document: "items: [{a: banana},{a: cat},{a: apple}]",
|
||||||
|
expression: `.items | first(.a == "cat")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[items 1], (!!map)::{a: cat}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element with no matches",
|
||||||
|
document: "[{a: banana},{a: cat},{a: apple}]",
|
||||||
|
expression: `first(.a == "dog")`,
|
||||||
|
expected: []string{
|
||||||
|
// No output expected when no matches
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from empty array",
|
||||||
|
document: "[]",
|
||||||
|
expression: `first(.a == "cat")`,
|
||||||
|
expected: []string{
|
||||||
|
// No output expected when array is empty
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from scalar node",
|
||||||
|
document: "hello",
|
||||||
|
expression: `first(. == "hello")`,
|
||||||
|
expected: []string{
|
||||||
|
// No output expected when node is scalar (no content to splat)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from null node",
|
||||||
|
document: "null",
|
||||||
|
expression: `first(. == "hello")`,
|
||||||
|
expected: []string{
|
||||||
|
// No output expected when node is null (no content to splat)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element with string condition",
|
||||||
|
document: "[{a: banana},{a: cat},{a: apple}]",
|
||||||
|
expression: `first(.a | test("^c"))`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!map)::{a: cat}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element with length condition",
|
||||||
|
document: "[{a: hi},{a: hello},{a: world}]",
|
||||||
|
expression: `first(.a | length > 4)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!map)::{a: hello}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from array of strings",
|
||||||
|
document: "[banana, cat, apple]",
|
||||||
|
expression: `first(. == "cat")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!str)::cat\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First matching element from array of numbers",
|
||||||
|
document: "[10, 100, 1]",
|
||||||
|
expression: `first(. > 50)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[1], (!!int)::100\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// New tests for no RHS (return first child)
|
||||||
|
{
|
||||||
|
description: "First element with no RHS from array",
|
||||||
|
document: "[10, 100, 1]",
|
||||||
|
expression: `first`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[0], (!!int)::10\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "First element with no RHS from array of maps",
|
||||||
|
document: "[{a: 10},{a: 100}]",
|
||||||
|
expression: `first`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[0], (!!map)::{a: 10}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No RHS on empty array returns nothing",
|
||||||
|
document: "[]",
|
||||||
|
expression: `first`,
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No RHS on scalar returns nothing",
|
||||||
|
document: "hello",
|
||||||
|
expression: `first`,
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "No RHS on null returns nothing",
|
||||||
|
document: "null",
|
||||||
|
expression: `first`,
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFirstOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range firstOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentOperatorScenarios(t, "first", firstOperatorScenarios)
|
||||||
|
}
|
||||||
@ -385,13 +385,13 @@ func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formatt
|
|||||||
|
|
||||||
inputs, err = readDocument(formattedDoc, "sample.yml", 0)
|
inputs, err = readDocument(formattedDoc, "sample.yml", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err, s.document, s.expression)
|
t.Error(err, formattedDoc, "exp: "+s.expression)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s.document2 != "" {
|
if s.document2 != "" {
|
||||||
moreInputs, err := readDocument(formattedDoc2, "another.yml", 1)
|
moreInputs, err := readDocument(formattedDoc2, "another.yml", 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err, s.document, s.expression)
|
t.Error(err, formattedDoc2, "exp: "+s.expression)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inputs.PushBackList(moreInputs)
|
inputs.PushBackList(moreInputs)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"go.yaml.in/yaml/v3"
|
"go.yaml.in/yaml/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nodeInfoPrinter struct {
|
type nodeInfoPrinter struct {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user