Compare commits

..
Author SHA1 Message Date
Mike Farah 23a7b173bf Fixing merge anchor key order 2025-07-19 15:27:44 +10:00
Mike Farah 4019d42d60 Install specific version of gosec 2025-07-16 15:51:42 +10:00
Mike Farah 55daf6d93c Fixed panic for syntax error when creating a map #2423 2025-07-16 15:35:12 +10:00
Mike Farah 8e731ac13c Added "debug-node-info" flag for inspecting yq AST 2025-07-15 21:35:54 +10:00
ryenusandMike Farah d0c897f5e6 skip format check for filenames ending with dot
also add a unit test for func FormatStringFromFilename to cover such case
2025-07-15 19:30:07 +10:00
Mike Farah 6e8cc00030 Added flag to fix #2110 2025-07-14 16:26:26 +10:00
Mike Farah b9d9e2fbad Moar tests 2025-07-11 22:00:20 +10:00
dependabot[bot]andMike Farah a98921213f Bump golang.org/x/text from 0.26.0 to 0.27.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.26.0 to 0.27.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.26.0...v0.27.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-version: 0.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-11 11:18:33 +10:00
dependabot[bot]andMike Farah 0153cccda9 Bump golang from 1.24.4 to 1.24.5
Bumps golang from 1.24.4 to 1.24.5.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.24.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-11 11:18:23 +10:00
Mike Farah 9b299649f7 Refactored initCommand 2025-07-11 10:17:21 +10:00
Mike Farah 369fe56e2d Adding test file (CursorAI) 2025-07-11 09:59:59 +10:00
Mike Farah 422300457c (attempt) to build snapcract for all architectures 2025-07-10 16:17:23 +10:00
cloudclaimandMike Farah 544bd9ff6f chore: fix some minor issues in the comments
Signed-off-by: cloudclaim <824973921@qq.com>
2025-07-10 13:33:36 +10:00
Mike Farah 1187c954ec Bumping version 2025-07-10 13:09:34 +10:00
Mike Farah 08d3789daa Preparing release 2025-07-10 13:07:43 +10:00
Mike Farah 6a5e95ac84 Merge branch 'imzue-build-small-yq' 2025-07-10 13:06:54 +10:00
Mike Farah 221a5b1106 Adding tinygo to project words 2025-07-10 13:06:42 +10:00
Mike Farah c10bfe0602 Switching to YAML org supported go-yaml 2025-07-10 13:04:54 +10:00
Mike Farah 99f88df21e Switching to YAML org supported go-yaml 2025-07-10 13:01:09 +10:00
Zue 7696723d5c Add more build tags to reduce binary size 2025-07-01 15:52:40 +08:00
Zue a9f7cc1ebb Add TinyGo build example 2025-07-01 13:28:10 +08:00
Zue 384d227efe Add yq_noini tag to build-small-yq 2025-07-01 13:27:21 +08:00
Zue 25365a0f0b Fix compilation errors when building build-small-yq 2025-07-01 13:26:26 +08:00
dependabot[bot]andMike Farah 40808838ba Bump golang.org/x/net from 0.40.0 to 0.41.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.40.0 to 0.41.0.
- [Commits](https://github.com/golang/net/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-13 10:47:51 +10:00
Mike Farah 176873e93a Refining add op 2025-06-12 15:41:27 +10:00
dependabot[bot]andMike Farah fd7e750040 Bump golang from 1.24.3 to 1.24.4
Bumps golang from 1.24.3 to 1.24.4.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.24.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-12 15:01:24 +10:00
Mike Farah ff9d2acd4a Preparing release notes (won't release till I get back from holiday) 2025-06-12 14:53:39 +10:00
Mike Farah 88bfbec97b Fix add op when there are not matches piped in, #2383, #2384 2025-06-12 14:48:10 +10:00
Antoine DeschênesandMike Farah b15ce77cad fix: correct key order after add, flatten, keys, sort, reverse and shuffle
Signed-off-by: Antoine Deschênes <antoine.deschenes@linux.com>
2025-06-07 21:04:26 +10:00
Mike Farah b84fd47934 More goccy progress 2025-06-07 15:23:38 +10:00
dependabot[bot]andMike Farah 58e0cd2600 Bump github.com/goccy/go-yaml from 1.17.1 to 1.18.0
Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.17.1 to 1.18.0.
- [Release notes](https://github.com/goccy/go-yaml/releases)
- [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/goccy/go-yaml/compare/v1.17.1...v1.18.0)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-yaml
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 10:51:48 +10:00
o.bilkoandMike Farah c58d9e7da4 fix tests 2025-05-17 18:55:53 +10:00
beliysandMike Farah 3ac203ebb8 Add INI support 2025-05-17 18:55:53 +10:00
Mike Farah b534aa9ee5 Bumping version 2025-05-11 21:31:47 +10:00
Mike Farah 39a65b62d2 Updating release notes 2025-05-10 07:41:19 +10:00
Mike Farah 1e3006e951 Removing old issue template 2025-05-10 07:25:48 +10:00
Mike Farah 22949df0fd Fixing running map against empty array bug #2359 2025-05-10 07:24:48 +10:00
Mike Farah 734e2cd254 Bumping version 2025-05-09 15:48:44 +10:00
Mike Farah 082b76affa Preparing release notes 2025-05-09 15:48:13 +10:00
Mike Farah 5bc2cd03da Bumping golang version 2025-05-09 15:45:28 +10:00
Mike Farah 20407a07a5 Adding more tests to prevent regression again :sweat 2025-05-09 15:40:29 +10:00
dependabot[bot]andMike Farah 0a83da6b38 Bump github.com/pelletier/go-toml/v2 from 2.2.3 to 2.2.4
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.2.3 to 2.2.4.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.2.3...v2.2.4)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-version: 2.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 15:40:02 +10:00
dependabot[bot]andMike Farah 77da8b7d32 Bump golang from 1.24.2 to 1.24.3
Bumps golang from 1.24.2 to 1.24.3.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.24.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 15:39:48 +10:00
dependabot[bot]andMike Farah a27db3aacc Bump golang.org/x/net from 0.39.0 to 0.40.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.39.0 to 0.40.0.
- [Commits](https://github.com/golang/net/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 15:35:30 +10:00
dependabot[bot]andMike Farah ed7fd54dac Bump github.com/a8m/envsubst from 1.4.2 to 1.4.3
Bumps [github.com/a8m/envsubst](https://github.com/a8m/envsubst) from 1.4.2 to 1.4.3.
- [Release notes](https://github.com/a8m/envsubst/releases)
- [Commits](https://github.com/a8m/envsubst/compare/v1.4.2...v1.4.3)

---
updated-dependencies:
- dependency-name: github.com/a8m/envsubst
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-09 15:33:53 +10:00
Mike Farah 2c2487c0bd Fixing Regression in #2353, #2359, #2325 2025-05-09 15:29:44 +10:00
Mike Farah 2c7cce0878 Line and Column working with Goccy, Fixed Anchor Map problem 2025-05-04 19:29:42 +10:00
Mike Farah 2bd08ea4e8 Dont panic on tests 2025-05-04 10:33:09 +10:00
Mike Farah 72fa3cca98 goccy decoder supports merge maps 2025-05-03 20:20:50 +10:00
Mike Farah f89605e3c0 goccy yaml decoder supports anchor/aliases 2025-05-03 20:01:16 +10:00
Mike Farah c59fa8de59 Bumping version 2025-05-03 16:40:24 +10:00
Mike Farah ef8b520f89 Updating release notes 2025-05-03 16:39:36 +10:00
Mike Farah 20b5129120 Updating golanglint 2025-05-03 16:34:21 +10:00
Mike Farah 89518a09b8 Bumping golint 2025-05-03 15:58:14 +10:00
Mike Farah d01ac7801d Regen doc 2025-05-03 11:37:52 +10:00
Mike Farah 67520b5e7b Adding windows arm build #2344 2025-05-03 11:37:52 +10:00
Romain Lebrun ThaurontandMike Farah 7b6e61ba2c Update README.md 2025-05-03 11:36:12 +10:00
Romain Lebrun ThaurontandMike Farah cf8621d9e3 Install to /usr/local in README install instruction 2025-05-03 11:36:12 +10:00
dependabot[bot]andMike Farah 340b897252 Bump github.com/spf13/cobra from 1.8.1 to 1.9.1
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.1 to 1.9.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.1...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-03 11:28:22 +10:00
n4zukkerandMike Farah 4155c99a12 Typo in cats example
The quotation mark and closing parenthesis were swapped.

Before:
```
$ echo "Hello kitty" | yq eval 'test("(?i)cats)"'
Error: bad expression - probably missing close bracket on TEST
```

After:
```
$ echo "Hello kitty" | yq eval 'test("(?i)cats")'
false
```
2025-05-03 11:27:51 +10:00
dependabot[bot]andMike Farah b26de94758 Bump github.com/magiconair/properties from 1.8.9 to 1.8.10
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.9 to 1.8.10.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.9...v1.8.10)

---
updated-dependencies:
- dependency-name: github.com/magiconair/properties
  dependency-version: 1.8.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-03 11:24:22 +10:00
dependabot[bot]andMike Farah 7dc5efee62 Bump golang.org/x/net from 0.34.0 to 0.39.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.34.0 to 0.39.0.
- [Commits](https://github.com/golang/net/compare/v0.34.0...v0.39.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-03 11:24:12 +10:00
Alberto CavalcanteandMike Farah 2cb7498444 chore(release): add windows arm64 binary target for release 2025-05-03 11:23:55 +10:00
Alberto CavalcanteandMike Farah 649c9a083a chore(check): use golangci-lint from GOPATH/bin as first option 2025-05-03 11:23:55 +10:00
Alberto CavalcanteandMike Farah 074d785396 chore(devtools): use curl instead of wget for better compatibility with MacOS 2025-05-03 11:23:55 +10:00
dependabot[bot]andMike Farah de2f77b49c Bump github.com/alecthomas/participle/v2 from 2.1.1 to 2.1.4
Bumps [github.com/alecthomas/participle/v2](https://github.com/alecthomas/participle) from 2.1.1 to 2.1.4.
- [Release notes](https://github.com/alecthomas/participle/releases)
- [Changelog](https://github.com/alecthomas/participle/blob/master/CHANGES.md)
- [Commits](https://github.com/alecthomas/participle/compare/v2.1.1...v2.1.4)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/participle/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-05 21:36:59 +11:00
Ashok PariyaandMike Farah fe06096514 Add s390x platform support to Docker release workflow
Updated the list of supported platforms in the GitHub Actions
Docker release workflow to include linux/s390x.

Signed-off-by: Ashok Pariya <ashok.pariya@ibm.com>
2025-04-05 20:18:39 +11:00
dependabot[bot]andMike Farah 40f2c2381b Bump golang from 1.24.1 to 1.24.2
Bumps golang from 1.24.1 to 1.24.2.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.24.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-05 20:17:56 +11:00
dependabot[bot]andMike Farah 64bee0fbe3 Bump golang from 1.24.0 to 1.24.1
Bumps golang from 1.24.0 to 1.24.1.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 15:45:53 +11:00
dependabot[bot]andMike Farah 6e6575d5b0 Bump golang.org/x/text from 0.22.0 to 0.23.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.22.0 to 0.23.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-25 15:45:42 +11:00
Oleksandr RedkoandMike Farah ce2ce51236 Remove mistakenly committed cover.out 2025-03-25 15:45:27 +11:00
Zoltán ReegnandMike Farah c9766c1cab Also push docker images to ghcr.io
With docker-hub starting to introduce a much stricter limit on anonymous
pulls, it makes sense to also host the image in ghcr.io as well,
allowing users flexibility in where they pull the docker images from.

Also with the github action it makes more sense hosting the docker image
on github infrastructure.

I've introduced a github action for logging into registries as well.
2025-03-25 15:45:15 +11:00
Mike Farah 337960a6d1 Fixing add when there is no node match #2325 2025-03-25 15:35:16 +11:00
damuzhi0810andMike Farah 1395d6e230 refactor: using slices.Contains to simplify the code
Signed-off-by: damuzhi0810 <rust@before.tech>
2025-03-25 15:10:25 +11:00
Mike Farah ba1b9737b6 Added recursive update map keys example 2025-03-03 15:10:42 +11:00
dependabot[bot]andMike Farah d74e70104f Bump golang.org/x/text from 0.21.0 to 0.22.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.21.0 to 0.22.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-22 11:19:34 +11:00
dependabot[bot]andMike Farah 15071ba393 Bump github.com/goccy/go-json from 0.10.4 to 0.10.5
Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.10.4 to 0.10.5.
- [Release notes](https://github.com/goccy/go-json/releases)
- [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md)
- [Commits](https://github.com/goccy/go-json/compare/v0.10.4...v0.10.5)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-22 11:19:24 +11:00
dependabot[bot]andMike Farah a9495ff395 Bump github.com/spf13/pflag from 1.0.5 to 1.0.6
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.5 to 1.0.6.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.5...v1.0.6)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-22 11:19:12 +11:00
dependabot[bot]andMike Farah e176ec649f Bump golang from 1.23.5 to 1.24.0
Bumps golang from 1.23.5 to 1.24.0.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-22 11:19:00 +11:00
dependabot[bot]andMike Farah c34d3f7805 Bump golang from 1.23.4 to 1.23.5
Bumps golang from 1.23.4 to 1.23.5.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-23 22:04:08 +11:00
dependabot[bot]andMike Farah 99e24956b3 Bump github.com/elliotchance/orderedmap from 1.7.1 to 1.8.0
Bumps [github.com/elliotchance/orderedmap](https://github.com/elliotchance/orderedmap) from 1.7.1 to 1.8.0.
- [Release notes](https://github.com/elliotchance/orderedmap/releases)
- [Commits](https://github.com/elliotchance/orderedmap/compare/v1.7.1...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/elliotchance/orderedmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 14:40:05 +11:00
dependabot[bot]andMike Farah 2bce6b2608 Bump golang.org/x/net from 0.33.0 to 0.34.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.34.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.34.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 14:39:55 +11:00
Mike Farah f202d06d82 sort_by works on maps 2025-01-22 14:36:50 +11:00
Stephen UridgeandMike Farah 1508f1fd5f Update docs and tests for --properties-separator flag 2025-01-22 09:53:51 +11:00
Mike Farah 8bf425b4d1 Bumping version 2025-01-12 14:17:39 +11:00
Mike Farah f755755bb0 Updated release notes 2025-01-12 14:17:20 +11:00
Mike Farah 0f390b27aa Bumping goccy 2025-01-12 14:13:31 +11:00
dependabot[bot]andMike Farah 31ad7fbaa2 Bump github.com/magiconair/properties from 1.8.7 to 1.8.9
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.7 to 1.8.9.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.7...v1.8.9)

---
updated-dependencies:
- dependency-name: github.com/magiconair/properties
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-12 14:03:15 +11:00
dependabot[bot]andMike Farah 566cf82d08 Bump github.com/goccy/go-json from 0.10.3 to 0.10.4
Bumps [github.com/goccy/go-json](https://github.com/goccy/go-json) from 0.10.3 to 0.10.4.
- [Release notes](https://github.com/goccy/go-json/releases)
- [Changelog](https://github.com/goccy/go-json/blob/master/CHANGELOG.md)
- [Commits](https://github.com/goccy/go-json/compare/v0.10.3...v0.10.4)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-12 14:03:05 +11:00
dependabot[bot]andMike Farah 2c9f8331c4 Bump github.com/elliotchance/orderedmap from 1.7.0 to 1.7.1
Bumps [github.com/elliotchance/orderedmap](https://github.com/elliotchance/orderedmap) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/elliotchance/orderedmap/releases)
- [Commits](https://github.com/elliotchance/orderedmap/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/elliotchance/orderedmap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-12 14:02:52 +11:00
dependabot[bot]andMike Farah c02d44de63 Bump golang.org/x/net from 0.32.0 to 0.33.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.32.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.32.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-12 14:02:38 +11:00
Rudolf ThomasandMike Farah f73c862cc5 feat: Create parent directories if --split-exp is used.
Problem: When --split-exp is used and produces filenames with slashes in
them, the target directories must already exist otherwise yq fails.

Fix/feature: Create the necessary directories with os.MkdirAll().
The permissions 0750 were chosen to satisfy the vulnerability checker.
2024-12-14 19:52:09 +11:00
Mike Farah 294a1709ad Bumping version 2024-12-07 17:33:47 +11:00
129 changed files with 4515 additions and 3633 deletions
-51
View File
@@ -1,51 +0,0 @@
---
name: Bug report - V3
about: Create a report to help us improve
title: ''
labels: bug, v3
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
Note that any how to questions should be posted in the discussion board and not raised as an issue.
Version of yq: 3.X.X
Operating system: mac/linux/windows/....
Installed via: docker/binary release/homebrew/snap/...
**Input Yaml**
Concise yaml document(s) (as simple as possible to show the bug, please keep it to 10 lines or less)
data1.yml:
```yaml
this: should really work
```
data2.yml:
```yaml
but: it strangely didn't
```
**Command**
The command you ran:
```
yq merge data1.yml data2.yml
```
**Actual behavior**
```yaml
cat: meow
```
**Expected behavior**
```yaml
this: should really work
but: it strangely didn't
```
**Additional context**
Add any other context about the problem here.
+20 -2
View File
@@ -30,6 +30,19 @@ jobs:
- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} && docker version
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
run: |
echo "GithubRef: ${GITHUB_REF}"
@@ -38,10 +51,9 @@ jobs:
IMAGE_VERSION=${VERSION:1}
echo "IMAGE_VERSION: ${IMAGE_VERSION}"
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64,linux/arm/v7"
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64,linux/arm/v7,linux/s390x"
echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}"
echo '${{ secrets.DOCKER_PASSWORD }}' | docker login -u '${{ secrets.DOCKER_USERNAME }}' --password-stdin
docker buildx build \
--label "org.opencontainers.image.authors=https://github.com/mikefarah/yq/graphs/contributors" \
--label "org.opencontainers.image.created=$(date --rfc-3339=seconds)" \
@@ -59,6 +71,9 @@ jobs:
-t "${IMAGE_NAME}:${IMAGE_VERSION}" \
-t "${IMAGE_NAME}:4" \
-t "${IMAGE_NAME}:latest" \
-t "ghcr.io/${IMAGE_NAME}:${IMAGE_VERSION}" \
-t "ghcr.io/${IMAGE_NAME}:4" \
-t "ghcr.io/${IMAGE_NAME}:latest" \
.
cd github-action
@@ -79,4 +94,7 @@ jobs:
-t "${IMAGE_NAME}:${IMAGE_VERSION}-githubaction" \
-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}:latest-githubaction" \
.
+3
View File
@@ -22,6 +22,7 @@ _cgo_gotypes.go
_cgo_export.*
_testmain.go
cover.out
coverage.out
coverage.html
*.exe
@@ -44,6 +45,8 @@ test*.yml
test*.xml
test*.toml
test*.yaml
test_dir1/
test_dir2/
0.yml
1.yml
2.yml
+38
View File
@@ -0,0 +1,38 @@
run:
timeout: 5m
linters:
enable:
- asciicheck
- depguard
- errorlint
- gci
- gochecknoinits
- gofmt
- goimports
- gosec
- gosimple
- staticcheck
- unused
- misspell
- nakedret
- nolintlint
- predeclared
- revive
- unconvert
- unparam
linters-settings:
depguard:
rules:
prevent_unmaintained_packages:
list-mode: lax
files:
- $all
- "!$test"
deny:
- pkg: io/ioutil
desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil"
issues:
exclude-rules:
- linters:
- revive
text: "var-naming"
+37 -23
View File
@@ -1,18 +1,11 @@
run:
timeout: 5m
version: "2"
linters:
enable:
- asciicheck
- depguard
- errorlint
- gci
- gochecknoinits
- gofmt
- goimports
- gosec
- gosimple
- staticcheck
- unused
- misspell
- nakedret
- nolintlint
@@ -20,19 +13,40 @@ linters:
- revive
- unconvert
- unparam
linters-settings:
depguard:
settings:
depguard:
rules:
prevent_unmaintained_packages:
list-mode: lax
files:
- $all
- '!$test'
deny:
- pkg: io/ioutil
desc: 'replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil'
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
prevent_unmaintained_packages:
list-mode: lax
files:
- $all
- "!$test"
deny:
- pkg: io/ioutil
desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil"
issues:
exclude-rules:
- linters:
- revive
text: "var-naming"
- linters:
- revive
text: var-naming
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+2
View File
@@ -38,6 +38,8 @@ builds:
- openbsd_amd64
- windows_386
- windows_amd64
- windows_arm
- windows_arm64
no_unique_dist_dir: true
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.23.4 AS builder
FROM golang:1.24.5 AS builder
WORKDIR /go/src/mikefarah/yq
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.23.4
FROM golang:1.24.5
RUN apt-get update && \
apt-get install -y npm && \
+37 -16
View File
@@ -3,7 +3,7 @@
![Build](https://github.com/mikefarah/yq/workflows/Build/badge.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq) ![CodeQL](https://github.com/mikefarah/yq/workflows/CodeQL/badge.svg)
a lightweight and portable command-line YAML, JSON and XML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json, xml, properties, csv and tsv. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
a lightweight and portable command-line YAML, JSON, INI and XML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json, xml, ini, properties, csv and tsv. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
yq is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as Docker and Podman, all listed below.
@@ -76,21 +76,21 @@ For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
#### Compressed via tar.gz
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\
tar xz && mv ${BINARY} /usr/bin/yq
tar xz && mv ${BINARY} /usr/local/bin/yq
```
#### Plain binary
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
chmod +x /usr/bin/yq
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
```
#### Latest version
```bash
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq &&\
chmod +x /usr/bin/yq
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
```
### MacOS / Linux via Homebrew:
@@ -327,7 +327,7 @@ flox 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
- Uses similar syntax as `jq` but works with YAML, [JSON](https://mikefarah.gitbook.io/yq/usage/convert) and [XML](https://mikefarah.gitbook.io/yq/usage/xml) files
- Uses similar syntax as `jq` but works with YAML, INI, [JSON](https://mikefarah.gitbook.io/yq/usage/convert) and [XML](https://mikefarah.gitbook.io/yq/usage/xml) files
- Fully supports multi document yaml files
- Supports yaml [front matter](https://mikefarah.gitbook.io/yq/usage/front-matter) blocks (e.g. jekyll/assemble)
- Colorized yaml output
@@ -366,31 +366,52 @@ yq -i '.stuff = "foo"' myfile.yml # update myfile.yml in place
Available Commands:
completion Generate the autocompletion script for the specified shell
eval (default) Apply the expression to each document in each yaml file in sequence
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
help Help about any command
completion Generate the autocompletion script for the specified shell
eval (default) Apply the expression to each document in each yaml file in sequence
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
help Help about any command
Flags:
-C, --colors force print with colors
--csv-auto-parse parse CSV YAML/JSON values (default true)
--csv-separator char CSV Separator character (default ,)
-e, --exit-status set exit status if there are no matches or null or false is returned
--expression string forcibly set the expression argument. Useful when yq argument detection thinks your expression is a file.
--from-file string Load expression from specified file.
-f, --front-matter string (extract|process) first input as yaml front-matter. Extract will pull out the yaml content, process will run the expression against the yaml content, leaving the remaining data intact
--header-preprocess Slurp any header comments and separators before processing expression. (default true)
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-i, --inplace update the file in place of first file given.
-p, --input-format string [yaml|y|xml|x] parse format for input. Note that json is a subset of yaml. (default "yaml")
-p, --input-format string [auto|a|yaml|y|json|j|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|lua|l|ini|i] parse format for input. (default "auto")
--lua-globals output keys as top-level global variables
--lua-prefix string prefix (default "return ")
--lua-suffix string suffix (default ";\n")
--lua-unquoted output unquoted string keys (e.g. {foo="bar"})
-M, --no-colors force print with no colors
-N, --no-doc Don't print document separators (---)
-0, --nul-output Use NUL char to separate values. If unwrap scalar is also set, fail if unwrapped scalar contains NUL char.
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.
-o, --output-format string [yaml|y|json|j|props|p|xml|x] output format type. (default "yaml")
-o, --output-format string [auto|a|yaml|y|json|j|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|shell|s|lua|l|ini|i] output format type. (default "auto")
-P, --prettyPrint pretty print, shorthand for '... style = ""'
-s, --split-exp string print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter.
--unwrapScalar unwrap scalar, print the value with no quotes, colors or comments (default true)
--properties-array-brackets use [x] in array paths (e.g. for SpringBoot)
--properties-separator string separator to use between keys and values (default " = ")
-s, --split-exp string print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter. The necessary directories will be created.
--split-exp-file string Use a file to specify the split-exp expression.
--string-interpolation Toggles strings interpolation of \(exp) (default true)
--tsv-auto-parse parse TSV YAML/JSON values (default true)
-r, --unwrapScalar unwrap scalar, print the value with no quotes, colors or comments. Defaults to true for yaml (default true)
-v, --verbose verbose mode
-V, --version Print version information and quit
--xml-attribute-prefix string prefix for xml attributes (default "+")
--xml-attribute-prefix string prefix for xml attributes (default "+@")
--xml-content-name string name for xml content (if no attribute name is present). (default "+content")
--xml-directive-name string name for xml directives (e.g. <!DOCTYPE thing cat>) (default "+directive")
--xml-keep-namespace enables keeping namespace after parsing attributes (default true)
--xml-proc-inst-prefix string prefix for xml processing instructions (e.g. <?xml version="1"?>) (default "+p_")
--xml-raw-token enables using RawToken method instead Token. Commonly disables namespace translations. See https://pkg.go.dev/encoding/xml#Decoder.RawToken for details. (default true)
--xml-skip-directives skip over directives (e.g. <!DOCTYPE thing cat>)
--xml-skip-proc-inst skip over process instructions (e.g. <?xml version="1"?>)
--xml-strict-mode enables strict parsing of XML. See https://pkg.go.dev/encoding/xml for more details.
Use "yq [command] --help" for more information about a command.
```
+2 -2
View File
@@ -3,7 +3,7 @@
testLoadFileNotExist() {
result=$(./yq e -n 'load("cat.yml")' 2>&1)
assertEquals 1 $?
assertEquals "Error: Failed to load cat.yml: open cat.yml: no such file or directory" "$result"
assertEquals "Error: failed to load cat.yml: open cat.yml: no such file or directory" "$result"
}
testLoadFileExpNotExist() {
@@ -15,7 +15,7 @@ testLoadFileExpNotExist() {
testStrLoadFileNotExist() {
result=$(./yq e -n 'strload("cat.yml")' 2>&1)
assertEquals 1 $?
assertEquals "Error: Failed to load cat.yml: open cat.yml: no such file or directory" "$result"
assertEquals "Error: failed to load cat.yml: open cat.yml: no such file or directory" "$result"
}
testStrLoadFileExpNotExist() {
+21 -1
View File
@@ -2,6 +2,7 @@
setUp() {
rm test*.yml || true
rm -rf test_dir* || true
}
testBasicSplitWithName() {
@@ -204,4 +205,23 @@ EOM
assertEquals "$expectedDoc3" "$doc3"
}
source ./scripts/shunit2
testSplitWithDirectories() {
cat >test.yml <<EOL
f: test_dir1/test_file1
---
f: test_dir2/dir22/test_file2
---
f: test_file3
EOL
./yq e --no-doc -s ".f" test.yml
doc1=$(cat test_dir1/test_file1.yml)
assertEquals "f: test_dir1/test_file1" "$doc1"
doc2=$(cat test_dir2/dir22/test_file2.yml)
assertEquals "f: test_dir2/dir22/test_file2" "$doc2"
doc3=$(cat test_file3.yml)
assertEquals "f: test_file3" "$doc3"
}
source ./scripts/shunit2
+2
View File
@@ -2,6 +2,8 @@ package cmd
var unwrapScalarFlag = newUnwrapFlag()
var printNodeInfo = false
var unwrapScalar = false
var writeInplace = false
+5
View File
@@ -105,6 +105,11 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
}
printer := yqlib.NewPrinter(encoder, printerWriter)
if printNodeInfo {
printer = yqlib.NewNodeInfoPrinter(printerWriter)
}
if nulSepOutput {
printer.SetNulSepOutput(true)
}
+3 -1
View File
@@ -99,6 +99,7 @@ yq -P -oy sample.json
}
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().BoolVarP(&printNodeInfo, "debug-node-info", "", false, "debug node info")
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "(deprecated) output as json. Set indent to 0 to print json in one line.")
err := rootCmd.PersistentFlags().MarkDeprecated("tojson", "please use -o=json instead")
@@ -196,8 +197,9 @@ yq -P -oy sample.json
panic(err)
}
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.LeadingContentPreProcessing, "header-preprocess", "", true, "Slurp any header comments and separators before processing expression.")
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.FixMergeAnchorToSpec, "yaml-fix-merge-anchor-to-spec", "", false, "Fix merge anchor to match YAML spec. Will default to true in late 2025")
rootCmd.PersistentFlags().StringVarP(&splitFileExp, "split-exp", "s", "", "print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter.")
rootCmd.PersistentFlags().StringVarP(&splitFileExp, "split-exp", "s", "", "print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter. The necessary directories will be created.")
if err = rootCmd.RegisterFlagCompletionFunc("split-exp", cobra.NoFileCompletions); err != nil {
panic(err)
}
+68 -17
View File
@@ -18,52 +18,100 @@ func isAutomaticOutputFormat() bool {
func initCommand(cmd *cobra.Command, args []string) (string, []string, error) {
cmd.SilenceUsage = true
fileInfo, _ := os.Stdout.Stat()
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
colorsEnabled = true
}
setupColors()
expression, args, err := processArgs(args)
if err != nil {
return "", nil, err
}
if err := loadSplitFileExpression(); err != nil {
return "", nil, err
}
handleBackwardsCompatibility()
if err := validateCommandFlags(args); err != nil {
return "", nil, err
}
if err := configureFormats(args); err != nil {
return "", nil, err
}
configureUnwrapScalar()
return expression, args, nil
}
func setupColors() {
fileInfo, _ := os.Stdout.Stat()
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
colorsEnabled = true
}
}
func loadSplitFileExpression() error {
if splitFileExpFile != "" {
splitExpressionBytes, err := os.ReadFile(splitFileExpFile)
if err != nil {
return "", nil, err
return err
}
splitFileExp = string(splitExpressionBytes)
}
return nil
}
func handleBackwardsCompatibility() {
// backwards compatibility
if outputToJSON {
outputFormat = "json"
}
}
func validateCommandFlags(args []string) error {
if writeInplace && (len(args) == 0 || args[0] == "-") {
return "", nil, fmt.Errorf("write in place flag only applicable when giving an expression and at least one file")
return fmt.Errorf("write in place flag only applicable when giving an expression and at least one file")
}
if frontMatter != "" && len(args) == 0 {
return "", nil, fmt.Errorf("front matter flag only applicable when giving an expression and at least one file")
return fmt.Errorf("front matter flag only applicable when giving an expression and at least one file")
}
if writeInplace && splitFileExp != "" {
return "", nil, fmt.Errorf("write in place cannot be used with split file")
return fmt.Errorf("write in place cannot be used with split file")
}
if nullInput && len(args) > 0 {
return "", nil, fmt.Errorf("cannot pass files in when using null-input flag")
return fmt.Errorf("cannot pass files in when using null-input flag")
}
return nil
}
func configureFormats(args []string) error {
inputFilename := ""
if len(args) > 0 {
inputFilename = args[0]
}
if inputFormat == "" || inputFormat == "auto" || inputFormat == "a" {
if err := configureInputFormat(inputFilename); err != nil {
return err
}
if err := configureOutputFormat(); err != nil {
return err
}
yqlib.GetLogger().Debug("Using input format %v", inputFormat)
yqlib.GetLogger().Debug("Using output format %v", outputFormat)
return nil
}
func configureInputFormat(inputFilename string) error {
if inputFormat == "" || inputFormat == "auto" || inputFormat == "a" {
inputFormat = yqlib.FormatStringFromFilename(inputFilename)
_, err := yqlib.FormatFromString(inputFormat)
@@ -88,24 +136,27 @@ func initCommand(cmd *cobra.Command, args []string) (string, []string, error) {
}
outputFormat = "yaml"
}
return nil
}
func configureOutputFormat() error {
outputFormatType, err := yqlib.FormatFromString(outputFormat)
if err != nil {
return "", nil, err
return err
}
yqlib.GetLogger().Debug("Using input format %v", inputFormat)
yqlib.GetLogger().Debug("Using output format %v", outputFormat)
if outputFormatType == yqlib.YamlFormat ||
outputFormatType == yqlib.PropertiesFormat {
unwrapScalar = true
}
return nil
}
func configureUnwrapScalar() {
if unwrapScalarFlag.IsExplicitlySet() {
unwrapScalar = unwrapScalarFlag.IsSet()
}
return expression, args, nil
}
func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
+1435
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "v4.44.6"
Version = "v4.46.1"
// 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
@@ -45,5 +45,5 @@ func getHumanVersion() string {
}
// Strip off any single quotes added by the git information.
return strings.Replace(version, "'", "", -1)
return strings.ReplaceAll(version, "'", "")
}
+8 -11
View File
@@ -1,11 +1,8 @@
# block comments come through
person: # neither do comments on maps
name: Mike Wazowski # comments on values appear
pets:
- cat # comments on array values appear
- dog # comments on array values appear
- things:
- frog
food: [pizza] # comments on arrays do not
emptyArray: []
emptyMap: []
# 001
---
abc: # 001
- 1 # one
- 2 # two
---
def # 002
+1 -1
View File
@@ -1,6 +1,6 @@
---
a: apple
b: bannana
b: banana
---
hello there
apples: great
+14
View File
@@ -0,0 +1,14 @@
; This is a INI document
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
db_host = "localhost"
db_port = 5432
db_user = "postgres"
db_password = ""
db_name = "postgres"
+8
View File
@@ -0,0 +1,8 @@
; This is a INI document
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
enabled = true
ip = "10.0.0.1"
role = "frontend"
treads = 4
+17 -16
View File
@@ -1,34 +1,35 @@
module github.com/mikefarah/yq/v4
require (
github.com/a8m/envsubst v1.4.2
github.com/alecthomas/participle/v2 v2.1.1
github.com/a8m/envsubst v1.4.3
github.com/alecthomas/participle/v2 v2.1.4
github.com/alecthomas/repr v0.4.0
github.com/dimchansky/utfbom v1.1.1
github.com/elliotchance/orderedmap v1.7.0
github.com/elliotchance/orderedmap v1.8.0
github.com/fatih/color v1.18.0
github.com/goccy/go-json v0.10.3
github.com/goccy/go-yaml v1.13.0
github.com/go-ini/ini v1.67.0
github.com/goccy/go-json v0.10.5
github.com/goccy/go-yaml v1.18.0
github.com/jinzhu/copier v0.4.0
github.com/magiconair/properties v1.8.7
github.com/pelletier/go-toml/v2 v2.2.3
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.8.1
github.com/spf13/pflag v1.0.5
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/yuin/gopher-lua v1.1.1
golang.org/x/net v0.32.0
golang.org/x/text v0.21.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/net v0.41.0
golang.org/x/text v0.27.0
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/sys v0.33.0 // indirect
)
go 1.21.0
go 1.24
toolchain go1.22.5
toolchain go1.24.1
+35 -49
View File
@@ -1,77 +1,63 @@
github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8=
github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
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/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/elliotchance/orderedmap v1.7.0 h1:FirjcM/NbcyudJhaIF9MG/RjIh5XHm2xb1SFquZ8k0g=
github.com/elliotchance/orderedmap v1.7.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
github.com/elliotchance/orderedmap v1.8.0 h1:TrOREecvh3JbS+NCgwposXG5ZTFHtEsQiCGOhPElnMw=
github.com/elliotchance/orderedmap v1.8.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.13.0 h1:0Wtp0FZLd7Sm8gERmR9S6Iczzb3vItJj7NaHmFg8pTs=
github.com/goccy/go-yaml v1.13.0/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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/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=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
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=
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=
+118 -8
View File
@@ -53,6 +53,20 @@ func createScalarNode(value interface{}, stringValue string) *CandidateNode {
return node
}
type NodeInfo struct {
Kind string `yaml:"kind"`
Style string `yaml:"style,omitempty"`
Anchor string `yaml:"anchor,omitempty"`
Tag string `yaml:"tag,omitempty"`
HeadComment string `yaml:"headComment,omitempty"`
LineComment string `yaml:"lineComment,omitempty"`
FootComment string `yaml:"footComment,omitempty"`
Value string `yaml:"value,omitempty"`
Line int `yaml:"line,omitempty"`
Column int `yaml:"column,omitempty"`
Content []*NodeInfo `yaml:"content,omitempty"`
}
type CandidateNode struct {
Kind Kind
Style Style
@@ -155,6 +169,18 @@ func (n *CandidateNode) getParsedKey() interface{} {
}
func (n *CandidateNode) FilterMapContentByKey(keyPredicate func(*CandidateNode) bool) []*CandidateNode {
var result []*CandidateNode
for index := 0; index < len(n.Content); index = index + 2 {
keyNode := n.Content[index]
valueNode := n.Content[index+1]
if keyPredicate(keyNode) {
result = append(result, keyNode, valueNode)
}
}
return result
}
func (n *CandidateNode) GetPath() []interface{} {
key := n.getParsedKey()
if n.Parent != nil && key != nil {
@@ -198,6 +224,30 @@ func (n *CandidateNode) SetParent(parent *CandidateNode) {
n.Parent = parent
}
type ValueVisitor func(*CandidateNode) error
func (n *CandidateNode) VisitValues(visitor ValueVisitor) error {
switch n.Kind {
case MappingNode:
for i := 1; i < len(n.Content); i = i + 2 {
if err := visitor(n.Content[i]); err != nil {
return err
}
}
case SequenceNode:
for i := 0; i < len(n.Content); i = i + 1 {
if err := visitor(n.Content[i]); err != nil {
return err
}
}
}
return nil
}
func (n *CandidateNode) CanVisitValues() bool {
return n.Kind == MappingNode || n.Kind == SequenceNode
}
func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *CandidateNode) (*CandidateNode, *CandidateNode) {
key := rawKey.Copy()
key.SetParent(n)
@@ -215,14 +265,13 @@ func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *Candid
func (n *CandidateNode) AddChild(rawChild *CandidateNode) {
value := rawChild.Copy()
value.SetParent(n)
if value.Key != nil {
value.Key.SetParent(n)
} else {
index := len(n.Content)
keyNode := createScalarNode(index, fmt.Sprintf("%v", index))
keyNode.SetParent(n)
value.Key = keyNode
}
value.IsMapKey = false
index := len(n.Content)
keyNode := createScalarNode(index, fmt.Sprintf("%v", index))
keyNode.SetParent(n)
value.Key = keyNode
n.Content = append(n.Content, value)
}
@@ -428,3 +477,64 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP
n.LineComment = other.LineComment
}
}
func (n *CandidateNode) ConvertToNodeInfo() *NodeInfo {
info := &NodeInfo{
Kind: kindToString(n.Kind),
Style: styleToString(n.Style),
Anchor: n.Anchor,
Tag: n.Tag,
HeadComment: n.HeadComment,
LineComment: n.LineComment,
FootComment: n.FootComment,
Value: n.Value,
Line: n.Line,
Column: n.Column,
}
if len(n.Content) > 0 {
info.Content = make([]*NodeInfo, len(n.Content))
for i, child := range n.Content {
info.Content[i] = child.ConvertToNodeInfo()
}
}
return info
}
// Helper functions to convert Kind and Style to string for NodeInfo
func kindToString(k Kind) string {
switch k {
case SequenceNode:
return "SequenceNode"
case MappingNode:
return "MappingNode"
case ScalarNode:
return "ScalarNode"
case AliasNode:
return "AliasNode"
default:
return "Unknown"
}
}
func styleToString(s Style) string {
var styles []string
if s&TaggedStyle != 0 {
styles = append(styles, "TaggedStyle")
}
if s&DoubleQuotedStyle != 0 {
styles = append(styles, "DoubleQuotedStyle")
}
if s&SingleQuotedStyle != 0 {
styles = append(styles, "SingleQuotedStyle")
}
if s&LiteralStyle != 0 {
styles = append(styles, "LiteralStyle")
}
if s&FoldedStyle != 0 {
styles = append(styles, "FoldedStyle")
}
if s&FlowStyle != 0 {
styles = append(styles, "FlowStyle")
}
return strings.Join(styles, ",")
}
+47 -17
View File
@@ -9,14 +9,14 @@ import (
goccyToken "github.com/goccy/go-yaml/token"
)
func (o *CandidateNode) goccyDecodeIntoChild(childNode ast.Node, cm yaml.CommentMap) (*CandidateNode, error) {
func (o *CandidateNode) goccyDecodeIntoChild(childNode ast.Node, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) (*CandidateNode, error) {
newChild := o.CreateChild()
err := newChild.UnmarshalGoccyYAML(childNode, cm)
err := newChild.UnmarshalGoccyYAML(childNode, cm, anchorMap)
return newChild, err
}
func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) error {
func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) error {
log.Debugf("UnmarshalYAML %v", node)
log.Debugf("UnmarshalYAML %v", node.Type().String())
log.Debugf("UnmarshalYAML Node Value: %v", node.String())
@@ -51,6 +51,9 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
}
o.Value = node.String()
o.Line = node.GetToken().Position.Line
o.Column = node.GetToken().Position.Column
switch node.Type() {
case ast.IntegerType:
o.Kind = ScalarNode
@@ -62,9 +65,13 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
o.Kind = ScalarNode
o.Tag = "!!bool"
case ast.NullType:
log.Debugf("its a null type with value %v", node.GetToken().Value)
o.Kind = ScalarNode
o.Tag = "!!null"
o.Value = node.GetToken().Value
if node.GetToken().Type == goccyToken.ImplicitNullType {
o.Value = ""
}
case ast.StringType:
o.Kind = ScalarNode
o.Tag = "!!str"
@@ -92,7 +99,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
// to solve the multiline > problem
o.Value = astLiteral.Value.Value
case ast.TagType:
if err := o.UnmarshalGoccyYAML(node.(*ast.TagNode).Value, cm); err != nil {
if err := o.UnmarshalGoccyYAML(node.(*ast.TagNode).Value, cm, anchorMap); err != nil {
return err
}
o.Tag = node.(*ast.TagNode).Start.Value
@@ -106,9 +113,9 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
o.Style = FlowStyle
}
for _, mappingValueNode := range mappingNode.Values {
err := o.goccyProcessMappingValueNode(mappingValueNode, cm)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm, anchorMap)
if err != nil {
return ast.ErrInvalidAnchorName
return err
}
}
if mappingNode.FootComment != nil {
@@ -120,9 +127,9 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
o.Kind = MappingNode
o.Tag = "!!map"
mappingValueNode := node.(*ast.MappingValueNode)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm, anchorMap)
if err != nil {
return ast.ErrInvalidAnchorName
return err
}
case ast.SequenceType:
log.Debugf("UnmarshalYAML - a sequence node")
@@ -141,7 +148,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
keyNode.Kind = ScalarNode
keyNode.Value = fmt.Sprintf("%v", i)
valueNode, err := o.goccyDecodeIntoChild(astSeq[i], cm)
valueNode, err := o.goccyDecodeIntoChild(astSeq[i], cm, anchorMap)
if err != nil {
return err
}
@@ -149,32 +156,55 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
valueNode.Key = keyNode
o.Content[i] = valueNode
}
case ast.AnchorType:
log.Debugf("UnmarshalYAML - an anchor node")
anchorNode := node.(*ast.AnchorNode)
err := o.UnmarshalGoccyYAML(anchorNode.Value, cm, anchorMap)
if err != nil {
return err
}
o.Anchor = anchorNode.Name.String()
anchorMap[o.Anchor] = o
case ast.AliasType:
log.Debugf("UnmarshalYAML - an alias node")
aliasNode := node.(*ast.AliasNode)
o.Kind = AliasNode
o.Value = aliasNode.Value.String()
o.Alias = anchorMap[o.Value]
case ast.MergeKeyType:
log.Debugf("UnmarshalYAML - a merge key")
o.Kind = ScalarNode
o.Tag = "!!merge" // note - I should be able to get rid of this.
o.Value = "<<"
default:
log.Debugf("UnmarshalYAML - node idea of the type!!")
log.Debugf("UnmarshalYAML - no idea of the type!!\n%v: %v", node.Type(), node.String())
}
log.Debugf("KIND: %v", o.Kind)
return nil
}
func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingValueNode, cm yaml.CommentMap) error {
func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingValueNode, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) error {
log.Debug("UnmarshalYAML MAP KEY entry %v", mappingEntry.Key)
keyNode, err := o.goccyDecodeIntoChild(mappingEntry.Key, cm)
if err != nil {
// AddKeyValueFirst because it clones the nodes, and we want to have the real refs when Unmarshalling
// particularly for the anchorMap
keyNode, valueNode := o.AddKeyValueChild(&CandidateNode{}, &CandidateNode{})
if err := keyNode.UnmarshalGoccyYAML(mappingEntry.Key, cm, anchorMap); err != nil {
return err
}
keyNode.IsMapKey = true
log.Debug("UnmarshalYAML MAP VALUE entry %v", mappingEntry.Value)
valueNode, err := o.goccyDecodeIntoChild(mappingEntry.Value, cm)
if err != nil {
if err := valueNode.UnmarshalGoccyYAML(mappingEntry.Value, cm, anchorMap); err != nil {
return err
}
if mappingEntry.FootComment != nil {
valueNode.FootComment = mappingEntry.FootComment.String()
}
o.AddKeyValueChild(keyNode, valueNode)
return nil
}
+44
View File
@@ -160,3 +160,47 @@ func TestCandidateNodeAddKeyValueChild(t *testing.T) {
test.AssertResult(t, key.IsMapKey, true)
}
func TestConvertToNodeInfo(t *testing.T) {
child := &CandidateNode{
Kind: ScalarNode,
Style: DoubleQuotedStyle,
Tag: "!!str",
Value: "childValue",
Line: 2,
Column: 3,
}
parent := &CandidateNode{
Kind: MappingNode,
Style: TaggedStyle,
Tag: "!!map",
Value: "",
Line: 1,
Column: 1,
Content: []*CandidateNode{child},
HeadComment: "head",
LineComment: "line",
FootComment: "foot",
Anchor: "anchor",
}
info := parent.ConvertToNodeInfo()
test.AssertResult(t, "MappingNode", info.Kind)
test.AssertResult(t, "TaggedStyle", info.Style)
test.AssertResult(t, "!!map", info.Tag)
test.AssertResult(t, "head", info.HeadComment)
test.AssertResult(t, "line", info.LineComment)
test.AssertResult(t, "foot", info.FootComment)
test.AssertResult(t, "anchor", info.Anchor)
test.AssertResult(t, 1, info.Line)
test.AssertResult(t, 1, info.Column)
if len(info.Content) != 1 {
t.Fatalf("Expected 1 child, got %d", len(info.Content))
}
childInfo := info.Content[0]
test.AssertResult(t, "ScalarNode", childInfo.Kind)
test.AssertResult(t, "DoubleQuotedStyle", childInfo.Style)
test.AssertResult(t, "!!str", childInfo.Tag)
test.AssertResult(t, "childValue", childInfo.Value)
test.AssertResult(t, 2, childInfo.Line)
test.AssertResult(t, 3, childInfo.Column)
}
+1 -4
View File
@@ -3,7 +3,7 @@ package yqlib
import (
"fmt"
yaml "gopkg.in/yaml.v3"
yaml "go.yaml.in/yaml/v3"
)
func MapYamlStyle(original yaml.Style) Style {
@@ -78,9 +78,6 @@ func (o *CandidateNode) copyToYamlNode(node *yaml.Node) {
node.Value = o.Value
node.Anchor = o.Anchor
// node.Alias = TODO - find Alias in our own structure
// might need to be a post process thing
node.HeadComment = o.HeadComment
node.LineComment = o.LineComment
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nojson
package yqlib
import (
+210
View File
@@ -2,9 +2,11 @@ package yqlib
import (
"container/list"
"strings"
"testing"
"github.com/mikefarah/yq/v4/test"
logging "gopkg.in/op/go-logging.v1"
)
func TestChildContext(t *testing.T) {
@@ -49,3 +51,211 @@ func TestChildContextNoVariables(t *testing.T) {
test.AssertResultComplex(t, make(map[string]*list.List), clone.Variables)
}
func TestSingleReadonlyChildContext(t *testing.T) {
original := Context{
DontAutoCreate: false,
datetimeLayout: "2006-01-02",
}
candidate := &CandidateNode{Value: "test"}
clone := original.SingleReadonlyChildContext(candidate)
// Should have DontAutoCreate set to true
test.AssertResultComplex(t, true, clone.DontAutoCreate)
// Should have the candidate node in MatchingNodes
test.AssertResultComplex(t, 1, clone.MatchingNodes.Len())
test.AssertResultComplex(t, candidate, clone.MatchingNodes.Front().Value)
}
func TestSingleChildContext(t *testing.T) {
original := Context{
DontAutoCreate: true,
datetimeLayout: "2006-01-02",
}
candidate := &CandidateNode{Value: "test"}
clone := original.SingleChildContext(candidate)
// Should preserve DontAutoCreate
test.AssertResultComplex(t, true, clone.DontAutoCreate)
// Should have the candidate node in MatchingNodes
test.AssertResultComplex(t, 1, clone.MatchingNodes.Len())
test.AssertResultComplex(t, candidate, clone.MatchingNodes.Front().Value)
}
func TestSetDateTimeLayout(t *testing.T) {
context := Context{}
// Test setting datetime layout
context.SetDateTimeLayout("2006-01-02T15:04:05Z07:00")
test.AssertResultComplex(t, "2006-01-02T15:04:05Z07:00", context.datetimeLayout)
}
func TestGetDateTimeLayout(t *testing.T) {
// Test with custom layout
context := Context{datetimeLayout: "2006-01-02"}
result := context.GetDateTimeLayout()
test.AssertResultComplex(t, "2006-01-02", result)
// Test with empty layout (should return default)
context = Context{}
result = context.GetDateTimeLayout()
test.AssertResultComplex(t, "2006-01-02T15:04:05Z07:00", result)
}
func TestGetVariable(t *testing.T) {
// Test with nil Variables
context := Context{}
result := context.GetVariable("nonexistent")
test.AssertResultComplex(t, (*list.List)(nil), result)
// Test with existing variable
variables := make(map[string]*list.List)
variables["test"] = list.New()
variables["test"].PushBack(&CandidateNode{Value: "value"})
context = Context{Variables: variables}
result = context.GetVariable("test")
test.AssertResultComplex(t, variables["test"], result)
// Test with non-existent variable
result = context.GetVariable("nonexistent")
test.AssertResultComplex(t, (*list.List)(nil), result)
}
func TestSetVariable(t *testing.T) {
// Test setting variable when Variables is nil
context := Context{}
value := list.New()
value.PushBack(&CandidateNode{Value: "test"})
context.SetVariable("key", value)
test.AssertResultComplex(t, value, context.Variables["key"])
// Test setting variable when Variables already exists
context.SetVariable("key2", value)
test.AssertResultComplex(t, value, context.Variables["key2"])
}
func TestToString(t *testing.T) {
context := Context{
DontAutoCreate: true,
MatchingNodes: list.New(),
}
// Add a node to test the full string representation
node := &CandidateNode{Value: "test"}
context.MatchingNodes.PushBack(node)
// Test with debug logging disabled (default)
result := context.ToString()
test.AssertResultComplex(t, "", result)
// Test with debug logging enabled
logging.SetLevel(logging.DEBUG, "")
defer logging.SetLevel(logging.INFO, "") // Reset to default
result2 := context.ToString()
test.AssertResultComplex(t, true, len(result2) > 0)
test.AssertResultComplex(t, true, strings.Contains(result2, "Context"))
test.AssertResultComplex(t, true, strings.Contains(result2, "DontAutoCreate: true"))
}
func TestDeepClone(t *testing.T) {
// Create original context with variables and matching nodes
originalVariables := make(map[string]*list.List)
originalVariables["test"] = list.New()
originalVariables["test"].PushBack(&CandidateNode{Value: "original"})
original := Context{
DontAutoCreate: true,
datetimeLayout: "2006-01-02",
Variables: originalVariables,
MatchingNodes: list.New(),
}
// Add a node to MatchingNodes
node := &CandidateNode{Value: "test"}
original.MatchingNodes.PushBack(node)
clone := original.DeepClone()
// Should preserve DontAutoCreate and datetimeLayout
test.AssertResultComplex(t, original.DontAutoCreate, clone.DontAutoCreate)
test.AssertResultComplex(t, original.datetimeLayout, clone.datetimeLayout)
// Should have copied variables
test.AssertResultComplex(t, 1, len(clone.Variables))
test.AssertResultComplex(t, "original", clone.Variables["test"].Front().Value.(*CandidateNode).Value)
// Should have deep copied MatchingNodes
test.AssertResultComplex(t, 1, clone.MatchingNodes.Len())
// Verify it's a deep copy by modifying the original
original.MatchingNodes.Front().Value.(*CandidateNode).Value = "modified"
test.AssertResultComplex(t, "test", clone.MatchingNodes.Front().Value.(*CandidateNode).Value)
}
func TestClone(t *testing.T) {
// Create original context
original := Context{
DontAutoCreate: true,
datetimeLayout: "2006-01-02",
MatchingNodes: list.New(),
}
node := &CandidateNode{Value: "test"}
original.MatchingNodes.PushBack(node)
clone := original.Clone()
// Should preserve DontAutoCreate and datetimeLayout
test.AssertResultComplex(t, original.DontAutoCreate, clone.DontAutoCreate)
test.AssertResultComplex(t, original.datetimeLayout, clone.datetimeLayout)
// Should have the same MatchingNodes reference
test.AssertResultComplex(t, original.MatchingNodes, clone.MatchingNodes)
}
func TestReadOnlyClone(t *testing.T) {
original := Context{
DontAutoCreate: false,
datetimeLayout: "2006-01-02",
MatchingNodes: list.New(),
}
node := &CandidateNode{Value: "test"}
original.MatchingNodes.PushBack(node)
clone := original.ReadOnlyClone()
// Should set DontAutoCreate to true
test.AssertResultComplex(t, true, clone.DontAutoCreate)
// Should preserve other fields
test.AssertResultComplex(t, original.datetimeLayout, clone.datetimeLayout)
test.AssertResultComplex(t, original.MatchingNodes, clone.MatchingNodes)
}
func TestWritableClone(t *testing.T) {
original := Context{
DontAutoCreate: true,
datetimeLayout: "2006-01-02",
MatchingNodes: list.New(),
}
node := &CandidateNode{Value: "test"}
original.MatchingNodes.PushBack(node)
clone := original.WritableClone()
// Should set DontAutoCreate to false
test.AssertResultComplex(t, false, clone.DontAutoCreate)
// Should preserve other fields
test.AssertResultComplex(t, original.datetimeLayout, clone.datetimeLayout)
test.AssertResultComplex(t, original.MatchingNodes, clone.MatchingNodes)
}
-3111
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -64,6 +64,6 @@ func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *Ex
if handler != nil {
return handler(d, context, expressionNode)
}
return Context{}, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
return Context{}, fmt.Errorf("unknown operator %v", expressionNode.Operation.OperationType.Type)
}
+437
View File
@@ -0,0 +1,437 @@
package yqlib
import (
"container/list"
"testing"
"github.com/mikefarah/yq/v4/test"
)
func TestGetMatchingNodes_NilExpressionNode(t *testing.T) {
navigator := NewDataTreeNavigator()
context := Context{
MatchingNodes: list.New(),
}
result, err := navigator.GetMatchingNodes(context, nil)
test.AssertResult(t, nil, err)
test.AssertResultComplex(t, context, result)
}
func TestGetMatchingNodes_UnknownOperator(t *testing.T) {
navigator := NewDataTreeNavigator()
context := Context{
MatchingNodes: list.New(),
}
// Create an expression node with an unknown operation type
unknownOpType := &operationType{Type: "UNKNOWN", Handler: nil}
expressionNode := &ExpressionNode{
Operation: &Operation{OperationType: unknownOpType},
}
result, err := navigator.GetMatchingNodes(context, expressionNode)
test.AssertResult(t, "unknown operator UNKNOWN", err.Error())
test.AssertResultComplex(t, Context{}, result)
}
func TestGetMatchingNodes_ValidOperator(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a simple context with a scalar node
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "test",
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(scalarNode)
// Create an expression node with a valid operation (self reference)
expressionNode := &ExpressionNode{
Operation: &Operation{OperationType: selfReferenceOpType},
}
result, err := navigator.GetMatchingNodes(context, expressionNode)
test.AssertResult(t, nil, err)
test.AssertResult(t, 1, result.MatchingNodes.Len())
// Verify the result contains the same node
resultNode := result.MatchingNodes.Front().Value.(*CandidateNode)
test.AssertResult(t, scalarNode, resultNode)
}
func TestDeeplyAssign_ScalarNode(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "existing", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "old_value"},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a scalar node to assign
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "new_value",
}
// Assign to path ["new_key"]
path := []interface{}{"new_key"}
err := navigator.DeeplyAssign(context, path, scalarNode)
test.AssertResult(t, nil, err)
// Verify the assignment was made
// The root node should now have the new key-value pair
test.AssertResult(t, 4, len(rootNode.Content)) // 2 original + 2 new
// Find the new key-value pair
found := false
for i := 0; i < len(rootNode.Content)-1; i += 2 {
key := rootNode.Content[i]
value := rootNode.Content[i+1]
if key.Value == "new_key" && value.Value == "new_value" {
found = true
break
}
}
test.AssertResult(t, true, found)
}
func TestDeeplyAssign_MappingNode(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "existing", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "old_value"},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a mapping node to assign (this should trigger deep merge)
mappingNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "nested_key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "nested_value"},
},
}
// Assign to path ["new_map"]
path := []interface{}{"new_map"}
err := navigator.DeeplyAssign(context, path, mappingNode)
test.AssertResult(t, nil, err)
// Verify the assignment was made
// The root node should now have the new mapping
test.AssertResult(t, 4, len(rootNode.Content)) // 2 original + 2 new
// Find the new mapping
found := false
for i := 0; i < len(rootNode.Content); i += 2 {
if i+1 < len(rootNode.Content) {
key := rootNode.Content[i]
value := rootNode.Content[i+1]
if key.Value == "new_map" && value.Kind == MappingNode {
found = true
// Verify the nested content
test.AssertResult(t, 2, len(value.Content))
test.AssertResult(t, "nested_key", value.Content[0].Value)
test.AssertResult(t, "nested_value", value.Content[1].Value)
break
}
}
}
test.AssertResult(t, true, found)
}
func TestDeeplyAssign_DeepPath(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "level1", IsMapKey: true},
{Kind: MappingNode, Tag: "!!map", Content: []*CandidateNode{}},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a scalar node to assign
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "deep_value",
}
// Assign to deep path ["level1", "level2", "level3"]
path := []interface{}{"level1", "level2", "level3"}
err := navigator.DeeplyAssign(context, path, scalarNode)
test.AssertResult(t, nil, err)
// Verify the deep assignment was made
level1Node := rootNode.Content[1] // The mapping node
test.AssertResult(t, 2, len(level1Node.Content)) // Should have level2 key-value
level2Key := level1Node.Content[0]
level2Value := level1Node.Content[1]
test.AssertResult(t, "level2", level2Key.Value)
test.AssertResult(t, MappingNode, level2Value.Kind)
level3Key := level2Value.Content[0]
level3Value := level2Value.Content[1]
test.AssertResult(t, "level3", level3Key.Value)
test.AssertResult(t, "deep_value", level3Value.Value)
}
func TestDeeplyAssign_ArrayPath(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node containing an array
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "array", IsMapKey: true},
{Kind: SequenceNode, Tag: "!!seq", Content: []*CandidateNode{}},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a scalar node to assign
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "array_value",
}
// Assign to array path ["array", 0]
path := []interface{}{"array", 0}
err := navigator.DeeplyAssign(context, path, scalarNode)
test.AssertResult(t, nil, err)
// Verify the array assignment was made
arrayNode := rootNode.Content[1] // The sequence node
test.AssertResult(t, 1, len(arrayNode.Content)) // Should have one element
arrayElement := arrayNode.Content[0]
test.AssertResult(t, "array_value", arrayElement.Value)
}
func TestDeeplyAssign_OverwriteExisting(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "old_value"},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a scalar node to assign
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "new_value",
}
// Assign to existing path ["key"]
path := []interface{}{"key"}
err := navigator.DeeplyAssign(context, path, scalarNode)
test.AssertResult(t, nil, err)
// Verify the value was overwritten
test.AssertResult(t, 2, len(rootNode.Content)) // Should still have 2 elements
key := rootNode.Content[0]
value := rootNode.Content[1]
test.AssertResult(t, "key", key.Value)
test.AssertResult(t, "new_value", value.Value) // Should be overwritten
}
func TestDeeplyAssign_ErrorHandling(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a scalar node (not a mapping)
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "not_a_map",
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(scalarNode)
// Create a scalar node to assign
assignNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "value",
}
// Try to assign to a path on a scalar (should fail)
path := []interface{}{"key"}
err := navigator.DeeplyAssign(context, path, assignNode)
// Print the actual error for debugging
if err != nil {
t.Logf("Actual error: %v", err)
}
// This should fail because we can't assign to a scalar
test.AssertResult(t, nil, err)
}
func TestGetMatchingNodes_WithVariables(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with variables
variables := make(map[string]*list.List)
varList := list.New()
varList.PushBack(&CandidateNode{Kind: ScalarNode, Tag: "!!str", Value: "var_value"})
variables["test_var"] = varList
context := Context{
MatchingNodes: list.New(),
Variables: variables,
}
// Create an expression node that gets a variable
expressionNode := &ExpressionNode{
Operation: &Operation{OperationType: getVariableOpType, StringValue: "test_var"},
}
result, err := navigator.GetMatchingNodes(context, expressionNode)
test.AssertResult(t, nil, err)
test.AssertResult(t, 1, result.MatchingNodes.Len())
// Verify the variable was retrieved
resultNode := result.MatchingNodes.Front().Value.(*CandidateNode)
test.AssertResult(t, "var_value", resultNode.Value)
}
func TestGetMatchingNodes_EmptyContext(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create an empty context
context := Context{
MatchingNodes: list.New(),
}
// Create an expression node with self reference
expressionNode := &ExpressionNode{
Operation: &Operation{OperationType: selfReferenceOpType},
}
result, err := navigator.GetMatchingNodes(context, expressionNode)
test.AssertResult(t, nil, err)
test.AssertResult(t, 0, result.MatchingNodes.Len())
}
func TestDeeplyAssign_ComplexMappingMerge(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node containing nested data
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "config", IsMapKey: true},
{Kind: MappingNode, Tag: "!!map", Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "existing_key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "existing_value"},
}},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a mapping node to merge
mappingNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "new_key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "new_value"},
{Kind: ScalarNode, Tag: "!!str", Value: "existing_key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "updated_value"},
},
}
// Assign to path ["config"] (should merge with existing mapping)
path := []interface{}{"config"}
err := navigator.DeeplyAssign(context, path, mappingNode)
test.AssertResult(t, nil, err)
// Verify the merge was successful
configNode := rootNode.Content[1] // The config mapping node
test.AssertResult(t, 4, len(configNode.Content)) // Should have 2 key-value pairs
// Check that both existing and new keys are present
foundExisting := false
foundNew := false
for i := 0; i < len(configNode.Content); i += 2 {
if i+1 < len(configNode.Content) {
key := configNode.Content[i]
value := configNode.Content[i+1]
switch key.Value {
case "existing_key":
foundExisting = true
test.AssertResult(t, "updated_value", value.Value) // Should be updated
case "new_key":
foundNew = true
test.AssertResult(t, "new_value", value.Value)
}
}
}
test.AssertResult(t, true, foundExisting)
test.AssertResult(t, true, foundNew)
}
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nobase64
package yqlib
import (
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nocsv
package yqlib
import (
+4 -1
View File
@@ -16,6 +16,8 @@ import (
type goccyYamlDecoder struct {
decoder yaml.Decoder
cm yaml.CommentMap
// anchor map persists over multiple documents for convenience.
anchorMap map[string]*CandidateNode
}
func NewGoccyYAMLDecoder() Decoder {
@@ -25,6 +27,7 @@ func NewGoccyYAMLDecoder() Decoder {
func (dec *goccyYamlDecoder) Init(reader io.Reader) error {
dec.cm = yaml.CommentMap{}
dec.decoder = *yaml.NewDecoder(reader, yaml.CommentToMap(dec.cm), yaml.UseOrderedMap())
dec.anchorMap = make(map[string]*CandidateNode)
return nil
}
@@ -38,7 +41,7 @@ func (dec *goccyYamlDecoder) Decode() (*CandidateNode, error) {
}
candidateNode := &CandidateNode{}
if err := candidateNode.UnmarshalGoccyYAML(ast, dec.cm); err != nil {
if err := candidateNode.UnmarshalGoccyYAML(ast, dec.cm, dec.anchorMap); err != nil {
return nil, err
}
+106
View File
@@ -0,0 +1,106 @@
//go:build !yq_noini
package yqlib
import (
"fmt"
"io"
"github.com/go-ini/ini"
)
type iniDecoder struct {
reader io.Reader
finished bool // Flag to signal completion of processing
}
func NewINIDecoder() Decoder {
return &iniDecoder{
finished: false, // Initialize the flag as false
}
}
func (dec *iniDecoder) Init(reader io.Reader) error {
// Store the reader for use in Decode
dec.reader = reader
return nil
}
func (dec *iniDecoder) Decode() (*CandidateNode, error) {
// If processing is already finished, return io.EOF
if dec.finished {
return nil, io.EOF
}
// Read all content from the stored reader
content, err := io.ReadAll(dec.reader)
if err != nil {
return nil, fmt.Errorf("failed to read INI content: %w", err)
}
// Parse the INI content
cfg, err := ini.Load(content)
if err != nil {
return nil, fmt.Errorf("failed to parse INI content: %w", err)
}
// Create a root CandidateNode as a MappingNode (since INI is key-value based)
root := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Value: "",
}
// Process each section in the INI file
for _, section := range cfg.Sections() {
sectionName := section.Name()
if sectionName == ini.DefaultSection {
// For the default section, add key-value pairs directly to the root node
for _, key := range section.Keys() {
keyName := key.Name()
keyValue := key.String()
// Create a key node (scalar for the key name)
keyNode := createStringScalarNode(keyName)
// Create a value node (scalar for the value)
valueNode := createStringScalarNode(keyValue)
// Add key-value pair to the root node
root.AddKeyValueChild(keyNode, valueNode)
}
} else {
// For named sections, create a nested map
sectionNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Value: "",
}
// Add key-value pairs to the section node
for _, key := range section.Keys() {
keyName := key.Name()
keyValue := key.String()
// Create a key node (scalar for the key name)
keyNode := createStringScalarNode(keyName)
// Create a value node (scalar for the value)
valueNode := createStringScalarNode(keyValue)
// Add key-value pair to the section node
sectionNode.AddKeyValueChild(keyNode, valueNode)
}
// Create a key node for the section name
sectionKeyNode := createStringScalarNode(sectionName)
// Add the section as a nested map to the root node
root.AddKeyValueChild(sectionKeyNode, sectionNode)
}
}
// Set the finished flag to true to prevent further Decode calls
dec.finished = true
// Return the root node
return root, nil
}
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_noprops
package yqlib
import (
+2 -1
View File
@@ -68,7 +68,8 @@ func mustProcessFormatScenario(s formatScenario, decoder Decoder, encoder Encode
result, err := processFormatScenario(s, decoder, encoder)
if err != nil {
panic(fmt.Errorf("Bad scenario %v: %w", s.description, err))
log.Error("Bad scenario %v: %w", s.description, err)
return fmt.Sprintf("Bad scenario %v: %v", s.description, err.Error())
}
return result
+4 -3
View File
@@ -249,11 +249,12 @@ func (dec *tomlDecoder) processTopLevelNode(currentNode *toml.Node) (bool, error
var runAgainstCurrentExp bool
var err error
log.Debug("processTopLevelNode: Going to process %v state is current %v", currentNode.Kind, NodeToString(dec.rootMap))
if currentNode.Kind == toml.Table {
switch currentNode.Kind {
case toml.Table:
runAgainstCurrentExp, err = dec.processTable(currentNode)
} else if currentNode.Kind == toml.ArrayTable {
case toml.ArrayTable:
runAgainstCurrentExp, err = dec.processArrayTable(currentNode)
} else {
default:
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(dec.rootMap, currentNode)
}
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nouri
package yqlib
import (
+4 -3
View File
@@ -315,13 +315,14 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
case xml.Comment:
commentStr := string(xml.CharData(se))
if elem.state == "started" {
switch elem.state {
case "started":
applyFootComment(elem, commentStr)
} else if elem.state == "chardata" {
case "chardata":
log.Debug("got a line comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
elem.n.LineComment = joinComments([]string{elem.n.LineComment, commentStr}, " ")
} else {
default:
log.Debug("got a head comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
elem.n.HeadComment = joinComments([]string{elem.n.HeadComment, commentStr}, " ")
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"regexp"
"strings"
yaml "gopkg.in/yaml.v3"
yaml "go.yaml.in/yaml/v3"
)
type yamlDecoder struct {
@@ -34,68 +34,6 @@ y: 2
r: 10
```
## Merge multiple maps
see https://yaml.org/type/merge.html
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
r: 10
x: 1
y: 2
```
## Override
see https://yaml.org/type/merge.html
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
x: 1
y: 2
```
## Get anchor
Given a sample.yml file of:
```yaml
@@ -254,7 +192,101 @@ f:
cat: b
```
## Explode with merge anchors
## Dereference and update a field
Use explode with multiply to dereference an object
Given a sample.yml file of:
```yaml
item_value: &item_value
value: true
thingOne:
name: item_1
!!merge <<: *item_value
thingTwo:
name: item_2
!!merge <<: *item_value
```
then
```bash
yq '.thingOne |= explode(.) * {"value": false}' sample.yml
```
will output
```yaml
item_value: &item_value
value: true
thingOne:
name: item_1
value: false
thingTwo:
name: item_2
!!merge <<: *item_value
```
## Merge multiple maps
see https://yaml.org/type/merge.html
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
r: 10
x: 1
y: 2
```
## Override
see https://yaml.org/type/merge.html
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
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
@@ -301,33 +333,52 @@ foobar:
thing: foobar_thing
```
## Dereference and update a field
Use explode with multiply to dereference an object
## 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
Given a sample.yml file of:
```yaml
item_value: &item_value
value: true
thingOne:
name: item_1
!!merge <<: *item_value
thingTwo:
name: item_2
!!merge <<: *item_value
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 '.thingOne |= explode(.) * {"value": false}' sample.yml
yq 'explode(.)' sample.yml
```
will output
```yaml
item_value: &item_value
value: true
thingOne:
name: item_1
value: false
thingTwo:
name: item_2
!!merge <<: *item_value
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
```
+24
View File
@@ -2,6 +2,8 @@
Similar to the same named functions in `jq` these functions convert to/from an object and an array of key-value pairs. This is most useful for performing operations on keys of maps.
Use `with_entries(op)` as a syntactic sugar for doing `to_entries | op | from_entries`.
## to_entries Map
Given a sample.yml file of:
```yaml
@@ -101,6 +103,28 @@ KEY_a: 1
KEY_b: 2
```
## Use with_entries to update keys recursively
We use (.. | select(tag="map")) to find all the maps in the doc, then |= to update each one of those maps. In the update, with_entries is used.
Given a sample.yml file of:
```yaml
a: 1
b:
b_a: nested
b_b: thing
```
then
```bash
yq '(.. | select(tag=="!!map")) |= with_entries(.key |= "KEY_" + .)' sample.yml
```
will output
```yaml
KEY_a: 1
KEY_b:
KEY_b_a: nested
KEY_b_b: thing
```
## Custom sort map keys
Use to_entries to convert to an array of key/value pairs, sort the array using sort/sort_by/etc, and convert it back.
@@ -1,3 +1,5 @@
# Entries
Similar to the same named functions in `jq` these functions convert to/from an object and an array of key-value pairs. This is most useful for performing operations on keys of maps.
Use `with_entries(op)` as a syntactic sugar for doing `to_entries | op | from_entries`.
+2 -2
View File
@@ -12,5 +12,5 @@ diff file1.yml file2.yml
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if you are using merge anchors.
For more advanced sorting, using `to_entries` to convert the map to an array, then sort/process the array as you like (e.g. using `sort_by`) and convert back to a map using `from_entries`.
See [here](https://mikefarah.gitbook.io/yq/operators/entries#custom-sort-map-keys) for an example.
For more advanced sorting, you can use the [sort_by](https://mikefarah.gitbook.io/yq/operators/sort) function on a map, and give it a custom function like `sort_by(key | downcase)`.
@@ -3,7 +3,7 @@
## RegEx
This uses Golang's native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats)"`.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats")`.
### match(regEx)
This operator returns the substring match details of the given regEx.
+2 -2
View File
@@ -12,8 +12,8 @@ diff file1.yml file2.yml
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if you are using merge anchors.
For more advanced sorting, using `to_entries` to convert the map to an array, then sort/process the array as you like (e.g. using `sort_by`) and convert back to a map using `from_entries`.
See [here](https://mikefarah.gitbook.io/yq/operators/entries#custom-sort-map-keys) for an example.
For more advanced sorting, you can use the [sort_by](https://mikefarah.gitbook.io/yq/operators/sort) function on a map, and give it a custom function like `sort_by(key | downcase)`.
## Sort keys of map
Given a sample.yml file of:
+40
View File
@@ -109,6 +109,46 @@ cool:
- c: banana
```
## Sort a map
Sorting a map, by default this will sort by the values
Given a sample.yml file of:
```yaml
y: b
z: a
x: c
```
then
```bash
yq 'sort' sample.yml
```
will output
```yaml
z: a
y: b
x: c
```
## Sort a map by keys
Use sort_by to sort a map using a custom function
Given a sample.yml file of:
```yaml
Y: b
z: a
x: c
```
then
```bash
yq 'sort_by(key | downcase)' sample.yml
```
will output
```yaml
x: c
Y: b
z: a
```
## Sort is stable
Note the order of the elements in unchanged when equal in sorting.
+1 -1
View File
@@ -3,7 +3,7 @@
## RegEx
This uses Golang's native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats)"`.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats")`.
### match(regEx)
This operator returns the substring match details of the given regEx.
+2 -2
View File
@@ -71,7 +71,7 @@ person.food[0] = pizza
```
## Encode properties - custom separator
Use the --properties-customer-separator flag to specify your own key/value separator.
Use the --properties-separator flag to specify your own key/value separator.
Given a sample.yml file of:
```yaml
@@ -89,7 +89,7 @@ emptyMap: []
```
then
```bash
yq -o=props --properties-customer-separator=" :@ " sample.yml
yq -o=props --properties-separator=" :@ " sample.yml
```
will output
```properties
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nobase64
package yqlib
import (
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nocsv
package yqlib
import (
+113
View File
@@ -0,0 +1,113 @@
//go:build !yq_noini
package yqlib
import (
"bytes"
"fmt"
"io"
"github.com/go-ini/ini"
)
type iniEncoder struct {
indentString string
}
// NewINIEncoder creates a new INI encoder
func NewINIEncoder() Encoder {
// Hardcoded indent value of 0, meaning no additional spacing.
return &iniEncoder{""}
}
// CanHandleAliases indicates whether the encoder supports aliases. INI does not support aliases.
func (ie *iniEncoder) CanHandleAliases() bool {
return false
}
// PrintDocumentSeparator is a no-op since INI does not support multiple documents.
func (ie *iniEncoder) PrintDocumentSeparator(_ io.Writer) error {
return nil
}
// PrintLeadingContent is a no-op since INI does not support leading content or comments at the encoder level.
func (ie *iniEncoder) PrintLeadingContent(_ io.Writer, _ string) error {
return nil
}
// Encode converts a CandidateNode into INI format and writes it to the provided writer.
func (ie *iniEncoder) Encode(writer io.Writer, node *CandidateNode) error {
log.Debugf("I need to encode %v", NodeToString(node))
log.Debugf("kids %v", len(node.Content))
if node.Kind == ScalarNode {
return writeStringINI(writer, node.Value+"\n")
}
// Create a new INI configuration.
cfg := ini.Empty()
if node.Kind == MappingNode {
// Default section for key-value pairs at the root level.
defaultSection, err := cfg.NewSection(ini.DefaultSection)
if err != nil {
return err
}
// Process the node's content.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
key := keyNode.Value
switch valueNode.Kind {
case ScalarNode:
// Add key-value pair to the default section.
_, err := defaultSection.NewKey(key, valueNode.Value)
if err != nil {
return err
}
case MappingNode:
// Create a new section for nested MappingNode.
section, err := cfg.NewSection(key)
if err != nil {
return err
}
// Process nested key-value pairs.
for j := 0; j < len(valueNode.Content); j += 2 {
nestedKeyNode := valueNode.Content[j]
nestedValueNode := valueNode.Content[j+1]
if nestedValueNode.Kind == ScalarNode {
_, err := section.NewKey(nestedKeyNode.Value, nestedValueNode.Value)
if err != nil {
return err
}
} else {
log.Debugf("Skipping nested non-scalar value for key %s: %v", nestedKeyNode.Value, nestedValueNode.Kind)
}
}
default:
log.Debugf("Skipping non-scalar value for key %s: %v", key, valueNode.Kind)
}
}
} else {
return fmt.Errorf("INI encoder supports only MappingNode at the root level, got %v", node.Kind)
}
// Use a buffer to store the INI output as the library doesn't support direct io.Writer with indent.
var buffer bytes.Buffer
_, err := cfg.WriteToIndent(&buffer, ie.indentString)
if err != nil {
return err
}
// Write the buffer content to the provided writer.
_, err = writer.Write(buffer.Bytes())
return err
}
// writeStringINI is a helper function to write a string to the provided writer for INI encoder.
func writeStringINI(writer io.Writer, content string) error {
_, err := writer.Write([]byte(content))
return err
}
+6 -2
View File
@@ -167,10 +167,14 @@ func needsQuoting(s string) bool {
// [%a_][%w_]*
for i, c := range s {
if i == 0 {
// keeping for legacy reasons, upgraded linter
//nolint:staticcheck
if !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
return true
}
} else {
// keeping for legacy reasons, upgraded linter
//nolint:staticcheck
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
return true
}
@@ -299,10 +303,10 @@ func (le *luaEncoder) encodeAny(writer io.Writer, node *CandidateNode) error {
return writeString(writer, node.Value)
}
default:
return fmt.Errorf("Lua encoder NYI -- %s", node.Tag)
return fmt.Errorf("lua encoder NYI -- %s", node.Tag)
}
default:
return fmt.Errorf("Lua encoder NYI -- %s", node.Tag)
return fmt.Errorf("lua encoder NYI -- %s", node.Tag)
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
//go:build !yq_noprops
package yqlib
import (
@@ -107,7 +109,7 @@ func (pe *propertiesEncoder) doEncode(p *properties.Properties, node *CandidateN
case AliasNode:
return pe.doEncode(p, node.Alias, path, nil)
default:
return fmt.Errorf("Unsupported node %v", node.Tag)
return fmt.Errorf("unsupported node %v", node.Tag)
}
}
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nosh
package yqlib
import (
+3 -1
View File
@@ -1,3 +1,5 @@
//go:build !yq_noshell
package yqlib
import (
@@ -75,7 +77,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
case AliasNode:
return pe.doEncode(w, node.Alias, path)
default:
return fmt.Errorf("Unsupported node %v", node.Tag)
return fmt.Errorf("unsupported node %v", node.Tag)
}
}
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nouri
package yqlib
import (
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"strings"
"github.com/fatih/color"
"gopkg.in/yaml.v3"
"go.yaml.in/yaml/v3"
)
type yamlEncoder struct {
+9 -2
View File
@@ -9,6 +9,7 @@ type ExpressionNode struct {
Operation *Operation
LHS *ExpressionNode
RHS *ExpressionNode
Parent *ExpressionNode
}
type ExpressionParserInterface interface {
@@ -50,20 +51,26 @@ func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*
log.Debugf("pathTree %v ", Operation.toString())
if Operation.OperationType.NumArgs > 0 {
numArgs := Operation.OperationType.NumArgs
if numArgs == 1 {
switch numArgs {
case 1:
if len(stack) < 1 {
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]
newNode.RHS = rhs
rhs.Parent = &newNode
stack = remaining
} else if numArgs == 2 {
case 2:
if len(stack) < 2 {
return nil, fmt.Errorf("'%v' expects 2 args but there is %v", strings.TrimSpace(Operation.StringValue), len(stack))
}
remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1]
newNode.LHS = lhs
lhs.Parent = &newNode
newNode.RHS = rhs
rhs.Parent = &newNode
stack = remaining
}
}
+5 -4
View File
@@ -26,11 +26,12 @@ func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operatio
}
func validateNoOpenTokens(token *token) error {
if token.TokenType == openCollect {
switch token.TokenType {
case openCollect:
return fmt.Errorf(("bad expression, could not find matching `]`"))
} else if token.TokenType == openCollectObject {
case openCollectObject:
return fmt.Errorf(("bad expression, could not find matching `}`"))
} else if token.TokenType == openBracket {
case openBracket:
return fmt.Errorf(("bad expression, could not find matching `)`"))
}
return nil
@@ -64,7 +65,7 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
opStack, result = popOpToResult(opStack, result)
}
if len(opStack) == 0 {
return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket")
return nil, errors.New("bad path expression, got close collect brackets without matching opening bracket")
}
// now we should have [ as the last element on the opStack, get rid of it
opStack = opStack[0 : len(opStack)-1]
+9 -7
View File
@@ -3,6 +3,7 @@ package yqlib
import (
"fmt"
"path/filepath"
"slices"
"strings"
)
@@ -76,6 +77,11 @@ var LuaFormat = &Format{"lua", []string{"l"},
func() Decoder { return NewLuaDecoder(ConfiguredLuaPreferences) },
}
var INIFormat = &Format{"ini", []string{"i"},
func() Encoder { return NewINIEncoder() },
func() Decoder { return NewINIDecoder() },
}
var Formats = []*Format{
YamlFormat,
JSONFormat,
@@ -89,18 +95,14 @@ var Formats = []*Format{
TomlFormat,
ShellVariablesFormat,
LuaFormat,
INIFormat,
}
func (f *Format) MatchesName(name string) bool {
if f.FormalName == name {
return true
}
for _, n := range f.Names {
if n == name {
return true
}
}
return false
return slices.Contains(f.Names, name)
}
func (f *Format) GetConfiguredEncoder() Encoder {
@@ -111,7 +113,7 @@ func FormatStringFromFilename(filename string) string {
if filename != "" {
GetLogger().Debugf("checking filename '%s' for auto format detection", filename)
ext := filepath.Ext(filename)
if ext != "" && ext[0] == '.' {
if len(ext) >= 2 && ext[0] == '.' {
format := strings.ToLower(ext[1:])
GetLogger().Debugf("detected format '%s'", format)
return format
+141 -29
View File
@@ -20,17 +20,11 @@ var goccyYamlFormatScenarios = []formatScenario{
expected: "3.1\n",
},
{
description: "basic - 3.1",
description: "basic - mike",
skipDoc: true,
input: "mike: 3",
expected: "mike: 3\n",
},
{
description: "basic - 3.1",
skipDoc: true,
input: "{mike: 3}",
expected: "{mike: 3}\n",
},
{
description: "basic - map multiple entries",
skipDoc: true,
@@ -40,23 +34,23 @@ var goccyYamlFormatScenarios = []formatScenario{
{
description: "basic - 3.1",
skipDoc: true,
input: "{\nmike: 3\n}",
input: "{\n mike: 3\n}",
expected: "{mike: 3}\n",
},
{
description: "basic - 3.1",
description: "basic - tag with number",
skipDoc: true,
input: "mike: !!cat 3",
expected: "mike: !!cat 3\n",
},
{
description: "basic - 3.1",
description: "basic - array of numbers",
skipDoc: true,
input: "- 3",
expected: "- 3\n",
},
{
description: "basic - 3.1",
description: "basic - single line array",
skipDoc: true,
input: "[3]",
expected: "[3]\n",
@@ -103,12 +97,18 @@ var goccyYamlFormatScenarios = []formatScenario{
input: "a: meow # line comment\n",
expected: "a: meow # line comment\n",
},
{
description: "basic - line comment",
skipDoc: true,
input: "# head comment\na: #line comment\n meow\n",
expected: "# head comment\na: meow #line comment\n", // go-yaml does this
},
// {
// description: "basic - head comment",
// skipDoc: true,
// input: "# head comment\na: meow\n",
// expected: "# head comment\na: meow\n", // go-yaml does this
// },
// {
// description: "basic - head and line comment",
// skipDoc: true,
// input: "# head comment\na: #line comment\n meow\n",
// expected: "# head comment\na: meow #line comment\n", // go-yaml does this
// },
{
description: "basic - foot comment",
skipDoc: true,
@@ -139,24 +139,136 @@ var goccyYamlFormatScenarios = []formatScenario{
input: "a: ~\n",
expected: "a: ~\n",
},
{
description: "basic - ~",
skipDoc: true,
input: "null\n",
expected: "null\n",
},
{
skipDoc: true,
description: "blank value round trip",
input: "test:",
expected: "test:\n",
},
{
skipDoc: true,
description: "trailing comment",
input: "test: null\n# this comment will be removed",
expected: "test: null\n# this comment will be removed\n",
},
// {
// description: "basic - ~",
// description: "doc separator",
// skipDoc: true,
// input: "null\n",
// expected: "null\n",
// input: "# hi\n---\na: cat\n---",
// expected: "---\na: cat\n",
// },
// {
// description: "scalar with doc separator",
// skipDoc: true,
// description: "trailing comment",
// input: "test:",
// expected: "test:",
// },
// {
// skipDoc: true,
// description: "trailing comment",
// input: "test:\n# this comment will be removed",
// expected: "test:\n# this comment will be removed",
// input: "--- cat",
// expected: "---\ncat\n",
// },
{
description: "scalar with doc separator",
skipDoc: true,
input: "---cat",
expected: "---cat\n",
},
{
description: "basic - null",
skipDoc: true,
input: "null",
expected: "null\n",
},
{
description: "basic - ~",
skipDoc: true,
input: "~",
expected: "~\n",
},
{
description: "octal",
skipDoc: true,
input: "0o30",
expression: "tag",
expected: "!!int\n",
},
{
description: "basic - [null]",
skipDoc: true,
input: "[null]",
expected: "[null]\n",
},
{
description: "multi document",
skipDoc: true,
input: "a: mike\n---\nb: remember",
expected: "a: mike\n---\nb: remember\n",
},
{
description: "single doc anchor map",
skipDoc: true,
input: "a: &remember mike\nb: *remember",
expected: "a: &remember mike\nb: *remember\n",
},
{
description: "explode doc anchor map",
skipDoc: true,
input: "a: &remember mike\nb: *remember",
expression: "explode(.)",
expected: "a: mike\nb: mike\n",
},
{
description: "multi document anchor map",
skipDoc: true,
input: "a: &remember mike\n---\nb: *remember",
expression: "explode(.)",
expected: "a: mike\n---\nb: mike\n",
},
{
description: "merge anchor",
skipDoc: true,
input: "a: &remember\n c: mike\nb:\n <<: *remember",
// fine to have !!merge as that's what the current impl does
expected: "a: &remember\n c: mike\nb:\n !!merge <<: *remember\n",
},
{
description: "custom tag",
skipDoc: true,
input: "a: !cat mike",
expected: "a: !cat mike\n",
},
{
description: "basic - [~]",
skipDoc: true,
input: "[~]",
expected: "[~]\n",
},
{
description: "basic - null map value",
skipDoc: true,
input: "a: null",
expected: "a: null\n",
},
{
description: "basic - number",
skipDoc: true,
input: "3",
expected: "3\n",
},
{
description: "basic - float",
skipDoc: true,
input: "3.1",
expected: "3.1\n",
},
{
description: "basic - float",
skipDoc: true,
input: "[1, 2]",
expected: "[1, 2]\n",
},
}
func testGoccyYamlScenario(t *testing.T, s formatScenario) {
+19
View File
@@ -0,0 +1,19 @@
package yqlib
type INIPreferences struct {
ColorsEnabled bool
}
func NewDefaultINIPreferences() INIPreferences {
return INIPreferences{
ColorsEnabled: false,
}
}
func (p *INIPreferences) Copy() INIPreferences {
return INIPreferences{
ColorsEnabled: p.ColorsEnabled,
}
}
var ConfiguredINIPreferences = NewDefaultINIPreferences()
+187
View File
@@ -0,0 +1,187 @@
//go:build !yq_noini
package yqlib
import (
"bufio"
"fmt"
"testing"
"github.com/mikefarah/yq/v4/test"
)
const simpleINIInput = `[section]
key = value
`
const expectedSimpleINIOutput = `[section]
key = value
`
const expectedSimpleINIYaml = `section:
key: value
`
var iniScenarios = []formatScenario{
{
description: "Parse INI: simple",
input: simpleINIInput,
scenarioType: "decode",
expected: expectedSimpleINIYaml,
},
{
description: "Encode INI: simple",
input: `section: {key: value}`,
expected: expectedSimpleINIOutput,
scenarioType: "encode",
},
{
description: "Roundtrip INI: simple",
input: simpleINIInput,
expected: expectedSimpleINIOutput,
scenarioType: "roundtrip",
},
{
description: "bad ini",
input: `[section\nkey = value`,
expectedError: `bad file 'sample.yml': failed to parse INI content: unclosed section: [section\nkey = value`,
scenarioType: "decode-error",
},
}
func documentRoundtripINIScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.ini file of:\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=ini -o=ini '%v' sample.ini\n```\n", expression))
} else {
writeOrPanic(w, "```bash\nyq -p=ini -o=ini sample.ini\n```\n")
}
writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v```\n\n", mustProcessFormatScenario(s, NewINIDecoder(), NewINIEncoder())))
}
func documentDecodeINIScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.ini file of:\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=ini '%v' sample.ini\n```\n", expression))
} else {
writeOrPanic(w, "```bash\nyq -p=ini sample.ini\n```\n")
}
writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", mustProcessFormatScenario(s, NewINIDecoder(), NewYamlEncoder(ConfiguredYamlPreferences))))
}
func testINIScenario(t *testing.T, s formatScenario) {
switch s.scenarioType {
case "encode":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewINIEncoder()), s.description)
case "decode":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(), NewYamlEncoder(ConfiguredYamlPreferences)), s.description)
case "roundtrip":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(), NewINIEncoder()), s.description)
case "decode-error":
result, err := processFormatScenario(s, NewINIDecoder(), NewINIEncoder())
if err == nil {
t.Errorf("Expected error '%v' but it worked: %v", s.expectedError, result)
} else {
test.AssertResultComplexWithContext(t, s.expectedError, err.Error(), s.description)
}
default:
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
}
}
func documentINIScenario(_ *testing.T, w *bufio.Writer, i interface{}) {
s := i.(formatScenario)
if s.skipDoc {
return
}
switch s.scenarioType {
case "encode":
documentINIEncodeScenario(w, s)
case "decode":
documentDecodeINIScenario(w, s)
case "roundtrip":
documentRoundtripINIScenario(w, s)
case "decode-error":
documentDecodeErrorINIScenario(w, s)
default:
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
}
}
func documentINIEncodeScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression == "" {
expression = "."
}
writeOrPanic(w, fmt.Sprintf("```bash\nyq -o=ini '%v' sample.yml\n```\n", expression))
writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewINIEncoder())))
}
func documentDecodeErrorINIScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.ini file of:\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v\n```\n", s.input))
writeOrPanic(w, "then an error is expected:\n")
writeOrPanic(w, fmt.Sprintf("```\n%v\n```\n\n", s.expectedError))
}
func TestINIScenarios(t *testing.T) {
for _, tt := range iniScenarios {
testINIScenario(t, tt)
}
genericScenarios := make([]interface{}, len(iniScenarios))
for i, s := range iniScenarios {
genericScenarios[i] = s
}
documentScenarios(t, "usage", "convert", genericScenarios, documentINIScenario)
}
+9 -8
View File
@@ -31,24 +31,25 @@ type token struct {
}
func (t *token) toString(detail bool) string {
if t.TokenType == operationToken {
switch t.TokenType {
case operationToken:
if detail {
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
}
return t.Operation.toString()
} else if t.TokenType == openBracket {
case openBracket:
return "("
} else if t.TokenType == closeBracket {
case closeBracket:
return ")"
} else if t.TokenType == openCollect {
case openCollect:
return "["
} else if t.TokenType == closeCollect {
case closeCollect:
return "]"
} else if t.TokenType == openCollectObject {
case openCollectObject:
return "{"
} else if t.TokenType == closeCollectObject {
case closeCollectObject:
return "}"
} else if t.TokenType == traverseArrayCollect {
case traverseArrayCollect:
return ".["
}
+11
View File
@@ -28,6 +28,17 @@ func GetLogger() *logging.Logger {
return log
}
func getContentValueByKey(content []*CandidateNode, key string) *CandidateNode {
for index := 0; index < len(content); index = index + 2 {
keyNode := content[index]
valueNode := content[index+1]
if keyNode.Value == key {
return valueNode
}
}
return nil
}
func recurseNodeArrayEqual(lhs *CandidateNode, rhs *CandidateNode) bool {
if len(lhs.Content) != len(rhs.Content) {
return false
+11
View File
@@ -0,0 +1,11 @@
//go:build yq_nobase64
package yqlib
func NewBase64Decoder() Decoder {
return nil
}
func NewBase64Encoder() Encoder {
return nil
}
+11
View File
@@ -0,0 +1,11 @@
//go:build yq_nocsv
package yqlib
func NewCSVObjectDecoder(prefs CsvPreferences) Decoder {
return nil
}
func NewCsvEncoder(prefs CsvPreferences) Encoder {
return nil
}
+11
View File
@@ -0,0 +1,11 @@
//go:build yq_noini
package yqlib
func NewINIDecoder() Decoder {
return nil
}
func NewINIEncoder() Encoder {
return nil
}
+1 -1
View File
@@ -6,6 +6,6 @@ func NewJSONDecoder() Decoder {
return nil
}
func NewJSONEncoder(indent int, colorise bool, unwrapScalar bool) Encoder {
func NewJSONEncoder(prefs JsonPreferences) Encoder {
return nil
}
+11
View File
@@ -0,0 +1,11 @@
//go:build yq_noprops
package yqlib
func NewPropertiesDecoder() Decoder {
return nil
}
func NewPropertiesEncoder(prefs PropertiesPreferences) Encoder {
return nil
}
+7
View File
@@ -0,0 +1,7 @@
//go:build yq_nosh
package yqlib
func NewShEncoder() Encoder {
return nil
}
+7
View File
@@ -0,0 +1,7 @@
//go:build yq_noshell
package yqlib
func NewShellVariablesEncoder() Encoder {
return nil
}
+11
View File
@@ -0,0 +1,11 @@
//go:build yq_nouri
package yqlib
func NewUriDecoder() Decoder {
return nil
}
func NewUriEncoder() Encoder {
return nil
}
+1 -1
View File
@@ -6,6 +6,6 @@ func NewXMLDecoder(prefs XmlPreferences) Decoder {
return nil
}
func NewXMLEncoder(indent int, prefs XmlPreferences) Encoder {
func NewXMLEncoder(prefs XmlPreferences) Encoder {
return nil
}
+11 -2
View File
@@ -38,14 +38,23 @@ func toNodes(candidate *CandidateNode, lhs *CandidateNode) []*CandidateNode {
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("Add operator")
// only calculate when empty IF we are the root expression; OR
// calcWhenEmpty := expressionNode.Parent == nil || expressionNode.Parent.LHS == expressionNode
calcWhenEmpty := context.MatchingNodes.Len() > 0
return crossFunction(d, context.ReadOnlyClone(), expressionNode, add, false)
return crossFunction(d, context.ReadOnlyClone(), expressionNode, add, calcWhenEmpty)
}
func add(_ *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhsNode := lhs
if lhsNode.Tag == "!!null" {
if lhs == nil && rhs == nil {
return nil, nil
} else if lhs == nil {
return rhs.Copy(), nil
} else if rhs == nil {
return lhs.Copy(), nil
} else if lhsNode.Tag == "!!null" {
return lhs.CopyAsReplacement(rhs), nil
}
+124
View File
@@ -5,6 +5,107 @@ import (
)
var addOperatorScenarios = []expressionScenario{
{
skipDoc: true,
expression: `"foo" + "bar"`,
expected: []string{
"D0, P[], (!!str)::foobar\n",
},
},
{
skipDoc: true,
expression: `[] | .[] | "foo" + .`,
expected: []string{},
},
{
skipDoc: true,
expression: `[] | .[] | . + "foo"`,
expected: []string{},
},
{
skipDoc: true,
expression: `select(.) | "foo" + "bar"`,
expected: []string{
"D0, P[], (!!str)::foobar\n", // jq does not do this :/ - but yq has for quite some time.
},
},
{
skipDoc: true,
document: "apples: 3",
expression: `.apples + 3`,
expected: []string{
"D0, P[apples], (!!int)::6\n",
},
},
{
skipDoc: true,
document: "apples: 3",
expression: `.bobo + 3`,
expected: []string{
"D0, P[], (!!int)::3\n",
},
},
{
skipDoc: true,
expression: `select(.) | "cat" + .`,
expected: []string{},
},
{
skipDoc: true,
document: `[]`,
expression: `.[] | (.a + "|" + .b)`,
expected: []string{},
},
{
skipDoc: true,
document: `[]`,
expression: `.[] | (.a + "|")`,
expected: []string{},
},
{
skipDoc: true,
document: `[]`,
expression: `.[] | ("|" + .a)`,
expected: []string{},
},
{
skipDoc: true,
document: `resources: [foo, bar, baz]`,
expression: `.missing + .resources | .[]`,
expected: []string{
"D0, P[resources 0], (!!str)::foo\n",
"D0, P[resources 1], (!!str)::bar\n",
"D0, P[resources 2], (!!str)::baz\n",
},
},
{
skipDoc: true,
document: `resources: [foo, bar, baz]`,
expression: `. | .missing + .resources | .[]`,
expected: []string{
"D0, P[resources 0], (!!str)::foo\n",
"D0, P[resources 1], (!!str)::bar\n",
"D0, P[resources 2], (!!str)::baz\n",
},
},
{
skipDoc: true,
document: `resources: [foo, bar, baz]`,
expression: `. | .missing + .resources`,
expected: []string{
"D0, P[resources], (!!seq)::[foo, bar, baz]\n",
},
},
{
skipDoc: true,
document: `resources: [foo, bar, baz]`,
expression: `. | .missing + .resources | .[]`,
expected: []string{
"D0, P[resources 0], (!!str)::foo\n",
"D0, P[resources 1], (!!str)::bar\n",
"D0, P[resources 2], (!!str)::baz\n",
},
},
{
skipDoc: true,
document: `[{a: foo, b: bar}, {a: 1, b: 2}]`,
@@ -310,6 +411,29 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::a: !cat Saturday, 15-Dec-01 at 6:00AM GMT\n",
},
},
{
skipDoc: true,
description: "empty add shouldn't add",
document: `[]`,
expression: `.[] | (.a + "cat")`,
expected: []string{},
},
{
skipDoc: true,
description: "empty add shouldn't add",
document: `[]`,
expression: `.[] | (.a + "cat" + .b)`,
expected: []string{},
},
{
skipDoc: true,
description: "Add to empty",
subdescription: "should behave like null",
expression: `.nada + "cat"`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
},
{
description: "Add to null",
subdescription: "Adding to null simply returns the rhs",
+39
View File
@@ -138,6 +138,41 @@ func explodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
return context, nil
}
func fixedReconstructAliasedMap(node *CandidateNode) error {
var newContent = []*CandidateNode{}
for index := 0; index < len(node.Content); index = index + 2 {
keyNode := node.Content[index]
valueNode := node.Content[index+1]
if keyNode.Value != "<<" {
// always add in plain nodes
newContent = append(newContent, keyNode, valueNode)
} else {
sequence := valueNode
if sequence.Kind != SequenceNode {
sequence = &CandidateNode{Content: []*CandidateNode{sequence}}
}
for index := 0; index < len(sequence.Content); index = index + 1 {
// for merge anchors, we only set them if the key is not already in node or the newContent
mergeNodeSeq := sequence.Content[index]
if mergeNodeSeq.Kind == AliasNode {
mergeNodeSeq = mergeNodeSeq.Alias
}
if mergeNodeSeq.Kind != MappingNode {
return fmt.Errorf("merge anchor only supports maps, got !!seq instead")
}
itemsToAdd := mergeNodeSeq.FilterMapContentByKey(func(keyNode *CandidateNode) bool {
return getContentValueByKey(node.Content, keyNode.Value) == nil &&
getContentValueByKey(newContent, keyNode.Value) == nil
})
newContent = append(newContent, itemsToAdd...)
}
}
}
node.Content = newContent
return nil
}
func reconstructAliasedMap(node *CandidateNode, context Context) error {
var newContent = list.New()
// can I short cut here by prechecking if there's an anchor in the map?
@@ -215,6 +250,10 @@ func explodeNode(node *CandidateNode, context Context) error {
}
if hasAlias {
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.")
// this is a slow op, which is why we want to check before running it.
return reconstructAliasedMap(node, context)
}
+180 -41
View File
@@ -34,6 +34,25 @@ thingTwo:
!!merge <<: *item_value
`
var explodeMergeAnchorsFixedExpected = `D0, P[], (!!map)::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
`
var explodeMergeAnchorsExpected = `D0, P[], (!!map)::foo:
a: foo_a
thing: foo_thing
@@ -53,7 +72,146 @@ foobar:
thing: foobar_thing
`
var explodeWhenKeysExistDocument = `objects:
- &circle
name: circle
shape: round
- name: ellipse
!!merge <<: *circle
- !!merge <<: *circle
name: egg
`
var explodeWhenKeysExistLegacy = `D0, P[], (!!map)::objects:
- name: circle
shape: round
- name: circle
shape: round
- shape: round
name: egg
`
var explodeWhenKeysExistExpected = `D0, P[], (!!map)::objects:
- name: circle
shape: round
- name: ellipse
shape: round
- shape: round
name: egg
`
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"},
},
{
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 ",
document: mergeDocSample,
expression: `explode(.)`,
expected: []string{explodeMergeAnchorsFixedExpected},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(.) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: foobarList_b, a: foo_a, thing: foo_thing, c: foobarList_c}\n",
"D0, P[foobar], (!!map)::{c: foobar_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: foobarList_b, a: foo_a, thing: foo_thing, c: foobarList_c}\n",
"D0, P[foobar], (!!map)::{c: foobar_c, a: foo_a, thing: foobar_thing}\n",
},
},
}
var badAnchorOperatorScenarios = []expressionScenario{
{
skipDoc: true, // incorrect overrides
description: "LEGACY: merge anchor after existing keys",
document: explodeWhenKeysExistDocument,
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",
document: mergeDocSample,
expression: `explode(.)`,
expected: []string{explodeMergeAnchorsExpected},
},
{
skipDoc: true,
document: mergeDocSample, // incorrect overrides
expression: `.foo* | explode(.) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
}
var anchorOperatorScenarios = []expressionScenario{
{
skipDoc: true,
description: "merge anchor to alias alias",
document: "b: &b 10\na: &a { k: *b }\nc:\n <<: [*a]",
expression: "explode(.)",
expected: []string{"D0, P[], (!!map)::b: 10\na: {k: 10}\nc:\n k: 10\n"},
},
{
skipDoc: true,
description: "merge anchor not map",
@@ -68,20 +226,6 @@ var anchorOperatorScenarios = []expressionScenario{
expression: ".[4] | explode(.)",
expected: []string{expectedSpecResult},
},
{
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)::r: 10\nx: 1\ny: 2\n"},
},
{
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\nx: 1\ny: 2\n"},
},
{
description: "Get anchor",
document: `a: &billyBob cat`,
@@ -224,37 +368,22 @@ var anchorOperatorScenarios = []expressionScenario{
},
},
{
description: "Explode with alias keys",
document: `{f : {a: &a cat, *a: b}}`,
expression: `explode(.f)`,
description: "Explode with alias keys",
subdescription: "No space between alias",
skipDoc: true,
document: `{f : {a: &a cat, *a: b}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[], (!!map)::{f: {a: cat, cat: b}}\n",
},
skipForGoccy: true, // can't handle no space between alias
},
{
description: "Explode with merge anchors",
document: mergeDocSample,
expression: `explode(.)`,
expected: []string{explodeMergeAnchorsExpected},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(.) | (. style="flow")`,
description: "Explode with alias keys",
document: `{f : {a: &a cat, *a : b}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
"D0, P[], (!!map)::{f: {a: cat, cat: b}}\n",
},
},
{
@@ -275,8 +404,18 @@ var anchorOperatorScenarios = []expressionScenario{
}
func TestAnchorAliasOperatorScenarios(t *testing.T) {
for _, tt := range anchorOperatorScenarios {
for _, tt := range append(anchorOperatorScenarios, badAnchorOperatorScenarios...) {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "anchor-and-alias-operators", anchorOperatorScenarios)
documentOperatorScenarios(t, "anchor-and-alias-operators", append(anchorOperatorScenarios, badAnchorOperatorScenarios...))
}
func TestAnchorAliasOperatorAlignedToSpecScenarios(t *testing.T) {
ConfiguredYamlPreferences.FixMergeAnchorToSpec = true
for _, tt := range append(fixedAnchorOperatorScenarios, anchorOperatorScenarios...) {
testScenario(t, &tt)
}
appendOperatorDocumentScenario(t, "anchor-and-alias-operators", fixedAnchorOperatorScenarios)
ConfiguredYamlPreferences.FixMergeAnchorToSpec = false
}
+4
View File
@@ -2,6 +2,7 @@ package yqlib
import (
"container/list"
"fmt"
)
/*
@@ -34,6 +35,9 @@ func collectObjectOperator(d *dataTreeNavigator, originalContext Context, _ *Exp
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidateNode := el.Value.(*CandidateNode)
if len(candidateNode.Content) < len(first.Content) {
return Context{}, fmt.Errorf("CollectObject: mismatching node sizes; are you creating a map with mismatching key value pairs?")
}
for i := 0; i < len(first.Content); i++ {
log.Debugf("rotate[%v] = %v", i, NodeToString(candidateNode.Content[i]))
@@ -12,6 +12,11 @@ var collectObjectOperatorScenarios = []expressionScenario{
"D0, P[name], (!!str)::mike\n",
},
},
{
skipDoc: true,
expression: `{"c": "a", "b", "d"}`,
expectedError: "CollectObject: mismatching node sizes; are you creating a map with mismatching key value pairs?",
},
{
skipDoc: true,
expression: `{"person": {"names": ["mike"]}} | .person.names[0]`,
+4 -3
View File
@@ -26,11 +26,12 @@ func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *
candidatePath := candidate.GetPath()
childPath := candidatePath[len(candidatePath)-1]
if parentNode.Kind == MappingNode {
switch parentNode.Kind {
case MappingNode:
deleteFromMap(candidate.Parent, childPath)
} else if parentNode.Kind == SequenceNode {
case SequenceNode:
deleteFromArray(candidate.Parent, childPath)
} else {
default:
return Context{}, fmt.Errorf("cannot delete nodes from parent of tag %v", parentNode.Tag)
}
}
+54
View File
@@ -119,6 +119,60 @@ var deleteOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::a: []\n",
},
},
{
skipDoc: true,
description: "Delete entry appended to an array",
document: `[1,2]`,
expression: `. += [3] | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
description: "Delete entry after sorting an array",
document: `[3,2,1]`,
expression: `sort | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
description: "Delete entry after reversing an array",
document: `[1,2,3]`,
expression: `reverse | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[3, 2]\n",
},
},
{
skipDoc: true,
description: "Delete entry after shuffling an array",
document: `[1,2,3]`,
expression: `shuffle | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[3, 1]\n",
},
},
{
skipDoc: true,
description: "Delete entry from keys array",
document: `{"a": 1, "b": 2, "c": 3}`,
expression: `keys | del(.[] | select(.=="b"))`,
expected: []string{
"D0, P[], (!!seq)::- \"a\"\n- \"c\"\n",
},
},
{
skipDoc: true,
description: "Delete entry after flattening an array",
document: `[1,[2],[[3]]]`,
expression: `flatten | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
document: `a: [10,x,10, 10, x, 10]`,
+9
View File
@@ -72,6 +72,15 @@ var entriesOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::KEY_a: 1\nKEY_b: 2\n",
},
},
{
description: "Use with_entries to update keys recursively",
document: `{a: 1, b: {b_a: nested, b_b: thing}}`,
expression: `(.. | select(tag=="!!map")) |= with_entries(.key |= "KEY_" + .)`,
subdescription: "We use (.. | select(tag=\"map\")) to find all the maps in the doc, then |= to update each one of those maps. In the update, with_entries is used.",
expected: []string{
"D0, P[], (!!map)::{KEY_a: 1, KEY_b: {KEY_b_a: nested, KEY_b_b: thing}}\n",
},
},
{
skipDoc: true,
description: "Use with_entries to update keys comment",
+4 -4
View File
@@ -21,8 +21,8 @@ var flattenOperatorScenarios = []expressionScenario{
expression: `flatten[]`,
expected: []string{
"D0, P[0], (!!int)::1\n",
"D0, P[0], (!!int)::2\n",
"D0, P[0], (!!int)::3\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!int)::3\n",
},
},
{
@@ -40,8 +40,8 @@ var flattenOperatorScenarios = []expressionScenario{
expression: `flatten(1)[]`,
expected: []string{
"D0, P[0], (!!int)::1\n",
"D0, P[0], (!!int)::2\n",
"D0, P[0], (!!seq)::[3]\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!seq)::[3]\n",
},
},
{
+13 -11
View File
@@ -45,12 +45,13 @@ func keysOperator(_ *dataTreeNavigator, context Context, _ *ExpressionNode) (Con
candidate := el.Value.(*CandidateNode)
var targetNode *CandidateNode
if candidate.Kind == MappingNode {
switch candidate.Kind {
case MappingNode:
targetNode = getMapKeys(candidate)
} else if candidate.Kind == SequenceNode {
case SequenceNode:
targetNode = getIndices(candidate)
} else {
return Context{}, fmt.Errorf("Cannot get keys of %v, keys only works for maps and arrays", candidate.Tag)
default:
return Context{}, fmt.Errorf("cannot get keys of %v, keys only works for maps and arrays", candidate.Tag)
}
results.PushBack(targetNode)
@@ -64,19 +65,20 @@ func getMapKeys(node *CandidateNode) *CandidateNode {
for index := 0; index < len(node.Content); index = index + 2 {
contents = append(contents, node.Content[index])
}
return &CandidateNode{Kind: SequenceNode, Tag: "!!seq", Content: contents}
seq := &CandidateNode{Kind: SequenceNode, Tag: "!!seq"}
seq.AddChildren(contents)
return seq
}
func getIndices(node *CandidateNode) *CandidateNode {
var contents = make([]*CandidateNode, len(node.Content))
for index := range node.Content {
contents[index] = &CandidateNode{
Kind: ScalarNode,
Tag: "!!int",
Value: fmt.Sprintf("%v", index),
}
contents[index] = createScalarNode(index, fmt.Sprintf("%v", index))
}
return &CandidateNode{Kind: SequenceNode, Tag: "!!seq", Content: contents}
seq := &CandidateNode{Kind: SequenceNode, Tag: "!!seq"}
seq.AddChildren(contents)
return seq
}
+2 -2
View File
@@ -45,8 +45,8 @@ var keysOperatorScenarios = []expressionScenario{
document: `{dog: woof, cat: meow}`,
expression: `keys[]`,
expected: []string{
"D0, P[dog], (!!str)::dog\n",
"D0, P[cat], (!!str)::cat\n",
"D0, P[0], (!!str)::dog\n",
"D0, P[1], (!!str)::cat\n",
},
},
{
+2 -2
View File
@@ -82,7 +82,7 @@ func loadStringOperator(d *dataTreeNavigator, context Context, expressionNode *E
contentsCandidate, err := loadString(filename)
if err != nil {
return Context{}, fmt.Errorf("Failed to load %v: %w", filename, err)
return Context{}, fmt.Errorf("failed to load %v: %w", filename, err)
}
results.PushBack(contentsCandidate)
@@ -118,7 +118,7 @@ func loadOperator(d *dataTreeNavigator, context Context, expressionNode *Express
contentsCandidate, err := loadWithDecoder(filename, loadPrefs.decoder)
if err != nil {
return Context{}, fmt.Errorf("Failed to load %v: %w", filename, err)
return Context{}, fmt.Errorf("failed to load %v: %w", filename, err)
}
results.PushBack(contentsCandidate)
+4
View File
@@ -41,6 +41,10 @@ func mapOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
if err != nil {
return Context{}, err
}
if splatted.MatchingNodes.Len() == 0 {
results.PushBack(candidate.Copy())
continue
}
result, err := d.GetMatchingNodes(splatted, expressionNode.RHS)
log.Debug("expressionNode.Rhs %v", expressionNode.RHS.Operation.OperationType)
+67
View File
@@ -15,6 +15,53 @@ var mapOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[6, 7, 8]\n",
},
},
{
description: "mapping against an empty array should do nothing",
skipDoc: true,
document: `[]`,
document2: `["cat"]`,
expression: `map(3)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
"D0, P[], (!!seq)::[3]\n",
},
},
{
description: "mapping against an empty array should do nothing",
skipDoc: true,
document: `[[], [5]]`,
expression: `.[] |= map(3)`,
expected: []string{
"D0, P[], (!!seq)::[[], [3]]\n",
},
},
{
description: "mapping against an empty array should do nothing #2",
skipDoc: true,
document: `[]`,
document2: `[5]`,
expression: `map(3 + .)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
"D0, P[], (!!seq)::[8]\n",
},
},
{
description: "mapping against an empty array should do nothing",
skipDoc: true,
document: `[[], [5]]`,
expression: `.[] |= map(3 + .)`,
expected: []string{
"D0, P[], (!!seq)::[[], [8]]\n",
},
},
{
skipDoc: true,
expression: `[] | map(. + 42)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `[1,2]`,
@@ -32,6 +79,26 @@ var mapOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[2, 3, 4]\n",
},
},
{
skipDoc: true,
document: `{}`,
document2: `{b: 12}`,
expression: `map_values(3)`,
expected: []string{
"D0, P[], (!!map)::{}\n",
"D0, P[], (!!map)::{b: 3}\n",
},
},
{
skipDoc: true,
document: `{}`,
document2: `{b: 12}`,
expression: `map_values(3 + .)`,
expected: []string{
"D0, P[], (!!map)::{}\n",
"D0, P[], (!!map)::{b: 15}\n",
},
},
{
skipDoc: true,
document: `{a: 1, b: 2, c: 3}`,
+2 -2
View File
@@ -154,9 +154,9 @@ func repeatString(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error
if err != nil {
return nil, err
} else if count < 0 {
return nil, fmt.Errorf("Cannot repeat string by a negative number (%v)", count)
return nil, fmt.Errorf("cannot repeat string by a negative number (%v)", count)
} else if count > 10000000 {
return nil, fmt.Errorf("Cannot repeat string by more than 100 million (%v)", count)
return nil, fmt.Errorf("cannot repeat string by more than 100 million (%v)", count)
}
target.Value = strings.Repeat(stringNode.Value, count)
+2 -2
View File
@@ -206,7 +206,7 @@ var multiplyOperatorScenarios = []expressionScenario{
skipDoc: true,
document: `n: -4`,
expression: `"banana" * .n`,
expectedError: "Cannot repeat string by a negative number (-4)",
expectedError: "cannot repeat string by a negative number (-4)",
},
{
description: "Multiply string X by more than 100 million",
@@ -214,7 +214,7 @@ var multiplyOperatorScenarios = []expressionScenario{
skipDoc: true,
document: `n: 100000001`,
expression: `"banana" * .n`,
expectedError: "Cannot repeat string by more than 100 million (100000001)",
expectedError: "cannot repeat string by more than 100 million (100000001)",
},
{
description: "Multiply int node X string",
+4 -3
View File
@@ -58,11 +58,12 @@ func omitOperator(d *dataTreeNavigator, context Context, expressionNode *Express
var replacement *CandidateNode
if node.Kind == MappingNode {
switch node.Kind {
case MappingNode:
replacement = omitMap(node, indicesToOmit)
} else if node.Kind == SequenceNode {
case SequenceNode:
replacement = omitSequence(node, indicesToOmit)
} else {
default:
log.Debugf("Omit from type %v (%v) is noop", node.Tag, node.GetNicePath())
return context, nil
}
+4 -3
View File
@@ -22,15 +22,16 @@ func getPathArrayFromNode(funcName string, node *CandidateNode) ([]interface{},
path := make([]interface{}, len(node.Content))
for i, childNode := range node.Content {
if childNode.Tag == "!!str" {
switch childNode.Tag {
case "!!str":
path[i] = childNode.Value
} else if childNode.Tag == "!!int" {
case "!!int":
number, err := parseInt(childNode.Value)
if err != nil {
return nil, fmt.Errorf("%v: could not parse %v as an int: %w", funcName, childNode.Value, err)
}
path[i] = number
} else {
default:
return nil, fmt.Errorf("%v: expected either a !!str or !!int in the path, found %v instead", funcName, childNode.Tag)
}
+4 -3
View File
@@ -64,15 +64,16 @@ func pickOperator(d *dataTreeNavigator, context Context, expressionNode *Express
node := el.Value.(*CandidateNode)
var replacement *CandidateNode
if node.Kind == MappingNode {
switch node.Kind {
case MappingNode:
replacement = pickMap(node, indicesToPick)
} else if node.Kind == SequenceNode {
case SequenceNode:
replacement, err = pickSequence(node, indicesToPick)
if err != nil {
return Context{}, err
}
} else {
default:
return Context{}, fmt.Errorf("cannot pick indices from type %v (%v)", node.Tag, node.GetNicePath())
}
+2 -2
View File
@@ -17,8 +17,8 @@ var reverseOperatorScenarios = []expressionScenario{
document: "[1, 2]",
expression: `reverse[]`,
expected: []string{
"D0, P[1], (!!int)::2\n",
"D0, P[0], (!!int)::1\n",
"D0, P[0], (!!int)::2\n",
"D0, P[1], (!!int)::1\n",
},
},
{
+6
View File
@@ -5,6 +5,12 @@ import (
)
var selectOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `cat: pants`,
expression: `select(.nope) | key + " why though?"`,
expected: []string{},
},
{
skipDoc: true,
document: `cat`,
+6 -1
View File
@@ -26,7 +26,12 @@ func shuffleOperator(_ *dataTreeNavigator, context Context, _ *ExpressionNode) (
a := result.Content
myRand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] })
myRand.Shuffle(len(a), func(i, j int) {
a[i], a[j] = a[j], a[i]
oldIndex := a[i].Key.Value
a[i].Key.Value = a[j].Key.Value
a[j].Key.Value = oldIndex
})
results.PushBack(result)
}

Some files were not shown because too many files have changed in this diff Show More