mirror of
https://github.com/mikefarah/yq.git
synced 2026-08-24 08:22:13 +08:00
Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6251e95af8 | ||
|
|
b2155ebb00 | ||
|
|
911e3942e3 | ||
|
|
35d338cc24 | ||
|
|
6d7aa384c4 | ||
|
|
e89001156a | ||
|
|
c34edcf983 | ||
|
|
a46a5766fa | ||
|
|
af2f86828c | ||
|
|
76c817009f | ||
|
|
f03c9dc599 | ||
|
|
023c85e1e2 | ||
|
|
a3d9e0172f | ||
|
|
ab3be228dc | ||
|
|
93fed3fd7a | ||
|
|
f42534ea0f | ||
|
|
b968963ed4 | ||
|
|
92309b17a4 | ||
|
|
d5757fc82b | ||
|
|
db2a4550e5 | ||
|
|
3018396ed2 | ||
|
|
84b095bbc4 | ||
|
|
f35e57d901 | ||
|
|
70ac3d6c7a | ||
|
|
904215ef4d | ||
|
|
41cc4fb4ac | ||
|
|
9c95a9f379 | ||
|
|
a4720c089a | ||
|
|
3431aebb2c | ||
|
|
ae87394f4a | ||
|
|
5e75db824b | ||
|
|
08ecd39a1e | ||
|
|
b7aa711d94 | ||
|
|
a5b8ef6cb1 | ||
|
|
a47e882c8f | ||
|
|
128bf80eed | ||
|
|
8c06478ade | ||
|
|
fa6dc5c9fb | ||
|
|
78c096fa8f | ||
|
|
ce9a4af0df | ||
|
|
4734be9a4d | ||
|
|
4d88d51b1b | ||
|
|
31628e7324 | ||
|
|
bfcb3fc6b7 | ||
|
|
c3782799c5 | ||
|
|
162ea5437c |
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
IMAGE_NAME: mikefarah/yq
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
@@ -95,6 +95,6 @@ jobs:
|
||||
-t "${IMAGE_NAME}:4-githubaction" \
|
||||
-t "${IMAGE_NAME}:latest-githubaction" \
|
||||
-t "ghcr.io/${IMAGE_NAME}:${IMAGE_VERSION}-githubaction" \
|
||||
-t "ghrc.io/${IMAGE_NAME}:4-githubaction" \
|
||||
-t "ghcr.io/${IMAGE_NAME}:4-githubaction" \
|
||||
-t "ghcr.io/${IMAGE_NAME}:latest-githubaction" \
|
||||
.
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
publishGitRelease:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
environment: snap
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: snapcore/action-build@v1
|
||||
id: build
|
||||
- uses: snapcore/action-publish@v1
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Get test
|
||||
id: get_value
|
||||
uses: mikefarah/yq@master
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.24.5 AS builder
|
||||
FROM golang:1.25.0 AS builder
|
||||
|
||||
WORKDIR /go/src/mikefarah/yq
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.24.5
|
||||
FROM golang:1.25.0
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y npm && \
|
||||
|
||||
@@ -294,7 +294,7 @@ Using [winget](https://learn.microsoft.com/en-us/windows/package-manager/)
|
||||
winget install --id MikeFarah.yq
|
||||
```
|
||||
|
||||
### Mac:
|
||||
### MacPorts:
|
||||
Using [MacPorts](https://www.macports.org/)
|
||||
```
|
||||
sudo port selfupdate
|
||||
@@ -324,6 +324,14 @@ Flox can be used to install yq on Linux, MacOS, and Windows through WSL.
|
||||
flox install yq
|
||||
```
|
||||
|
||||
|
||||
### MacOS / Linux via gah:
|
||||
Using [gah](https://github.com/marverix/gah)
|
||||
|
||||
```
|
||||
gah install yq
|
||||
```
|
||||
|
||||
## Features
|
||||
- [Detailed documentation with many examples](https://mikefarah.gitbook.io/yq/)
|
||||
- Written in portable go, so you can download a lovely dependency free binary
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "v4.46.1"
|
||||
Version = "v4.47.2"
|
||||
|
||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/mikefarah/yq/v4
|
||||
require (
|
||||
github.com/a8m/envsubst v1.4.3
|
||||
github.com/alecthomas/participle/v2 v2.1.4
|
||||
github.com/alecthomas/repr v0.4.0
|
||||
github.com/alecthomas/repr v0.5.1
|
||||
github.com/dimchansky/utfbom v1.1.1
|
||||
github.com/elliotchance/orderedmap v1.8.0
|
||||
github.com/fatih/color v1.18.0
|
||||
@@ -14,12 +14,12 @@ require (
|
||||
github.com/magiconair/properties v1.8.10
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/yuin/gopher-lua v1.1.1
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/text v0.27.0
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/text v0.28.0
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
)
|
||||
|
||||
go 1.24
|
||||
|
||||
@@ -4,8 +4,8 @@ github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8v
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
|
||||
github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
|
||||
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -40,10 +40,11 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -51,13 +52,13 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M
|
||||
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/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
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/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/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
||||
|
||||
@@ -282,13 +282,13 @@ func (dec *tomlDecoder) processTable(currentNode *toml.Node) (bool, error) {
|
||||
tableValue = dec.parser.Expression()
|
||||
// next expression is not table data, so we are done
|
||||
if tableValue.Kind != toml.KeyValue {
|
||||
log.Debug("got an empty table, returning")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return false, err
|
||||
log.Debug("got an empty table")
|
||||
runAgainstCurrentExp = true
|
||||
} else {
|
||||
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,18 @@ Use the `alias` and `anchor` operators to read and write yaml aliases and anchor
|
||||
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||
|
||||
|
||||
## NOTE --yaml-fix-merge-anchor-to-spec flag
|
||||
`yq` doesn't merge anchors `<<:` to spec, in some circumstances it incorrectly overrides existing keys when the spec documents not to do that.
|
||||
|
||||
To minimise disruption while still fixing the issue, a flag has been added to toggle this behaviour. This will first default to false; and log warnings to users. Then it will default to true (and still allow users to specify false if needed).
|
||||
|
||||
This flag also enables advanced merging, like inline maps, as well as fixes to ensure when exploding a particular path, neighbours are not affect ed.
|
||||
|
||||
Long story short, you should be setting this flag to true.
|
||||
|
||||
See examples of the flag differences below, where LEGACY is with the flag off; and FIXED is with the flag on.
|
||||
|
||||
|
||||
## Merge one map
|
||||
see https://yaml.org/type/merge.html
|
||||
|
||||
@@ -208,7 +220,7 @@ thingTwo:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.thingOne |= explode(.) * {"value": false}' sample.yml
|
||||
yq '.thingOne |= (explode(.) | sort_keys(.)) * {"value": false}' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -222,8 +234,57 @@ thingTwo:
|
||||
!!merge <<: *item_value
|
||||
```
|
||||
|
||||
## Merge multiple maps
|
||||
see https://yaml.org/type/merge.html
|
||||
## LEGACY: Explode with merge anchors
|
||||
Caution: this is for when --yaml-fix-merge-anchor-to-spec=false; it's not to YAML spec because the merge anchors incorrectly override the object values (foobarList.b is set to bar_b when it should still be foobarList_b). Flag will default to true in late 2025
|
||||
|
||||
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: bar_b
|
||||
thing: foo_thing
|
||||
c: foobarList_c
|
||||
a: foo_a
|
||||
foobar:
|
||||
c: foo_c
|
||||
a: foo_a
|
||||
thing: foobar_thing
|
||||
```
|
||||
|
||||
## LEGACY: Merge multiple maps
|
||||
see https://yaml.org/type/merge.html. This has the correct data, but the wrong key order; set --yaml-fix-merge-anchor-to-spec=true to fix the key order.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -252,8 +313,8 @@ x: 1
|
||||
y: 2
|
||||
```
|
||||
|
||||
## Override
|
||||
see https://yaml.org/type/merge.html
|
||||
## LEGACY: Override
|
||||
see https://yaml.org/type/merge.html. This has the correct data, but the wrong key order; set --yaml-fix-merge-anchor-to-spec=true to fix the key order.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -284,57 +345,9 @@ x: 1
|
||||
y: 2
|
||||
```
|
||||
|
||||
## LEGACY: Explode with merge anchors
|
||||
Caution: this is for when --yaml-fix-merge-anchor-to-spec=false; it's not to YAML spec because the merge anchors incorrectly override the object values. Flag will default to true in late 2025
|
||||
|
||||
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: bar_b
|
||||
thing: foo_thing
|
||||
c: foobarList_c
|
||||
a: foo_a
|
||||
foobar:
|
||||
c: foo_c
|
||||
a: foo_a
|
||||
thing: foobar_thing
|
||||
```
|
||||
|
||||
## 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
|
||||
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
|
||||
@@ -382,3 +395,89 @@ foobar:
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
@@ -4,3 +4,15 @@ Use the `alias` and `anchor` operators to read and write yaml aliases and anchor
|
||||
|
||||
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||
|
||||
|
||||
## NOTE --yaml-fix-merge-anchor-to-spec flag
|
||||
`yq` doesn't merge anchors `<<:` to spec, in some circumstances it incorrectly overrides existing keys when the spec documents not to do that.
|
||||
|
||||
To minimise disruption while still fixing the issue, a flag has been added to toggle this behaviour. This will first default to false; and log warnings to users. Then it will default to true (and still allow users to specify false if needed).
|
||||
|
||||
This flag also enables advanced merging, like inline maps, as well as fixes to ensure when exploding a particular path, neighbours are not affect ed.
|
||||
|
||||
Long story short, you should be setting this flag to true.
|
||||
|
||||
See examples of the flag differences below, where LEGACY is with the flag off; and FIXED is with the flag on.
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
# Traverse (Read)
|
||||
|
||||
This is the simplest (and perhaps most used) operator. It is used to navigate deeply into yaml structures.
|
||||
|
||||
|
||||
## NOTE --yaml-fix-merge-anchor-to-spec flag
|
||||
`yq` doesn't merge anchors `<<:` to spec, in some circumstances it incorrectly overrides existing keys when the spec documents not to do that.
|
||||
|
||||
To minimise disruption while still fixing the issue, a flag has been added to toggle this behaviour. This will first default to false; and log warnings to users. Then it will default to true (and still allow users to specify false if needed)
|
||||
|
||||
See examples of the flag differences below, where LEGACY is the flag off; and FIXED is with the flag on.
|
||||
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
This is the simplest (and perhaps most used) operator. It is used to navigate deeply into yaml structures.
|
||||
|
||||
|
||||
## NOTE --yaml-fix-merge-anchor-to-spec flag
|
||||
`yq` doesn't merge anchors `<<:` to spec, in some circumstances it incorrectly overrides existing keys when the spec documents not to do that.
|
||||
|
||||
To minimise disruption while still fixing the issue, a flag has been added to toggle this behaviour. This will first default to false; and log warnings to users. Then it will default to true (and still allow users to specify false if needed)
|
||||
|
||||
See examples of the flag differences below, where LEGACY is the flag off; and FIXED is with the flag on.
|
||||
|
||||
|
||||
## Simple map navigation
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -303,37 +312,6 @@ will output
|
||||
foo_a
|
||||
```
|
||||
|
||||
## Traversing merge anchors with override
|
||||
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 '.foobar.c' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foo_c
|
||||
```
|
||||
|
||||
## Traversing merge anchors with local override
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -365,7 +343,93 @@ will output
|
||||
foobar_thing
|
||||
```
|
||||
|
||||
## Splatting merge anchors
|
||||
## Select multiple indices
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a[0, 2]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a
|
||||
c
|
||||
```
|
||||
|
||||
## LEGACY: Traversing merge anchors with override
|
||||
This is legacy behaviour, see --yaml-fix-merge-anchor-to-spec
|
||||
|
||||
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 '.foobar.c' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foo_c
|
||||
```
|
||||
|
||||
## LEGACY: Traversing merge anchor lists
|
||||
Note that the later merge anchors override previous, but this is legacy behaviour, see --yaml-fix-merge-anchor-to-spec
|
||||
|
||||
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 '.foobarList.thing' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
bar_thing
|
||||
```
|
||||
|
||||
## LEGACY: Splatting merge anchors
|
||||
With legacy override behaviour, see --yaml-fix-merge-anchor-to-spec
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -398,40 +462,9 @@ foo_a
|
||||
foobar_thing
|
||||
```
|
||||
|
||||
## Traversing merge anchor lists
|
||||
Note that the later merge anchors override previous
|
||||
## LEGACY: Splatting merge anchor lists
|
||||
With legacy override behaviour, see --yaml-fix-merge-anchor-to-spec
|
||||
|
||||
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 '.foobarList.thing' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
bar_thing
|
||||
```
|
||||
|
||||
## Splatting merge anchor lists
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -465,21 +498,140 @@ bar_thing
|
||||
foobarList_c
|
||||
```
|
||||
|
||||
## Select multiple indices
|
||||
## FIXED: Traversing merge anchors with override
|
||||
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
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 '.a[0, 2]' sample.yml
|
||||
yq '.foobar.c' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a
|
||||
c
|
||||
foobar_c
|
||||
```
|
||||
|
||||
## FIXED: Traversing merge anchor lists
|
||||
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones
|
||||
|
||||
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 '.foobarList.thing' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foo_thing
|
||||
```
|
||||
|
||||
## FIXED: Splatting merge anchors
|
||||
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones
|
||||
|
||||
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 '.foobar[]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foo_a
|
||||
foobar_thing
|
||||
foobar_c
|
||||
```
|
||||
|
||||
## FIXED: Splatting merge anchor lists
|
||||
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones
|
||||
|
||||
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 '.foobarList[]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foobarList_b
|
||||
foo_thing
|
||||
foobarList_c
|
||||
foo_a
|
||||
```
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var showMergeAnchorToSpecWarning = true
|
||||
|
||||
func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
log.Debugf("AssignAlias operator!")
|
||||
@@ -144,11 +146,21 @@ func fixedReconstructAliasedMap(node *CandidateNode) error {
|
||||
for index := 0; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
valueNode := node.Content[index+1]
|
||||
if keyNode.Value != "<<" {
|
||||
if keyNode.Tag != "!!merge" {
|
||||
// always add in plain nodes
|
||||
// explode both the key and value nodes
|
||||
if err := explodeNode(keyNode, Context{}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := explodeNode(valueNode, Context{}); err != nil {
|
||||
return err
|
||||
}
|
||||
newContent = append(newContent, keyNode, valueNode)
|
||||
} else {
|
||||
sequence := valueNode
|
||||
if sequence.Kind == AliasNode {
|
||||
sequence = sequence.Alias
|
||||
}
|
||||
if sequence.Kind != SequenceNode {
|
||||
sequence = &CandidateNode{Content: []*CandidateNode{sequence}}
|
||||
}
|
||||
@@ -159,13 +171,21 @@ func fixedReconstructAliasedMap(node *CandidateNode) error {
|
||||
mergeNodeSeq = mergeNodeSeq.Alias
|
||||
}
|
||||
if mergeNodeSeq.Kind != MappingNode {
|
||||
return fmt.Errorf("merge anchor only supports maps, got !!seq instead")
|
||||
return fmt.Errorf("can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing %v", mergeNodeSeq.Tag)
|
||||
}
|
||||
itemsToAdd := mergeNodeSeq.FilterMapContentByKey(func(keyNode *CandidateNode) bool {
|
||||
return getContentValueByKey(node.Content, keyNode.Value) == nil &&
|
||||
getContentValueByKey(newContent, keyNode.Value) == nil
|
||||
})
|
||||
newContent = append(newContent, itemsToAdd...)
|
||||
|
||||
for _, item := range itemsToAdd {
|
||||
// copy to ensure exploding doesn't modify the original node
|
||||
itemCopy := item.Copy()
|
||||
if err := explodeNode(itemCopy, Context{}); err != nil {
|
||||
return err
|
||||
}
|
||||
newContent = append(newContent, itemCopy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +273,10 @@ func explodeNode(node *CandidateNode, context Context) error {
|
||||
if ConfiguredYamlPreferences.FixMergeAnchorToSpec {
|
||||
return fixedReconstructAliasedMap(node)
|
||||
}
|
||||
log.Warning("--yaml-fix-merge-anchor-to-spec is false; causing merge anchors to override the existing values which isn't to the yaml spec. This flag will default to true in late 2025.")
|
||||
if showMergeAnchorToSpecWarning {
|
||||
log.Warning("--yaml-fix-merge-anchor-to-spec is false; causing merge anchors to override the existing values which isn't to the yaml spec. This flag will default to true in late 2025.")
|
||||
showMergeAnchorToSpecWarning = false
|
||||
}
|
||||
// this is a slow op, which is why we want to check before running it.
|
||||
return reconstructAliasedMap(node, context)
|
||||
}
|
||||
@@ -283,7 +306,7 @@ func applyAlias(node *CandidateNode, alias *CandidateNode, aliasIndex int, newCo
|
||||
}
|
||||
log.Debug("alias: %v", NodeToString(alias))
|
||||
if alias.Kind != MappingNode {
|
||||
return fmt.Errorf("merge anchor only supports maps, got %v instead", alias.Tag)
|
||||
return fmt.Errorf("can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing %v", alias.Tag)
|
||||
}
|
||||
for index := 0; index < len(alias.Content); index = index + 2 {
|
||||
keyNode := alias.Content[index]
|
||||
|
||||
@@ -102,31 +102,16 @@ var explodeWhenKeysExistExpected = `D0, P[], (!!map)::objects:
|
||||
|
||||
var fixedAnchorOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "merge anchor after existing keys",
|
||||
document: explodeWhenKeysExistDocument,
|
||||
expression: "explode(.)",
|
||||
expected: []string{explodeWhenKeysExistExpected},
|
||||
},
|
||||
{
|
||||
skipDoc: true, // skip doc for now, only difference is order of keys
|
||||
description: "Merge multiple maps",
|
||||
subdescription: "see https://yaml.org/type/merge.html",
|
||||
document: specDocument + "- << : [ *CENTER, *BIG ]\n",
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{"D0, P[4], (!!map)::x: 1\ny: 2\nr: 10\n"},
|
||||
},
|
||||
{
|
||||
skipDoc: true, // skip doc for now, only difference is order of keys
|
||||
description: "Override",
|
||||
subdescription: "see https://yaml.org/type/merge.html",
|
||||
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{"D0, P[4], (!!map)::r: 10\ny: 2\nx: 1\n"},
|
||||
skipDoc: true,
|
||||
description: "merge anchor after existing keys",
|
||||
subdescription: "Does not override existing keys - note the name field in the second element is still ellipse.",
|
||||
document: explodeWhenKeysExistDocument,
|
||||
expression: "explode(.)",
|
||||
expected: []string{explodeWhenKeysExistExpected},
|
||||
},
|
||||
{
|
||||
description: "FIXED: Explode with merge anchors",
|
||||
subdescription: "Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Flag will default to true in late 2025 ",
|
||||
subdescription: "Observe that foobarList.b property is still foobarList_b.",
|
||||
document: mergeDocSample,
|
||||
expression: `explode(.)`,
|
||||
expected: []string{explodeMergeAnchorsFixedExpected},
|
||||
@@ -151,6 +136,95 @@ var fixedAnchorOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foobar], (!!map)::{c: foobar_c, a: foo_a, thing: foobar_thing}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "FIXED: Merge multiple maps",
|
||||
subdescription: "Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the correct key order.",
|
||||
document: specDocument + "- << : [ *CENTER, *BIG ]\n",
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{"D0, P[4], (!!map)::x: 1\ny: 2\nr: 10\n"},
|
||||
},
|
||||
{
|
||||
description: "FIXED: Override",
|
||||
subdescription: "Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the correct key order.",
|
||||
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{"D0, P[4], (!!map)::r: 10\ny: 2\nx: 1\n"},
|
||||
},
|
||||
{
|
||||
description: "Exploding inline merge anchor",
|
||||
// subdescription: "`<<` map must be exploded, otherwise `c: *b` will become invalid",
|
||||
document: `{a: {b: &b 42}, <<: {c: *b}}`,
|
||||
expression: `explode(.) | sort_keys(.)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {b: 42}, c: 42}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Exploding inline merge anchor in sequence",
|
||||
subdescription: "`<<` map must be exploded, otherwise `c: *b` will become invalid",
|
||||
document: `{a: {b: &b 42}, <<: [{c: *b}]}`,
|
||||
expression: `explode(.) | sort_keys(.)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {b: 42}, c: 42}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Exploding merge anchor should not explode neighbors",
|
||||
subdescription: "b must not be exploded, as `r: *a` will become invalid",
|
||||
document: `{b: &b {a: &a 42}, r: *a, c: {<<: *b}}`,
|
||||
expression: `explode(.c)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{b: &b {a: &a 42}, r: *a, c: {a: 42}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Exploding sequence merge anchor should not explode neighbors",
|
||||
subdescription: "b must not be exploded, as `r: *a` will become invalid",
|
||||
document: `{b: &b {a: &a 42}, r: *a, c: {<<: [*b]}}`,
|
||||
expression: `explode(.c)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{b: &b {a: &a 42}, r: *a, c: {a: 42}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Merge anchor with inline map",
|
||||
document: `{<<: {a: 42}}`,
|
||||
expression: `explode(.)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: 42}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Merge anchor with sequence with inline map",
|
||||
document: `{<<: [{a: 42}]}`,
|
||||
expression: `explode(.)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: 42}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Merge anchor with aliased sequence with inline map",
|
||||
document: `{s: &s [{a: 42}], m: {<<: *s}}`,
|
||||
expression: `.m | explode(.)`,
|
||||
expected: []string{
|
||||
"D0, P[m], (!!map)::{a: 42}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "deleting after explode",
|
||||
document: "x: 37\na: &a\n b: 42\n<<: *a",
|
||||
expression: `explode(.) | del(.x)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a:\n b: 42\nb: 42\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var badAnchorOperatorScenarios = []expressionScenario{
|
||||
@@ -161,23 +235,9 @@ var badAnchorOperatorScenarios = []expressionScenario{
|
||||
expression: "explode(.)",
|
||||
expected: []string{explodeWhenKeysExistLegacy},
|
||||
},
|
||||
{
|
||||
description: "Merge multiple maps", // functionally correct, but key order gets mangled
|
||||
subdescription: "see https://yaml.org/type/merge.html",
|
||||
document: specDocument + "- << : [ *CENTER, *BIG ]\n",
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
|
||||
},
|
||||
{
|
||||
description: "Override", // functionally correct, but key order gets mangled
|
||||
subdescription: "see https://yaml.org/type/merge.html",
|
||||
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
|
||||
},
|
||||
{
|
||||
description: "LEGACY: Explode with merge anchors", // incorrect overrides
|
||||
subdescription: "Caution: this is for when --yaml-fix-merge-anchor-to-spec=false; it's not to YAML spec because the merge anchors incorrectly override the object values. Flag will default to true in late 2025",
|
||||
subdescription: "Caution: this is for when --yaml-fix-merge-anchor-to-spec=false; it's not to YAML spec because the merge anchors incorrectly override the object values (foobarList.b is set to bar_b when it should still be foobarList_b). Flag will default to true in late 2025",
|
||||
document: mergeDocSample,
|
||||
expression: `explode(.)`,
|
||||
expected: []string{explodeMergeAnchorsExpected},
|
||||
@@ -202,6 +262,21 @@ var badAnchorOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LEGACY: Merge multiple maps",
|
||||
subdescription: "see https://yaml.org/type/merge.html. This has the correct data, but the wrong key order; set --yaml-fix-merge-anchor-to-spec=true to fix the key order.",
|
||||
document: specDocument + "- << : [ *CENTER, *BIG ]\n",
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
|
||||
},
|
||||
{
|
||||
description: "LEGACY: Override",
|
||||
subdescription: "see https://yaml.org/type/merge.html. This has the correct data, but the wrong key order; set --yaml-fix-merge-anchor-to-spec=true to fix the key order.",
|
||||
|
||||
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
|
||||
},
|
||||
}
|
||||
|
||||
var anchorOperatorScenarios = []expressionScenario{
|
||||
@@ -216,7 +291,7 @@ var anchorOperatorScenarios = []expressionScenario{
|
||||
skipDoc: true,
|
||||
description: "merge anchor not map",
|
||||
document: "a: &a\n - 0\nc:\n <<: [*a]\n",
|
||||
expectedError: "merge anchor only supports maps, got !!seq instead",
|
||||
expectedError: "can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing !!seq",
|
||||
expression: "explode(.)",
|
||||
},
|
||||
{
|
||||
@@ -226,6 +301,7 @@ var anchorOperatorScenarios = []expressionScenario{
|
||||
expression: ".[4] | explode(.)",
|
||||
expected: []string{expectedSpecResult},
|
||||
},
|
||||
|
||||
{
|
||||
description: "Get anchor",
|
||||
document: `a: &billyBob cat`,
|
||||
@@ -398,9 +474,29 @@ var anchorOperatorScenarios = []expressionScenario{
|
||||
description: "Dereference and update a field",
|
||||
subdescription: "Use explode with multiply to dereference an object",
|
||||
document: simpleArrayRef,
|
||||
expression: `.thingOne |= explode(.) * {"value": false}`,
|
||||
expression: `.thingOne |= (explode(.) | sort_keys(.)) * {"value": false}`,
|
||||
expected: []string{expectedUpdatedArrayRef},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Duplicate keys",
|
||||
subdescription: "outside merge anchor",
|
||||
document: `{a: 1, a: 2}`,
|
||||
expression: `explode(.)`,
|
||||
expected: []string{
|
||||
// {a: 2} would also be fine
|
||||
"D0, P[], (!!map)::{a: 1, a: 2}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "!!str << should not be treated as merge anchor",
|
||||
document: `{!!str <<: {a: 37}}`,
|
||||
expression: `explode(.).a`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAnchorAliasOperatorScenarios(t *testing.T) {
|
||||
@@ -414,7 +510,10 @@ func TestAnchorAliasOperatorAlignedToSpecScenarios(t *testing.T) {
|
||||
ConfiguredYamlPreferences.FixMergeAnchorToSpec = true
|
||||
for _, tt := range append(fixedAnchorOperatorScenarios, anchorOperatorScenarios...) {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
|
||||
for i, tt := range fixedAnchorOperatorScenarios {
|
||||
fixedAnchorOperatorScenarios[i].subdescription = "Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).\n" + tt.subdescription
|
||||
}
|
||||
appendOperatorDocumentScenario(t, "anchor-and-alias-operators", fixedAnchorOperatorScenarios)
|
||||
ConfiguredYamlPreferences.FixMergeAnchorToSpec = false
|
||||
|
||||
@@ -3,6 +3,7 @@ package yqlib
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/elliotchance/orderedmap"
|
||||
)
|
||||
@@ -265,26 +266,58 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, node *CandidateNode, wante
|
||||
// if we don't find a match directly on this node first.
|
||||
|
||||
var contents = node.Content
|
||||
|
||||
if !prefs.DontFollowAlias {
|
||||
if ConfiguredYamlPreferences.FixMergeAnchorToSpec {
|
||||
// First evaluate merge keys to make explicit keys take precedence, following spec
|
||||
// We also iterate in reverse to make earlier merge keys take precedence,
|
||||
// although normally there's just one '<<'
|
||||
for index := len(node.Content) - 2; index >= 0; index -= 2 {
|
||||
keyNode := node.Content[index]
|
||||
valueNode := node.Content[index+1]
|
||||
if keyNode.Tag == "!!merge" {
|
||||
log.Debug("Merge anchor")
|
||||
err := traverseMergeAnchor(newMatches, valueNode, wantedKey, prefs, splat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for index := 0; index+1 < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
|
||||
//skip the 'merge' tag, find a direct match first
|
||||
if key.Tag == "!!merge" && !prefs.DontFollowAlias && wantedKey != "<<" {
|
||||
log.Debug("Merge anchor")
|
||||
err := traverseMergeAnchor(newMatches, value, wantedKey, prefs, splat)
|
||||
if err != nil {
|
||||
return err
|
||||
if key.Tag == "!!merge" && !prefs.DontFollowAlias && wantedKey != key.Value {
|
||||
if !ConfiguredYamlPreferences.FixMergeAnchorToSpec {
|
||||
log.Debug("Merge anchor")
|
||||
if showMergeAnchorToSpecWarning {
|
||||
log.Warning("--yaml-fix-merge-anchor-to-spec is false; causing merge anchors to override the existing values which isn't to the yaml spec. This flag will default to true in late 2025. See https://mikefarah.gitbook.io/yq/operators/traverse-read for more details.")
|
||||
showMergeAnchorToSpecWarning = false
|
||||
}
|
||||
err := traverseMergeAnchor(newMatches, value, wantedKey, prefs, splat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if splat || keyMatches(key, wantedKey) {
|
||||
log.Debug("MATCHED")
|
||||
if prefs.IncludeMapKeys {
|
||||
log.Debug("including key")
|
||||
newMatches.Set(key.GetKey(), key)
|
||||
keyName := key.GetKey()
|
||||
if !newMatches.Set(keyName, key) {
|
||||
log.Debug("overwriting existing key")
|
||||
}
|
||||
}
|
||||
if !prefs.DontIncludeMapValues {
|
||||
log.Debug("including value")
|
||||
newMatches.Set(value.GetKey(), value)
|
||||
valueName := value.GetKey()
|
||||
if !newMatches.Set(valueName, value) {
|
||||
log.Debug("overwriting existing value")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,22 +325,39 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, node *CandidateNode, wante
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, value *CandidateNode, wantedKey string, prefs traversePreferences, splat bool) error {
|
||||
switch value.Kind {
|
||||
case AliasNode:
|
||||
if value.Alias.Kind != MappingNode {
|
||||
return fmt.Errorf("can only use merge anchors with maps (!!map), but got %v", value.Alias.Tag)
|
||||
}
|
||||
return doTraverseMap(newMatches, value.Alias, wantedKey, prefs, splat)
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, merge *CandidateNode, wantedKey string, prefs traversePreferences, splat bool) error {
|
||||
if merge.Kind == AliasNode {
|
||||
merge = merge.Alias
|
||||
}
|
||||
switch merge.Kind {
|
||||
case MappingNode:
|
||||
return doTraverseMap(newMatches, merge, wantedKey, prefs, splat)
|
||||
case SequenceNode:
|
||||
for _, childValue := range value.Content {
|
||||
err := traverseMergeAnchor(newMatches, childValue, wantedKey, prefs, splat)
|
||||
content := slices.All(merge.Content)
|
||||
if ConfiguredYamlPreferences.FixMergeAnchorToSpec {
|
||||
// Reverse to make earlier values take precedence, following spec
|
||||
content = slices.Backward(merge.Content)
|
||||
}
|
||||
for _, childValue := range content {
|
||||
if childValue.Kind == AliasNode {
|
||||
childValue = childValue.Alias
|
||||
}
|
||||
if childValue.Kind != MappingNode {
|
||||
log.Debugf(
|
||||
"can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing %v",
|
||||
childValue.Tag)
|
||||
return nil
|
||||
}
|
||||
err := doTraverseMap(newMatches, childValue, wantedKey, prefs, splat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
log.Debugf("can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got %v", merge.Tag)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseArray(candidate *CandidateNode, operation *Operation, prefs traversePreferences) (*list.List, error) {
|
||||
|
||||
@@ -25,14 +25,111 @@ foobar:
|
||||
thing: foobar_thing
|
||||
`
|
||||
|
||||
// cannot use merge anchors with arrays
|
||||
var badAliasSample = `
|
||||
_common: &common-docker-file
|
||||
- FROM ubuntu:18.04
|
||||
var fixedTraversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "FIXED: Traversing merge anchors with override",
|
||||
subdescription: "Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour.",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.c`,
|
||||
expected: []string{
|
||||
"D0, P[foobar c], (!!str)::foobar_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "FIXED: Traversing merge anchor lists",
|
||||
subdescription: "Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.thing`,
|
||||
expected: []string{
|
||||
"D0, P[foo thing], (!!str)::foo_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "FIXED: Splatting merge anchors",
|
||||
subdescription: "Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar[]`,
|
||||
expected: []string{
|
||||
"D0, P[foo a], (!!str)::foo_a\n",
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
"D0, P[foobar c], (!!str)::foobar_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "FIXED: Splatting merge anchor lists",
|
||||
subdescription: "Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList b], (!!str)::foobarList_b\n",
|
||||
"D0, P[foo thing], (!!str)::foo_thing\n",
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
"D0, P[foo a], (!!str)::foo_a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.b`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList b], (!!str)::foobarList_b\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
steps:
|
||||
<<: *common-docker-file
|
||||
`
|
||||
var badTraversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "LEGACY: Traversing merge anchors with override",
|
||||
subdescription: "This is legacy behaviour, see --yaml-fix-merge-anchor-to-spec",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.c`,
|
||||
expected: []string{
|
||||
"D0, P[foo c], (!!str)::foo_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LEGACY: Traversing merge anchor lists",
|
||||
subdescription: "Note that the later merge anchors override previous, " +
|
||||
"but this is legacy behaviour, see --yaml-fix-merge-anchor-to-spec",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.thing`,
|
||||
expected: []string{
|
||||
"D0, P[bar thing], (!!str)::bar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LEGACY: Splatting merge anchors",
|
||||
subdescription: "With legacy override behaviour, see --yaml-fix-merge-anchor-to-spec",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar[]`,
|
||||
expected: []string{
|
||||
"D0, P[foo c], (!!str)::foo_c\n",
|
||||
"D0, P[foo a], (!!str)::foo_a\n",
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LEGACY: Splatting merge anchor lists",
|
||||
subdescription: "With legacy override behaviour, see --yaml-fix-merge-anchor-to-spec",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList[]`,
|
||||
expected: []string{
|
||||
"D0, P[bar b], (!!str)::bar_b\n",
|
||||
"D0, P[foo a], (!!str)::foo_a\n",
|
||||
"D0, P[bar thing], (!!str)::bar_thing\n",
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
subdescription: "This is legacy behaviour, see --yaml-fix-merge-anchor-to-spec",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.b`,
|
||||
expected: []string{
|
||||
"D0, P[bar b], (!!str)::bar_b\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
@@ -365,6 +462,33 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[0], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Merge anchor with inline map",
|
||||
document: `{<<: {a: 42}}`,
|
||||
expression: `.a`,
|
||||
expected: []string{
|
||||
"D0, P[<< a], (!!int)::42\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Merge anchor with sequence with inline map",
|
||||
document: `{<<: [{a: 42}]}`,
|
||||
expression: `.a`,
|
||||
expected: []string{
|
||||
"D0, P[<< 0 a], (!!int)::42\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Merge anchor with aliased sequence with inline map",
|
||||
document: `{s: &s [{a: 42}], m: {<<: *s}}`,
|
||||
expression: `.m.a`,
|
||||
expected: []string{
|
||||
"D0, P[s 0 a], (!!int)::42\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
@@ -381,14 +505,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foo a], (!!str)::foo_a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Traversing merge anchors with override",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.c`,
|
||||
expected: []string{
|
||||
"D0, P[foo c], (!!str)::foo_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Traversing merge anchors with local override",
|
||||
document: mergeDocSample,
|
||||
@@ -397,16 +513,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Splatting merge anchors",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar[]`,
|
||||
expected: []string{
|
||||
"D0, P[foo c], (!!str)::foo_c\n",
|
||||
"D0, P[foo a], (!!str)::foo_a\n",
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
@@ -423,15 +529,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foo a], (!!str)::foo_a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Traversing merge anchor lists",
|
||||
subdescription: "Note that the later merge anchors override previous",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.thing`,
|
||||
expected: []string{
|
||||
"D0, P[bar thing], (!!str)::bar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
@@ -440,25 +537,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.b`,
|
||||
expected: []string{
|
||||
"D0, P[bar b], (!!str)::bar_b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Splatting merge anchor lists",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList[]`,
|
||||
expected: []string{
|
||||
"D0, P[bar b], (!!str)::bar_b\n",
|
||||
"D0, P[foo a], (!!str)::foo_a\n",
|
||||
"D0, P[bar thing], (!!str)::bar_thing\n",
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[a,b,c]`,
|
||||
@@ -550,17 +628,57 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: badAliasSample,
|
||||
expression: ".steps[]",
|
||||
expectedError: "can only use merge anchors with maps (!!map), but got !!seq",
|
||||
skipForGoccy: true, // throws an error on parsing, that's fine
|
||||
skipDoc: true,
|
||||
description: "Duplicate keys",
|
||||
subdescription: "outside merge anchor",
|
||||
document: `{a: 1, a: 2}`,
|
||||
expression: `.a`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!int)::2\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Traversing map with invalid merge anchor should not fail",
|
||||
subdescription: "Otherwise code cannot do anything with it",
|
||||
document: `{a: 42, <<: 37}`,
|
||||
expression: `.a`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!int)::42\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Directly accessing invalid merge anchor should not fail",
|
||||
document: `{<<: 37}`,
|
||||
expression: `.<<`,
|
||||
expected: []string{
|
||||
"D0, P[<<], (!!int)::37\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "!!str << should not be treated as merge anchor",
|
||||
document: `{!!str <<: {a: 37}}`,
|
||||
expression: `.a`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestTraversePathOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range traversePathOperatorScenarios {
|
||||
for _, tt := range append(traversePathOperatorScenarios, badTraversePathOperatorScenarios...) {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentOperatorScenarios(t, "traverse-read", traversePathOperatorScenarios)
|
||||
documentOperatorScenarios(t, "traverse-read", append(traversePathOperatorScenarios, badTraversePathOperatorScenarios...))
|
||||
}
|
||||
|
||||
func TestTraversePathOperatorAlignedToSpecScenarios(t *testing.T) {
|
||||
ConfiguredYamlPreferences.FixMergeAnchorToSpec = true
|
||||
for _, tt := range append(fixedTraversePathOperatorScenarios, traversePathOperatorScenarios...) {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
appendOperatorDocumentScenario(t, "traverse-read", fixedTraversePathOperatorScenarios)
|
||||
ConfiguredYamlPreferences.FixMergeAnchorToSpec = false
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ func documentScenarios(t *testing.T, folder string, title string, scenarios []in
|
||||
|
||||
func appendOperatorDocumentScenario(t *testing.T, title string, scenarios []expressionScenario) {
|
||||
filename := fmt.Sprintf("doc/%v/%v.md", "operators", title)
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND, fs.ModeAppend)
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR|os.O_APPEND, fs.ModeAppend)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
||||
@@ -23,6 +23,7 @@ street = "first street"
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
`
|
||||
|
||||
var expectedTableArrayBeforeOwners = `owner:
|
||||
addresses:
|
||||
- street: first street
|
||||
@@ -67,6 +68,29 @@ var emptyTable = `
|
||||
|
||||
var emptyTableExpected = "dependencies: {}\n"
|
||||
|
||||
var multipleEmptyTables = `
|
||||
[firstEmptyTable]
|
||||
[firstTableWithContent]
|
||||
key = "value"
|
||||
[secondEmptyTable]
|
||||
[thirdEmptyTable]
|
||||
[secondTableWithContent]
|
||||
key = "value"
|
||||
[fourthEmptyTable]
|
||||
[fifthEmptyTable]
|
||||
`
|
||||
|
||||
var expectedMultipleEmptyTables = `firstEmptyTable: {}
|
||||
firstTableWithContent:
|
||||
key: value
|
||||
secondEmptyTable: {}
|
||||
thirdEmptyTable: {}
|
||||
secondTableWithContent:
|
||||
key: value
|
||||
fourthEmptyTable: {}
|
||||
fifthEmptyTable: {}
|
||||
`
|
||||
|
||||
var sampleWithHeader = `
|
||||
[servers]
|
||||
|
||||
@@ -238,6 +262,13 @@ var tomlScenarios = []formatScenario{
|
||||
expected: expectedSampleWithHeader,
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "Parse: multiple empty tables",
|
||||
skipDoc: true,
|
||||
input: multipleEmptyTables,
|
||||
expected: expectedMultipleEmptyTables,
|
||||
scenarioType: "decode",
|
||||
},
|
||||
}
|
||||
|
||||
func testTomlScenario(t *testing.T, s formatScenario) {
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
4.47.2:
|
||||
- Conversion from TOML to JSON no longer omits empty tables #2459 (thanks @louislouislouislouis)
|
||||
- Bumped dependencies
|
||||
|
||||
4.47.1:
|
||||
- Fixed merge anchor behaviour (`<<`); #2404, #2110, #2386, #2178 Huge thanks to @stevenwdv!
|
||||
Note that you will need to set --yaml-fix-merge-anchor-to-spec to see the fixes
|
||||
- Fixed panic for syntax error when creating a map #2423
|
||||
- Bumped dependencies
|
||||
|
||||
4.46.1:
|
||||
- Added INI support
|
||||
- Fixed 'add' operator when piped in with no data #2378, #2383, #2384
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
name: yq
|
||||
version: 'v4.46.1'
|
||||
version: 'v4.47.2'
|
||||
summary: A lightweight and portable command-line data file processor
|
||||
description: |
|
||||
`yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml, json, xml, csv, properties and TOML files.
|
||||
@@ -27,6 +27,6 @@ parts:
|
||||
build-environment:
|
||||
- CGO_ENABLED: 0
|
||||
source: https://github.com/mikefarah/yq.git
|
||||
source-tag: v4.46.1
|
||||
source-tag: v4.47.2
|
||||
build-snaps:
|
||||
- go/latest/stable
|
||||
|
||||
Reference in New Issue
Block a user