mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
143 Commits
4.0.0-beta
...
cross_func
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
edb5f213d7 | ||
|
|
5cfd9b05ee | ||
|
|
0e764c59ce | ||
|
|
1b887e23b3 | ||
|
|
a76b72e691 | ||
|
|
9509831cff | ||
|
|
e92180e89d | ||
|
|
a6ae33c3f1 | ||
|
|
94a563dfd8 | ||
|
|
0328cfd619 | ||
|
|
88663a6ce3 | ||
|
|
b10a9ccfc6 | ||
|
|
9e9e15df73 | ||
|
|
6cc6fdf322 | ||
|
|
a88c2dc5d3 | ||
|
|
ea231006ed | ||
|
|
80f187f1a4 | ||
|
|
98e8b3479f | ||
|
|
eb539ff326 | ||
|
|
c09f7aa707 | ||
|
|
7f5c380d16 | ||
|
|
6a05e517f1 | ||
|
|
8ee6f7dc1a | ||
|
|
8bd54cd603 | ||
|
|
f2f7b6db0f | ||
|
|
e082fee5d4 | ||
|
|
412911561f | ||
|
|
52ae633cb7 | ||
|
|
9356ca59bb | ||
|
|
6dec167f74 | ||
|
|
00f6981314 | ||
|
|
4c60a2a967 | ||
|
|
f4529614c4 | ||
|
|
f059e13f94 | ||
|
|
4dbf158505 | ||
|
|
39a93a2836 | ||
|
|
7c158fce26 | ||
|
|
0bf43d2a55 | ||
|
|
1b0bce5da6 | ||
|
|
f112bde5fe | ||
|
|
e5aa4a87a4 | ||
|
|
f305e8fa12 | ||
|
|
d2d0c2c111 | ||
|
|
2aab79431c | ||
|
|
540d4953f5 | ||
|
|
7849232255 | ||
|
|
57cd67f055 | ||
|
|
6b17fd4fc1 | ||
|
|
ca8cd78616 | ||
|
|
9876b0ce8f | ||
|
|
a23272727d | ||
|
|
1fb37785d6 | ||
|
|
efb9027540 | ||
|
|
2577fe5425 | ||
|
|
f39c57fed9 | ||
|
|
1e8f755e7c | ||
|
|
bb088f6aa2 | ||
|
|
a96b74e779 | ||
|
|
09a9e1e7f0 | ||
|
|
8846255d1c | ||
|
|
fbe53885ae | ||
|
|
f42728eef7 | ||
|
|
93136ba840 | ||
|
|
5665dc7b71 | ||
|
|
9302279dd2 | ||
|
|
5972bb2f23 | ||
|
|
ec43f5e7c3 | ||
|
|
62f262147c | ||
|
|
0259bb44c1 | ||
|
|
e07a5b6065 | ||
|
|
f69a81b79b | ||
|
|
ccb718cd0f | ||
|
|
815edef86a | ||
|
|
cd83d94b6a | ||
|
|
6afc2e9189 |
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report - V3
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ''
|
||||||
labels: bug
|
labels: bug, v3
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -10,11 +10,14 @@ 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.
|
||||||
|
|
||||||
version of yq:
|
Note that any how to questions should be posted in the discussion board and not raised as an issue.
|
||||||
operating system:
|
|
||||||
|
Version of yq: 3.X.X
|
||||||
|
Operating system: mac/linux/windows/....
|
||||||
|
Installed via: docker/binary release/homebrew/snap/...
|
||||||
|
|
||||||
**Input Yaml**
|
**Input Yaml**
|
||||||
Concise yaml document(s) (as simple as possible to show the bug)
|
Concise yaml document(s) (as simple as possible to show the bug, please keep it to 10 lines or less)
|
||||||
data1.yml:
|
data1.yml:
|
||||||
```yaml
|
```yaml
|
||||||
this: should really work
|
this: should really work
|
||||||
51
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
Normal file
51
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: Bug report - V4
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug, v4
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
Note that any how to questions should be posted in the discussion board and not raised as an issue.
|
||||||
|
|
||||||
|
Version of yq: 4.X.X
|
||||||
|
Operating system: mac/linux/windows/....
|
||||||
|
Installed via: docker/binary release/homebrew/snap/...
|
||||||
|
|
||||||
|
**Input Yaml**
|
||||||
|
Concise yaml document(s) (as simple as possible to show the bug, please keep it to 10 lines or less)
|
||||||
|
data1.yml:
|
||||||
|
```yaml
|
||||||
|
this: should really work
|
||||||
|
```
|
||||||
|
|
||||||
|
data2.yml:
|
||||||
|
```yaml
|
||||||
|
but: it strangely didn't
|
||||||
|
```
|
||||||
|
|
||||||
|
**Command**
|
||||||
|
The command you ran:
|
||||||
|
```
|
||||||
|
yq eval-all 'select(fileIndex==0) | .a.b.c' data1.yml data2.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Actual behavior**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cat: meow
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
this: should really work
|
||||||
|
but: it strangely didn't
|
||||||
|
```
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,17 +1,23 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request - V4
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: ''
|
title: ''
|
||||||
labels: enhancement
|
labels: enhancement, v4
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Please describe your feature request.**
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
A clear and concise description of what the request is and what it would solve.
|
||||||
|
Eg. I wish I could use yq to [...]
|
||||||
|
|
||||||
|
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:
|
||||||
|
(please keep to around 10 lines )
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
country: Australia
|
country: Australia
|
||||||
@@ -20,7 +26,7 @@ country: Australia
|
|||||||
And we run a command:
|
And we run a command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yq predictWeather data1.yml
|
yq eval 'predictWeatherOf(.country)'
|
||||||
```
|
```
|
||||||
|
|
||||||
it could output
|
it could output
|
||||||
|
|||||||
3
.github/workflows/go.yml
vendored
3
.github/workflows/go.yml
vendored
@@ -28,7 +28,4 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
export PATH=${PATH}:`go env GOPATH`/bin
|
export PATH=${PATH}:`go env GOPATH`/bin
|
||||||
scripts/devtools.sh
|
scripts/devtools.sh
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
export PATH=${PATH}:`go env GOPATH`/bin
|
|
||||||
make local build
|
make local build
|
||||||
80
.github/workflows/release.yml
vendored
Normal file
80
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
name: Release YQ
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publishGitRelease:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '^1.15'
|
||||||
|
- name: Cross compile
|
||||||
|
run: |
|
||||||
|
sudo apt-get install rhash -y
|
||||||
|
go get github.com/mitchellh/gox
|
||||||
|
./scripts/xcompile.sh
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1.0.0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ github.ref }}
|
||||||
|
draft: true
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
|
- uses: shogo82148/actions-upload-release-asset@v1
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: build/*
|
||||||
|
|
||||||
|
publishDocker:
|
||||||
|
environment: dockerhub
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: mikefarah/yq
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Get latest release tag
|
||||||
|
uses: oprypin/find-latest-tag@v1
|
||||||
|
with:
|
||||||
|
repository: mikefarah/yq # The repository to scan.
|
||||||
|
releases-only: true # We know that all relevant tags have a GitHub release for them.
|
||||||
|
id: yq
|
||||||
|
|
||||||
|
- name: Clone source code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.yq.outputs.tag }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
- name: Available platforms
|
||||||
|
run: echo ${{ steps.buildx.outputs.platforms }} && docker version
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
run: |
|
||||||
|
IMAGE_V_VERSION="$(git describe --tags --abbrev=0)"
|
||||||
|
IMAGE_VERSION=${IMAGE_V_VERSION:1}
|
||||||
|
|
||||||
|
SHORT_SHA1=$(git rev-parse --short HEAD)
|
||||||
|
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64"
|
||||||
|
echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}"
|
||||||
|
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||||
|
docker buildx build --platform "${PLATFORMS}" -t "${IMAGE_NAME}:${IMAGE_VERSION}" -t "${IMAGE_NAME}:latest" -t "${IMAGE_NAME}:4" \
|
||||||
|
--push .
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ RUN CGO_ENABLED=0 make local build
|
|||||||
|
|
||||||
# Choose alpine as a base image to make this useful for CI, as many
|
# Choose alpine as a base image to make this useful for CI, as many
|
||||||
# CI tools expect an interactive shell inside the container
|
# CI tools expect an interactive shell inside the container
|
||||||
FROM alpine:3.12 as production
|
FROM alpine:3.12.3 as production
|
||||||
|
|
||||||
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
||||||
RUN chmod +x /usr/bin/yq
|
RUN chmod +x /usr/bin/yq
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ COPY scripts/devtools.sh /opt/devtools.sh
|
|||||||
|
|
||||||
RUN set -e -x \
|
RUN set -e -x \
|
||||||
&& /opt/devtools.sh
|
&& /opt/devtools.sh
|
||||||
|
ENV PATH=/go/bin:$PATH
|
||||||
|
|
||||||
# install mkdocs
|
# install mkdocs
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
|
|||||||
136
README.md
136
README.md
@@ -3,24 +3,64 @@
|
|||||||
   
|
   
|
||||||
|
|
||||||
|
|
||||||
a lightweight and portable command-line YAML processor
|
a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
|
||||||
|
|
||||||
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
|
yq is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as docker, all listed below.
|
||||||
|
|
||||||
|
## V4 released!
|
||||||
|
V4 is now officially released, it's quite different from V3 (sorry for the migration), however it is much more similar to ```jq```, using a similar expression syntax and therefore support much more complex functionality!
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
### MacOS:
|
### wget
|
||||||
|
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
|
||||||
|
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
|
||||||
|
chmod +x /usr/bin/yq
|
||||||
|
```
|
||||||
|
|
||||||
|
For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
|
||||||
|
|
||||||
|
### MacOS / Linux via Homebrew:
|
||||||
|
Using [Homebrew](https://brew.sh/)
|
||||||
```
|
```
|
||||||
brew install yq
|
brew install yq
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ubuntu and other Linux distros supporting `snap` packages:
|
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:
|
||||||
```
|
```
|
||||||
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:
|
||||||
|
|
||||||
@@ -39,18 +79,6 @@ sudo mv /etc/myfile.tmp /etc/myfile
|
|||||||
rm /etc/myfile.tmp
|
rm /etc/myfile.tmp
|
||||||
```
|
```
|
||||||
|
|
||||||
### wget
|
|
||||||
|
|
||||||
Use wget to download the pre-compiled binaries:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
wget https://github.com/mikefarah/yq/releases/download/{VERSION}/{BINARY} -O /usr/bin/yq &&\
|
|
||||||
chmod +x /usr/bin/yq
|
|
||||||
```
|
|
||||||
|
|
||||||
For instance, VERSION=4.0.0 and BINARY=yq_linux_amd64
|
|
||||||
|
|
||||||
|
|
||||||
### Run with Docker
|
### Run with Docker
|
||||||
|
|
||||||
#### Oneshot use:
|
#### Oneshot use:
|
||||||
@@ -62,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:
|
||||||
@@ -75,12 +103,20 @@ yq() {
|
|||||||
|
|
||||||
### Go Get:
|
### Go Get:
|
||||||
```
|
```
|
||||||
GO111MODULE=on go get github.com/mikefarah/yq/v4
|
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:
|
||||||
```
|
```
|
||||||
@@ -88,6 +124,14 @@ choco install yq
|
|||||||
```
|
```
|
||||||
Supported by @chillum (https://chocolatey.org/packages/yq)
|
Supported by @chillum (https://chocolatey.org/packages/yq)
|
||||||
|
|
||||||
|
### Mac:
|
||||||
|
Using [MacPorts](https://www.macports.org/)
|
||||||
|
```
|
||||||
|
sudo port selfupdate
|
||||||
|
sudo port install yq
|
||||||
|
```
|
||||||
|
Supported by @herbygillot (https://ports.macports.org/maintainer/github/herbygillot)
|
||||||
|
|
||||||
### Alpine Linux
|
### Alpine Linux
|
||||||
- Enable edge/community repo by adding ```$MIRROR/alpine/edge/community``` to ```/etc/apk/repositories```
|
- Enable edge/community repo by adding ```$MIRROR/alpine/edge/community``` to ```/etc/apk/repositories```
|
||||||
- Update database index with ```apk update```
|
- Update database index with ```apk update```
|
||||||
@@ -108,21 +152,18 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Written in portable go, so you can download a lovely dependency free binary
|
- Written in portable go, so you can download a lovely dependency free binary
|
||||||
|
- Uses similar syntax as `jq` but works with YAML and JSON files
|
||||||
|
- Fully supports multi document yaml files
|
||||||
- Colorized yaml output
|
- Colorized yaml output
|
||||||
- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
|
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
|
||||||
- [Return the lengths of arrays/object/scalars](https://mikefarah.gitbook.io/yq/commands/read#printing-length-of-the-results)
|
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/v/v4.x/sort-keys)
|
||||||
- Update a yaml file given a [path expression](https://mikefarah.gitbook.io/yq/commands/write-update#basic) or [script file](https://mikefarah.gitbook.io/yq/commands/write-update#basic)
|
- Manipulate yaml [comments](https://mikefarah.gitbook.io/yq/comment-operators), [styling](https://mikefarah.gitbook.io/yq/style), [tags](https://mikefarah.gitbook.io/yq/tag) and [anchors and aliases](https://mikefarah.gitbook.io/yq/anchor-and-alias-operators).
|
||||||
- Update creates any missing entries in the path on the fly
|
- [Update yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags)
|
||||||
- Deeply [compare](https://mikefarah.gitbook.io/yq/commands/compare) yaml files
|
- [Complex expressions to select and update](https://mikefarah.gitbook.io/yq/v/v4.x/select#select-and-update-matching-values-in-map)
|
||||||
- Keeps yaml formatting and comments when updating
|
- Keeps yaml formatting and comments when updating (though there are issues with whitespace)
|
||||||
- [Validate a yaml file](https://mikefarah.gitbook.io/yq/commands/validate)
|
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
|
||||||
- Create a yaml file given a [deep path and value](https://mikefarah.gitbook.io/yq/commands/create#creating-a-simple-yaml-file) or a [script file](https://mikefarah.gitbook.io/yq/commands/create#creating-using-a-create-script)
|
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate)
|
||||||
- [Prefix a path to a yaml file](https://mikefarah.gitbook.io/yq/commands/prefix)
|
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
|
||||||
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/usage/convert)
|
|
||||||
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/commands/read#from-stdin)
|
|
||||||
- [Merge](https://mikefarah.gitbook.io/yq/commands/merge) multiple yaml files with various options for [overriding](https://mikefarah.gitbook.io/yq/commands/merge#overwrite-values) and [appending](https://mikefarah.gitbook.io/yq/commands/merge#append-values-with-arrays)
|
|
||||||
- Supports multiple documents in a single yaml file for [reading](https://mikefarah.gitbook.io/yq/commands/read#multiple-documents), [writing](https://mikefarah.gitbook.io/yq/commands/write-update#multiple-documents) and [merging](https://mikefarah.gitbook.io/yq/commands/merge#multiple-documents)
|
|
||||||
- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/commands/shell-completion)
|
|
||||||
|
|
||||||
## [Usage](https://mikefarah.gitbook.io/yq/)
|
## [Usage](https://mikefarah.gitbook.io/yq/)
|
||||||
|
|
||||||
@@ -134,32 +175,33 @@ Usage:
|
|||||||
yq [command]
|
yq [command]
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
|
eval Apply expression to each document in each yaml file given in sequence
|
||||||
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
|
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
|
||||||
help Help about any command
|
help Help about any command
|
||||||
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
|
shell-completion Generate completion script
|
||||||
new yq n [--script/-s script_file] a.b.c newValue
|
|
||||||
prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c
|
|
||||||
read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'
|
|
||||||
shell-completion Generates shell completion scripts
|
|
||||||
validate yq v sample.yaml
|
|
||||||
write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue
|
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-C, --colors print with colors
|
-C, --colors force print with colors
|
||||||
|
-e, --exit-status set exit status if there are no matches or null or false is returned
|
||||||
-h, --help help for yq
|
-h, --help help for yq
|
||||||
-I, --indent int sets indent level for output (default 2)
|
-I, --indent int sets indent level for output (default 2)
|
||||||
-P, --prettyPrint pretty print
|
-i, --inplace update the yaml file inplace of first yaml file given.
|
||||||
-j, --tojson output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc.
|
-M, --no-colors force print with no colors
|
||||||
|
-N, --no-doc Don't print document separators (---)
|
||||||
|
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.
|
||||||
|
-P, --prettyPrint pretty print, shorthand for '... style = ""'
|
||||||
|
-j, --tojson output as json. Set indent to 0 to print json in one line.
|
||||||
-v, --verbose verbose mode
|
-v, --verbose verbose mode
|
||||||
-V, --version Print version information and quit
|
-V, --version Print version information and quit
|
||||||
|
|
||||||
Use "yq [command] --help" for more information about a command.
|
Use "yq [command] --help" for more information about a command.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Upgrade from V2
|
Simple Example:
|
||||||
If you've been using v2 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/upgrading-from-v2).
|
|
||||||
|
```bash
|
||||||
|
yq e '.a.b | length' f1.yml f2.yml
|
||||||
|
```
|
||||||
|
|
||||||
## Known Issues / Missing Features
|
## Known Issues / Missing Features
|
||||||
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
|
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
|
||||||
- You cannot (yet) select multiple paths/keys from the yaml to be printed out (https://github.com/mikefarah/yq/issues/287)
|
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ var noDocSeparators = false
|
|||||||
var nullInput = false
|
var nullInput = false
|
||||||
var verbose = false
|
var verbose = false
|
||||||
var version = false
|
var version = false
|
||||||
|
var prettyPrint = false
|
||||||
|
|
||||||
var completedSuccessfully = false
|
var completedSuccessfully = false
|
||||||
|
|||||||
@@ -59,25 +59,29 @@ 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()
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
if pipingStdIn {
|
if pipingStdIn {
|
||||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
|
||||||
} else {
|
} else {
|
||||||
cmd.Println(cmd.UsageString())
|
cmd.Println(cmd.UsageString())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
if nullInput {
|
if nullInput {
|
||||||
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
|
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer)
|
||||||
} else {
|
} else {
|
||||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
completedSuccessfully = err == nil
|
completedSuccessfully = err == nil
|
||||||
|
|||||||
@@ -33,6 +33,16 @@ yq e '.a.b = "cool"' -i file.yaml
|
|||||||
}
|
}
|
||||||
return cmdEvalSequence
|
return cmdEvalSequence
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processExpression(expression string) string {
|
||||||
|
if prettyPrint && expression == "" {
|
||||||
|
return `... style=""`
|
||||||
|
} else if prettyPrint {
|
||||||
|
return fmt.Sprintf("%v | ... style= \"\"", expression)
|
||||||
|
}
|
||||||
|
return expression
|
||||||
|
}
|
||||||
|
|
||||||
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
// 0 args, read std in
|
// 0 args, read std in
|
||||||
@@ -73,19 +83,23 @@ 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 {
|
||||||
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
|
||||||
} else {
|
} else {
|
||||||
cmd.Println(cmd.UsageString())
|
cmd.Println(cmd.UsageString())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
if nullInput {
|
if nullInput {
|
||||||
err = streamEvaluator.EvaluateNew(args[0], printer)
|
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer)
|
||||||
} else {
|
} else {
|
||||||
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ func New() *cobra.Command {
|
|||||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
||||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
|
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print, shorthand for '... style = \"\"'")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
|
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
|
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
|
||||||
|
|||||||
@@ -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.0.0-beta1"
|
Version = "4.4.0"
|
||||||
|
|
||||||
// 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:3
|
FROM mikefarah/yq:4.4.0
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
44
go.sum
44
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,21 +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/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=
|
||||||
@@ -288,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=
|
||||||
@@ -321,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=
|
||||||
@@ -334,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,12 +15,39 @@ 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) 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!
|
||||||
|
newPath := make([]interface{}, len(n.Path)+1)
|
||||||
|
copy(newPath, n.Path)
|
||||||
|
newPath[len(n.Path)] = path
|
||||||
|
return newPath
|
||||||
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) Copy() (*CandidateNode, error) {
|
func (n *CandidateNode) Copy() (*CandidateNode, error) {
|
||||||
clone := &CandidateNode{}
|
clone := &CandidateNode{}
|
||||||
err := copier.Copy(clone, n)
|
err := copier.Copy(clone, n)
|
||||||
@@ -32,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,85 @@ 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
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
73
pkg/yqlib/doc/Alternative (Default value).md
Normal file
73
pkg/yqlib/doc/Alternative (Default value).md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
|
||||||
|
|
||||||
|
## LHS is defined
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: bridge
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a // "hello"' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
bridge
|
||||||
|
```
|
||||||
|
|
||||||
|
## LHS is not defined
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
{}
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a // "hello"' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
hello
|
||||||
|
```
|
||||||
|
|
||||||
|
## LHS is null
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: ~
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a // "hello"' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
hello
|
||||||
|
```
|
||||||
|
|
||||||
|
## LHS is false
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: false
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a // "hello"' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
hello
|
||||||
|
```
|
||||||
|
|
||||||
|
## RHS is an expression
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: false
|
||||||
|
b: cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a // .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
cat
|
||||||
|
```
|
||||||
|
|
||||||
194
pkg/yqlib/doc/Anchor and Alias Operators.md
Normal file
194
pkg/yqlib/doc/Anchor and Alias Operators.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
|
||||||
|
|
||||||
|
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||||
|
|
||||||
|
|
||||||
|
## Get anchor
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: &billyBob cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a | anchor' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
billyBob
|
||||||
|
```
|
||||||
|
|
||||||
|
## Set anchor
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a anchor = "foobar"' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
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
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
b: &billyBob meow
|
||||||
|
a: *billyBob
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a | alias' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
billyBob
|
||||||
|
```
|
||||||
|
|
||||||
|
## Set alias
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
b: &meow purr
|
||||||
|
a: cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a alias = "meow"' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
b: &meow purr
|
||||||
|
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
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
f:
|
||||||
|
a: &a cat
|
||||||
|
b: *a
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'explode(.f)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
f:
|
||||||
|
a: cat
|
||||||
|
b: cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Explode with no aliases or anchors
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: mike
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'explode(.a)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: mike
|
||||||
|
```
|
||||||
|
|
||||||
|
## Explode with alias keys
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
f:
|
||||||
|
a: &a cat
|
||||||
|
*a: b
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'explode(.f)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
f:
|
||||||
|
a: cat
|
||||||
|
cat: b
|
||||||
|
```
|
||||||
|
|
||||||
|
## Explode with merge anchors
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
foo: &foo
|
||||||
|
a: foo_a
|
||||||
|
thing: foo_thing
|
||||||
|
c: foo_c
|
||||||
|
bar: &bar
|
||||||
|
b: bar_b
|
||||||
|
thing: bar_thing
|
||||||
|
c: bar_c
|
||||||
|
foobarList:
|
||||||
|
b: foobarList_b
|
||||||
|
!!merge <<:
|
||||||
|
- *foo
|
||||||
|
- *bar
|
||||||
|
c: foobarList_c
|
||||||
|
foobar:
|
||||||
|
c: foobar_c
|
||||||
|
!!merge <<: *foo
|
||||||
|
thing: foobar_thing
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'explode(.)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
foo:
|
||||||
|
a: foo_a
|
||||||
|
thing: foo_thing
|
||||||
|
c: foo_c
|
||||||
|
bar:
|
||||||
|
b: bar_b
|
||||||
|
thing: bar_thing
|
||||||
|
c: bar_c
|
||||||
|
foobarList:
|
||||||
|
b: bar_b
|
||||||
|
a: foo_a
|
||||||
|
thing: bar_thing
|
||||||
|
c: foobarList_c
|
||||||
|
foobar:
|
||||||
|
c: foo_c
|
||||||
|
a: foo_a
|
||||||
|
thing: foobar_thing
|
||||||
|
```
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -112,7 +133,7 @@ a:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '(.a.[] | select(. == "apple")) = "frog"' sample.yml
|
yq eval '(.a[] | select(. == "apple")) = "frog"' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```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
|
||||||
|
|||||||
@@ -14,6 +14,23 @@ will output
|
|||||||
a: cat
|
a: cat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Delete nested entry in map
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
a1: fred
|
||||||
|
a2: frood
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'del(.a.a1)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
a2: frood
|
||||||
|
```
|
||||||
|
|
||||||
## Delete entry in array
|
## Delete entry in array
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -31,6 +48,21 @@ will output
|
|||||||
- 3
|
- 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Delete nested entry in array
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
- a: cat
|
||||||
|
b: dog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'del(.[0].a)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- b: dog
|
||||||
|
```
|
||||||
|
|
||||||
## Delete no matches
|
## Delete no matches
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -63,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
|
||||||
@@ -26,7 +44,23 @@ a: frog
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval 'select(. | documentIndex == 1)' sample.yml
|
yq eval 'select(documentIndex == 1)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
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
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -42,7 +76,7 @@ a: frog
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml
|
yq eval '.a | ({"match": ., "doc": documentIndex})' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```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,98 +0,0 @@
|
|||||||
Explodes (or dereferences) aliases and anchors.
|
|
||||||
## Explode alias and anchor
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
f:
|
|
||||||
a: &a cat
|
|
||||||
b: *a
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval 'explode(.f)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
f:
|
|
||||||
a: cat
|
|
||||||
b: cat
|
|
||||||
```
|
|
||||||
|
|
||||||
## Explode with no aliases or anchors
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: mike
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval 'explode(.a)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a: mike
|
|
||||||
```
|
|
||||||
|
|
||||||
## Explode with alias keys
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
f:
|
|
||||||
a: &a cat
|
|
||||||
*a: b
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval 'explode(.f)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
f:
|
|
||||||
a: cat
|
|
||||||
cat: b
|
|
||||||
```
|
|
||||||
|
|
||||||
## Explode with merge anchors
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
foo: &foo
|
|
||||||
a: foo_a
|
|
||||||
thing: foo_thing
|
|
||||||
c: foo_c
|
|
||||||
bar: &bar
|
|
||||||
b: bar_b
|
|
||||||
thing: bar_thing
|
|
||||||
c: bar_c
|
|
||||||
foobarList:
|
|
||||||
b: foobarList_b
|
|
||||||
!!merge <<:
|
|
||||||
- *foo
|
|
||||||
- *bar
|
|
||||||
c: foobarList_c
|
|
||||||
foobar:
|
|
||||||
c: foobar_c
|
|
||||||
!!merge <<: *foo
|
|
||||||
thing: foobar_thing
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval 'explode(.)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
foo:
|
|
||||||
a: foo_a
|
|
||||||
thing: foo_thing
|
|
||||||
c: foo_c
|
|
||||||
bar:
|
|
||||||
b: bar_b
|
|
||||||
thing: bar_thing
|
|
||||||
c: bar_c
|
|
||||||
foobarList:
|
|
||||||
b: bar_b
|
|
||||||
a: foo_a
|
|
||||||
thing: bar_thing
|
|
||||||
c: foobarList_c
|
|
||||||
foobar:
|
|
||||||
c: foo_c
|
|
||||||
a: foo_a
|
|
||||||
thing: foobar_thing
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
154
pkg/yqlib/doc/Recursive Descent (Glob).md
Normal file
154
pkg/yqlib/doc/Recursive Descent (Glob).md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
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 `..`
|
||||||
|
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||||
|
|
||||||
|
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yq eval '.. style= "flow"' file.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## match values and map keys form `...`
|
||||||
|
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
|
||||||
|
|
||||||
|
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yq eval '... style= "flow"' file.yaml
|
||||||
|
```
|
||||||
|
## Recurse map (values only)
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: frog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '..' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: 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)
|
||||||
|
Note that the map key appears in the results
|
||||||
|
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: frog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '...' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: frog
|
||||||
|
a
|
||||||
|
frog
|
||||||
|
```
|
||||||
|
|
||||||
|
## Aliases are not traversed
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: &cat
|
||||||
|
c: frog
|
||||||
|
b: *cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '[..]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- a: &cat
|
||||||
|
c: frog
|
||||||
|
b: *cat
|
||||||
|
- &cat
|
||||||
|
c: frog
|
||||||
|
- frog
|
||||||
|
- *cat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Merge docs are not traversed
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
foo: &foo
|
||||||
|
a: foo_a
|
||||||
|
thing: foo_thing
|
||||||
|
c: foo_c
|
||||||
|
bar: &bar
|
||||||
|
b: bar_b
|
||||||
|
thing: bar_thing
|
||||||
|
c: bar_c
|
||||||
|
foobarList:
|
||||||
|
b: foobarList_b
|
||||||
|
!!merge <<:
|
||||||
|
- *foo
|
||||||
|
- *bar
|
||||||
|
c: foobarList_c
|
||||||
|
foobar:
|
||||||
|
c: foobar_c
|
||||||
|
!!merge <<: *foo
|
||||||
|
thing: foobar_thing
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.foobar | [..]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
- c: foobar_c
|
||||||
|
!!merge <<: *foo
|
||||||
|
thing: foobar_thing
|
||||||
|
- foobar_c
|
||||||
|
- *foo
|
||||||
|
- foobar_thing
|
||||||
|
```
|
||||||
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
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, for instance to set the `style` of all nodes in a yaml doc:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yq eval '.. style= "flow"' file.yaml
|
|
||||||
```
|
|
||||||
## Aliases are not traversed
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: &cat
|
|
||||||
c: frog
|
|
||||||
b: *cat
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '[..]' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
- a: &cat
|
|
||||||
c: frog
|
|
||||||
b: *cat
|
|
||||||
- &cat
|
|
||||||
c: frog
|
|
||||||
- frog
|
|
||||||
- *cat
|
|
||||||
```
|
|
||||||
|
|
||||||
## Merge docs are not traversed
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
foo: &foo
|
|
||||||
a: foo_a
|
|
||||||
thing: foo_thing
|
|
||||||
c: foo_c
|
|
||||||
bar: &bar
|
|
||||||
b: bar_b
|
|
||||||
thing: bar_thing
|
|
||||||
c: bar_c
|
|
||||||
foobarList:
|
|
||||||
b: foobarList_b
|
|
||||||
!!merge <<:
|
|
||||||
- *foo
|
|
||||||
- *bar
|
|
||||||
c: foobarList_c
|
|
||||||
foobar:
|
|
||||||
c: foobar_c
|
|
||||||
!!merge <<: *foo
|
|
||||||
thing: foobar_thing
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.foobar | [..]' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
- c: foobar_c
|
|
||||||
!!merge <<: *foo
|
|
||||||
thing: foobar_thing
|
|
||||||
- foobar_c
|
|
||||||
- *foo
|
|
||||||
- foobar_thing
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
||||||
|
```
|
||||||
|
|
||||||
@@ -40,6 +40,26 @@ c: "3.2"
|
|||||||
e: "true"
|
e: "true"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Set double quote style on map keys too
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: cat
|
||||||
|
b: 5
|
||||||
|
c: 3.2
|
||||||
|
e: true
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '... style="double"' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
"a": "cat"
|
||||||
|
"b": "5"
|
||||||
|
"c": "3.2"
|
||||||
|
"e": "true"
|
||||||
|
```
|
||||||
|
|
||||||
## Set single quote style
|
## Set single quote style
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -125,19 +145,19 @@ 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
|
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:
|
||||||
```yaml
|
```yaml
|
||||||
a: cat
|
a: cat
|
||||||
b: 5
|
"b": 5
|
||||||
c: 3.2
|
'c': 3.2
|
||||||
e: true
|
"e": true
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.. style=""' sample.yml
|
yq eval '... style=""' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -147,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
|
||||||
|
|
||||||
@@ -106,7 +124,7 @@ b: *cat
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.b.[]' sample.yml
|
yq eval '.b[]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -290,7 +308,7 @@ foobar:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.foobar.[]' sample.yml
|
yq eval '.foobar[]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -356,7 +374,7 @@ foobar:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.foobarList.[]' sample.yml
|
yq eval '.foobarList[]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -366,3 +384,21 @@ bar_thing
|
|||||||
foobarList_c
|
foobarList_c
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Select multiple indices
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
|
- c
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a[0, 2]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a
|
||||||
|
c
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
pkg/yqlib/doc/headers/Alternative (Default value).md
Normal file
1
pkg/yqlib/doc/headers/Alternative (Default value).md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
|
||||||
4
pkg/yqlib/doc/headers/Anchor and Alias Operators.md
Normal file
4
pkg/yqlib/doc/headers/Anchor and Alias Operators.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
|
||||||
|
|
||||||
|
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||||
|
|
||||||
@@ -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 +0,0 @@
|
|||||||
Explodes (or dereferences) aliases and anchors.
|
|
||||||
@@ -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
|
||||||
|
|||||||
19
pkg/yqlib/doc/headers/Recursive Descent (Glob).md
Normal file
19
pkg/yqlib/doc/headers/Recursive Descent (Glob).md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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 `..`
|
||||||
|
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||||
|
|
||||||
|
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yq eval '.. style= "flow"' file.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## match values and map keys form `...`
|
||||||
|
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
|
||||||
|
|
||||||
|
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yq eval '... style= "flow"' file.yaml
|
||||||
|
```
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
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, for instance to set the `style` of all nodes in a yaml doc:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yq eval '.. style= "flow"' file.yaml
|
|
||||||
```
|
|
||||||
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
pkg/yqlib/expression_parser.go
Normal file
76
pkg/yqlib/expression_parser.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var myPathTokeniser = newExpressionTokeniser()
|
||||||
|
var myPathPostfixer = newExpressionPostFixer()
|
||||||
|
|
||||||
|
type ExpressionNode struct {
|
||||||
|
Operation *Operation
|
||||||
|
Lhs *ExpressionNode
|
||||||
|
Rhs *ExpressionNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpressionParser interface {
|
||||||
|
ParseExpression(expression string) (*ExpressionNode, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type expressionParserImpl struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExpressionParser() ExpressionParser {
|
||||||
|
return &expressionParserImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
|
||||||
|
tokens, err := myPathTokeniser.Tokenise(expression)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var Operations []*Operation
|
||||||
|
Operations, err = myPathPostfixer.ConvertToPostfix(tokens)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p.createExpressionTree(Operations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*ExpressionNode, error) {
|
||||||
|
var stack = make([]*ExpressionNode, 0)
|
||||||
|
|
||||||
|
if len(postFixPath) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, Operation := range postFixPath {
|
||||||
|
var newNode = ExpressionNode{Operation: Operation}
|
||||||
|
log.Debugf("pathTree %v ", Operation.toString())
|
||||||
|
if Operation.OperationType.NumArgs > 0 {
|
||||||
|
numArgs := Operation.OperationType.NumArgs
|
||||||
|
if numArgs == 1 {
|
||||||
|
if len(stack) < 1 {
|
||||||
|
return nil, fmt.Errorf("'%v' expects 1 arg but received none", strings.TrimSpace(Operation.StringValue))
|
||||||
|
}
|
||||||
|
remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1]
|
||||||
|
newNode.Rhs = rhs
|
||||||
|
stack = remaining
|
||||||
|
} else if numArgs == 2 {
|
||||||
|
if len(stack) < 2 {
|
||||||
|
return nil, fmt.Errorf("'%v' expects 2 args but there is %v", strings.TrimSpace(Operation.StringValue), len(stack))
|
||||||
|
}
|
||||||
|
remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1]
|
||||||
|
newNode.Lhs = lhs
|
||||||
|
newNode.Rhs = rhs
|
||||||
|
stack = remaining
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stack = append(stack, &newNode)
|
||||||
|
}
|
||||||
|
if len(stack) != 1 {
|
||||||
|
return nil, fmt.Errorf("expected end of expression but found '%v', please check expression syntax", strings.TrimSpace(stack[1].Operation.StringValue))
|
||||||
|
}
|
||||||
|
return stack[0], nil
|
||||||
|
}
|
||||||
42
pkg/yqlib/expression_parser_test.go
Normal file
42
pkg/yqlib/expression_parser_test.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mikefarah/yq/v4/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPathTreeNoArgsForTwoArgOp(t *testing.T) {
|
||||||
|
_, err := NewExpressionParser().ParseExpression("=")
|
||||||
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) {
|
||||||
|
_, err := NewExpressionParser().ParseExpression(".a =")
|
||||||
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) {
|
||||||
|
_, err := NewExpressionParser().ParseExpression("= .a")
|
||||||
|
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) {
|
||||||
|
_, err := NewExpressionParser().ParseExpression(".a = .b")
|
||||||
|
test.AssertResultComplex(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathTreeNoArgsForOneArgOp(t *testing.T) {
|
||||||
|
_, err := NewExpressionParser().ParseExpression("explode")
|
||||||
|
test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathTreeOneArgForOneArgOp(t *testing.T) {
|
||||||
|
_, err := NewExpressionParser().ParseExpression("explode(.)")
|
||||||
|
test.AssertResultComplex(t, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathTreeExtraArgs(t *testing.T) {
|
||||||
|
_, err := NewExpressionParser().ParseExpression("sortKeys(.) explode(.)")
|
||||||
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,20 +17,35 @@ var pathTests = []struct {
|
|||||||
append(make([]interface{}, 0), "[", "]"),
|
append(make([]interface{}, 0), "[", "]"),
|
||||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`.[]`,
|
||||||
|
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]"),
|
||||||
|
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`.a[]`,
|
`.a[]`,
|
||||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"),
|
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
|
||||||
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`.a.[]`,
|
`.a.[]`,
|
||||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"),
|
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
|
||||||
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.a[0]`,
|
||||||
|
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||||
|
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.a.[0]`,
|
||||||
|
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||||
|
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`.a[].c`,
|
`.a[].c`,
|
||||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]", "SHORT_PIPE", "c"),
|
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "c"),
|
||||||
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE", "c", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "c", "SHORT_PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`[3]`,
|
`[3]`,
|
||||||
@@ -38,24 +53,34 @@ 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"`,
|
||||||
append(make([]interface{}, 0), "a", "PIPE", "[]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
|
append(make([]interface{}, 0), "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
|
||||||
append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
|
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`(.a | .[].b) == "apple"`,
|
`(.a | .[].b) == "apple"`,
|
||||||
append(make([]interface{}, 0), "(", "a", "PIPE", "[]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
|
append(make([]interface{}, 0), "(", "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
|
||||||
append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
|
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`.[] | select(. == "*at")`,
|
`.[] | select(. == "*at")`,
|
||||||
append(make([]interface{}, 0), "[]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
|
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
|
||||||
append(make([]interface{}, 0), "[]", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
|
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`[true]`,
|
`[true]`,
|
||||||
@@ -89,8 +114,8 @@ var pathTests = []struct {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
`{.a: .c, .b.[]: .f.g.[]}`,
|
`{.a: .c, .b.[]: .f.g.[]}`,
|
||||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "SHORT_PIPE", "[]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "SHORT_PIPE", "[]", "}"),
|
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "}"),
|
||||||
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "[]", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
|
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
`explode(.a.b)`,
|
`explode(.a.b)`,
|
||||||
@@ -122,7 +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)"),
|
||||||
@@ -145,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
|
||||||
|
}
|
||||||
108
pkg/yqlib/lib.go
108
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,77 +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:
|
||||||
// - cookbook doc for common things
|
|
||||||
// - 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 assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
|
||||||
|
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 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 GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
|
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
|
||||||
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
|
var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator}
|
||||||
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
|
var getAliasOptype = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: getAliasOperator}
|
||||||
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
|
var getDocumentIndexOpType = &operationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: getDocumentIndexOperator}
|
||||||
|
var getFilenameOpType = &operationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: getFilenameOperator}
|
||||||
|
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 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 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
|
||||||
|
|
||||||
@@ -99,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},
|
||||||
@@ -108,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,31 +4,26 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toNodes(candidates *list.List) []*yaml.Node {
|
func toNodes(candidate *CandidateNode) []*yaml.Node {
|
||||||
|
|
||||||
if candidates.Len() == 0 {
|
|
||||||
return []*yaml.Node{}
|
|
||||||
}
|
|
||||||
candidate := candidates.Front().Value.(*CandidateNode)
|
|
||||||
|
|
||||||
if candidate.Node.Tag == "!!null" {
|
if candidate.Node.Tag == "!!null" {
|
||||||
return []*yaml.Node{}
|
return []*yaml.Node{}
|
||||||
}
|
}
|
||||||
@@ -42,29 +37,18 @@ func toNodes(candidates *list.List) []*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")
|
||||||
var results = list.New()
|
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
|
||||||
|
|
||||||
if err != nil {
|
return crossFunction(d, matchingNodes, expressionNode, add)
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhsCandidate := el.Value.(*CandidateNode)
|
lhs.Node = unwrapDoc(lhs.Node)
|
||||||
lhsNode := UnwrapDoc(lhsCandidate.Node)
|
rhs.Node = unwrapDoc(rhs.Node)
|
||||||
|
|
||||||
target := &CandidateNode{
|
target := lhs.CreateChild(nil, &yaml.Node{})
|
||||||
Path: lhsCandidate.Path,
|
lhsNode := lhs.Node
|
||||||
Document: lhsCandidate.Document,
|
|
||||||
Filename: lhsCandidate.Filename,
|
|
||||||
Node: &yaml.Node{},
|
|
||||||
}
|
|
||||||
|
|
||||||
switch lhsNode.Kind {
|
switch lhsNode.Kind {
|
||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
@@ -74,10 +58,50 @@ func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
|
|||||||
target.Node.Style = lhsNode.Style
|
target.Node.Style = lhsNode.Style
|
||||||
target.Node.Tag = "!!seq"
|
target.Node.Tag = "!!seq"
|
||||||
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||||
results.PushBack(target)
|
|
||||||
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 results, nil
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]}}`,
|
||||||
@@ -21,6 +30,14 @@ var addOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[a], (!!seq)::[1, 2, 3, 4]\n",
|
"D0, P[a], (!!seq)::[1, 2, 3, 4]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
expression: `[1] + ([2], [3])`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- 1\n- 2\n",
|
||||||
|
"D0, P[], (!!seq)::- 1\n- 3\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Concatenate null to array",
|
description: "Concatenate null to array",
|
||||||
document: `{a: [1,2]}`,
|
document: `{a: [1,2]}`,
|
||||||
@@ -30,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",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -53,6 +70,48 @@ 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",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddOperatorScenarios(t *testing.T) {
|
func TestAddOperatorScenarios(t *testing.T) {
|
||||||
|
|||||||
28
pkg/yqlib/operator_alternative.go
Normal file
28
pkg/yqlib/operator_alternative.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
)
|
||||||
|
|
||||||
|
// corssFunction no matches
|
||||||
|
// can boolean use crossfunction
|
||||||
|
|
||||||
|
func alternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- alternative")
|
||||||
|
return crossFunction(d, matchingNodes, expressionNode, alternativeFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
|
lhs.Node = unwrapDoc(lhs.Node)
|
||||||
|
rhs.Node = unwrapDoc(rhs.Node)
|
||||||
|
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
|
||||||
|
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||||
|
|
||||||
|
isTrue, err := isTruthy(lhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if isTrue {
|
||||||
|
return lhs, nil
|
||||||
|
}
|
||||||
|
return rhs, nil
|
||||||
|
}
|
||||||
62
pkg/yqlib/operator_alternative_test.go
Normal file
62
pkg/yqlib/operator_alternative_test.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var alternativeOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "LHS is defined",
|
||||||
|
expression: `.a // "hello"`,
|
||||||
|
document: `{a: bridge}`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!str)::bridge\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "LHS is not defined",
|
||||||
|
expression: `.a // "hello"`,
|
||||||
|
document: `{}`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!str)::hello\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "LHS is null",
|
||||||
|
expression: `.a // "hello"`,
|
||||||
|
document: `{a: ~}`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!str)::hello\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "LHS is false",
|
||||||
|
expression: `.a // "hello"`,
|
||||||
|
document: `{a: false}`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!str)::hello\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "RHS is an expression",
|
||||||
|
expression: `.a // .b`,
|
||||||
|
document: `{a: false, b: cat}`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[b], (!!str)::cat\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
expression: `false // true`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!bool)::true\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlternativeOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range alternativeOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "Alternative (Default value)", alternativeOperatorScenarios)
|
||||||
|
}
|
||||||
@@ -3,16 +3,126 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func assignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
|
log.Debugf("AssignAlias operator!")
|
||||||
|
|
||||||
|
aliasName := ""
|
||||||
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
|
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, expressionNode.Lhs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
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.Value = aliasName
|
||||||
|
}
|
||||||
|
return matchingNodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("GetAlias operator!")
|
||||||
|
var results = list.New()
|
||||||
|
|
||||||
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
|
||||||
|
result := candidate.CreateChild(nil, node)
|
||||||
|
results.PushBack(result)
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
|
||||||
|
log.Debugf("AssignAnchor operator!")
|
||||||
|
|
||||||
|
anchorName := ""
|
||||||
|
if !expressionNode.Operation.UpdateAssign {
|
||||||
|
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, expressionNode.Lhs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return matchingNodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
log.Debugf("GetAnchor operator!")
|
||||||
|
var results = list.New()
|
||||||
|
|
||||||
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
anchor := candidate.Node.Anchor
|
||||||
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
|
||||||
|
result := candidate.CreateChild(nil, node)
|
||||||
|
results.PushBack(result)
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@@ -4,7 +4,55 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var explodeTest = []expressionScenario{
|
var anchorOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Get anchor",
|
||||||
|
document: `a: &billyBob cat`,
|
||||||
|
expression: `.a | anchor`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!str)::billyBob\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Set anchor",
|
||||||
|
document: `a: cat`,
|
||||||
|
expression: `.a anchor = "foobar"`,
|
||||||
|
expected: []string{
|
||||||
|
"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",
|
||||||
|
document: `{b: &billyBob meow, a: *billyBob}`,
|
||||||
|
expression: `.a | alias`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!str)::billyBob\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Set alias",
|
||||||
|
document: `{b: &meow purr, a: cat}`,
|
||||||
|
expression: `.a alias = "meow"`,
|
||||||
|
expected: []string{
|
||||||
|
"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}}`,
|
||||||
@@ -82,9 +130,9 @@ foobar:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExplodeOperatorScenarios(t *testing.T) {
|
func TestAnchorAliaseOperatorScenarios(t *testing.T) {
|
||||||
for _, tt := range explodeTest {
|
for _, tt := range anchorOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
documentScenarios(t, "Explode", explodeTest)
|
documentScenarios(t, "Anchor and Alias Operators", anchorOperatorScenarios)
|
||||||
}
|
}
|
||||||
@@ -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}`,
|
||||||
@@ -80,6 +90,14 @@ var assignOperatorScenarios = []expressionScenario{
|
|||||||
{
|
{
|
||||||
description: "Update selected results",
|
description: "Update selected results",
|
||||||
document: `{a: {b: apple, c: cactus}}`,
|
document: `{a: {b: apple, c: cactus}}`,
|
||||||
|
expression: `(.a[] | select(. == "apple")) = "frog"`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: {b: apple, c: cactus}}`,
|
||||||
expression: `(.a.[] | select(. == "apple")) = "frog"`,
|
expression: `(.a.[] | select(. == "apple")) = "frog"`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
||||||
|
|||||||
@@ -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" {
|
||||||
@@ -25,77 +25,42 @@ func isTruthy(c *CandidateNode) (bool, error) {
|
|||||||
|
|
||||||
type boolOp func(bool, bool) bool
|
type boolOp func(bool, bool) bool
|
||||||
|
|
||||||
func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
|
func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
lhs.Node = unwrapDoc(lhs.Node)
|
||||||
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
rhs.Node = unwrapDoc(rhs.Node)
|
||||||
|
|
||||||
|
lhsTrue, errDecoding := isTruthy(lhs)
|
||||||
if errDecoding != nil {
|
if errDecoding != nil {
|
||||||
return errDecoding
|
return nil, errDecoding
|
||||||
}
|
}
|
||||||
|
|
||||||
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
|
rhsTrue, errDecoding := isTruthy(rhs)
|
||||||
rhsCandidate := rhsChild.Value.(*CandidateNode)
|
|
||||||
rhsTrue, errDecoding := isTruthy(rhsCandidate)
|
|
||||||
if errDecoding != nil {
|
if errDecoding != nil {
|
||||||
return errDecoding
|
return nil, errDecoding
|
||||||
}
|
}
|
||||||
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
|
|
||||||
results.PushBack(boolResult)
|
return createBooleanCandidate(lhs, op(lhsTrue, rhsTrue)), nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
|
func orOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
var results = list.New()
|
|
||||||
|
|
||||||
if matchingNodes.Len() == 0 {
|
|
||||||
lhs, err := d.GetMatchingNodes(list.New(), pathNode.Lhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rhs, err := d.GetMatchingNodes(list.New(), pathNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return results, performBoolOp(results, lhs, rhs, op)
|
|
||||||
}
|
|
||||||
|
|
||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
|
||||||
candidate := el.Value.(*CandidateNode)
|
|
||||||
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = performBoolOp(results, lhs, rhs, op)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
|
||||||
log.Debugf("-- orOp")
|
log.Debugf("-- orOp")
|
||||||
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
return crossFunction(d, matchingNodes, expressionNode, performBoolOp(
|
||||||
|
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 booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
return crossFunction(d, matchingNodes, expressionNode, performBoolOp(
|
||||||
|
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,22 +52,15 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode {
|
|
||||||
return &CandidateNode{
|
|
||||||
Document: candidate.Document,
|
|
||||||
Path: append(candidate.Path, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
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{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
|
||||||
@@ -94,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 !expressionNode.Operation.UpdateAssign {
|
||||||
|
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if rhs.Front() != nil {
|
if rhs.Front() != nil {
|
||||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
|
if expressionNode.Operation.UpdateAssign {
|
||||||
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
if rhs.Front() != nil {
|
||||||
|
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
|
||||||
candidate := el.Value.(*CandidateNode)
|
|
||||||
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`,
|
||||||
@@ -39,10 +72,11 @@ 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
|
||||||
|
|||||||
@@ -2,41 +2,69 @@ package yqlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
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) {
|
||||||
// for each lhs, splat the node,
|
|
||||||
// the intersect it against the rhs expression
|
nodesToDelete, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
// recreate the contents using only the intersection result.
|
|
||||||
|
|
||||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
|
||||||
candidate := el.Value.(*CandidateNode)
|
|
||||||
elMap := list.New()
|
|
||||||
elMap.PushBack(candidate)
|
|
||||||
nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs)
|
|
||||||
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
realNode := UnwrapDoc(candidate.Node)
|
for el := nodesToDelete.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
if realNode.Kind == yaml.SequenceNode {
|
deleteImmediateChildOp := &Operation{
|
||||||
deleteFromArray(candidate, nodesToDelete)
|
OperationType: deleteImmediateChildOpType,
|
||||||
} else if realNode.Kind == yaml.MappingNode {
|
Value: candidate.Path[len(candidate.Path)-1],
|
||||||
deleteFromMap(candidate, nodesToDelete)
|
}
|
||||||
} else {
|
|
||||||
log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate))
|
deleteImmediateChildOpNode := &ExpressionNode{
|
||||||
|
Operation: deleteImmediateChildOp,
|
||||||
|
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
func deleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
|
||||||
|
parents, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
childPath := expressionNode.Operation.Value
|
||||||
|
|
||||||
|
log.Debug("childPath to remove %v", childPath)
|
||||||
|
|
||||||
|
for el := parents.Front(); el != nil; el = el.Next() {
|
||||||
|
parent := el.Value.(*CandidateNode)
|
||||||
|
parentNode := unwrapDoc(parent.Node)
|
||||||
|
if parentNode.Kind == yaml.MappingNode {
|
||||||
|
deleteFromMap(parent, childPath)
|
||||||
|
} else if parentNode.Kind == yaml.SequenceNode {
|
||||||
|
deleteFromArray(parent, childPath)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return matchingNodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -44,18 +72,9 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
|||||||
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: append(candidate.Path, key.Value),
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldDelete := false
|
shouldDelete := key.Value == childPath
|
||||||
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
|
|
||||||
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
|
|
||||||
shouldDelete = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
|
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
|
||||||
|
|
||||||
@@ -66,27 +85,16 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
|||||||
node.Content = newContents
|
node.Content = newContents
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
|
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)
|
||||||
|
|
||||||
for index := 0; index < len(contents); index = index + 1 {
|
for index := 0; index < len(contents); index = index + 1 {
|
||||||
value := contents[index]
|
value := contents[index]
|
||||||
|
|
||||||
childCandidate := &CandidateNode{
|
shouldDelete := fmt.Sprintf("%v", index) == fmt.Sprintf("%v", childPath)
|
||||||
Node: value,
|
|
||||||
Document: candidate.Document,
|
|
||||||
Path: append(candidate.Path, index),
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldDelete := false
|
|
||||||
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
|
|
||||||
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
|
|
||||||
shouldDelete = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !shouldDelete {
|
if !shouldDelete {
|
||||||
newContents = append(newContents, value)
|
newContents = append(newContents, value)
|
||||||
|
|||||||
@@ -13,6 +13,22 @@ var deleteOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::{a: cat}\n",
|
"D0, P[], (doc)::{a: cat}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Delete nested entry in map",
|
||||||
|
document: `{a: {a1: fred, a2: frood}}`,
|
||||||
|
expression: `del(.a.a1)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: {a2: frood}}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: {a1: fred, a2: frood}}`,
|
||||||
|
expression: `del(.. | select(.=="frood"))`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{a: {a1: fred}}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Delete entry in array",
|
description: "Delete entry in array",
|
||||||
document: `[1,2,3]`,
|
document: `[1,2,3]`,
|
||||||
@@ -21,6 +37,14 @@ var deleteOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::[1, 3]\n",
|
"D0, P[], (doc)::[1, 3]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Delete nested entry in array",
|
||||||
|
document: `[{a: cat, b: dog}]`,
|
||||||
|
expression: `del(.[0].a)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::[{b: dog}]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Delete no matches",
|
description: "Delete no matches",
|
||||||
document: `{a: cat, b: dog}`,
|
document: `{a: cat, b: dog}`,
|
||||||
@@ -37,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,10 +14,27 @@ 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",
|
||||||
expression: `select(. | documentIndex == 1)`,
|
expression: `select(documentIndex == 1)`,
|
||||||
|
expected: []string{
|
||||||
|
"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{
|
expected: []string{
|
||||||
"D1, P[], (doc)::a: frog\n",
|
"D1, P[], (doc)::a: frog\n",
|
||||||
},
|
},
|
||||||
@@ -25,7 +42,7 @@ var documentIndexScenarios = []expressionScenario{
|
|||||||
{
|
{
|
||||||
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",
|
||||||
expression: `.a | ({"match": ., "doc": (. | documentIndex)})`,
|
expression: `.a | ({"match": ., "doc": documentIndex})`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::match: cat\ndoc: 0\n",
|
"D0, P[], (!!map)::match: cat\ndoc: 0\n",
|
||||||
"D0, P[], (!!map)::match: frog\ndoc: 1\n",
|
"D0, P[], (!!map)::match: frog\ndoc: 1\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,49 +42,91 @@ 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type multiplyPreferences struct {
|
||||||
AppendArrays bool
|
AppendArrays bool
|
||||||
|
TraversePrefs traversePreferences
|
||||||
}
|
}
|
||||||
|
|
||||||
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
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
|
||||||
err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays)
|
prefs := recursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
|
||||||
|
TraversePreferences: traversePreferences{DontFollowAlias: true}}
|
||||||
|
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -96,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
|
||||||
}
|
}
|
||||||
@@ -104,35 +145,22 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
|
|||||||
return lhs, nil
|
return lhs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTraversalTree(path []interface{}) *PathTreeNode {
|
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error {
|
||||||
if len(path) == 0 {
|
shouldAppendArrays := preferences.AppendArrays
|
||||||
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
|
|
||||||
} else if len(path) == 1 {
|
|
||||||
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
|
|
||||||
}
|
|
||||||
return &PathTreeNode{
|
|
||||||
Operation: &Operation{OperationType: ShortPipe},
|
|
||||||
Lhs: createTraversalTree(path[0:1]),
|
|
||||||
Rhs: createTraversalTree(path[1:])}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
|
|
||||||
|
|
||||||
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,22 @@ 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",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
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 +152,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 +177,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
|
||||||
|
|||||||
@@ -13,6 +13,23 @@ var pathOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[a b], (!!seq)::- a\n- b\n",
|
"D0, P[a b], (!!seq)::- a\n- b\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `a:
|
||||||
|
b:
|
||||||
|
c:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3`,
|
||||||
|
expression: `.a.b.c.[]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a b c 0], (!!int)::0\n",
|
||||||
|
"D0, P[a b c 1], (!!int)::1\n",
|
||||||
|
"D0, P[a b c 2], (!!int)::2\n",
|
||||||
|
"D0, P[a b c 3], (!!int)::3\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Get map key",
|
description: "Get map key",
|
||||||
document: `{a: {b: cat}}`,
|
document: `{a: {b: cat}}`,
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user