mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c2ed3a3e6d | ||
|
cc4c69bc89 | ||
|
917fd0eb6a | ||
|
b2056a2056 | ||
|
ba59201217 | ||
|
cfddee9101 | ||
|
1941bb66a5 | ||
|
29af9e4c63 | ||
|
af3a9ae846 | ||
|
5fb88627ca | ||
|
4fc3793fcb | ||
|
d612897e89 | ||
|
806f906041 | ||
|
e8b2f6e383 | ||
|
e419b5a39d | ||
|
a5b6e08282 | ||
|
2417c0ee8f | ||
|
d3b2e37b66 | ||
|
54723c1a36 | ||
|
0318c80d33 | ||
|
8980846632 | ||
|
0fb62d3ae1 | ||
|
85398727ad | ||
|
0bf3d781f6 | ||
|
5c15936bf3 | ||
|
b0735d8152 | ||
|
f17cbfd007 | ||
|
7b7ab70286 | ||
|
43165fa340 | ||
|
feda9f044d | ||
|
85629af59c | ||
|
5fc26e9453 | ||
|
07a6fa4df5 | ||
|
ec29ee7c14 | ||
|
24538c0cc7 | ||
|
61398bfd3a | ||
|
f7d95021c1 | ||
|
6bb8b1fb77 | ||
|
5e2c19cc86 | ||
|
30c269a66c | ||
|
70a1c60d7b | ||
|
e4d48bbc0d | ||
|
369ff94ad0 | ||
|
1c7fb14631 | ||
|
f4de5c4300 | ||
|
a72c14f06b | ||
|
5ed52aca66 | ||
|
5a5ac0dfef | ||
|
5a55869745 | ||
|
15e18bb98b | ||
|
644063646e | ||
|
aabed1a237 | ||
|
c12764dba8 | ||
|
1d5ecb244d | ||
|
7798a141cf | ||
|
b7583a538d | ||
|
529e88b630 | ||
|
658006eb93 | ||
|
2743a7e058 | ||
|
f95862fba3 | ||
|
3f58c4bc38 | ||
|
a7975df7cd | ||
|
3d5a5e0360 | ||
|
155755ae2f | ||
|
601c13531c | ||
|
69d00c89df | ||
|
2faff7b05f | ||
|
165949041d | ||
|
dbd7ab0f13 | ||
|
6d512ad718 | ||
|
4fef4a7ab1 | ||
|
dcb17b51a9 | ||
|
385417556d |
2
.github/ISSUE_TEMPLATE/bug_report_v3.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report_v3.md
vendored
@ -10,6 +10,8 @@ assignees: ''
|
|||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
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
|
Version of yq: 3.X.X
|
||||||
Operating system: mac/linux/windows/....
|
Operating system: mac/linux/windows/....
|
||||||
Installed via: docker/binary release/homebrew/snap/...
|
Installed via: docker/binary release/homebrew/snap/...
|
||||||
|
2
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
@ -10,6 +10,8 @@ assignees: ''
|
|||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
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: 4.X.X
|
Version of yq: 4.X.X
|
||||||
Operating system: mac/linux/windows/....
|
Operating system: mac/linux/windows/....
|
||||||
Installed via: docker/binary release/homebrew/snap/...
|
Installed via: docker/binary release/homebrew/snap/...
|
||||||
|
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -9,9 +9,11 @@ assignees: ''
|
|||||||
|
|
||||||
**Please describe your feature request.**
|
**Please describe your feature request.**
|
||||||
A clear and concise description of what the request is and what it would solve.
|
A clear and concise description of what the request is and what it would solve.
|
||||||
Ex. I wish I could use yq to [...]
|
Eg. I wish I could use yq to [...]
|
||||||
|
|
||||||
Please note that V3 will no longer have any enhancements.
|
Note:
|
||||||
|
- how to questions should be posted in the discussion board and not raised as an issue.
|
||||||
|
- V3 will no longer have any enhancements.
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
**Describe the solution you'd like**
|
||||||
If we have data1.yml like:
|
If we have data1.yml like:
|
||||||
|
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@ -3,6 +3,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
- 'dockerfix'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publishGitRelease:
|
publishGitRelease:
|
||||||
|
34
README.md
34
README.md
@ -12,6 +12,8 @@ V4 is now officially released, it's quite different from V3 (sorry for the migra
|
|||||||
|
|
||||||
If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3).
|
If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3).
|
||||||
|
|
||||||
|
Support for v3 will cease August 2021, until then, critical bug and security fixes will still get applied if required.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
|
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
|
||||||
@ -19,6 +21,14 @@ If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](h
|
|||||||
### wget
|
### wget
|
||||||
Use wget to download the pre-compiled binaries:
|
Use wget to download the pre-compiled binaries:
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Plain binary
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
|
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
|
||||||
chmod +x /usr/bin/yq
|
chmod +x /usr/bin/yq
|
||||||
@ -32,11 +42,25 @@ Using [Homebrew](https://brew.sh/)
|
|||||||
brew install yq
|
brew install yq
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or, for the (deprecated) v3 version:
|
||||||
|
|
||||||
|
```
|
||||||
|
brew install yq@3
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that for v3, as it is a versioned brew it will not add the `yq` command to your path automatically. Please follow the instructions given by brew upon installation.
|
||||||
|
|
||||||
### Linux via snap:
|
### Linux via snap:
|
||||||
```
|
```
|
||||||
snap install yq
|
snap install yq
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or, for the (deprecated) v3 version:
|
||||||
|
|
||||||
|
```
|
||||||
|
snap install yq --channel=v3/stable
|
||||||
|
```
|
||||||
|
|
||||||
#### Snap notes
|
#### Snap notes
|
||||||
`yq` installs with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
|
`yq` installs with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
|
||||||
|
|
||||||
@ -66,7 +90,7 @@ docker run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression
|
|||||||
#### Run commands interactively:
|
#### Run commands interactively:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
|
docker run --rm -it -v "${PWD}":/workdir --entrypoint sh mikefarah/yq
|
||||||
```
|
```
|
||||||
|
|
||||||
It can be useful to have a bash function to avoid typing the whole docker command:
|
It can be useful to have a bash function to avoid typing the whole docker command:
|
||||||
@ -85,6 +109,14 @@ GO111MODULE=on go get github.com/mikefarah/yq
|
|||||||
## Community Supported Installation methods
|
## Community Supported Installation methods
|
||||||
As these are supported by the community :heart: - however, they may be out of date with the officially supported releases.
|
As these are supported by the community :heart: - however, they may be out of date with the officially supported releases.
|
||||||
|
|
||||||
|
# Webi
|
||||||
|
|
||||||
|
```
|
||||||
|
webi yq
|
||||||
|
```
|
||||||
|
|
||||||
|
See [webi](https://webinstall.dev/)
|
||||||
|
Supported by @adithyasunil26 (https://github.com/webinstall/webi-installers/tree/master/yq)
|
||||||
|
|
||||||
### Windows:
|
### Windows:
|
||||||
```
|
```
|
||||||
|
@ -59,6 +59,10 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
|||||||
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
|
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nullInput && len(args) > 1 {
|
||||||
|
return errors.New("Cannot pass files in when using null-input flag")
|
||||||
|
}
|
||||||
|
|
||||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||||
|
|
||||||
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
||||||
|
@ -83,6 +83,10 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
streamEvaluator := yqlib.NewStreamEvaluator()
|
streamEvaluator := yqlib.NewStreamEvaluator()
|
||||||
|
|
||||||
|
if nullInput && len(args) > 1 {
|
||||||
|
return errors.New("Cannot pass files in when using null-input flag")
|
||||||
|
}
|
||||||
|
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
if pipingStdIn {
|
if pipingStdIn {
|
||||||
|
@ -11,7 +11,7 @@ var (
|
|||||||
GitDescribe string
|
GitDescribe string
|
||||||
|
|
||||||
// Version is main version number that is being run at the moment.
|
// Version is main version number that is being run at the moment.
|
||||||
Version = "4.2.0"
|
Version = "4.4.1"
|
||||||
|
|
||||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
// 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
|
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM mikefarah/yq:4.2.0
|
FROM mikefarah/yq:4.4.1
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
12
go.mod
12
go.mod
@ -2,17 +2,15 @@ module github.com/mikefarah/yq/v4
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/elliotchance/orderedmap v1.3.0
|
github.com/elliotchance/orderedmap v1.3.0
|
||||||
github.com/fatih/color v1.9.0
|
github.com/fatih/color v1.10.0
|
||||||
github.com/goccy/go-yaml v1.8.1
|
github.com/goccy/go-yaml v1.8.4
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
github.com/jinzhu/copier v0.1.0
|
||||||
github.com/mattn/go-colorable v0.1.7 // indirect
|
|
||||||
github.com/spf13/cobra v1.1.1
|
github.com/spf13/cobra v1.1.1
|
||||||
github.com/timtadh/data-structures v0.5.3 // indirect
|
github.com/timtadh/data-structures v0.5.3 // indirect
|
||||||
github.com/timtadh/lexmachine v0.2.2
|
github.com/timtadh/lexmachine v0.2.2
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
|
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
|
||||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
43
go.sum
43
go.sum
@ -39,19 +39,21 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
|
|||||||
github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
|
github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
|
||||||
github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
|
github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/goccy/go-yaml v1.8.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
|
github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
|
||||||
github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
|
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
@ -99,8 +101,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
|||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88=
|
||||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
@ -118,15 +120,9 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
|
||||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
|
||||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
@ -204,6 +200,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -251,22 +248,17 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
@ -289,10 +281,9 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
|
||||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
@ -322,8 +313,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
|
||||||
gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
||||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
|
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
|
||||||
@ -335,8 +324,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
@ -1,29 +1,50 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import "container/list"
|
import (
|
||||||
|
"container/list"
|
||||||
|
|
||||||
/**
|
yaml "gopkg.in/yaml.v3"
|
||||||
Loads all yaml documents of all files given into memory, then runs the given expression once.
|
)
|
||||||
**/
|
|
||||||
|
// A yaml expression evaluator that runs the expression once against all files/nodes in memory.
|
||||||
type Evaluator interface {
|
type Evaluator interface {
|
||||||
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
||||||
|
|
||||||
|
// EvaluateNodes takes an expression and one or more yaml nodes, returning a list of matching candidate nodes
|
||||||
|
EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error)
|
||||||
|
|
||||||
|
// EvaluateCandidateNodes takes an expression and list of candidate nodes, returning a list of matching candidate nodes
|
||||||
|
EvaluateCandidateNodes(expression string, inputCandidateNodes *list.List) (*list.List, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type allAtOnceEvaluator struct {
|
type allAtOnceEvaluator struct {
|
||||||
treeNavigator DataTreeNavigator
|
treeNavigator DataTreeNavigator
|
||||||
treeCreator PathTreeCreator
|
treeCreator ExpressionParser
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAllAtOnceEvaluator() Evaluator {
|
func NewAllAtOnceEvaluator() Evaluator {
|
||||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewExpressionParser()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *allAtOnceEvaluator) EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error) {
|
||||||
|
inputCandidates := list.New()
|
||||||
|
for _, node := range nodes {
|
||||||
|
inputCandidates.PushBack(&CandidateNode{Node: node})
|
||||||
|
}
|
||||||
|
return e.EvaluateCandidateNodes(expression, inputCandidates)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *allAtOnceEvaluator) EvaluateCandidateNodes(expression string, inputCandidates *list.List) (*list.List, error) {
|
||||||
|
node, err := e.treeCreator.ParseExpression(expression)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.treeNavigator.GetMatchingNodes(inputCandidates, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||||
fileIndex := 0
|
fileIndex := 0
|
||||||
node, err := treeCreator.ParsePath(expression)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var allDocuments *list.List = list.New()
|
var allDocuments *list.List = list.New()
|
||||||
for _, filename := range filenames {
|
for _, filename := range filenames {
|
||||||
reader, err := readStream(filename)
|
reader, err := readStream(filename)
|
||||||
@ -37,7 +58,7 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
|
|||||||
allDocuments.PushBackList(fileDocuments)
|
allDocuments.PushBackList(fileDocuments)
|
||||||
fileIndex = fileIndex + 1
|
fileIndex = fileIndex + 1
|
||||||
}
|
}
|
||||||
matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
|
matches, err := e.EvaluateCandidateNodes(expression, allDocuments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
40
pkg/yqlib/all_at_once_evaluator_test.go
Normal file
40
pkg/yqlib/all_at_once_evaluator_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mikefarah/yq/v4/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var evaluateNodesScenario = []expressionScenario{
|
||||||
|
{
|
||||||
|
document: `a: hello`,
|
||||||
|
expression: `.a`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!str)::hello\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: `a: hello`,
|
||||||
|
expression: `.`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::a: hello\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
document: `- a: "yes"`,
|
||||||
|
expression: `.[] | has("a")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[0], (!!bool)::true\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllAtOnceEvaluateNodes(t *testing.T) {
|
||||||
|
var evaluator = NewAllAtOnceEvaluator()
|
||||||
|
for _, tt := range evaluateNodesScenario {
|
||||||
|
node := test.ParseData(tt.document)
|
||||||
|
list, _ := evaluator.EvaluateNodes(tt.expression, &node)
|
||||||
|
test.AssertResultComplex(t, tt.expected, resultsToString(list))
|
||||||
|
}
|
||||||
|
}
|
@ -15,13 +15,32 @@ type CandidateNode struct {
|
|||||||
Document uint // the document index of this node
|
Document uint // the document index of this node
|
||||||
Filename string
|
Filename string
|
||||||
FileIndex int
|
FileIndex int
|
||||||
|
// when performing op against all nodes given, this will treat all the nodes as one
|
||||||
|
// (e.g. top level cross document merge). This property does not propegate to child nodes.
|
||||||
|
EvaluateTogether bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) GetKey() string {
|
func (n *CandidateNode) GetKey() string {
|
||||||
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) CreateChildPath(path interface{}) []interface{} {
|
func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode {
|
||||||
|
return &CandidateNode{
|
||||||
|
Node: node,
|
||||||
|
Path: n.createChildPath(path),
|
||||||
|
Document: n.Document,
|
||||||
|
Filename: n.Filename,
|
||||||
|
FileIndex: n.FileIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *CandidateNode) createChildPath(path interface{}) []interface{} {
|
||||||
|
if path == nil {
|
||||||
|
newPath := make([]interface{}, len(n.Path))
|
||||||
|
copy(newPath, n.Path)
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
|
||||||
//don't use append as they may actually modify the path of the orignal node!
|
//don't use append as they may actually modify the path of the orignal node!
|
||||||
newPath := make([]interface{}, len(n.Path)+1)
|
newPath := make([]interface{}, len(n.Path)+1)
|
||||||
copy(newPath, n.Path)
|
copy(newPath, n.Path)
|
||||||
@ -40,10 +59,12 @@ func (n *CandidateNode) Copy() (*CandidateNode, error) {
|
|||||||
|
|
||||||
// updates this candidate from the given candidate node
|
// updates this candidate from the given candidate node
|
||||||
func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
|
func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
|
||||||
|
|
||||||
n.UpdateAttributesFrom(other)
|
n.UpdateAttributesFrom(other)
|
||||||
n.Node.Content = other.Node.Content
|
n.Node.Content = other.Node.Content
|
||||||
n.Node.Value = other.Node.Value
|
n.Node.Value = other.Node.Value
|
||||||
n.Node.Alias = other.Node.Alias
|
n.Node.Alias = other.Node.Alias
|
||||||
|
n.Node.Anchor = other.Node.Anchor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
||||||
|
@ -17,8 +17,8 @@ func format(attr color.Attribute) string {
|
|||||||
return fmt.Sprintf("%s[%dm", escape, attr)
|
return fmt.Sprintf("%s[%dm", escape, attr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ColorizeAndPrint(bytes []byte, writer io.Writer) error {
|
func colorizeAndPrint(yamlBytes []byte, writer io.Writer) error {
|
||||||
tokens := lexer.Tokenize(string(bytes))
|
tokens := lexer.Tokenize(string(yamlBytes))
|
||||||
var p printer.Printer
|
var p printer.Printer
|
||||||
p.Bool = func() *printer.Property {
|
p.Bool = func() *printer.Property {
|
||||||
return &printer.Property{
|
return &printer.Property{
|
||||||
|
@ -9,10 +9,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DataTreeNavigator interface {
|
type DataTreeNavigator interface {
|
||||||
// given a list of CandidateEntities and a pathNode,
|
// given a list of CandidateEntities and a expressionNode,
|
||||||
// this will process the list against the given pathNode and return
|
// this will process the list against the given expressionNode and return
|
||||||
// a new list of matching candidates
|
// a new list of matching candidates
|
||||||
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
|
GetMatchingNodes(matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dataTreeNavigator struct {
|
type dataTreeNavigator struct {
|
||||||
@ -22,22 +22,22 @@ func NewDataTreeNavigator() DataTreeNavigator {
|
|||||||
return &dataTreeNavigator{}
|
return &dataTreeNavigator{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
if pathNode == nil {
|
if expressionNode == nil {
|
||||||
log.Debugf("getMatchingNodes - nothing to do")
|
log.Debugf("getMatchingNodes - nothing to do")
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
log.Debugf("Processing Op: %v", pathNode.Operation.toString())
|
log.Debugf("Processing Op: %v", expressionNode.Operation.toString())
|
||||||
if log.IsEnabledFor(logging.DEBUG) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
log.Debug(NodeToString(el.Value.(*CandidateNode)))
|
log.Debug(NodeToString(el.Value.(*CandidateNode)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug(">>")
|
log.Debug(">>")
|
||||||
handler := pathNode.Operation.OperationType.Handler
|
handler := expressionNode.Operation.OperationType.Handler
|
||||||
if handler != nil {
|
if handler != nil {
|
||||||
return handler(d, matchingNodes, pathNode)
|
return handler(d, matchingNodes, expressionNode)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Unknown operator %v", pathNode.Operation.OperationType)
|
return nil, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
Add behaves differently according to the type of the LHS:
|
Add behaves differently according to the type of the LHS:
|
||||||
- arrays: concatenate
|
- arrays: concatenate
|
||||||
- number scalars: arithmetic addition (soon)
|
- number scalars: arithmetic addition
|
||||||
- string scalars: concatenate (soon)
|
- string scalars: concatenate
|
||||||
|
|
||||||
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
|
||||||
|
|
||||||
## Concatenate and assign arrays
|
## Concatenate and assign arrays
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
@ -67,23 +67,19 @@ will output
|
|||||||
- 2
|
- 2
|
||||||
```
|
```
|
||||||
|
|
||||||
## Add object to array
|
## Add new object to array
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
a:
|
a:
|
||||||
- 1
|
- dog: woof
|
||||||
- 2
|
|
||||||
c:
|
|
||||||
cat: meow
|
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.a + .c' sample.yml
|
yq eval '.a + {"cat": "meow"}' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
- 1
|
- dog: woof
|
||||||
- 2
|
|
||||||
- cat: meow
|
- cat: meow
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -131,3 +127,97 @@ b:
|
|||||||
- 4
|
- 4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## String concatenation
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
b: meow
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a = .a + .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: catmeow
|
||||||
|
b: meow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Relative string concatenation
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
b: meow
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a += .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: catmeow
|
||||||
|
b: meow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Number addition - float
|
||||||
|
If the lhs or rhs are floats then the expression will be calculated with floats.
|
||||||
|
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: 3
|
||||||
|
b: 4.9
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a = .a + .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 7.9
|
||||||
|
b: 4.9
|
||||||
|
```
|
||||||
|
|
||||||
|
## Number addition - int
|
||||||
|
If both the lhs and rhs are ints then the expression will be calculated with ints.
|
||||||
|
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: 3
|
||||||
|
b: 4
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a = .a + .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 7
|
||||||
|
b: 4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Increment number
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: 3
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a += 1' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add to null
|
||||||
|
Adding to null simply returns the rhs
|
||||||
|
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
yq eval --null-input 'null + "cat"'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
cat
|
||||||
|
```
|
||||||
|
|
||||||
|
@ -31,6 +31,22 @@ will output
|
|||||||
a: &foobar cat
|
a: &foobar cat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Set anchor relatively using assign-update
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
b: cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a anchor |= .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: &cat
|
||||||
|
b: cat
|
||||||
|
```
|
||||||
|
|
||||||
## Get alias
|
## Get alias
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@ -62,6 +78,23 @@ b: &meow purr
|
|||||||
a: *meow
|
a: *meow
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Set alias relatively using assign-update
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
b: &meow purr
|
||||||
|
a:
|
||||||
|
f: meow
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a alias |= .f' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
b: &meow purr
|
||||||
|
a: *meow
|
||||||
|
```
|
||||||
|
|
||||||
## Explode alias and anchor
|
## Explode alias and anchor
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -34,6 +34,27 @@ a:
|
|||||||
g: foof
|
g: foof
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Update node from another file
|
||||||
|
Note this will also work when the second file is a scalar (string/number)
|
||||||
|
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: apples
|
||||||
|
```
|
||||||
|
And another sample another.yml file of:
|
||||||
|
```yaml
|
||||||
|
b: bob
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval-all 'select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)' sample.yml another.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
b: bob
|
||||||
|
```
|
||||||
|
|
||||||
## Update node to be the sibling value
|
## Update node to be the sibling value
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -13,6 +13,22 @@ will output
|
|||||||
a: cat # single
|
a: cat # single
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Use update assign to perform relative updates
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
b: dog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.. lineComment |= .' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat # cat
|
||||||
|
b: dog # dog
|
||||||
|
```
|
||||||
|
|
||||||
## Set head comment
|
## Set head comment
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@ -62,17 +78,22 @@ b: dog # leave this
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Remove all comments
|
## Remove all comments
|
||||||
|
Note the use of `...` to ensure key nodes are included.
|
||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
a: cat # comment
|
a: cat # comment
|
||||||
|
# great
|
||||||
|
b: # key comment
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.. comments=""' sample.yml
|
yq eval '... comments=""' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
a: cat
|
a: cat
|
||||||
|
b:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Get line comment
|
## Get line comment
|
||||||
|
@ -95,3 +95,23 @@ will output
|
|||||||
b: dog
|
b: dog
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Recursively delete matching keys
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
name: frog
|
||||||
|
b:
|
||||||
|
name: blog
|
||||||
|
age: 12
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'del(.. | select(has("name")).name)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
age: 12
|
||||||
|
```
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Use the `documentIndex` operator to select nodes of a particular document.
|
Use the `documentIndex` operator (or the `di` shorthand) to select nodes of a particular document.
|
||||||
## Retrieve a document index
|
## Retrieve a document index
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@ -17,6 +17,24 @@ will output
|
|||||||
1
|
1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Retrieve a document index, shorthand
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
---
|
||||||
|
a: frog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a | di' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
0
|
||||||
|
---
|
||||||
|
1
|
||||||
|
```
|
||||||
|
|
||||||
## Filter by document index
|
## Filter by document index
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@ -33,6 +51,22 @@ will output
|
|||||||
a: frog
|
a: frog
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Filter by document index shorthand
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
---
|
||||||
|
a: frog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'select(di == 1)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: frog
|
||||||
|
```
|
||||||
|
|
||||||
## Print Document Index with matches
|
## Print Document Index with matches
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
|
78
pkg/yqlib/doc/Env Variable Operators.md
Normal file
78
pkg/yqlib/doc/Env Variable Operators.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.
|
||||||
|
|
||||||
|
|
||||||
|
## Read string environment variable
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
myenv="cat meow" yq eval --null-input '.a = env(myenv)'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat meow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Read boolean environment variable
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
myenv="true" yq eval --null-input '.a = env(myenv)'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Read numeric environment variable
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
myenv="12" yq eval --null-input '.a = env(myenv)'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 12
|
||||||
|
```
|
||||||
|
|
||||||
|
## Read yaml environment variable
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
myenv="{b: fish}" yq eval --null-input '.a = env(myenv)'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: {b: fish}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Read boolean environment variable as a string
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
myenv="true" yq eval --null-input '.a = strenv(myenv)'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Read numeric environment variable as a string
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
myenv="12" yq eval --null-input '.a = strenv(myenv)'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: "12"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dynamic key lookup with environment variable
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
cat: meow
|
||||||
|
dog: woof
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
myenv="cat" yq eval '.[env(myenv)]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
meow
|
||||||
|
```
|
||||||
|
|
@ -1,9 +1,11 @@
|
|||||||
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
||||||
|
|
||||||
|
Note that the `fileIndex` operator has a short alias of `fi`.
|
||||||
|
|
||||||
## Merging files
|
## Merging files
|
||||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||||
```bash
|
```bash
|
||||||
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||||
```
|
```
|
||||||
## Get filename
|
## Get filename
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
@ -16,7 +18,7 @@ yq eval 'filename' sample.yml
|
|||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
sample.yaml
|
sample.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Get file index
|
## Get file index
|
||||||
@ -33,3 +35,17 @@ will output
|
|||||||
0
|
0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Get file index alias
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'fi' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
0
|
||||||
|
```
|
||||||
|
|
||||||
|
35
pkg/yqlib/doc/Keys.md
Normal file
35
pkg/yqlib/doc/Keys.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Keys
|
||||||
|
|
||||||
|
Use the `keys` operator to return map keys or array indices.
|
||||||
|
## Map keys
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
dog: woof
|
||||||
|
cat: meow
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'keys' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- dog
|
||||||
|
- cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Array keys
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- apple
|
||||||
|
- banana
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'keys' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
```
|
||||||
|
|
@ -1,13 +1,14 @@
|
|||||||
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
||||||
|
|
||||||
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
|
To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||||
|
|
||||||
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
|
||||||
|
|
||||||
|
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
|
||||||
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||||
|
|
||||||
|
Multiplication of strings and numbers are not yet supported.
|
||||||
|
|
||||||
## Merging files
|
## Merging files
|
||||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
Note the use of `eval-all` to ensure all documents are loaded into memory.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||||
@ -111,6 +112,26 @@ b:
|
|||||||
- 5
|
- 5
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Merge, only existing fields
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
thing: one
|
||||||
|
cat: frog
|
||||||
|
b:
|
||||||
|
missing: two
|
||||||
|
thing: two
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a *? .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
thing: two
|
||||||
|
cat: frog
|
||||||
|
```
|
||||||
|
|
||||||
## Merge, appending arrays
|
## Merge, appending arrays
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@ -143,6 +164,33 @@ array:
|
|||||||
value: banana
|
value: banana
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Merge, only existing fields, appending arrays
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
thing:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
b:
|
||||||
|
thing:
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
another:
|
||||||
|
- 1
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a *?+ .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
thing:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
```
|
||||||
|
|
||||||
## Merge to prefix an element
|
## Merge to prefix an element
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@ -180,7 +228,7 @@ g: thongs
|
|||||||
f: *cat
|
f: *cat
|
||||||
```
|
```
|
||||||
|
|
||||||
## Merge does not copy anchor names
|
## Merge copies anchor names
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
a:
|
a:
|
||||||
@ -197,7 +245,7 @@ yq eval '.c * .a' sample.yml
|
|||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
g: thongs
|
g: thongs
|
||||||
c: frog
|
c: &cat frog
|
||||||
```
|
```
|
||||||
|
|
||||||
## Merge with merge anchors
|
## Merge with merge anchors
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
||||||
|
|
||||||
## match values form `..`
|
## match values form `..`
|
||||||
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||||
@ -32,6 +32,50 @@ a: frog
|
|||||||
frog
|
frog
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Recursively find nodes with keys
|
||||||
|
Note that this example has wrapped the expression in `[]` to show that there are two matches returned. You do not have to wrap in `[]` in your path expression.
|
||||||
|
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
name: frog
|
||||||
|
b:
|
||||||
|
name: blog
|
||||||
|
age: 12
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '[.. | select(has("name"))]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- name: frog
|
||||||
|
b:
|
||||||
|
name: blog
|
||||||
|
age: 12
|
||||||
|
- name: blog
|
||||||
|
age: 12
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recursively find nodes with values
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
nameA: frog
|
||||||
|
b:
|
||||||
|
nameB: frog
|
||||||
|
age: 12
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.. | select(. == "frog")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
frog
|
||||||
|
frog
|
||||||
|
```
|
||||||
|
|
||||||
## Recurse map (values and keys)
|
## Recurse map (values and keys)
|
||||||
Note that the map key appears in the results
|
Note that the map key appears in the results
|
||||||
|
|
31
pkg/yqlib/doc/Split into Documents.md
Normal file
31
pkg/yqlib/doc/Split into Documents.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Split into Documents
|
||||||
|
|
||||||
|
This operator splits all matches into separate documents
|
||||||
|
|
||||||
|
## Split empty
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
yq eval --null-input 'splitDoc'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Split array
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: cat
|
||||||
|
- b: dog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.[] | splitDoc' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
---
|
||||||
|
b: dog
|
||||||
|
```
|
||||||
|
|
52
pkg/yqlib/doc/String Operators.md
Normal file
52
pkg/yqlib/doc/String Operators.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# String Operators
|
||||||
|
|
||||||
|
## Join strings
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- cat
|
||||||
|
- meow
|
||||||
|
- 1
|
||||||
|
- null
|
||||||
|
- true
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'join("; ")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
cat; meow; 1; ; true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Split strings
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
cat; meow; 1; ; true
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'split("; ")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- cat
|
||||||
|
- meow
|
||||||
|
- "1"
|
||||||
|
- ""
|
||||||
|
- "true"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Split strings one match
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
word
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'split("; ")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- word
|
||||||
|
```
|
||||||
|
|
@ -145,7 +145,7 @@ will output
|
|||||||
{a: cat, b: 5, c: 3.2, e: true}
|
{a: cat, b: 5, c: 3.2, e: true}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pretty print
|
## Reset style - or pretty print
|
||||||
Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.
|
Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.
|
||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
@ -167,6 +167,22 @@ c: 3.2
|
|||||||
e: true
|
e: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Set style relatively with assign-update
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: single
|
||||||
|
b: double
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.[] style |= .' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 'single'
|
||||||
|
b: "double"
|
||||||
|
```
|
||||||
|
|
||||||
## Read style
|
## Read style
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -22,7 +22,21 @@ will output
|
|||||||
!!seq
|
!!seq
|
||||||
```
|
```
|
||||||
|
|
||||||
## Convert numbers to strings
|
## Set custom tag
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: str
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a tag = "!!mikefarah"' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: !!mikefarah str
|
||||||
|
```
|
||||||
|
|
||||||
|
## Find numbers and convert them to strings
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
a: cat
|
a: cat
|
||||||
|
@ -48,6 +48,24 @@ will output
|
|||||||
frog
|
frog
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Dynamic keys
|
||||||
|
Expressions within [] can be used to dynamically lookup / calculate keys
|
||||||
|
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
b: apple
|
||||||
|
apple: crispy yum
|
||||||
|
banana: soft yum
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.[.b]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
crispy yum
|
||||||
|
```
|
||||||
|
|
||||||
## Children don't exist
|
## Children don't exist
|
||||||
Nodes are added dynamically while traversing
|
Nodes are added dynamically while traversing
|
||||||
|
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
# Operators
|
||||||
|
|
||||||
|
In `yq` expressions are made up of operators. Operators have 0-2 arguments and run against the current 'matching' nodes in the expression tree.
|
||||||
|
|
||||||
|
Lets look at a couple of examples.
|
||||||
|
|
||||||
|
The `length` operator take no arguments, and will simply return the length of _each_ matching node. So if there were 2 nodes, one string and one array, length will update the 'matching' nodes context to be two new numeric scalar nodes representing the lengths of the orignal 'matching' nodes.
|
||||||
|
|
||||||
|
The `=` operator takes two arguments, a `lhs` expression and `rhs` expression. It runs the 'matching' nodes context against the `lhs` expression to find the nodes to update, lets call it `lhsNodes`, and then runs the matching nodes against the `rhs` to find the new values, lets call that `rhsNodes`. It updates the `lhsNodes` values with the `rhsNodes` values and _returns the original matching nodes_. This is important, where length changed the matching nodes to be new nodes with the length values, `=` returns the original matching nodes, albeit with some of the nodes values updated. So `.a = 3` will still return the parent matching node, but with the matching child updated.
|
||||||
|
|
||||||
|
Please see the individual operator docs for more information and examples.
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
Add behaves differently according to the type of the LHS:
|
Add behaves differently according to the type of the LHS:
|
||||||
- arrays: concatenate
|
- arrays: concatenate
|
||||||
- number scalars: arithmetic addition (soon)
|
- number scalars: arithmetic addition
|
||||||
- string scalars: concatenate (soon)
|
- string scalars: concatenate
|
||||||
|
|
||||||
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
|
||||||
|
@ -1 +1 @@
|
|||||||
Use the `documentIndex` operator to select nodes of a particular document.
|
Use the `documentIndex` operator (or the `di` shorthand) to select nodes of a particular document.
|
2
pkg/yqlib/doc/headers/Env Variable Operators.md
Normal file
2
pkg/yqlib/doc/headers/Env Variable Operators.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.
|
||||||
|
|
@ -1,7 +1,9 @@
|
|||||||
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
||||||
|
|
||||||
|
Note that the `fileIndex` operator has a short alias of `fi`.
|
||||||
|
|
||||||
## Merging files
|
## Merging files
|
||||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||||
```bash
|
```bash
|
||||||
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||||
```
|
```
|
3
pkg/yqlib/doc/headers/Keys.md
Normal file
3
pkg/yqlib/doc/headers/Keys.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Keys
|
||||||
|
|
||||||
|
Use the `keys` operator to return map keys or array indices.
|
@ -1,13 +1,14 @@
|
|||||||
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
||||||
|
|
||||||
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
|
To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||||
|
|
||||||
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
|
||||||
|
|
||||||
|
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
|
||||||
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||||
|
|
||||||
|
Multiplication of strings and numbers are not yet supported.
|
||||||
|
|
||||||
## Merging files
|
## Merging files
|
||||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
Note the use of `eval-all` to ensure all documents are loaded into memory.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
||||||
|
|
||||||
## match values form `..`
|
## match values form `..`
|
||||||
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
3
pkg/yqlib/doc/headers/Split into Documents.md
Normal file
3
pkg/yqlib/doc/headers/Split into Documents.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Split into Documents
|
||||||
|
|
||||||
|
This operator splits all matches into separate documents
|
1
pkg/yqlib/doc/headers/String Operators.md
Normal file
1
pkg/yqlib/doc/headers/String Operators.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# String Operators
|
@ -50,7 +50,7 @@ func (ye *yamlEncoder) Encode(node *yaml.Node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ye.colorise {
|
if ye.colorise {
|
||||||
return ColorizeAndPrint(tempBuffer.Bytes(), ye.destination)
|
return colorizeAndPrint(tempBuffer.Bytes(), ye.destination)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -76,6 +76,8 @@ func mapKeysToStrings(node *yaml.Node) {
|
|||||||
|
|
||||||
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
||||||
var encoder = json.NewEncoder(destination)
|
var encoder = json.NewEncoder(destination)
|
||||||
|
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
||||||
|
|
||||||
var indentString = ""
|
var indentString = ""
|
||||||
|
|
||||||
for index := 0; index < indent; index++ {
|
for index := 0; index < indent; index++ {
|
||||||
@ -153,11 +155,15 @@ func (o *orderedMap) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
||||||
if o.kv == nil {
|
|
||||||
return json.Marshal(o.altVal)
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
enc := json.NewEncoder(buf)
|
enc := json.NewEncoder(buf)
|
||||||
|
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
||||||
|
if o.kv == nil {
|
||||||
|
if err := enc.Encode(o.altVal); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
buf.WriteByte('{')
|
buf.WriteByte('{')
|
||||||
for idx, el := range o.kv {
|
for idx, el := range o.kv {
|
||||||
if err := enc.Encode(el.K); err != nil {
|
if err := enc.Encode(el.K); err != nil {
|
||||||
|
@ -9,29 +9,11 @@ import (
|
|||||||
"github.com/mikefarah/yq/v4/test"
|
"github.com/mikefarah/yq/v4/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sampleYaml = `zabbix: winner
|
func yamlToJson(sampleYaml string, indent int) string {
|
||||||
apple: great
|
|
||||||
banana:
|
|
||||||
- {cobra: kai, angus: bob}
|
|
||||||
`
|
|
||||||
|
|
||||||
var expectedJson = `{
|
|
||||||
"zabbix": "winner",
|
|
||||||
"apple": "great",
|
|
||||||
"banana": [
|
|
||||||
{
|
|
||||||
"cobra": "kai",
|
|
||||||
"angus": "bob"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
|
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
writer := bufio.NewWriter(&output)
|
writer := bufio.NewWriter(&output)
|
||||||
|
|
||||||
var jsonEncoder = NewJsonEncoder(writer, 2)
|
var jsonEncoder = NewJsonEncoder(writer, indent)
|
||||||
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
|
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -42,6 +24,33 @@ func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
test.AssertResult(t, expectedJson, output.String())
|
|
||||||
|
|
||||||
|
return strings.TrimSuffix(output.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
|
||||||
|
var sampleYaml = `zabbix: winner
|
||||||
|
apple: great
|
||||||
|
banana:
|
||||||
|
- {cobra: kai, angus: bob}
|
||||||
|
`
|
||||||
|
var expectedJson = `{
|
||||||
|
"zabbix": "winner",
|
||||||
|
"apple": "great",
|
||||||
|
"banana": [
|
||||||
|
{
|
||||||
|
"cobra": "kai",
|
||||||
|
"angus": "bob"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
var actualJson = yamlToJson(sampleYaml, 2)
|
||||||
|
test.AssertResult(t, expectedJson, actualJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJsonEncoderDoesNotEscapeHTMLChars(t *testing.T) {
|
||||||
|
var sampleYaml = `build: "( ./lint && ./format && ./compile ) < src.code"`
|
||||||
|
var expectedJson = `{"build":"( ./lint && ./format && ./compile ) < src.code"}`
|
||||||
|
var actualJson = yamlToJson(sampleYaml, 0)
|
||||||
|
test.AssertResult(t, expectedJson, actualJson)
|
||||||
}
|
}
|
||||||
|
@ -5,29 +5,28 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var myPathTokeniser = NewPathTokeniser()
|
var myPathTokeniser = newExpressionTokeniser()
|
||||||
var myPathPostfixer = NewPathPostFixer()
|
var myPathPostfixer = newExpressionPostFixer()
|
||||||
|
|
||||||
type PathTreeNode struct {
|
type ExpressionNode struct {
|
||||||
Operation *Operation
|
Operation *Operation
|
||||||
Lhs *PathTreeNode
|
Lhs *ExpressionNode
|
||||||
Rhs *PathTreeNode
|
Rhs *ExpressionNode
|
||||||
}
|
}
|
||||||
|
|
||||||
type PathTreeCreator interface {
|
type ExpressionParser interface {
|
||||||
ParsePath(path string) (*PathTreeNode, error)
|
ParseExpression(expression string) (*ExpressionNode, error)
|
||||||
CreatePathTree(postFixPath []*Operation) (*PathTreeNode, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type pathTreeCreator struct {
|
type expressionParserImpl struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPathTreeCreator() PathTreeCreator {
|
func NewExpressionParser() ExpressionParser {
|
||||||
return &pathTreeCreator{}
|
return &expressionParserImpl{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pathTreeCreator) ParsePath(path string) (*PathTreeNode, error) {
|
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
|
||||||
tokens, err := myPathTokeniser.Tokenise(path)
|
tokens, err := myPathTokeniser.Tokenise(expression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -36,18 +35,18 @@ func (p *pathTreeCreator) ParsePath(path string) (*PathTreeNode, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return p.CreatePathTree(Operations)
|
return p.createExpressionTree(Operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pathTreeCreator) CreatePathTree(postFixPath []*Operation) (*PathTreeNode, error) {
|
func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*ExpressionNode, error) {
|
||||||
var stack = make([]*PathTreeNode, 0)
|
var stack = make([]*ExpressionNode, 0)
|
||||||
|
|
||||||
if len(postFixPath) == 0 {
|
if len(postFixPath) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, Operation := range postFixPath {
|
for _, Operation := range postFixPath {
|
||||||
var newNode = PathTreeNode{Operation: Operation}
|
var newNode = ExpressionNode{Operation: Operation}
|
||||||
log.Debugf("pathTree %v ", Operation.toString())
|
log.Debugf("pathTree %v ", Operation.toString())
|
||||||
if Operation.OperationType.NumArgs > 0 {
|
if Operation.OperationType.NumArgs > 0 {
|
||||||
numArgs := Operation.OperationType.NumArgs
|
numArgs := Operation.OperationType.NumArgs
|
@ -7,36 +7,36 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestPathTreeNoArgsForTwoArgOp(t *testing.T) {
|
func TestPathTreeNoArgsForTwoArgOp(t *testing.T) {
|
||||||
_, err := treeCreator.ParsePath("=")
|
_, err := NewExpressionParser().ParseExpression("=")
|
||||||
test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error())
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) {
|
func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) {
|
||||||
_, err := treeCreator.ParsePath(".a =")
|
_, err := NewExpressionParser().ParseExpression(".a =")
|
||||||
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) {
|
func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) {
|
||||||
_, err := treeCreator.ParsePath("= .a")
|
_, err := NewExpressionParser().ParseExpression("= .a")
|
||||||
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) {
|
func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) {
|
||||||
_, err := treeCreator.ParsePath(".a = .b")
|
_, err := NewExpressionParser().ParseExpression(".a = .b")
|
||||||
test.AssertResultComplex(t, nil, err)
|
test.AssertResultComplex(t, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathTreeNoArgsForOneArgOp(t *testing.T) {
|
func TestPathTreeNoArgsForOneArgOp(t *testing.T) {
|
||||||
_, err := treeCreator.ParsePath("explode")
|
_, err := NewExpressionParser().ParseExpression("explode")
|
||||||
test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error())
|
test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathTreeOneArgForOneArgOp(t *testing.T) {
|
func TestPathTreeOneArgForOneArgOp(t *testing.T) {
|
||||||
_, err := treeCreator.ParsePath("explode(.)")
|
_, err := NewExpressionParser().ParseExpression("explode(.)")
|
||||||
test.AssertResultComplex(t, nil, err)
|
test.AssertResultComplex(t, nil, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPathTreeExtraArgs(t *testing.T) {
|
func TestPathTreeExtraArgs(t *testing.T) {
|
||||||
_, err := treeCreator.ParsePath("sortKeys(.) explode(.)")
|
_, err := NewExpressionParser().ParseExpression("sortKeys(.) explode(.)")
|
||||||
test.AssertResultComplex(t, "expected end of expression but found 'explode', please check expression syntax", err.Error())
|
test.AssertResultComplex(t, "expected end of expression but found 'explode', please check expression syntax", err.Error())
|
||||||
}
|
}
|
@ -6,40 +6,40 @@ import (
|
|||||||
logging "gopkg.in/op/go-logging.v1"
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PathPostFixer interface {
|
type expressionPostFixer interface {
|
||||||
ConvertToPostfix([]*Token) ([]*Operation, error)
|
ConvertToPostfix([]*token) ([]*Operation, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type pathPostFixer struct {
|
type expressionPostFixerImpl struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPathPostFixer() PathPostFixer {
|
func newExpressionPostFixer() expressionPostFixer {
|
||||||
return &pathPostFixer{}
|
return &expressionPostFixerImpl{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func popOpToResult(opStack []*Token, result []*Operation) ([]*Token, []*Operation) {
|
func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) {
|
||||||
var newOp *Token
|
var newOp *token
|
||||||
opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1]
|
opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1]
|
||||||
return opStack, append(result, newOp.Operation)
|
return opStack, append(result, newOp.Operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, error) {
|
func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) {
|
||||||
var result []*Operation
|
var result []*Operation
|
||||||
// surround the whole thing with quotes
|
// surround the whole thing with quotes
|
||||||
var opStack = []*Token{&Token{TokenType: OpenBracket}}
|
var opStack = []*token{&token{TokenType: openBracket}}
|
||||||
var tokens = append(infixTokens, &Token{TokenType: CloseBracket})
|
var tokens = append(infixTokens, &token{TokenType: closeBracket})
|
||||||
|
|
||||||
for _, token := range tokens {
|
for _, currentToken := range tokens {
|
||||||
log.Debugf("postfix processing token %v, %v", token.toString(), token.Operation)
|
log.Debugf("postfix processing currentToken %v, %v", currentToken.toString(), currentToken.Operation)
|
||||||
switch token.TokenType {
|
switch currentToken.TokenType {
|
||||||
case OpenBracket, OpenCollect, OpenCollectObject:
|
case openBracket, openCollect, openCollectObject:
|
||||||
opStack = append(opStack, token)
|
opStack = append(opStack, currentToken)
|
||||||
case CloseCollect, CloseCollectObject:
|
case closeCollect, closeCollectObject:
|
||||||
var opener TokenType = OpenCollect
|
var opener tokenType = openCollect
|
||||||
var collectOperator *OperationType = Collect
|
var collectOperator *operationType = collectOpType
|
||||||
if token.TokenType == CloseCollectObject {
|
if currentToken.TokenType == closeCollectObject {
|
||||||
opener = OpenCollectObject
|
opener = openCollectObject
|
||||||
collectOperator = CollectObject
|
collectOperator = collectObjectOpType
|
||||||
}
|
}
|
||||||
itemsInMiddle := false
|
itemsInMiddle := false
|
||||||
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener {
|
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener {
|
||||||
@ -48,7 +48,7 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er
|
|||||||
}
|
}
|
||||||
if !itemsInMiddle {
|
if !itemsInMiddle {
|
||||||
// must be an empty collection, add the empty object as a LHS parameter
|
// must be an empty collection, add the empty object as a LHS parameter
|
||||||
result = append(result, &Operation{OperationType: Empty})
|
result = append(result, &Operation{OperationType: emptyOpType})
|
||||||
}
|
}
|
||||||
if len(opStack) == 0 {
|
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")
|
||||||
@ -56,10 +56,10 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er
|
|||||||
// now we should have [] as the last element on the opStack, get rid of it
|
// now we should have [] as the last element on the opStack, get rid of it
|
||||||
opStack = opStack[0 : len(opStack)-1]
|
opStack = opStack[0 : len(opStack)-1]
|
||||||
//and append a collect to the opStack
|
//and append a collect to the opStack
|
||||||
opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: ShortPipe}})
|
opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: shortPipeOpType}})
|
||||||
opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: collectOperator}})
|
opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: collectOperator}})
|
||||||
case CloseBracket:
|
case closeBracket:
|
||||||
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenBracket {
|
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket {
|
||||||
opStack, result = popOpToResult(opStack, result)
|
opStack, result = popOpToResult(opStack, result)
|
||||||
}
|
}
|
||||||
if len(opStack) == 0 {
|
if len(opStack) == 0 {
|
||||||
@ -69,22 +69,22 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er
|
|||||||
opStack = opStack[0 : len(opStack)-1]
|
opStack = opStack[0 : len(opStack)-1]
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var currentPrecedence = token.Operation.OperationType.Precedence
|
var currentPrecedence = currentToken.Operation.OperationType.Precedence
|
||||||
// pop off higher precedent operators onto the result
|
// pop off higher precedent operators onto the result
|
||||||
for len(opStack) > 0 &&
|
for len(opStack) > 0 &&
|
||||||
opStack[len(opStack)-1].TokenType == OperationToken &&
|
opStack[len(opStack)-1].TokenType == operationToken &&
|
||||||
opStack[len(opStack)-1].Operation.OperationType.Precedence >= currentPrecedence {
|
opStack[len(opStack)-1].Operation.OperationType.Precedence >= currentPrecedence {
|
||||||
opStack, result = popOpToResult(opStack, result)
|
opStack, result = popOpToResult(opStack, result)
|
||||||
}
|
}
|
||||||
// add this operator to the opStack
|
// add this operator to the opStack
|
||||||
opStack = append(opStack, token)
|
opStack = append(opStack, currentToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if log.IsEnabledFor(logging.DEBUG) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
log.Debugf("PostFix Result:")
|
log.Debugf("PostFix Result:")
|
||||||
for _, token := range result {
|
for _, currentToken := range result {
|
||||||
log.Debugf("> %v", token.toString())
|
log.Debugf("> %v", currentToken.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -53,9 +53,19 @@ var pathTests = []struct {
|
|||||||
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`d0.a`,
|
`.key.array + .key.array2`,
|
||||||
append(make([]interface{}, 0), "d0", "SHORT_PIPE", "a"),
|
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ADD", "key", "SHORT_PIPE", "array2"),
|
||||||
append(make([]interface{}, 0), "d0", "a", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ADD"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.key.array * .key.array2`,
|
||||||
|
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "MULTIPLY", "key", "SHORT_PIPE", "array2"),
|
||||||
|
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "MULTIPLY"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.key.array // .key.array2`,
|
||||||
|
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ALTERNATIVE", "key", "SHORT_PIPE", "array2"),
|
||||||
|
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ALTERNATIVE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`.a | .[].b == "apple"`,
|
`.a | .[].b == "apple"`,
|
||||||
@ -137,6 +147,11 @@ var pathTests = []struct {
|
|||||||
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
|
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
|
||||||
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
|
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`. lineComment |= "str"`,
|
||||||
|
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
|
||||||
|
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`.a.b tag="!!str"`,
|
`.a.b tag="!!str"`,
|
||||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"),
|
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"),
|
||||||
@ -159,8 +174,8 @@ var pathTests = []struct {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokeniser = NewPathTokeniser()
|
var tokeniser = newExpressionTokeniser()
|
||||||
var postFixer = NewPathPostFixer()
|
var postFixer = newExpressionPostFixer()
|
||||||
|
|
||||||
func TestPathParsing(t *testing.T) {
|
func TestPathParsing(t *testing.T) {
|
||||||
for _, tt := range pathTests {
|
for _, tt := range pathTests {
|
412
pkg/yqlib/expression_tokeniser.go
Normal file
412
pkg/yqlib/expression_tokeniser.go
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
lex "github.com/timtadh/lexmachine"
|
||||||
|
"github.com/timtadh/lexmachine/machines"
|
||||||
|
)
|
||||||
|
|
||||||
|
func skip(*lex.Scanner, *machines.Match) (interface{}, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
operationToken = 1 << iota
|
||||||
|
openBracket
|
||||||
|
closeBracket
|
||||||
|
openCollect
|
||||||
|
closeCollect
|
||||||
|
openCollectObject
|
||||||
|
closeCollectObject
|
||||||
|
traverseArrayCollect
|
||||||
|
)
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
TokenType tokenType
|
||||||
|
Operation *Operation
|
||||||
|
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
|
||||||
|
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *token) toString() string {
|
||||||
|
if t.TokenType == operationToken {
|
||||||
|
log.Debug("toString, its an op")
|
||||||
|
return t.Operation.toString()
|
||||||
|
} else if t.TokenType == openBracket {
|
||||||
|
return "("
|
||||||
|
} else if t.TokenType == closeBracket {
|
||||||
|
return ")"
|
||||||
|
} else if t.TokenType == openCollect {
|
||||||
|
return "["
|
||||||
|
} else if t.TokenType == closeCollect {
|
||||||
|
return "]"
|
||||||
|
} else if t.TokenType == openCollectObject {
|
||||||
|
return "{"
|
||||||
|
} else if t.TokenType == closeCollectObject {
|
||||||
|
return "}"
|
||||||
|
} else if t.TokenType == traverseArrayCollect {
|
||||||
|
return ".["
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return "NFI"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathToken(wrapped bool) lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
value := string(m.Bytes)
|
||||||
|
value = value[1:]
|
||||||
|
if wrapped {
|
||||||
|
value = unwrap(value)
|
||||||
|
}
|
||||||
|
log.Debug("PathToken %v", value)
|
||||||
|
op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value, Preferences: traversePreferences{}}
|
||||||
|
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opToken(op *operationType) lex.Action {
|
||||||
|
return opTokenWithPrefs(op, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func opAssignableToken(opType *operationType, assignOpType *operationType) lex.Action {
|
||||||
|
return opTokenWithPrefs(opType, assignOpType, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignOpToken(updateAssign bool) lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
log.Debug("assignOpToken %v", string(m.Bytes))
|
||||||
|
value := string(m.Bytes)
|
||||||
|
op := &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, UpdateAssign: updateAssign}
|
||||||
|
return &token{TokenType: operationToken, Operation: op}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func multiplyWithPrefs() lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
prefs := multiplyPreferences{}
|
||||||
|
options := string(m.Bytes)
|
||||||
|
if strings.Contains(options, "+") {
|
||||||
|
prefs.AppendArrays = true
|
||||||
|
}
|
||||||
|
if strings.Contains(options, "?") {
|
||||||
|
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
|
||||||
|
}
|
||||||
|
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
|
||||||
|
return &token{TokenType: operationToken, Operation: op}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
|
||||||
|
value := string(m.Bytes)
|
||||||
|
op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences}
|
||||||
|
var assign *Operation
|
||||||
|
if assignOpType != nil {
|
||||||
|
assign = &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, Preferences: preferences}
|
||||||
|
}
|
||||||
|
return &token{TokenType: operationToken, Operation: op, AssignOperation: assign}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignAllCommentsOp(updateAssign bool) lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
log.Debug("assignAllCommentsOp %v", string(m.Bytes))
|
||||||
|
value := string(m.Bytes)
|
||||||
|
op := &Operation{
|
||||||
|
OperationType: assignCommentOpType,
|
||||||
|
Value: assignCommentOpType.Type,
|
||||||
|
StringValue: value,
|
||||||
|
UpdateAssign: updateAssign,
|
||||||
|
Preferences: commentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
|
||||||
|
}
|
||||||
|
return &token{TokenType: operationToken, Operation: op}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func literalToken(pType tokenType, checkForPost bool) lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
return &token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrap(value string) string {
|
||||||
|
return value[1 : len(value)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberValue() lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
var numberString = string(m.Bytes)
|
||||||
|
var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint
|
||||||
|
if errParsingInt != nil {
|
||||||
|
return nil, errParsingInt
|
||||||
|
}
|
||||||
|
|
||||||
|
return &token{TokenType: operationToken, Operation: createValueOperation(number, numberString)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func floatValue() lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
var numberString = string(m.Bytes)
|
||||||
|
var number, errParsingInt = strconv.ParseFloat(numberString, 64) // nolint
|
||||||
|
if errParsingInt != nil {
|
||||||
|
return nil, errParsingInt
|
||||||
|
}
|
||||||
|
return &token{TokenType: operationToken, Operation: createValueOperation(number, numberString)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func booleanValue(val bool) lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
return &token{TokenType: operationToken, Operation: createValueOperation(val, string(m.Bytes))}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringValue(wrapped bool) lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
value := string(m.Bytes)
|
||||||
|
if wrapped {
|
||||||
|
value = unwrap(value)
|
||||||
|
}
|
||||||
|
return &token{TokenType: operationToken, Operation: createValueOperation(value, value)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func envOp(strenv bool) lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
value := string(m.Bytes)
|
||||||
|
preferences := envOpPreferences{}
|
||||||
|
|
||||||
|
if strenv {
|
||||||
|
// strenv( )
|
||||||
|
value = value[7 : len(value)-1]
|
||||||
|
preferences.StringValue = true
|
||||||
|
} else {
|
||||||
|
//env( )
|
||||||
|
value = value[4 : len(value)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
envOperation := createValueOperation(value, value)
|
||||||
|
envOperation.OperationType = envOpType
|
||||||
|
envOperation.Preferences = preferences
|
||||||
|
|
||||||
|
return &token{TokenType: operationToken, Operation: envOperation}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullValue() lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
return &token{TokenType: operationToken, Operation: createValueOperation(nil, string(m.Bytes))}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func selfToken() lex.Action {
|
||||||
|
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||||
|
op := &Operation{OperationType: selfReferenceOpType}
|
||||||
|
return &token{TokenType: operationToken, Operation: op}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initLexer() (*lex.Lexer, error) {
|
||||||
|
lexer := lex.NewLexer()
|
||||||
|
lexer.Add([]byte(`\(`), literalToken(openBracket, false))
|
||||||
|
lexer.Add([]byte(`\)`), literalToken(closeBracket, true))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false))
|
||||||
|
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
|
||||||
|
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: false}}))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
|
||||||
|
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`,`), opToken(unionOpType))
|
||||||
|
lexer.Add([]byte(`:\s*`), opToken(createMapOpType))
|
||||||
|
lexer.Add([]byte(`length`), opToken(lengthOpType))
|
||||||
|
lexer.Add([]byte(`sortKeys`), opToken(sortKeysOpType))
|
||||||
|
lexer.Add([]byte(`select`), opToken(selectOpType))
|
||||||
|
lexer.Add([]byte(`has`), opToken(hasOpType))
|
||||||
|
lexer.Add([]byte(`explode`), opToken(explodeOpType))
|
||||||
|
lexer.Add([]byte(`or`), opToken(orOpType))
|
||||||
|
lexer.Add([]byte(`and`), opToken(andOpType))
|
||||||
|
lexer.Add([]byte(`not`), opToken(notOpType))
|
||||||
|
lexer.Add([]byte(`\/\/`), opToken(alternativeOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType))
|
||||||
|
lexer.Add([]byte(`di`), opToken(getDocumentIndexOpType))
|
||||||
|
lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`join`), opToken(joinStringOpType))
|
||||||
|
lexer.Add([]byte(`split`), opToken(splitStringOpType))
|
||||||
|
lexer.Add([]byte(`keys`), opToken(keysOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`tag`), opAssignableToken(getTagOpType, assignTagOpType))
|
||||||
|
lexer.Add([]byte(`anchor`), opAssignableToken(getAnchorOpType, assignAnchorOpType))
|
||||||
|
lexer.Add([]byte(`alias`), opAssignableToken(getAliasOptype, assignAliasOpType))
|
||||||
|
lexer.Add([]byte(`filename`), opToken(getFilenameOpType))
|
||||||
|
lexer.Add([]byte(`fileIndex`), opToken(getFileIndexOpType))
|
||||||
|
lexer.Add([]byte(`fi`), opToken(getFileIndexOpType))
|
||||||
|
lexer.Add([]byte(`path`), opToken(getPathOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{LineComment: true}))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`headComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{HeadComment: true}))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`footComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{FootComment: true}))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
|
||||||
|
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`collect`), opToken(collectOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType))
|
||||||
|
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`del`), opToken(deleteChildOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`\s*\|=\s*`), assignOpToken(true))
|
||||||
|
|
||||||
|
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
|
||||||
|
|
||||||
|
lexer.Add([]byte(`\."[^ "]+"`), pathToken(true))
|
||||||
|
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false))
|
||||||
|
lexer.Add([]byte(`\.`), selfToken())
|
||||||
|
|
||||||
|
lexer.Add([]byte(`\|`), opToken(pipeOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`-?\d+(\.\d+)`), floatValue())
|
||||||
|
lexer.Add([]byte(`-?[1-9](\.\d+)?[Ee][-+]?\d+`), floatValue())
|
||||||
|
lexer.Add([]byte(`-?\d+`), numberValue())
|
||||||
|
|
||||||
|
lexer.Add([]byte(`[Tt][Rr][Uu][Ee]`), booleanValue(true))
|
||||||
|
lexer.Add([]byte(`[Ff][Aa][Ll][Ss][Ee]`), booleanValue(false))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`[Nn][Uu][Ll][Ll]`), nullValue())
|
||||||
|
lexer.Add([]byte(`~`), nullValue())
|
||||||
|
|
||||||
|
lexer.Add([]byte(`"[^"]*"`), stringValue(true))
|
||||||
|
lexer.Add([]byte(`strenv\([^\)]+\)`), envOp(true))
|
||||||
|
lexer.Add([]byte(`env\([^\)]+\)`), envOp(false))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`\[`), literalToken(openCollect, false))
|
||||||
|
lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
|
||||||
|
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
|
||||||
|
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
|
||||||
|
lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs())
|
||||||
|
lexer.Add([]byte(`\+`), opToken(addOpType))
|
||||||
|
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
|
||||||
|
|
||||||
|
err := lexer.Compile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return lexer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type expressionTokeniser interface {
|
||||||
|
Tokenise(expression string) ([]*token, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type expressionTokeniserImpl struct {
|
||||||
|
lexer *lex.Lexer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExpressionTokeniser() expressionTokeniser {
|
||||||
|
var lexer, err = initLexer()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &expressionTokeniserImpl{lexer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *expressionTokeniserImpl) Tokenise(expression string) ([]*token, error) {
|
||||||
|
scanner, err := p.lexer.Scanner([]byte(expression))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Parsing expression: %v", err)
|
||||||
|
}
|
||||||
|
var tokens []*token
|
||||||
|
for tok, err, eof := scanner.Next(); !eof; tok, err, eof = scanner.Next() {
|
||||||
|
|
||||||
|
if tok != nil {
|
||||||
|
currentToken := tok.(*token)
|
||||||
|
log.Debugf("Tokenising %v", currentToken.toString())
|
||||||
|
tokens = append(tokens, currentToken)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Parsing expression: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var postProcessedTokens = make([]*token, 0)
|
||||||
|
|
||||||
|
skipNextToken := false
|
||||||
|
|
||||||
|
for index := range tokens {
|
||||||
|
if skipNextToken {
|
||||||
|
skipNextToken = false
|
||||||
|
} else {
|
||||||
|
postProcessedTokens, skipNextToken = p.handleToken(tokens, index, postProcessedTokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return postProcessedTokens, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postProcessedTokens []*token) (tokensAccum []*token, skipNextToken bool) {
|
||||||
|
skipNextToken = false
|
||||||
|
currentToken := tokens[index]
|
||||||
|
|
||||||
|
if currentToken.TokenType == traverseArrayCollect {
|
||||||
|
//need to put a traverse array then a collect currentToken
|
||||||
|
// do this by adding traverse then converting currentToken to collect
|
||||||
|
|
||||||
|
op := &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
|
||||||
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||||
|
|
||||||
|
currentToken = &token{TokenType: openCollect}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if index != len(tokens)-1 && currentToken.AssignOperation != nil &&
|
||||||
|
tokens[index+1].TokenType == operationToken &&
|
||||||
|
tokens[index+1].Operation.OperationType == assignOpType {
|
||||||
|
currentToken.Operation = currentToken.AssignOperation
|
||||||
|
currentToken.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
|
||||||
|
skipNextToken = true
|
||||||
|
}
|
||||||
|
|
||||||
|
postProcessedTokens = append(postProcessedTokens, currentToken)
|
||||||
|
|
||||||
|
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
|
||||||
|
tokens[index+1].TokenType == operationToken &&
|
||||||
|
tokens[index+1].Operation.OperationType == traversePathOpType {
|
||||||
|
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
|
||||||
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||||
|
}
|
||||||
|
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
|
||||||
|
tokens[index+1].TokenType == openCollect {
|
||||||
|
|
||||||
|
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
|
||||||
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||||
|
|
||||||
|
op = &Operation{OperationType: traverseArrayOpType}
|
||||||
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||||
|
}
|
||||||
|
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
|
||||||
|
tokens[index+1].TokenType == traverseArrayCollect {
|
||||||
|
|
||||||
|
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
|
||||||
|
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||||
|
|
||||||
|
}
|
||||||
|
return postProcessedTokens, skipNextToken
|
||||||
|
}
|
115
pkg/yqlib/lib.go
115
pkg/yqlib/lib.go
@ -1,3 +1,5 @@
|
|||||||
|
// Use the top level Evaluator or StreamEvaluator to evaluate expressions and return matches.
|
||||||
|
//
|
||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -11,84 +13,89 @@ import (
|
|||||||
|
|
||||||
var log = logging.MustGetLogger("yq-lib")
|
var log = logging.MustGetLogger("yq-lib")
|
||||||
|
|
||||||
type OperationType struct {
|
type operationType struct {
|
||||||
Type string
|
Type string
|
||||||
NumArgs uint // number of arguments to the op
|
NumArgs uint // number of arguments to the op
|
||||||
Precedence uint
|
Precedence uint
|
||||||
Handler OperatorHandler
|
Handler operatorHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// operators TODO:
|
// operators TODO:
|
||||||
// - keys operator for controlling key metadata (particularly anchors/aliases)
|
|
||||||
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||||
|
|
||||||
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator}
|
||||||
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
|
||||||
|
|
||||||
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
var unionOpType = &operationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: unionOperator}
|
||||||
|
|
||||||
var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: PipeOperator}
|
var pipeOpType = &operationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: pipeOperator}
|
||||||
|
|
||||||
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
|
var assignOpType = &operationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: assignUpdateOperator}
|
||||||
var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator}
|
var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator}
|
||||||
|
|
||||||
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
|
||||||
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
|
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
|
||||||
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
|
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
|
||||||
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
|
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
|
||||||
var AssignAnchor = &OperationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: AssignAnchorOperator}
|
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
|
||||||
var AssignAlias = &OperationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: AssignAliasOperator}
|
var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
|
||||||
|
|
||||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
|
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
|
||||||
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
|
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
|
||||||
var Alternative = &OperationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: AlternativeOperator}
|
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
|
||||||
|
|
||||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
|
||||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator}
|
||||||
|
|
||||||
var ShortPipe = &OperationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
|
var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: pipeOperator}
|
||||||
|
|
||||||
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
|
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
|
||||||
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
|
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
|
||||||
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
|
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
|
||||||
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
|
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
|
||||||
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
|
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
|
||||||
var GetAnchor = &OperationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: GetAnchorOperator}
|
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
|
||||||
var GetAlias = &OperationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: GetAliasOperator}
|
var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator}
|
||||||
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
|
var getAliasOptype = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: getAliasOperator}
|
||||||
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
|
var getDocumentIndexOpType = &operationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: getDocumentIndexOperator}
|
||||||
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
|
var getFilenameOpType = &operationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: getFilenameOperator}
|
||||||
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
|
var getFileIndexOpType = &operationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: getFileIndexOperator}
|
||||||
|
var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: getPathOperator}
|
||||||
|
|
||||||
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
||||||
var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator}
|
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||||
|
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
||||||
|
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
|
||||||
|
|
||||||
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
|
||||||
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
|
||||||
var TraverseArray = &OperationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: TraverseArrayOperator}
|
|
||||||
|
|
||||||
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
|
||||||
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
|
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator}
|
||||||
var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator}
|
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator}
|
||||||
var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator}
|
|
||||||
var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
|
|
||||||
|
|
||||||
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
|
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: selfOperator}
|
||||||
|
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
|
||||||
|
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
|
||||||
|
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
|
||||||
|
var emptyOpType = &operationType{Type: "EMPTY", NumArgs: 50, Handler: emptyOperator}
|
||||||
|
|
||||||
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
|
var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}
|
||||||
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
|
||||||
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
var selectOpType = &operationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: selectOperator}
|
||||||
var DeleteImmediateChild = &OperationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: DeleteImmediateChildOperator}
|
var hasOpType = &operationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: hasOperator}
|
||||||
|
var deleteChildOpType = &operationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: deleteChildOperator}
|
||||||
|
var deleteImmediateChildOpType = &operationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: deleteImmediateChildOperator}
|
||||||
|
|
||||||
type Operation struct {
|
type Operation struct {
|
||||||
OperationType *OperationType
|
OperationType *operationType
|
||||||
Value interface{}
|
Value interface{}
|
||||||
StringValue string
|
StringValue string
|
||||||
CandidateNode *CandidateNode // used for Value Path elements
|
CandidateNode *CandidateNode // used for Value Path elements
|
||||||
Preferences interface{}
|
Preferences interface{}
|
||||||
|
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateValueOperation(value interface{}, stringValue string) *Operation {
|
func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||||
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
|
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
|
||||||
node.Value = stringValue
|
node.Value = stringValue
|
||||||
|
|
||||||
@ -106,7 +113,7 @@ func CreateValueOperation(value interface{}, stringValue string) *Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Operation{
|
return &Operation{
|
||||||
OperationType: ValueOp,
|
OperationType: valueOpType,
|
||||||
Value: value,
|
Value: value,
|
||||||
StringValue: stringValue,
|
StringValue: stringValue,
|
||||||
CandidateNode: &CandidateNode{Node: &node},
|
CandidateNode: &CandidateNode{Node: &node},
|
||||||
@ -115,13 +122,11 @@ func CreateValueOperation(value interface{}, stringValue string) *Operation {
|
|||||||
|
|
||||||
// debugging purposes only
|
// debugging purposes only
|
||||||
func (p *Operation) toString() string {
|
func (p *Operation) toString() string {
|
||||||
if p.OperationType == TraversePath {
|
if p.OperationType == traversePathOpType {
|
||||||
return fmt.Sprintf("%v", p.Value)
|
return fmt.Sprintf("%v", p.Value)
|
||||||
} else if p.OperationType == DocumentFilter {
|
} else if p.OperationType == selfReferenceOpType {
|
||||||
return fmt.Sprintf("d%v", p.Value)
|
|
||||||
} else if p.OperationType == SelfReference {
|
|
||||||
return "SELF"
|
return "SELF"
|
||||||
} else if p.OperationType == ValueOp {
|
} else if p.OperationType == valueOpType {
|
||||||
return fmt.Sprintf("%v (%T)", p.Value, p.Value)
|
return fmt.Sprintf("%v (%T)", p.Value, p.Value)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf("%v", p.OperationType.Type)
|
return fmt.Sprintf("%v", p.OperationType.Type)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
func Match(name string, pattern string) (matched bool) {
|
func matchKey(name string, pattern string) (matched bool) {
|
||||||
if pattern == "" {
|
if pattern == "" {
|
||||||
return name == pattern
|
return name == pattern
|
||||||
}
|
}
|
||||||
|
@ -4,21 +4,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"container/list"
|
"container/list"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
|
func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
|
||||||
return &PathTreeNode{Operation: &Operation{OperationType: Add},
|
return &ExpressionNode{Operation: &Operation{OperationType: addOpType},
|
||||||
Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}},
|
Lhs: lhs,
|
||||||
Rhs: rhs}
|
Rhs: rhs}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func addAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
assignmentOp := &Operation{OperationType: Assign}
|
assignmentOp := &Operation{OperationType: assignOpType}
|
||||||
assignmentOp.Preferences = &AssignOpPreferences{true}
|
assignmentOp.UpdateAssign = false
|
||||||
|
|
||||||
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
|
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)}
|
||||||
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
|
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,24 +37,24 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func addOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("Add operator")
|
log.Debugf("Add operator")
|
||||||
|
|
||||||
return crossFunction(d, matchingNodes, pathNode, add)
|
return crossFunction(d, matchingNodes, expressionNode, add)
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhs.Node = UnwrapDoc(lhs.Node)
|
lhs.Node = unwrapDoc(lhs.Node)
|
||||||
rhs.Node = UnwrapDoc(rhs.Node)
|
rhs.Node = unwrapDoc(rhs.Node)
|
||||||
|
|
||||||
target := &CandidateNode{
|
|
||||||
Path: lhs.Path,
|
|
||||||
Document: lhs.Document,
|
|
||||||
Filename: lhs.Filename,
|
|
||||||
Node: &yaml.Node{},
|
|
||||||
}
|
|
||||||
lhsNode := lhs.Node
|
lhsNode := lhs.Node
|
||||||
|
|
||||||
|
if lhsNode.Tag == "!!null" {
|
||||||
|
return lhs.CreateChild(nil, rhs.Node), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
target := lhs.CreateChild(nil, &yaml.Node{})
|
||||||
|
|
||||||
switch lhsNode.Kind {
|
switch lhsNode.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
return nil, fmt.Errorf("Maps not yet supported for addition")
|
return nil, fmt.Errorf("Maps not yet supported for addition")
|
||||||
@ -63,7 +64,48 @@ func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Candida
|
|||||||
target.Node.Tag = "!!seq"
|
target.Node.Tag = "!!seq"
|
||||||
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||||
case yaml.ScalarNode:
|
case yaml.ScalarNode:
|
||||||
return nil, fmt.Errorf("Scalars not yet supported for addition")
|
if rhs.Node.Kind != yaml.ScalarNode {
|
||||||
|
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
|
||||||
|
}
|
||||||
|
target.Node.Kind = yaml.ScalarNode
|
||||||
|
target.Node.Style = lhsNode.Style
|
||||||
|
return addScalars(target, lhsNode, rhs.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) {
|
||||||
|
|
||||||
|
if lhs.Tag == "!!str" {
|
||||||
|
target.Node.Tag = "!!str"
|
||||||
|
target.Node.Value = lhs.Value + rhs.Value
|
||||||
|
} else if lhs.Tag == "!!int" && rhs.Tag == "!!int" {
|
||||||
|
lhsNum, err := strconv.Atoi(lhs.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rhsNum, err := strconv.Atoi(rhs.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sum := lhsNum + rhsNum
|
||||||
|
target.Node.Tag = "!!int"
|
||||||
|
target.Node.Value = fmt.Sprintf("%v", sum)
|
||||||
|
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") {
|
||||||
|
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sum := lhsNum + rhsNum
|
||||||
|
target.Node.Tag = "!!float"
|
||||||
|
target.Node.Value = fmt.Sprintf("%v", sum)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("%v cannot be added to %v", lhs.Tag, rhs.Tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return target, nil
|
return target, nil
|
||||||
|
@ -5,6 +5,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var addOperatorScenarios = []expressionScenario{
|
var addOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `[{a: foo, b: bar}, {a: 1, b: 2}]`,
|
||||||
|
expression: ".[] | .a + .b",
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[0 a], (!!str)::foobar\n",
|
||||||
|
"D0, P[1 a], (!!int)::3\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Concatenate and assign arrays",
|
description: "Concatenate and assign arrays",
|
||||||
document: `{a: {val: thing, b: [cat,dog]}}`,
|
document: `{a: {val: thing, b: [cat,dog]}}`,
|
||||||
@ -38,11 +47,11 @@ var addOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Add object to array",
|
description: "Add new object to array",
|
||||||
document: `{a: [1,2], c: {cat: meow}}`,
|
document: `a: [{dog: woof}]`,
|
||||||
expression: `.a + .c`,
|
expression: `.a + {"cat": "meow"}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[a], (!!seq)::[1, 2, {cat: meow}]\n",
|
"D0, P[a], (!!seq)::[{dog: woof}, {cat: meow}]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -61,6 +70,56 @@ var addOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
|
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "String concatenation",
|
||||||
|
document: `{a: cat, b: meow}`,
|
||||||
|
expression: `.a = .a + .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Relative string concatenation",
|
||||||
|
document: `{a: cat, b: meow}`,
|
||||||
|
expression: `.a += .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Number addition - float",
|
||||||
|
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
|
||||||
|
document: `{a: 3, b: 4.9}`,
|
||||||
|
expression: `.a = .a + .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: 7.9, b: 4.9}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Number addition - int",
|
||||||
|
subdescription: "If both the lhs and rhs are ints then the expression will be calculated with ints.",
|
||||||
|
document: `{a: 3, b: 4}`,
|
||||||
|
expression: `.a = .a + .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: 7, b: 4}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Increment number",
|
||||||
|
document: `{a: 3}`,
|
||||||
|
expression: `.a += 1`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: 4}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Add to null",
|
||||||
|
subdescription: "Adding to null simply returns the rhs",
|
||||||
|
expression: `null + "cat"`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!str)::cat\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddOperatorScenarios(t *testing.T) {
|
func TestAddOperatorScenarios(t *testing.T) {
|
||||||
|
@ -7,14 +7,14 @@ import (
|
|||||||
// corssFunction no matches
|
// corssFunction no matches
|
||||||
// can boolean use crossfunction
|
// can boolean use crossfunction
|
||||||
|
|
||||||
func AlternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func alternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- alternative")
|
log.Debugf("-- alternative")
|
||||||
return crossFunction(d, matchingNodes, pathNode, alternativeFunc)
|
return crossFunction(d, matchingNodes, expressionNode, alternativeFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhs.Node = UnwrapDoc(lhs.Node)
|
lhs.Node = unwrapDoc(lhs.Node)
|
||||||
rhs.Node = UnwrapDoc(rhs.Node)
|
rhs.Node = unwrapDoc(rhs.Node)
|
||||||
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
|
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
|
||||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||||
|
|
||||||
|
@ -3,23 +3,25 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func assignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
log.Debugf("AssignAlias operator!")
|
log.Debugf("AssignAlias operator!")
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aliasName := ""
|
aliasName := ""
|
||||||
if rhs.Front() != nil {
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
|
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -28,39 +30,53 @@ func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
|
|||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
log.Debugf("Setting aliasName : %v", candidate.GetKey())
|
log.Debugf("Setting aliasName : %v", candidate.GetKey())
|
||||||
|
|
||||||
|
if expressionNode.Operation.UpdateAssign {
|
||||||
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
candidate.Node.Kind = yaml.AliasNode
|
candidate.Node.Kind = yaml.AliasNode
|
||||||
candidate.Node.Value = aliasName
|
candidate.Node.Value = aliasName
|
||||||
}
|
}
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("GetAlias operator!")
|
log.Debugf("GetAlias operator!")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func assignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
log.Debugf("AssignAnchor operator!")
|
log.Debugf("AssignAnchor operator!")
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
anchorName := ""
|
anchorName := ""
|
||||||
if rhs.Front() != nil {
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
|
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -69,12 +85,24 @@ func AssignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
|
|||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
log.Debugf("Setting anchorName of : %v", candidate.GetKey())
|
log.Debugf("Setting anchorName of : %v", candidate.GetKey())
|
||||||
|
|
||||||
|
if expressionNode.Operation.UpdateAssign {
|
||||||
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
candidate.Node.Anchor = anchorName
|
candidate.Node.Anchor = anchorName
|
||||||
}
|
}
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("GetAnchor operator!")
|
log.Debugf("GetAnchor operator!")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
@ -82,19 +110,19 @@ func GetAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode
|
|||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
anchor := candidate.Node.Anchor
|
anchor := candidate.Node.Anchor
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func explodeOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- ExplodeOperation")
|
log.Debugf("-- ExplodeOperation")
|
||||||
|
|
||||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -21,6 +21,14 @@ var anchorOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::a: &foobar cat\n",
|
"D0, P[], (doc)::a: &foobar cat\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Set anchor relatively using assign-update",
|
||||||
|
document: `a: {b: cat}`,
|
||||||
|
expression: `.a anchor |= .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::a: &cat {b: cat}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Get alias",
|
description: "Get alias",
|
||||||
document: `{b: &billyBob meow, a: *billyBob}`,
|
document: `{b: &billyBob meow, a: *billyBob}`,
|
||||||
@ -37,6 +45,14 @@ var anchorOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
|
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Set alias relatively using assign-update",
|
||||||
|
document: `{b: &meow purr, a: {f: meow}}`,
|
||||||
|
expression: `.a alias |= .f`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Explode alias and anchor",
|
description: "Explode alias and anchor",
|
||||||
document: `{f : {a: &a cat, b: *a}}`,
|
document: `{f : {a: &a cat, b: *a}}`,
|
||||||
|
@ -2,27 +2,21 @@ package yqlib
|
|||||||
|
|
||||||
import "container/list"
|
import "container/list"
|
||||||
|
|
||||||
type AssignOpPreferences struct {
|
func assignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
UpdateAssign bool
|
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
|
||||||
}
|
|
||||||
|
|
||||||
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
|
|
||||||
|
|
||||||
var rhs *list.List
|
var rhs *list.List
|
||||||
if !preferences.UpdateAssign {
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
rhs, err = d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
if preferences.UpdateAssign {
|
if expressionNode.Operation.UpdateAssign {
|
||||||
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -33,27 +27,25 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
|
|||||||
first := rhs.Front()
|
first := rhs.Front()
|
||||||
|
|
||||||
if first != nil {
|
if first != nil {
|
||||||
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
rhsCandidate := first.Value.(*CandidateNode)
|
||||||
|
rhsCandidate.Node = unwrapDoc(rhsCandidate.Node)
|
||||||
|
candidate.UpdateFrom(rhsCandidate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// // if there was nothing given, perhaps we are creating a new yaml doc
|
|
||||||
// if matchingNodes.Len() == 0 {
|
|
||||||
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
|
|
||||||
// return lhs, nil
|
|
||||||
// }
|
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// does not update content or values
|
// does not update content or values
|
||||||
func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func assignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -20,6 +20,16 @@ var assignOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::{a: {g: foof}}\n",
|
"D0, P[], (doc)::{a: {g: foof}}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Update node from another file",
|
||||||
|
subdescription: "Note this will also work when the second file is a scalar (string/number)",
|
||||||
|
document: `{a: apples}`,
|
||||||
|
document2: "{b: bob}",
|
||||||
|
expression: `select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: {b: bob}}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Update node to be the sibling value",
|
description: "Update node to be the sibling value",
|
||||||
document: `{a: {b: child}, b: sibling}`,
|
document: `{a: {b: child}, b: sibling}`,
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func isTruthy(c *CandidateNode) (bool, error) {
|
func isTruthy(c *CandidateNode) (bool, error) {
|
||||||
node := UnwrapDoc(c.Node)
|
node := unwrapDoc(c.Node)
|
||||||
value := true
|
value := true
|
||||||
|
|
||||||
if node.Tag == "!!null" {
|
if node.Tag == "!!null" {
|
||||||
@ -27,8 +27,8 @@ type boolOp func(bool, bool) bool
|
|||||||
|
|
||||||
func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhs.Node = UnwrapDoc(lhs.Node)
|
lhs.Node = unwrapDoc(lhs.Node)
|
||||||
rhs.Node = UnwrapDoc(rhs.Node)
|
rhs.Node = unwrapDoc(rhs.Node)
|
||||||
|
|
||||||
lhsTrue, errDecoding := isTruthy(lhs)
|
lhsTrue, errDecoding := isTruthy(lhs)
|
||||||
if errDecoding != nil {
|
if errDecoding != nil {
|
||||||
@ -44,23 +44,23 @@ func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func orOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- orOp")
|
log.Debugf("-- orOp")
|
||||||
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
|
return crossFunction(d, matchingNodes, expressionNode, performBoolOp(
|
||||||
func(b1 bool, b2 bool) bool {
|
func(b1 bool, b2 bool) bool {
|
||||||
return b1 || b2
|
return b1 || b2
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func andOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- AndOp")
|
log.Debugf("-- AndOp")
|
||||||
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
|
return crossFunction(d, matchingNodes, expressionNode, performBoolOp(
|
||||||
func(b1 bool, b2 bool) bool {
|
func(b1 bool, b2 bool) bool {
|
||||||
return b1 && b2
|
return b1 && b2
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func notOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- notOperation")
|
log.Debugf("-- notOperation")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func collectOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- collectOperation")
|
log.Debugf("-- collectOperation")
|
||||||
|
|
||||||
if matchMap.Len() == 0 {
|
if matchMap.Len() == 0 {
|
||||||
@ -18,21 +18,22 @@ func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTr
|
|||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||||
|
var collectC *CandidateNode
|
||||||
var document uint = 0
|
if matchMap.Front() != nil {
|
||||||
var path []interface{}
|
collectC = matchMap.Front().Value.(*CandidateNode).CreateChild(nil, node)
|
||||||
|
if len(collectC.Path) > 0 {
|
||||||
|
collectC.Path = collectC.Path[:len(collectC.Path)-1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
collectC = &CandidateNode{Node: node}
|
||||||
|
}
|
||||||
|
|
||||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
log.Debugf("Collecting %v", NodeToString(candidate))
|
log.Debugf("Collecting %v", NodeToString(candidate))
|
||||||
if path == nil && candidate.Path != nil && len(candidate.Path) > 1 {
|
node.Content = append(node.Content, unwrapDoc(candidate.Node))
|
||||||
path = candidate.Path[:len(candidate.Path)-1]
|
|
||||||
document = candidate.Document
|
|
||||||
}
|
|
||||||
node.Content = append(node.Content, candidate.Node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
collectC := &CandidateNode{Node: node, Document: document, Path: path}
|
|
||||||
results.PushBack(collectC)
|
results.PushBack(collectC)
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
...
|
...
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func collectObjectOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- collectObjectOperation")
|
log.Debugf("-- collectObjectOperation")
|
||||||
|
|
||||||
if matchMap.Len() == 0 {
|
if matchMap.Len() == 0 {
|
||||||
@ -35,7 +35,7 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
|
|||||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidateNode := el.Value.(*CandidateNode)
|
candidateNode := el.Value.(*CandidateNode)
|
||||||
for i := 0; i < len(first.Node.Content); i++ {
|
for i := 0; i < len(first.Node.Content); i++ {
|
||||||
rotated[i].PushBack(createChildCandidate(candidateNode, i))
|
rotated[i].PushBack(candidateNode.CreateChild(i, candidateNode.Node.Content[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,15 +52,6 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode {
|
|
||||||
return &CandidateNode{
|
|
||||||
Document: candidate.Document,
|
|
||||||
Path: candidate.CreateChildPath(index),
|
|
||||||
Filename: candidate.Filename,
|
|
||||||
Node: candidate.Node.Content[index],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) {
|
func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) {
|
||||||
if remainingMatches.Len() == 0 {
|
if remainingMatches.Len() == 0 {
|
||||||
return aggregate, nil
|
return aggregate, nil
|
||||||
@ -68,8 +59,8 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
|
|||||||
|
|
||||||
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
|
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
|
||||||
|
|
||||||
splatted, err := Splat(d, nodeToMap(candidate),
|
splatted, err := splat(d, nodeToMap(candidate),
|
||||||
&TraversePreferences{FollowAlias: false, IncludeMapKeys: false})
|
traversePreferences{DontFollowAlias: true, IncludeMapKeys: false})
|
||||||
|
|
||||||
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||||
splatEl.Value.(*CandidateNode).Path = nil
|
splatEl.Value.(*CandidateNode).Path = nil
|
||||||
@ -96,7 +87,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
|
|||||||
|
|
||||||
newCandidate.Path = nil
|
newCandidate.Path = nil
|
||||||
|
|
||||||
newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -110,5 +110,5 @@ func TestCollectObjectOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range collectObjectOperatorScenarios {
|
for _, tt := range collectObjectOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
documentScenarios(t, "Collect into Object", collectObjectOperatorScenarios)
|
documentScenarios(t, "Create, Collect into Object", collectObjectOperatorScenarios)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,15 @@ var collectOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (!!seq)::[]\n",
|
"D0, P[], (!!seq)::[]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: "{a: apple}\n---\n{b: frog}",
|
||||||
|
|
||||||
|
expression: `[.]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- {a: apple}\n- {b: frog}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: ``,
|
document: ``,
|
||||||
|
@ -4,38 +4,53 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommentOpPreferences struct {
|
type commentOpPreferences struct {
|
||||||
LineComment bool
|
LineComment bool
|
||||||
HeadComment bool
|
HeadComment bool
|
||||||
FootComment bool
|
FootComment bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func assignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
log.Debugf("AssignComments operator!")
|
log.Debugf("AssignComments operator!")
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
||||||
|
|
||||||
comment := ""
|
comment := ""
|
||||||
if rhs.Front() != nil {
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
|
||||||
|
|
||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
|
if expressionNode.Operation.UpdateAssign {
|
||||||
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("Setting comment of : %v", candidate.GetKey())
|
log.Debugf("Setting comment of : %v", candidate.GetKey())
|
||||||
if preferences.LineComment {
|
if preferences.LineComment {
|
||||||
candidate.Node.LineComment = comment
|
candidate.Node.LineComment = comment
|
||||||
@ -51,8 +66,8 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
|
|||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
||||||
log.Debugf("GetComments operator!")
|
log.Debugf("GetComments operator!")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
@ -69,8 +84,8 @@ func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
|
|||||||
comment = strings.Replace(comment, "# ", "", 1)
|
comment = strings.Replace(comment, "# ", "", 1)
|
||||||
|
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,39 @@ var commentOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::a: cat # single\n",
|
"D0, P[], (doc)::a: cat # single\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: "a: cat\nb: dog",
|
||||||
|
expression: `.a lineComment=.b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::a: cat # dog\nb: dog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: "a: cat\n---\na: dog",
|
||||||
|
expression: `.a lineComment |= documentIndex`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::a: cat # 0\n",
|
||||||
|
"D1, P[], (doc)::a: dog # 1\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Use update assign to perform relative updates",
|
||||||
|
document: "a: cat\nb: dog",
|
||||||
|
expression: `.. lineComment |= .`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::a: cat # cat\nb: dog # dog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: "a: cat\nb: dog",
|
||||||
|
expression: `.. comments |= .`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::a: cat # cat\n# cat\n\n# cat\nb: dog # dog\n# dog\n\n# dog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Set head comment",
|
description: "Set head comment",
|
||||||
document: `a: cat`,
|
document: `a: cat`,
|
||||||
@ -38,11 +71,12 @@ var commentOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Remove all comments",
|
description: "Remove all comments",
|
||||||
document: "# hi\n\na: cat # comment\n\n# great\n",
|
subdescription: "Note the use of `...` to ensure key nodes are included.",
|
||||||
expression: `.. comments=""`,
|
document: "# hi\n\na: cat # comment\n\n# great\n\nb: # key comment",
|
||||||
|
expression: `... comments=""`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::a: cat\n",
|
"D0, P[], (!!map)::a: cat\nb:\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func createMapOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- createMapOperation")
|
log.Debugf("-- createMapOperation")
|
||||||
|
|
||||||
//each matchingNodes entry should turn into a sequence of keys to create.
|
//each matchingNodes entry should turn into a sequence of keys to create.
|
||||||
@ -22,14 +22,14 @@ func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode
|
|||||||
|
|
||||||
for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
|
for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
|
||||||
matchingNode := matchingNodeEl.Value.(*CandidateNode)
|
matchingNode := matchingNodeEl.Value.(*CandidateNode)
|
||||||
sequenceNode, err := sequenceFor(d, matchingNode, pathNode)
|
sequenceNode, err := sequenceFor(d, matchingNode, expressionNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sequences.PushBack(sequenceNode)
|
sequences.PushBack(sequenceNode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sequenceNode, err := sequenceFor(d, nil, pathNode)
|
sequenceNode, err := sequenceFor(d, nil, expressionNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathTreeNode) (*CandidateNode, error) {
|
func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, expressionNode *ExpressionNode) (*CandidateNode, error) {
|
||||||
var path []interface{}
|
var path []interface{}
|
||||||
var document uint = 0
|
var document uint = 0
|
||||||
var matches = list.New()
|
var matches = list.New()
|
||||||
@ -51,14 +51,14 @@ func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *Pa
|
|||||||
matches = nodeToMap(matchingNode)
|
matches = nodeToMap(matchingNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapPairs, err := crossFunction(d, matches, pathNode,
|
mapPairs, err := crossFunction(d, matches, expressionNode,
|
||||||
func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||||
log.Debugf("LHS:", NodeToString(lhs))
|
log.Debugf("LHS:", NodeToString(lhs))
|
||||||
log.Debugf("RHS:", NodeToString(rhs))
|
log.Debugf("RHS:", NodeToString(rhs))
|
||||||
node.Content = []*yaml.Node{
|
node.Content = []*yaml.Node{
|
||||||
UnwrapDoc(lhs.Node),
|
unwrapDoc(lhs.Node),
|
||||||
UnwrapDoc(rhs.Node),
|
unwrapDoc(rhs.Node),
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CandidateNode{Node: &node, Document: document, Path: path}, nil
|
return &CandidateNode{Node: &node, Document: document, Path: path}, nil
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func deleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
nodesToDelete, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
nodesToDelete, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -19,13 +19,13 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
|
|||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
deleteImmediateChildOp := &Operation{
|
deleteImmediateChildOp := &Operation{
|
||||||
OperationType: DeleteImmediateChild,
|
OperationType: deleteImmediateChildOpType,
|
||||||
Value: candidate.Path[len(candidate.Path)-1],
|
Value: candidate.Path[len(candidate.Path)-1],
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteImmediateChildOpNode := &PathTreeNode{
|
deleteImmediateChildOpNode := &ExpressionNode{
|
||||||
Operation: deleteImmediateChildOp,
|
Operation: deleteImmediateChildOp,
|
||||||
Rhs: createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]),
|
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
|
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
|
||||||
@ -36,20 +36,20 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
|
|||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func deleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
parents, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
parents, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
childPath := pathNode.Operation.Value
|
childPath := expressionNode.Operation.Value
|
||||||
|
|
||||||
log.Debug("childPath to remove %v", childPath)
|
log.Debug("childPath to remove %v", childPath)
|
||||||
|
|
||||||
for el := parents.Front(); el != nil; el = el.Next() {
|
for el := parents.Front(); el != nil; el = el.Next() {
|
||||||
parent := el.Value.(*CandidateNode)
|
parent := el.Value.(*CandidateNode)
|
||||||
parentNode := UnwrapDoc(parent.Node)
|
parentNode := unwrapDoc(parent.Node)
|
||||||
if parentNode.Kind == yaml.MappingNode {
|
if parentNode.Kind == yaml.MappingNode {
|
||||||
deleteFromMap(parent, childPath)
|
deleteFromMap(parent, childPath)
|
||||||
} else if parentNode.Kind == yaml.SequenceNode {
|
} else if parentNode.Kind == yaml.SequenceNode {
|
||||||
@ -64,7 +64,7 @@ func DeleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List
|
|||||||
|
|
||||||
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
||||||
log.Debug("deleteFromMap")
|
log.Debug("deleteFromMap")
|
||||||
node := UnwrapDoc(candidate.Node)
|
node := unwrapDoc(candidate.Node)
|
||||||
contents := node.Content
|
contents := node.Content
|
||||||
newContents := make([]*yaml.Node, 0)
|
newContents := make([]*yaml.Node, 0)
|
||||||
|
|
||||||
@ -72,11 +72,7 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
|||||||
key := contents[index]
|
key := contents[index]
|
||||||
value := contents[index+1]
|
value := contents[index+1]
|
||||||
|
|
||||||
childCandidate := &CandidateNode{
|
childCandidate := candidate.CreateChild(key.Value, value)
|
||||||
Node: value,
|
|
||||||
Document: candidate.Document,
|
|
||||||
Path: candidate.CreateChildPath(key.Value),
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldDelete := key.Value == childPath
|
shouldDelete := key.Value == childPath
|
||||||
|
|
||||||
@ -91,7 +87,7 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
|||||||
|
|
||||||
func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
|
func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
|
||||||
log.Debug("deleteFromArray")
|
log.Debug("deleteFromArray")
|
||||||
node := UnwrapDoc(candidate.Node)
|
node := unwrapDoc(candidate.Node)
|
||||||
contents := node.Content
|
contents := node.Content
|
||||||
newContents := make([]*yaml.Node, 0)
|
newContents := make([]*yaml.Node, 0)
|
||||||
|
|
||||||
|
@ -61,6 +61,14 @@ var deleteOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::{b: dog}\n",
|
"D0, P[], (doc)::{b: dog}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Recursively delete matching keys",
|
||||||
|
document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
|
||||||
|
expression: `del(.. | select(has("name")).name)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: {b: {age: 12}}}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteOperatorScenarios(t *testing.T) {
|
func TestDeleteOperatorScenarios(t *testing.T) {
|
||||||
|
@ -7,13 +7,13 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"}
|
||||||
scalar := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
scalar := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(scalar)
|
results.PushBack(scalar)
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
|
@ -14,6 +14,15 @@ var documentIndexScenarios = []expressionScenario{
|
|||||||
"D1, P[a], (!!int)::1\n",
|
"D1, P[a], (!!int)::1\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Retrieve a document index, shorthand",
|
||||||
|
document: "a: cat\n---\na: frog\n",
|
||||||
|
expression: `.a | di`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!int)::0\n",
|
||||||
|
"D1, P[a], (!!int)::1\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Filter by document index",
|
description: "Filter by document index",
|
||||||
document: "a: cat\n---\na: frog\n",
|
document: "a: cat\n---\na: frog\n",
|
||||||
@ -22,6 +31,14 @@ var documentIndexScenarios = []expressionScenario{
|
|||||||
"D1, P[], (doc)::a: frog\n",
|
"D1, P[], (doc)::a: frog\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Filter by document index shorthand",
|
||||||
|
document: "a: cat\n---\na: frog\n",
|
||||||
|
expression: `select(di == 1)`,
|
||||||
|
expected: []string{
|
||||||
|
"D1, P[], (doc)::a: frog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Print Document Index with matches",
|
description: "Print Document Index with matches",
|
||||||
document: "a: cat\n---\na: frog\n",
|
document: "a: cat\n---\na: frog\n",
|
||||||
|
50
pkg/yqlib/operator_env.go
Normal file
50
pkg/yqlib/operator_env.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type envOpPreferences struct {
|
||||||
|
StringValue bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func envOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
envName := expressionNode.Operation.CandidateNode.Node.Value
|
||||||
|
log.Debug("EnvOperator, env name:", envName)
|
||||||
|
|
||||||
|
rawValue := os.Getenv(envName)
|
||||||
|
|
||||||
|
preferences := expressionNode.Operation.Preferences.(envOpPreferences)
|
||||||
|
|
||||||
|
var node *yaml.Node
|
||||||
|
if preferences.StringValue {
|
||||||
|
node = &yaml.Node{
|
||||||
|
Kind: yaml.ScalarNode,
|
||||||
|
Tag: "!!str",
|
||||||
|
Value: rawValue,
|
||||||
|
}
|
||||||
|
} else if rawValue == "" {
|
||||||
|
return nil, fmt.Errorf("Value for env variable '%v' not provided in env()", envName)
|
||||||
|
} else {
|
||||||
|
var dataBucket yaml.Node
|
||||||
|
decoder := yaml.NewDecoder(strings.NewReader(rawValue))
|
||||||
|
errorReading := decoder.Decode(&dataBucket)
|
||||||
|
if errorReading != nil {
|
||||||
|
return nil, errorReading
|
||||||
|
}
|
||||||
|
//first node is a doc
|
||||||
|
node = unwrapDoc(&dataBucket)
|
||||||
|
}
|
||||||
|
log.Debug("ENV tag", node.Tag)
|
||||||
|
log.Debug("ENV value", node.Value)
|
||||||
|
log.Debug("ENV Kind", node.Kind)
|
||||||
|
|
||||||
|
target := &CandidateNode{Node: node}
|
||||||
|
|
||||||
|
return nodeToMap(target), nil
|
||||||
|
}
|
72
pkg/yqlib/operator_env_test.go
Normal file
72
pkg/yqlib/operator_env_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var envOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Read string environment variable",
|
||||||
|
environmentVariable: "cat meow",
|
||||||
|
expression: `.a = env(myenv)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], ()::a: cat meow\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Read boolean environment variable",
|
||||||
|
environmentVariable: "true",
|
||||||
|
expression: `.a = env(myenv)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], ()::a: true\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Read numeric environment variable",
|
||||||
|
environmentVariable: "12",
|
||||||
|
expression: `.a = env(myenv)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], ()::a: 12\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Read yaml environment variable",
|
||||||
|
environmentVariable: "{b: fish}",
|
||||||
|
expression: `.a = env(myenv)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], ()::a: {b: fish}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Read boolean environment variable as a string",
|
||||||
|
environmentVariable: "true",
|
||||||
|
expression: `.a = strenv(myenv)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], ()::a: \"true\"\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Read numeric environment variable as a string",
|
||||||
|
environmentVariable: "12",
|
||||||
|
expression: `.a = strenv(myenv)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], ()::a: \"12\"\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Dynamic key lookup with environment variable",
|
||||||
|
environmentVariable: "cat",
|
||||||
|
document: `{cat: meow, dog: woof}`,
|
||||||
|
expression: `.[env(myenv)]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[cat], (!!str)::meow\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range envOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "Env Variable Operators", envOperatorScenarios)
|
||||||
|
}
|
@ -4,18 +4,21 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EqualsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func equalsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- equalsOperation")
|
log.Debugf("-- equalsOperation")
|
||||||
return crossFunction(d, matchingNodes, pathNode, isEquals)
|
return crossFunction(d, matchingNodes, expressionNode, isEquals)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
value := false
|
value := false
|
||||||
|
|
||||||
if lhs.Node.Tag == "!!null" {
|
lhsNode := unwrapDoc(lhs.Node)
|
||||||
value = (rhs.Node.Tag == "!!null")
|
rhsNode := unwrapDoc(rhs.Node)
|
||||||
|
|
||||||
|
if lhsNode.Tag == "!!null" {
|
||||||
|
value = (rhsNode.Tag == "!!null")
|
||||||
} else {
|
} else {
|
||||||
value = Match(lhs.Node.Value, rhs.Node.Value)
|
value = matchKey(lhsNode.Value, rhsNode.Value)
|
||||||
}
|
}
|
||||||
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
|
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
|
||||||
return createBooleanCandidate(lhs, value), nil
|
return createBooleanCandidate(lhs, value), nil
|
||||||
|
@ -5,6 +5,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var equalsOperatorScenarios = []expressionScenario{
|
var equalsOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: "cat",
|
||||||
|
document2: "dog",
|
||||||
|
expression: "select(fi==0) == select(fi==1)",
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!bool)::false\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Match string",
|
description: "Match string",
|
||||||
document: `[cat,goat,dog]`,
|
document: `[cat,goat,dog]`,
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("GetFilename")
|
log.Debugf("GetFilename")
|
||||||
|
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
@ -15,14 +15,14 @@ func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
|
|||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("GetFileIndex")
|
log.Debugf("GetFileIndex")
|
||||||
|
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
@ -30,8 +30,8 @@ func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
|
|||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
@ -21,6 +21,22 @@ var fileOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (!!int)::0\n",
|
"D0, P[], (!!int)::0\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Get file index alias",
|
||||||
|
document: `{a: cat}`,
|
||||||
|
expression: `fi`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!int)::0\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: "a: cat\nb: dog",
|
||||||
|
expression: `.. lineComment |= filename`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::a: cat # sample.yml\nb: dog # sample.yml\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileOperatorsScenarios(t *testing.T) {
|
func TestFileOperatorsScenarios(t *testing.T) {
|
||||||
|
@ -7,12 +7,12 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func hasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
log.Debugf("-- hasOperation")
|
log.Debugf("-- hasOperation")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
wanted := rhs.Front().Value.(*CandidateNode).Node
|
wanted := rhs.Front().Value.(*CandidateNode).Node
|
||||||
wantedKey := wanted.Value
|
wantedKey := wanted.Value
|
||||||
|
|
||||||
@ -24,8 +24,9 @@ func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
|
|||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
// grab the first value
|
// grab the first value
|
||||||
var contents = candidate.Node.Content
|
candidateNode := unwrapDoc(candidate.Node)
|
||||||
switch candidate.Node.Kind {
|
var contents = candidateNode.Content
|
||||||
|
switch candidateNode.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
candidateHasKey := false
|
candidateHasKey := false
|
||||||
for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 {
|
for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 {
|
||||||
|
@ -5,6 +5,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var hasOperatorScenarios = []expressionScenario{
|
var hasOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `a: hello`,
|
||||||
|
expression: `has("a")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!bool)::true\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Has map key",
|
description: "Has map key",
|
||||||
document: `- a: "yes"
|
document: `- a: "yes"
|
||||||
|
54
pkg/yqlib/operator_keys.go
Normal file
54
pkg/yqlib/operator_keys.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func keysOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- keysOperator")
|
||||||
|
|
||||||
|
var results = list.New()
|
||||||
|
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
node := unwrapDoc(candidate.Node)
|
||||||
|
var targetNode *yaml.Node
|
||||||
|
if node.Kind == yaml.MappingNode {
|
||||||
|
targetNode = getMapKeys(node)
|
||||||
|
} else if node.Kind == yaml.SequenceNode {
|
||||||
|
targetNode = getIndicies(node)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Cannot get keys of %v, keys only works for maps and arrays", node.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := candidate.CreateChild(nil, targetNode)
|
||||||
|
results.PushBack(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMapKeys(node *yaml.Node) *yaml.Node {
|
||||||
|
contents := make([]*yaml.Node, 0)
|
||||||
|
for index := 0; index < len(node.Content); index = index + 2 {
|
||||||
|
contents = append(contents, node.Content[index])
|
||||||
|
}
|
||||||
|
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndicies(node *yaml.Node) *yaml.Node {
|
||||||
|
var contents = make([]*yaml.Node, len(node.Content))
|
||||||
|
|
||||||
|
for index := range node.Content {
|
||||||
|
contents[index] = &yaml.Node{
|
||||||
|
Kind: yaml.ScalarNode,
|
||||||
|
Tag: "!!int",
|
||||||
|
Value: fmt.Sprintf("%v", index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
|
||||||
|
}
|
47
pkg/yqlib/operator_keys_test.go
Normal file
47
pkg/yqlib/operator_keys_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var keysOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Map keys",
|
||||||
|
document: `{dog: woof, cat: meow}`,
|
||||||
|
expression: `keys`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- dog\n- cat\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{}`,
|
||||||
|
expression: `keys`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Array keys",
|
||||||
|
document: `[apple, banana]`,
|
||||||
|
expression: `keys`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- 0\n- 1\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `[]`,
|
||||||
|
expression: `keys`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeysOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range keysOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "Keys", keysOperatorScenarios)
|
||||||
|
}
|
@ -7,13 +7,13 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func lengthOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- lengthOperation")
|
log.Debugf("-- lengthOperation")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
targetNode := UnwrapDoc(candidate.Node)
|
targetNode := unwrapDoc(candidate.Node)
|
||||||
var length int
|
var length int
|
||||||
switch targetNode.Kind {
|
switch targetNode.Kind {
|
||||||
case yaml.ScalarNode:
|
case yaml.ScalarNode:
|
||||||
@ -27,8 +27,8 @@ func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTre
|
|||||||
}
|
}
|
||||||
|
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
@ -2,29 +2,28 @@ package yqlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
|
type crossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
|
||||||
|
|
||||||
func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*list.List, error) {
|
func doCrossFunc(d *dataTreeNavigator, contextList *list.List, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (*list.List, error) {
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
var results = list.New()
|
||||||
|
lhs, err := d.GetMatchingNodes(contextList, expressionNode.Lhs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("crossFunction LHS len: %v", lhs.Len())
|
log.Debugf("crossFunction LHS len: %v", lhs.Len())
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
rhs, err := d.GetMatchingNodes(contextList, expressionNode.Rhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("crossFunction RHS len: %v", rhs.Len())
|
|
||||||
|
|
||||||
var results = list.New()
|
|
||||||
|
|
||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
lhsCandidate := el.Value.(*CandidateNode)
|
lhsCandidate := el.Value.(*CandidateNode)
|
||||||
@ -43,50 +42,90 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type MultiplyPreferences struct {
|
func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (*list.List, error) {
|
||||||
AppendArrays bool
|
var results = list.New()
|
||||||
|
|
||||||
|
var evaluateAllTogether = true
|
||||||
|
for matchEl := matchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
|
||||||
|
evaluateAllTogether = evaluateAllTogether && matchEl.Value.(*CandidateNode).EvaluateTogether
|
||||||
|
if !evaluateAllTogether {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if evaluateAllTogether {
|
||||||
|
return doCrossFunc(d, matchingNodes, expressionNode, calculation)
|
||||||
|
}
|
||||||
|
|
||||||
|
for matchEl := matchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
|
||||||
|
contextList := nodeToMap(matchEl.Value.(*CandidateNode))
|
||||||
|
innerResults, err := doCrossFunc(d, contextList, expressionNode, calculation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results.PushBackList(innerResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
type multiplyPreferences struct {
|
||||||
|
AppendArrays bool
|
||||||
|
TraversePrefs traversePreferences
|
||||||
|
}
|
||||||
|
|
||||||
|
func multiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("-- MultiplyOperator")
|
log.Debugf("-- MultiplyOperator")
|
||||||
return crossFunction(d, matchingNodes, pathNode, multiply(pathNode.Operation.Preferences.(*MultiplyPreferences)))
|
return crossFunction(d, matchingNodes, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhs.Node = UnwrapDoc(lhs.Node)
|
lhs.Node = unwrapDoc(lhs.Node)
|
||||||
rhs.Node = UnwrapDoc(rhs.Node)
|
rhs.Node = unwrapDoc(rhs.Node)
|
||||||
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
||||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||||
|
|
||||||
shouldAppendArrays := preferences.AppendArrays
|
|
||||||
|
|
||||||
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||||
|
|
||||||
var newBlank = &CandidateNode{
|
var newBlank = lhs.CreateChild(nil, &yaml.Node{})
|
||||||
Path: lhs.Path,
|
var newThing, err = mergeObjects(d, newBlank, lhs, multiplyPreferences{})
|
||||||
Document: lhs.Document,
|
|
||||||
Filename: lhs.Filename,
|
|
||||||
Node: &yaml.Node{},
|
|
||||||
}
|
|
||||||
var newThing, err = mergeObjects(d, newBlank, lhs, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return mergeObjects(d, newThing, rhs, shouldAppendArrays)
|
return mergeObjects(d, newThing, rhs, preferences)
|
||||||
|
} else if lhs.Node.Tag == "!!int" && rhs.Node.Tag == "!!int" {
|
||||||
|
return multiplyIntegers(lhs, rhs)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
|
func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
|
target := lhs.CreateChild(nil, &yaml.Node{})
|
||||||
|
target.Node.Kind = yaml.ScalarNode
|
||||||
|
target.Node.Style = lhs.Node.Style
|
||||||
|
target.Node.Tag = "!!int"
|
||||||
|
|
||||||
|
lhsNum, err := strconv.Atoi(lhs.Node.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rhsNum, err := strconv.Atoi(rhs.Node.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
target.Node.Value = fmt.Sprintf("%v", lhsNum*rhsNum)
|
||||||
|
return target, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) (*CandidateNode, error) {
|
||||||
|
shouldAppendArrays := preferences.AppendArrays
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
// shouldn't recurse arrays if appending
|
// shouldn't recurse arrays if appending
|
||||||
prefs := &RecursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
|
prefs := recursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
|
||||||
TraversePreferences: &TraversePreferences{FollowAlias: false}}
|
TraversePreferences: traversePreferences{DontFollowAlias: true}}
|
||||||
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
|
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -98,7 +137,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for el := results.Front(); el != nil; el = el.Next() {
|
for el := results.Front(); el != nil; el = el.Next() {
|
||||||
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
|
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), preferences)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -106,22 +145,22 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
|
|||||||
return lhs, nil
|
return lhs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
|
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error {
|
||||||
|
shouldAppendArrays := preferences.AppendArrays
|
||||||
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
||||||
|
|
||||||
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
||||||
|
|
||||||
assignmentOp := &Operation{OperationType: AssignAttributes}
|
assignmentOp := &Operation{OperationType: assignAttributesOpType}
|
||||||
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
||||||
assignmentOp.OperationType = Assign
|
assignmentOp.OperationType = assignOpType
|
||||||
assignmentOp.Preferences = &AssignOpPreferences{false}
|
assignmentOp.UpdateAssign = false
|
||||||
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
|
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
|
||||||
assignmentOp.OperationType = AddAssign
|
assignmentOp.OperationType = addAssignOpType
|
||||||
}
|
}
|
||||||
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
|
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs}
|
||||||
|
|
||||||
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{Operation: rhsOp}}
|
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath, preferences.TraversePrefs), Rhs: &ExpressionNode{Operation: rhsOp}}
|
||||||
|
|
||||||
_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode)
|
_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode)
|
||||||
|
|
||||||
|
@ -13,6 +13,27 @@ var multiplyOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: 2, b: 5}`,
|
||||||
|
document2: `{a: 3, b: 10}`,
|
||||||
|
expression: `.a * .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!int)::10\n",
|
||||||
|
"D0, P[a], (!!int)::20\n",
|
||||||
|
"D0, P[a], (!!int)::15\n",
|
||||||
|
"D0, P[a], (!!int)::30\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: 2}`,
|
||||||
|
document2: `{b: 10}`,
|
||||||
|
expression: `select(fi ==0) * select(fi==1)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: 2, b: 10}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
expression: `{} * {"cat":"dog"}`,
|
expression: `{} * {"cat":"dog"}`,
|
||||||
@ -107,6 +128,30 @@ b:
|
|||||||
"D0, P[a], (!!seq)::[1, 2]\n",
|
"D0, P[a], (!!seq)::[1, 2]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Merge, only existing fields",
|
||||||
|
document: `{a: {thing: one, cat: frog}, b: {missing: two, thing: two}}`,
|
||||||
|
expression: `.a *? .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!map)::{thing: two, cat: frog}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: [{thing: one}], b: [{missing: two, thing: two}]}`,
|
||||||
|
expression: `.a *? .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!seq)::[{thing: two}]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: {array: [1]}, b: {}}`,
|
||||||
|
expression: `.b *+ .a`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[b], (!!map)::{array: [1]}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Merge, appending arrays",
|
description: "Merge, appending arrays",
|
||||||
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
|
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
|
||||||
@ -115,6 +160,14 @@ b:
|
|||||||
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
|
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Merge, only existing fields, appending arrays",
|
||||||
|
document: `{a: {thing: [1,2]}, b: {thing: [3,4], another: [1]}}`,
|
||||||
|
expression: `.a *?+ .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!map)::{thing: [1, 2, 3, 4]}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Merge to prefix an element",
|
description: "Merge to prefix an element",
|
||||||
document: `{a: cat, b: dog}`,
|
document: `{a: cat, b: dog}`,
|
||||||
@ -132,11 +185,11 @@ b:
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Merge does not copy anchor names",
|
description: "Merge copies anchor names",
|
||||||
document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`,
|
document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`,
|
||||||
expression: `.c * .a`,
|
expression: `.c * .a`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[c], (!!map)::{g: thongs, c: frog}\n",
|
"D0, P[c], (!!map)::{g: thongs, c: &cat frog}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,7 @@ func createPathNodeFor(pathElement interface{}) *yaml.Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPathOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getPathOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("GetPath")
|
log.Debugf("GetPath")
|
||||||
|
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
@ -31,8 +31,8 @@ func GetPathOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *P
|
|||||||
content[pathIndex] = createPathNodeFor(path)
|
content[pathIndex] = createPathNodeFor(path)
|
||||||
}
|
}
|
||||||
node.Content = content
|
node.Content = content
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
@ -2,10 +2,10 @@ package yqlib
|
|||||||
|
|
||||||
import "container/list"
|
import "container/list"
|
||||||
|
|
||||||
func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func pipeOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return d.GetMatchingNodes(lhs, pathNode.Rhs)
|
return d.GetMatchingNodes(lhs, expressionNode.Rhs)
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,15 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RecursiveDescentPreferences struct {
|
type recursiveDescentPreferences struct {
|
||||||
TraversePreferences *TraversePreferences
|
TraversePreferences traversePreferences
|
||||||
RecurseArray bool
|
RecurseArray bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func recursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
preferences := pathNode.Operation.Preferences.(*RecursiveDescentPreferences)
|
preferences := expressionNode.Operation.Preferences.(recursiveDescentPreferences)
|
||||||
err := recursiveDecent(d, results, matchMap, preferences)
|
err := recursiveDecent(d, results, matchMap, preferences)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -23,11 +23,11 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences *RecursiveDescentPreferences) error {
|
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences recursiveDescentPreferences) error {
|
||||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
candidate.Node = UnwrapDoc(candidate.Node)
|
candidate.Node = unwrapDoc(candidate.Node)
|
||||||
|
|
||||||
log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
|
log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
|
||||||
results.PushBack(candidate)
|
results.PushBack(candidate)
|
||||||
@ -35,7 +35,7 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li
|
|||||||
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
|
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
|
||||||
(preferences.RecurseArray || candidate.Node.Kind != yaml.SequenceNode) {
|
(preferences.RecurseArray || candidate.Node.Kind != yaml.SequenceNode) {
|
||||||
|
|
||||||
children, err := Splat(d, nodeToMap(candidate), preferences.TraversePreferences)
|
children, err := splat(d, nodeToMap(candidate), preferences.TraversePreferences)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -62,6 +62,24 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[a], (!!str)::frog\n",
|
"D0, P[a], (!!str)::frog\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Recursively find nodes with keys",
|
||||||
|
subdescription: "Note that this example has wrapped the expression in `[]` to show that there are two matches returned. You do not have to wrap in `[]` in your path expression.",
|
||||||
|
document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
|
||||||
|
expression: `[.. | select(has("name"))]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- {name: frog, b: {name: blog, age: 12}}\n- {name: blog, age: 12}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Recursively find nodes with values",
|
||||||
|
document: `{a: {nameA: frog, b: {nameB: frog, age: 12}}}`,
|
||||||
|
expression: `.. | select(. == "frog")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a nameA], (!!str)::frog\n",
|
||||||
|
"D0, P[a b nameB], (!!str)::frog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Recurse map (values and keys)",
|
description: "Recurse map (values and keys)",
|
||||||
subdescription: "Note that the map key appears in the results",
|
subdescription: "Note that the map key appears in the results",
|
||||||
@ -147,7 +165,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||||
expression: `[..]`,
|
expression: `[..]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
|
"D0, P[], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -169,7 +187,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
document: mergeDocSample,
|
document: mergeDocSample,
|
||||||
expression: `.foobar | [..]`,
|
expression: `.foobar | [..]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
|
"D0, P[], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -177,7 +195,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
document: mergeDocSample,
|
document: mergeDocSample,
|
||||||
expression: `.foobar | [...]`,
|
expression: `.foobar | [...]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- c\n- foobar_c\n- !!merge <<\n- *foo\n- thing\n- foobar_thing\n",
|
"D0, P[], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- c\n- foobar_c\n- !!merge <<\n- *foo\n- thing\n- foobar_thing\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -215,5 +233,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range recursiveDescentOperatorScenarios {
|
for _, tt := range recursiveDescentOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
documentScenarios(t, "Recursive Descent", recursiveDescentOperatorScenarios)
|
documentScenarios(t, "Recursive Descent (Glob)", recursiveDescentOperatorScenarios)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SelectOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func selectOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
log.Debugf("-- selectOperation")
|
log.Debugf("-- selectOperation")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
@ -12,7 +12,7 @@ func SelectOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pa
|
|||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -2,6 +2,6 @@ package yqlib
|
|||||||
|
|
||||||
import "container/list"
|
import "container/list"
|
||||||
|
|
||||||
func SelfOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func selfOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
return matchMap, nil
|
return matchMap, nil
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,17 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SortKeysOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func sortKeysOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
|
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
|
||||||
node := UnwrapDoc(childEl.Value.(*CandidateNode).Node)
|
node := unwrapDoc(childEl.Value.(*CandidateNode).Node)
|
||||||
if node.Kind == yaml.MappingNode {
|
if node.Kind == yaml.MappingNode {
|
||||||
sortKeys(node)
|
sortKeys(node)
|
||||||
}
|
}
|
||||||
|
18
pkg/yqlib/operator_split_document.go
Normal file
18
pkg/yqlib/operator_split_document.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
func splitDocumentOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- splitDocumentOperator")
|
||||||
|
|
||||||
|
var index uint = 0
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
candidate.Document = index
|
||||||
|
index = index + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchMap, nil
|
||||||
|
}
|
32
pkg/yqlib/operator_split_document_test.go
Normal file
32
pkg/yqlib/operator_split_document_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var splitDocOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Split empty",
|
||||||
|
document: ``,
|
||||||
|
expression: `splitDoc`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!null)::\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Split array",
|
||||||
|
document: `[{a: cat}, {b: dog}]`,
|
||||||
|
expression: `.[] | splitDoc`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[0], (!!map)::{a: cat}\n",
|
||||||
|
"D1, P[1], (!!map)::{b: dog}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitDocOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range splitDocOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "Split into Documents", splitDocOperatorScenarios)
|
||||||
|
}
|
96
pkg/yqlib/operator_strings.go
Normal file
96
pkg/yqlib/operator_strings.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func joinStringOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- joinStringOperator")
|
||||||
|
joinStr := ""
|
||||||
|
|
||||||
|
rhs, err := d.GetMatchingNodes(matchMap, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
joinStr = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = list.New()
|
||||||
|
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
node := unwrapDoc(candidate.Node)
|
||||||
|
if node.Kind != yaml.SequenceNode {
|
||||||
|
return nil, fmt.Errorf("Cannot join with %v, can only join arrays of scalars", node.Tag)
|
||||||
|
}
|
||||||
|
targetNode := join(node.Content, joinStr)
|
||||||
|
result := candidate.CreateChild(nil, targetNode)
|
||||||
|
results.PushBack(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func join(content []*yaml.Node, joinStr string) *yaml.Node {
|
||||||
|
var stringsToJoin []string
|
||||||
|
for _, node := range content {
|
||||||
|
str := node.Value
|
||||||
|
if node.Tag == "!!null" {
|
||||||
|
str = ""
|
||||||
|
}
|
||||||
|
stringsToJoin = append(stringsToJoin, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &yaml.Node{Kind: yaml.ScalarNode, Value: strings.Join(stringsToJoin, joinStr), Tag: "!!str"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitStringOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- splitStringOperator")
|
||||||
|
splitStr := ""
|
||||||
|
|
||||||
|
rhs, err := d.GetMatchingNodes(matchMap, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
splitStr = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = list.New()
|
||||||
|
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
node := unwrapDoc(candidate.Node)
|
||||||
|
if node.Tag == "!!null" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if node.Tag != "!!str" {
|
||||||
|
return nil, fmt.Errorf("Cannot split %v, can only split strings", node.Tag)
|
||||||
|
}
|
||||||
|
targetNode := split(node.Value, splitStr)
|
||||||
|
result := candidate.CreateChild(nil, targetNode)
|
||||||
|
results.PushBack(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(value string, spltStr string) *yaml.Node {
|
||||||
|
var contents []*yaml.Node
|
||||||
|
|
||||||
|
if value != "" {
|
||||||
|
var newStrings = strings.Split(value, spltStr)
|
||||||
|
contents = make([]*yaml.Node, len(newStrings))
|
||||||
|
|
||||||
|
for index, str := range newStrings {
|
||||||
|
contents[index] = &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: str}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
|
||||||
|
}
|
52
pkg/yqlib/operator_strings_test.go
Normal file
52
pkg/yqlib/operator_strings_test.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var stringsOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Join strings",
|
||||||
|
document: `[cat, meow, 1, null, true]`,
|
||||||
|
expression: `join("; ")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!str)::cat; meow; 1; ; true\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Split strings",
|
||||||
|
document: `"cat; meow; 1; ; true"`,
|
||||||
|
expression: `split("; ")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- cat\n- meow\n- \"1\"\n- \"\"\n- \"true\"\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Split strings one match",
|
||||||
|
document: `"word"`,
|
||||||
|
expression: `split("; ")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- word\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `""`,
|
||||||
|
expression: `split("; ")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n", // dont actually want this, just not to error
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
expression: `split("; ")`,
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringsOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range stringsOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "String Operators", stringsOperatorScenarios)
|
||||||
|
}
|
@ -4,40 +4,47 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func parseStyle(customStyle string) (yaml.Style, error) {
|
||||||
|
if customStyle == "tagged" {
|
||||||
|
return yaml.TaggedStyle, nil
|
||||||
|
} else if customStyle == "double" {
|
||||||
|
return yaml.DoubleQuotedStyle, nil
|
||||||
|
} else if customStyle == "single" {
|
||||||
|
return yaml.SingleQuotedStyle, nil
|
||||||
|
} else if customStyle == "literal" {
|
||||||
|
return yaml.LiteralStyle, nil
|
||||||
|
} else if customStyle == "folded" {
|
||||||
|
return yaml.FoldedStyle, nil
|
||||||
|
} else if customStyle == "flow" {
|
||||||
|
return yaml.FlowStyle, nil
|
||||||
|
} else if customStyle != "" {
|
||||||
|
return 0, fmt.Errorf("Unknown style %v", customStyle)
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
log.Debugf("AssignStyleOperator: %v")
|
log.Debugf("AssignStyleOperator: %v")
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
customStyle := ""
|
|
||||||
|
|
||||||
if rhs.Front() != nil {
|
|
||||||
customStyle = rhs.Front().Value.(*CandidateNode).Node.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
var style yaml.Style
|
var style yaml.Style
|
||||||
if customStyle == "tagged" {
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
style = yaml.TaggedStyle
|
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
} else if customStyle == "double" {
|
if err != nil {
|
||||||
style = yaml.DoubleQuotedStyle
|
return nil, err
|
||||||
} else if customStyle == "single" {
|
}
|
||||||
style = yaml.SingleQuotedStyle
|
|
||||||
} else if customStyle == "literal" {
|
if rhs.Front() != nil {
|
||||||
style = yaml.LiteralStyle
|
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
|
||||||
} else if customStyle == "folded" {
|
if err != nil {
|
||||||
style = yaml.FoldedStyle
|
return nil, err
|
||||||
} else if customStyle == "flow" {
|
}
|
||||||
style = yaml.FlowStyle
|
}
|
||||||
} else if customStyle != "" {
|
|
||||||
return nil, fmt.Errorf("Unknown style %v", customStyle)
|
|
||||||
}
|
}
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
|
||||||
|
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -46,13 +53,27 @@ func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
|
|||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
log.Debugf("Setting style of : %v", candidate.GetKey())
|
log.Debugf("Setting style of : %v", candidate.GetKey())
|
||||||
|
if expressionNode.Operation.UpdateAssign {
|
||||||
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
candidate.Node.Style = style
|
candidate.Node.Style = style
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("GetStyleOperator")
|
log.Debugf("GetStyleOperator")
|
||||||
|
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
@ -79,8 +100,8 @@ func GetStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *
|
|||||||
style = "<unknown>"
|
style = "<unknown>"
|
||||||
}
|
}
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: style, Tag: "!!str"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: style, Tag: "!!str"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
@ -86,7 +86,7 @@ e: >-
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Pretty print",
|
description: "Reset style - or pretty print",
|
||||||
subdescription: "Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.",
|
subdescription: "Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.",
|
||||||
document: `{a: cat, "b": 5, 'c': 3.2, "e": true}`,
|
document: `{a: cat, "b": 5, 'c': 3.2, "e": true}`,
|
||||||
expression: `... style=""`,
|
expression: `... style=""`,
|
||||||
@ -94,6 +94,14 @@ e: >-
|
|||||||
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
|
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Set style relatively with assign-update",
|
||||||
|
document: `{a: single, b: double}`,
|
||||||
|
expression: `.[] style |= .`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: 'single', b: \"double\"}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: `{a: cat, b: double}`,
|
document: `{a: cat, b: double}`,
|
||||||
|
@ -3,24 +3,26 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func assignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
log.Debugf("AssignTagOperator: %v")
|
log.Debugf("AssignTagOperator: %v")
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tag := ""
|
tag := ""
|
||||||
|
|
||||||
if rhs.Front() != nil {
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
tag = rhs.Front().Value.(*CandidateNode).Node.Value
|
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
tag = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -29,22 +31,32 @@ func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode
|
|||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
log.Debugf("Setting tag of : %v", candidate.GetKey())
|
log.Debugf("Setting tag of : %v", candidate.GetKey())
|
||||||
candidate.Node.Tag = tag
|
if expressionNode.Operation.UpdateAssign {
|
||||||
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
tag = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unwrapDoc(candidate.Node).Tag = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func getTagOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
log.Debugf("GetTagOperator")
|
log.Debugf("GetTagOperator")
|
||||||
|
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Tag, Tag: "!!str"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: unwrapDoc(candidate.Node).Tag, Tag: "!!str"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
result := candidate.CreateChild(nil, node)
|
||||||
results.PushBack(lengthCand)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
@ -19,13 +19,45 @@ var tagOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Convert numbers to strings",
|
skipDoc: true,
|
||||||
|
document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`,
|
||||||
|
expression: `tag`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!str)::'!!map'\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `32`,
|
||||||
|
expression: `. tag= "!!str"`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::\"32\"\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Set custom tag",
|
||||||
|
document: `{a: str}`,
|
||||||
|
expression: `.a tag = "!!mikefarah"`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: !!mikefarah str}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Find numbers and convert them to strings",
|
||||||
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
||||||
expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
|
expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
|
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: "!!frog", b: "!!customTag"}`,
|
||||||
|
expression: `.[] tag |= .`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: !!frog \"!!frog\", b: !!customTag \"!!customTag\"}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTagOperatorScenarios(t *testing.T) {
|
func TestTagOperatorScenarios(t *testing.T) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user