mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
1 Commits
v4.6.0
...
4.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12edcafd8c |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
|||||||
github: mikefarah
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
name: Bug report - V3
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: ''
|
||||||
labels: bug, v3
|
labels: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -10,14 +10,11 @@ assignees: ''
|
|||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
Note that any how to questions should be posted in the discussion board and not raised as an issue.
|
version of yq:
|
||||||
|
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, please keep it to 10 lines or less)
|
Concise yaml document(s) (as simple as possible to show the bug)
|
||||||
data1.yml:
|
data1.yml:
|
||||||
```yaml
|
```yaml
|
||||||
this: should really work
|
this: should really work
|
||||||
51
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
51
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
@@ -1,51 +0,0 @@
|
|||||||
---
|
|
||||||
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,23 +1,17 @@
|
|||||||
---
|
---
|
||||||
name: Feature request - V4
|
name: Feature request
|
||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: ''
|
title: ''
|
||||||
labels: enhancement, v4
|
labels: enhancement
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Please describe your feature request.**
|
**Is your feature request related to a problem? Please describe.**
|
||||||
A clear and concise description of what the request is and what it would solve.
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
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
|
||||||
@@ -26,7 +20,7 @@ country: Australia
|
|||||||
And we run a command:
|
And we run a command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yq eval 'predictWeatherOf(.country)'
|
yq predictWeather data1.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
it could output
|
it could output
|
||||||
|
|||||||
3
.github/workflows/go.yml
vendored
3
.github/workflows/go.yml
vendored
@@ -28,4 +28,7 @@ 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
|
||||||
70
.github/workflows/release.yml
vendored
70
.github/workflows/release.yml
vendored
@@ -1,70 +0,0 @@
|
|||||||
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:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- 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.3 as production
|
FROM alpine:3.12 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,7 +4,6 @@ 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 \
|
||||||
|
|||||||
134
README.md
134
README.md
@@ -3,64 +3,24 @@
|
|||||||
   
|
   
|
||||||
|
|
||||||
|
|
||||||
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.
|
a lightweight and portable command-line YAML processor
|
||||||
|
|
||||||
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.
|
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
|
||||||
|
|
||||||
## 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)
|
||||||
|
|
||||||
### wget
|
### MacOS:
|
||||||
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
|
||||||
```
|
```
|
||||||
|
|
||||||
or, for the (deprecated) v3 version:
|
### Ubuntu and other Linux distros supporting `snap` packages:
|
||||||
|
|
||||||
```
|
|
||||||
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:
|
||||||
|
|
||||||
@@ -79,6 +39,18 @@ 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:
|
||||||
@@ -90,7 +62,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 --entrypoint sh mikefarah/yq
|
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
|
||||||
```
|
```
|
||||||
|
|
||||||
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:
|
||||||
@@ -109,14 +81,6 @@ GO111MODULE=on go get github.com/mikefarah/yq/v4
|
|||||||
## 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:
|
||||||
```
|
```
|
||||||
@@ -124,14 +88,6 @@ 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```
|
||||||
@@ -152,18 +108,21 @@ 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
|
||||||
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
|
- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
|
||||||
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/v/v4.x/sort-keys)
|
- [Return the lengths of arrays/object/scalars](https://mikefarah.gitbook.io/yq/commands/read#printing-length-of-the-results)
|
||||||
- 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 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)
|
||||||
- [Update yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags)
|
- Update creates any missing entries in the path on the fly
|
||||||
- [Complex expressions to select and update](https://mikefarah.gitbook.io/yq/v/v4.x/select#select-and-update-matching-values-in-map)
|
- Deeply [compare](https://mikefarah.gitbook.io/yq/commands/compare) yaml files
|
||||||
- Keeps yaml formatting and comments when updating (though there are issues with whitespace)
|
- Keeps yaml formatting and comments when updating
|
||||||
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
|
- [Validate a yaml file](https://mikefarah.gitbook.io/yq/commands/validate)
|
||||||
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate)
|
- 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)
|
||||||
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
|
- [Prefix a path to a yaml file](https://mikefarah.gitbook.io/yq/commands/prefix)
|
||||||
|
- [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/)
|
||||||
|
|
||||||
@@ -175,33 +134,32 @@ Usage:
|
|||||||
yq [command]
|
yq [command]
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
eval Apply expression to each document in each yaml file given in sequence
|
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
|
||||||
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
|
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
|
||||||
help Help about any command
|
help Help about any command
|
||||||
shell-completion Generate completion script
|
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
|
||||||
|
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 force print with colors
|
-C, --colors 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)
|
||||||
-i, --inplace update the yaml file inplace of first yaml file given.
|
-P, --prettyPrint pretty print
|
||||||
-M, --no-colors force print with no colors
|
-j, --tojson output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc.
|
||||||
-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.
|
||||||
```
|
```
|
||||||
|
|
||||||
Simple Example:
|
## Upgrade from V2
|
||||||
|
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,6 +14,5 @@ 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
|
||||||
|
|||||||
@@ -42,21 +42,14 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
|||||||
colorsEnabled = true
|
colorsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
firstFileIndex := -1
|
if writeInplace && len(args) < 2 {
|
||||||
if !nullInput && len(args) == 1 {
|
|
||||||
firstFileIndex = 0
|
|
||||||
} else if len(args) > 1 {
|
|
||||||
firstFileIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if writeInplace && (firstFileIndex == -1) {
|
|
||||||
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if writeInplace {
|
if writeInplace {
|
||||||
// only use colors if its forced
|
// only use colors if its forced
|
||||||
colorsEnabled = forceColor
|
colorsEnabled = forceColor
|
||||||
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[firstFileIndex])
|
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
|
||||||
out, err = writeInPlaceHandler.CreateTempFile()
|
out, err = writeInPlaceHandler.CreateTempFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -66,29 +59,25 @@ 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(processExpression(""), []string{"-"}, printer)
|
err = allAtOnceEvaluator.EvaluateFiles("", []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(processExpression(args[0]), printer)
|
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
|
||||||
} else {
|
} else {
|
||||||
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
|
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
completedSuccessfully = err == nil
|
completedSuccessfully = err == nil
|
||||||
|
|||||||
@@ -33,16 +33,6 @@ 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
|
||||||
@@ -62,21 +52,14 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
|||||||
colorsEnabled = true
|
colorsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
firstFileIndex := -1
|
if writeInplace && len(args) < 2 {
|
||||||
if !nullInput && len(args) == 1 {
|
|
||||||
firstFileIndex = 0
|
|
||||||
} else if len(args) > 1 {
|
|
||||||
firstFileIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if writeInplace && (firstFileIndex == -1) {
|
|
||||||
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if writeInplace {
|
if writeInplace {
|
||||||
// only use colors if its forced
|
// only use colors if its forced
|
||||||
colorsEnabled = forceColor
|
colorsEnabled = forceColor
|
||||||
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[firstFileIndex])
|
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
|
||||||
out, err = writeInPlaceHandler.CreateTempFile()
|
out, err = writeInPlaceHandler.CreateTempFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -90,26 +73,22 @@ 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(processExpression(""), []string{"-"}, printer)
|
err = streamEvaluator.EvaluateFiles("", []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(processExpression(args[0]), printer)
|
err = streamEvaluator.EvaluateNew(args[0], printer)
|
||||||
} else {
|
} else {
|
||||||
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = streamEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
|
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||||
}
|
}
|
||||||
completedSuccessfully = err == nil
|
completedSuccessfully = err == nil
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ 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.6.0"
|
Version = "4.0.0-beta2"
|
||||||
|
|
||||||
// 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,6 +1,4 @@
|
|||||||
a:
|
a: simple # just the best
|
||||||
key1: "value1"
|
b: [1, 2]
|
||||||
key2: 2.6
|
c:
|
||||||
ab:
|
test: 1
|
||||||
key1: 6
|
|
||||||
key2: "h"
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM mikefarah/yq:4.6.0
|
FROM mikefarah/yq:3
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -1,16 +1,18 @@
|
|||||||
module github.com/mikefarah/yq/v4
|
module github.com/mikefarah/yq/v4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/elliotchance/orderedmap v1.4.0
|
github.com/elliotchance/orderedmap v1.3.0
|
||||||
github.com/fatih/color v1.10.0
|
github.com/fatih/color v1.9.0
|
||||||
github.com/goccy/go-yaml v1.8.8
|
github.com/goccy/go-yaml v1.8.1
|
||||||
github.com/jinzhu/copier v0.2.3
|
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||||
|
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-20210124154548-22da62e12c0c // indirect
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // 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-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|||||||
51
go.sum
51
go.sum
@@ -36,24 +36,22 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A=
|
github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
|
||||||
github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
|
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.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
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.8 h1:MGfRB1GeSn/hWXYWS2Pt67iC2GJNnebdIro01ddyucA=
|
github.com/goccy/go-yaml v1.8.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
|
||||||
github.com/goccy/go-yaml v1.8.8/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
|
||||||
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=
|
||||||
@@ -101,8 +99,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.2.3 h1:Oe09ju+9qft7TffZ7l/04AB2f8u1+V4ZMxmp/nnqeOs=
|
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
||||||
github.com/jinzhu/copier v0.2.3/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
||||||
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=
|
||||||
@@ -120,9 +118,15 @@ 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.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
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=
|
||||||
@@ -182,8 +186,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ=
|
github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ=
|
||||||
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
|
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
|
||||||
@@ -202,7 +204,6 @@ 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=
|
||||||
@@ -250,17 +251,21 @@ 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-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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=
|
||||||
@@ -283,9 +288,10 @@ 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=
|
||||||
@@ -315,6 +321,8 @@ 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=
|
||||||
@@ -326,9 +334,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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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,54 +1,29 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import (
|
import "container/list"
|
||||||
"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 ExpressionParser
|
treeCreator PathTreeCreator
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAllAtOnceEvaluator() Evaluator {
|
func NewAllAtOnceEvaluator() Evaluator {
|
||||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewExpressionParser()}
|
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
context, err := e.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputCandidates}, node)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return context.MatchingNodes, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -62,7 +37,7 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
|
|||||||
allDocuments.PushBackList(fileDocuments)
|
allDocuments.PushBackList(fileDocuments)
|
||||||
fileIndex = fileIndex + 1
|
fileIndex = fileIndex + 1
|
||||||
}
|
}
|
||||||
matches, err := e.EvaluateCandidateNodes(expression, allDocuments)
|
matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,8 @@ package yqlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
@@ -13,42 +15,10 @@ 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
|
|
||||||
IsMapKey bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) GetKey() string {
|
func (n *CandidateNode) GetKey() string {
|
||||||
keyPrefix := ""
|
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
||||||
if n.IsMapKey {
|
|
||||||
keyPrefix = "key-"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%v%v - %v", keyPrefix, 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) {
|
||||||
@@ -62,14 +32,13 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
||||||
log.Debug("UpdateAttributesFrom: n: %v other: %v", n.GetKey(), other.GetKey())
|
|
||||||
if n.Node.Kind != other.Node.Kind {
|
if n.Node.Kind != other.Node.Kind {
|
||||||
// clear out the contents when switching to a different type
|
// clear out the contents when switching to a different type
|
||||||
// e.g. map to array
|
// e.g. map to array
|
||||||
@@ -78,8 +47,6 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
|||||||
}
|
}
|
||||||
n.Node.Kind = other.Node.Kind
|
n.Node.Kind = other.Node.Kind
|
||||||
n.Node.Tag = other.Node.Tag
|
n.Node.Tag = other.Node.Tag
|
||||||
n.Node.Alias = other.Node.Alias
|
|
||||||
n.Node.Anchor = other.Node.Anchor
|
|
||||||
|
|
||||||
// merge will pickup the style of the new thing
|
// merge will pickup the style of the new thing
|
||||||
// when autocreating nodes
|
// when autocreating nodes
|
||||||
@@ -90,3 +57,46 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
|||||||
n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment
|
n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment
|
||||||
n.Node.LineComment = n.Node.LineComment + other.Node.LineComment
|
n.Node.LineComment = n.Node.LineComment + other.Node.LineComment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *CandidateNode) PathStackToString() string {
|
||||||
|
return mergePathStackToString(n.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergePathStackToString(pathStack []interface{}) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
for index, path := range pathStack {
|
||||||
|
switch path.(type) {
|
||||||
|
case int, int64:
|
||||||
|
// if arrayMergeStrategy == AppendArrayMergeStrategy {
|
||||||
|
// sb.WriteString("[+]")
|
||||||
|
// } else {
|
||||||
|
sb.WriteString(fmt.Sprintf("[%v]", path))
|
||||||
|
// }
|
||||||
|
|
||||||
|
default:
|
||||||
|
s := fmt.Sprintf("%v", path)
|
||||||
|
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
|
||||||
|
|
||||||
|
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
|
||||||
|
hasDoubleQuotes := strings.Contains(s, "\"")
|
||||||
|
wrappingCharacterStart := "\""
|
||||||
|
wrappingCharacterEnd := "\""
|
||||||
|
if hasDoubleQuotes {
|
||||||
|
wrappingCharacterStart = "("
|
||||||
|
wrappingCharacterEnd = ")"
|
||||||
|
}
|
||||||
|
if hasSpecial || errParsingInt == nil {
|
||||||
|
sb.WriteString(wrappingCharacterStart)
|
||||||
|
}
|
||||||
|
sb.WriteString(s)
|
||||||
|
if hasSpecial || errParsingInt == nil {
|
||||||
|
sb.WriteString(wrappingCharacterEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if index < len(pathStack)-1 {
|
||||||
|
sb.WriteString(".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(yamlBytes []byte, writer io.Writer) error {
|
func ColorizeAndPrint(bytes []byte, writer io.Writer) error {
|
||||||
tokens := lexer.Tokenize(string(yamlBytes))
|
tokens := lexer.Tokenize(string(bytes))
|
||||||
var p printer.Printer
|
var p printer.Printer
|
||||||
p.Bool = func() *printer.Property {
|
p.Bool = func() *printer.Property {
|
||||||
return &printer.Property{
|
return &printer.Property{
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/list"
|
|
||||||
|
|
||||||
"github.com/jinzhu/copier"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Context struct {
|
|
||||||
MatchingNodes *list.List
|
|
||||||
Variables map[string]*list.List
|
|
||||||
DontAutoCreate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Context) SingleChildContext(candidate *CandidateNode) Context {
|
|
||||||
list := list.New()
|
|
||||||
list.PushBack(candidate)
|
|
||||||
return n.ChildContext(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Context) GetVariable(name string) *list.List {
|
|
||||||
if n.Variables == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return n.Variables[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Context) SetVariable(name string, value *list.List) {
|
|
||||||
if n.Variables == nil {
|
|
||||||
n.Variables = make(map[string]*list.List)
|
|
||||||
}
|
|
||||||
n.Variables[name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Context) ChildContext(results *list.List) Context {
|
|
||||||
clone := Context{}
|
|
||||||
err := copier.Copy(&clone, n)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error cloning context :(")
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
clone.MatchingNodes = results
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Context) Clone() Context {
|
|
||||||
clone := Context{}
|
|
||||||
err := copier.Copy(&clone, n)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error cloning context :(")
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
@@ -3,14 +3,16 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"container/list"
|
||||||
|
|
||||||
logging "gopkg.in/op/go-logging.v1"
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataTreeNavigator interface {
|
type DataTreeNavigator interface {
|
||||||
// given the context and a expressionNode,
|
// given a list of CandidateEntities and a pathNode,
|
||||||
// this will process the against the given expressionNode and return
|
// this will process the list against the given pathNode and return
|
||||||
// a new context of matching candidates
|
// a new list of matching candidates
|
||||||
GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error)
|
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dataTreeNavigator struct {
|
type dataTreeNavigator struct {
|
||||||
@@ -20,22 +22,22 @@ func NewDataTreeNavigator() DataTreeNavigator {
|
|||||||
return &dataTreeNavigator{}
|
return &dataTreeNavigator{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
|
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
if expressionNode == nil {
|
if pathNode == nil {
|
||||||
log.Debugf("getMatchingNodes - nothing to do")
|
log.Debugf("getMatchingNodes - nothing to do")
|
||||||
return context, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
log.Debugf("Processing Op: %v", expressionNode.Operation.toString())
|
log.Debugf("Processing Op: %v", pathNode.Operation.toString())
|
||||||
if log.IsEnabledFor(logging.DEBUG) {
|
if log.IsEnabledFor(logging.DEBUG) {
|
||||||
for el := context.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 := expressionNode.Operation.OperationType.Handler
|
handler := pathNode.Operation.OperationType.Handler
|
||||||
if handler != nil {
|
if handler != nil {
|
||||||
return handler(d, context, expressionNode)
|
return handler(d, matchingNodes, pathNode)
|
||||||
}
|
}
|
||||||
return Context{}, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
|
return nil, fmt.Errorf("Unknown operator %v", pathNode.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
|
- number scalars: arithmetic addition (soon)
|
||||||
- string scalars: concatenate
|
- string scalars: concatenate (soon)
|
||||||
|
|
||||||
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
|
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
||||||
|
|
||||||
## Concatenate and assign arrays
|
## Concatenate and assign arrays
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
@@ -67,19 +67,23 @@ will output
|
|||||||
- 2
|
- 2
|
||||||
```
|
```
|
||||||
|
|
||||||
## Add new object to array
|
## Add object to array
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
a:
|
a:
|
||||||
- dog: woof
|
- 1
|
||||||
|
- 2
|
||||||
|
c:
|
||||||
|
cat: meow
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.a + {"cat": "meow"}' sample.yml
|
yq eval '.a + .c' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
- dog: woof
|
- 1
|
||||||
|
- 2
|
||||||
- cat: meow
|
- cat: meow
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -127,97 +131,3 @@ b:
|
|||||||
- 4
|
- 4
|
||||||
```
|
```
|
||||||
|
|
||||||
## String concatenation
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: cat
|
|
||||||
b: meow
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.a = .a + .b' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a: catmeow
|
|
||||||
b: meow
|
|
||||||
```
|
|
||||||
|
|
||||||
## Relative string concatenation
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: cat
|
|
||||||
b: meow
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.a += .b' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a: catmeow
|
|
||||||
b: meow
|
|
||||||
```
|
|
||||||
|
|
||||||
## Number addition - float
|
|
||||||
If the lhs or rhs are floats then the expression will be calculated with floats.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: 3
|
|
||||||
b: 4.9
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.a = .a + .b' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a: 7.9
|
|
||||||
b: 4.9
|
|
||||||
```
|
|
||||||
|
|
||||||
## Number addition - int
|
|
||||||
If both the lhs and rhs are ints then the expression will be calculated with ints.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: 3
|
|
||||||
b: 4
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.a = .a + .b' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a: 7
|
|
||||||
b: 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Increment number
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: 3
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.a += 1' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a: 4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Add to null
|
|
||||||
Adding to null simply returns the rhs
|
|
||||||
|
|
||||||
Running
|
|
||||||
```bash
|
|
||||||
yq eval --null-input 'null + "cat"'
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
cat
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
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,27 +34,6 @@ 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
|
||||||
@@ -133,7 +112,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,22 +13,6 @@ 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
|
||||||
@@ -78,22 +62,17 @@ 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,23 +14,6 @@ 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
|
||||||
@@ -48,21 +31,6 @@ 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
|
||||||
@@ -95,23 +63,3 @@ 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 (or the `di` shorthand) to select nodes of a particular document.
|
Use the `documentIndex` operator 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,24 +17,6 @@ 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
|
||||||
@@ -44,23 +26,7 @@ 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
|
||||||
@@ -76,7 +42,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
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -29,24 +29,6 @@ true
|
|||||||
false
|
false
|
||||||
```
|
```
|
||||||
|
|
||||||
## Don't match string
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- cat
|
|
||||||
- goat
|
|
||||||
- dog
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.[] | (. != "*at")' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
false
|
|
||||||
false
|
|
||||||
true
|
|
||||||
```
|
|
||||||
|
|
||||||
## Match number
|
## Match number
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -65,24 +47,6 @@ true
|
|||||||
false
|
false
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dont match number
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- 3
|
|
||||||
- 4
|
|
||||||
- 5
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.[] | (. != 4)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
true
|
|
||||||
false
|
|
||||||
true
|
|
||||||
```
|
|
||||||
|
|
||||||
## Match nulls
|
## Match nulls
|
||||||
Running
|
Running
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
98
pkg/yqlib/doc/Explode.md
Normal file
98
pkg/yqlib/doc/Explode.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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,11 +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(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
yq eval-all 'select(fileIndex == 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:
|
||||||
@@ -18,7 +16,7 @@ yq eval 'filename' sample.yml
|
|||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
sample.yml
|
sample.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Get file index
|
## Get file index
|
||||||
@@ -35,37 +33,3 @@ will output
|
|||||||
0
|
0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Get file indices of multiple documents
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: cat
|
|
||||||
```
|
|
||||||
And another sample another.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: cat
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval-all 'fileIndex' sample.yml another.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
0
|
|
||||||
---
|
|
||||||
1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Get file index alias
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: cat
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval 'fi' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
0
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -16,20 +16,6 @@ will output
|
|||||||
3
|
3
|
||||||
```
|
```
|
||||||
|
|
||||||
## null length
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: null
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.a | length' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Map length
|
## Map length
|
||||||
returns number of entries
|
returns number of entries
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,18 @@
|
|||||||
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
|
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.
|
||||||
|
|
||||||
## Objects and arrays - merging
|
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
|
||||||
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
|
|
||||||
|
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
### Merge Flags
|
## Merging files
|
||||||
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
|
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||||
|
|
||||||
- `+` to append arrays
|
|
||||||
- `?` to only merge existing fields
|
|
||||||
- `d` to deeply merge arrays
|
|
||||||
|
|
||||||
### Merging files
|
|
||||||
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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Multiply integers
|
|
||||||
Running
|
|
||||||
```bash
|
|
||||||
yq eval --null-input '3 * 4'
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
12
|
|
||||||
```
|
|
||||||
|
|
||||||
## Merge objects together, returning merged result only
|
## Merge objects together, returning merged result only
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -86,6 +70,7 @@ Given a sample.yml file of:
|
|||||||
a: {things: great}
|
a: {things: great}
|
||||||
b:
|
b:
|
||||||
also: "me"
|
also: "me"
|
||||||
|
|
||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
@@ -126,26 +111,6 @@ 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
|
||||||
@@ -178,59 +143,6 @@ 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, deeply merging arrays
|
|
||||||
Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a:
|
|
||||||
- name: fred
|
|
||||||
age: 12
|
|
||||||
- name: bob
|
|
||||||
age: 32
|
|
||||||
b:
|
|
||||||
- name: fred
|
|
||||||
age: 34
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.a *d .b' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
- name: fred
|
|
||||||
age: 34
|
|
||||||
- name: bob
|
|
||||||
age: 32
|
|
||||||
```
|
|
||||||
|
|
||||||
## Merge to prefix an element
|
## Merge to prefix an element
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -268,7 +180,7 @@ g: thongs
|
|||||||
f: *cat
|
f: *cat
|
||||||
```
|
```
|
||||||
|
|
||||||
## Merge copies anchor names
|
## Merge does not copy anchor names
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
a:
|
a:
|
||||||
@@ -285,7 +197,7 @@ yq eval '.c * .a' sample.yml
|
|||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
g: thongs
|
g: thongs
|
||||||
c: &cat frog
|
c: frog
|
||||||
```
|
```
|
||||||
|
|
||||||
## Merge with merge anchors
|
## Merge with merge anchors
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
63
pkg/yqlib/doc/Recursive Descent.md
Normal file
63
pkg/yqlib/doc/Recursive Descent.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
Reduce is a powerful way to process a collection of data into a new form.
|
|
||||||
|
|
||||||
```
|
|
||||||
<exp> as $<name> ireduce (<init>; <block>)
|
|
||||||
```
|
|
||||||
|
|
||||||
e.g.
|
|
||||||
|
|
||||||
```
|
|
||||||
.[] as $item ireduce (0; . + $item)
|
|
||||||
```
|
|
||||||
|
|
||||||
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
|
|
||||||
|
|
||||||
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
|
|
||||||
|
|
||||||
## yq vs jq syntax
|
|
||||||
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
|
|
||||||
|
|
||||||
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.
|
|
||||||
|
|
||||||
|
|
||||||
## Sum numbers
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- 10
|
|
||||||
- 2
|
|
||||||
- 5
|
|
||||||
- 3
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.[] as $item ireduce (0; . + $item)' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
20
|
|
||||||
```
|
|
||||||
|
|
||||||
## Merge all yaml files together
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: cat
|
|
||||||
```
|
|
||||||
And another sample another.yml file of:
|
|
||||||
```yaml
|
|
||||||
b: dog
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval-all '. as $item ireduce ({}; . * $item )' sample.yml another.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
a: cat
|
|
||||||
b: dog
|
|
||||||
```
|
|
||||||
|
|
||||||
## Convert an array to an object
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- name: Cathy
|
|
||||||
has: apples
|
|
||||||
- name: Bob
|
|
||||||
has: bananas
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
Cathy: apples
|
|
||||||
Bob: bananas
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# 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
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# 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,26 +40,6 @@ 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
|
||||||
@@ -145,19 +125,19 @@ will output
|
|||||||
{a: cat, b: 5, c: 3.2, e: true}
|
{a: cat, b: 5, c: 3.2, e: true}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reset style - or pretty print
|
## Pretty print
|
||||||
Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.
|
Set empty (default) quote style
|
||||||
|
|
||||||
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
|
||||||
@@ -167,22 +147,6 @@ 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,21 +22,7 @@ will output
|
|||||||
!!seq
|
!!seq
|
||||||
```
|
```
|
||||||
|
|
||||||
## Set custom tag
|
## Convert numbers to strings
|
||||||
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
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ c: banana
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Special characters
|
## Special characters
|
||||||
Use quotes with brackets around path elements with special characters
|
Use quotes around path elements with special characters
|
||||||
|
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -41,47 +41,13 @@ Given a sample.yml file of:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.["{}"]' sample.yml
|
yq eval '."{}"' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
frog
|
frog
|
||||||
```
|
```
|
||||||
|
|
||||||
## Keys with spaces
|
|
||||||
Use quotes with brackets around path elements with special characters
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
"red rabbit": frog
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.["red rabbit"]' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
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
|
||||||
|
|
||||||
@@ -140,7 +106,7 @@ b: *cat
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.b[]' sample.yml
|
yq eval '.b.[]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -324,7 +290,7 @@ foobar:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.foobar[]' sample.yml
|
yq eval '.foobar.[]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -390,7 +356,7 @@ foobar:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.foobarList[]' sample.yml
|
yq eval '.foobarList.[]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -400,21 +366,3 @@ 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
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
For more complex scenarios, variables can be used to hold values of expression to be used in other expressions.
|
|
||||||
|
|
||||||
## Single value variable
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
a: cat
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.a as $foo | $foo' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
cat
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multi value variable
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
- cat
|
|
||||||
- dog
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.[] as $foo | $foo' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
cat
|
|
||||||
dog
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using variables as a lookup
|
|
||||||
Example taken from [jq](https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...)
|
|
||||||
|
|
||||||
Given a sample.yml file of:
|
|
||||||
```yaml
|
|
||||||
"posts":
|
|
||||||
- "title": Frist psot
|
|
||||||
"author": anon
|
|
||||||
- "title": A well-written article
|
|
||||||
"author": person1
|
|
||||||
"realnames":
|
|
||||||
"anon": Anonymous Coward
|
|
||||||
"person1": Person McPherson
|
|
||||||
```
|
|
||||||
then
|
|
||||||
```bash
|
|
||||||
yq eval '.realnames as $names | .posts[] | {"title":.title, "author": $names[.author]}' sample.yml
|
|
||||||
```
|
|
||||||
will output
|
|
||||||
```yaml
|
|
||||||
title: Frist psot
|
|
||||||
author: Anonymous Coward
|
|
||||||
title: A well-written article
|
|
||||||
author: Person McPherson
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# 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
|
- number scalars: arithmetic addition (soon)
|
||||||
- string scalars: concatenate
|
- string scalars: concatenate (soon)
|
||||||
|
|
||||||
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
|
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
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 (or the `di` shorthand) to select nodes of a particular document.
|
Use the `documentIndex` operator to select nodes of a particular document.
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
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
pkg/yqlib/doc/headers/Explode.md
Normal file
1
pkg/yqlib/doc/headers/Explode.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Explodes (or dereferences) aliases and anchors.
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
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(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||||
```
|
```
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Keys
|
|
||||||
|
|
||||||
Use the `keys` operator to return map keys or array indices.
|
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
|
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.
|
||||||
|
|
||||||
## Objects and arrays - merging
|
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
|
||||||
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
|
|
||||||
|
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
### Merge Flags
|
## Merging files
|
||||||
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
|
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||||
|
|
||||||
- `+` to append arrays
|
|
||||||
- `?` to only merge existing fields
|
|
||||||
- `d` to deeply merge arrays
|
|
||||||
|
|
||||||
### Merging files
|
|
||||||
Note the use of `eval-all` to ensure all documents are loaded into memory.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
```
|
|
||||||
5
pkg/yqlib/doc/headers/Recursive Descent.md
Normal file
5
pkg/yqlib/doc/headers/Recursive Descent.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
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
|
||||||
|
```
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
Reduce is a powerful way to process a collection of data into a new form.
|
|
||||||
|
|
||||||
```
|
|
||||||
<exp> as $<name> ireduce (<init>; <block>)
|
|
||||||
```
|
|
||||||
|
|
||||||
e.g.
|
|
||||||
|
|
||||||
```
|
|
||||||
.[] as $item ireduce (0; . + $item)
|
|
||||||
```
|
|
||||||
|
|
||||||
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
|
|
||||||
|
|
||||||
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
|
|
||||||
|
|
||||||
## yq vs jq syntax
|
|
||||||
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
|
|
||||||
|
|
||||||
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Split into Documents
|
|
||||||
|
|
||||||
This operator splits all matches into separate documents
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# String Operators
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
For more complex scenarios, variables can be used to hold values of expression to be used in other expressions.
|
|
||||||
@@ -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,8 +76,6 @@ func mapKeysToStrings(node *yaml.Node) {
|
|||||||
|
|
||||||
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
||||||
var encoder = json.NewEncoder(destination)
|
var encoder = json.NewEncoder(destination)
|
||||||
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
|
||||||
|
|
||||||
var indentString = ""
|
var indentString = ""
|
||||||
|
|
||||||
for index := 0; index < indent; index++ {
|
for index := 0; index < indent; index++ {
|
||||||
@@ -155,15 +153,11 @@ func (o *orderedMap) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
||||||
|
if o.kv == nil {
|
||||||
|
return json.Marshal(o.altVal)
|
||||||
|
}
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
enc := json.NewEncoder(buf)
|
enc := json.NewEncoder(buf)
|
||||||
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
|
||||||
if o.kv == nil {
|
|
||||||
if err := enc.Encode(o.altVal); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
buf.WriteByte('{')
|
buf.WriteByte('{')
|
||||||
for idx, el := range o.kv {
|
for idx, el := range o.kv {
|
||||||
if err := enc.Encode(el.K); err != nil {
|
if err := enc.Encode(el.K); err != nil {
|
||||||
|
|||||||
@@ -9,11 +9,29 @@ import (
|
|||||||
"github.com/mikefarah/yq/v4/test"
|
"github.com/mikefarah/yq/v4/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func yamlToJson(sampleYaml string, indent int) string {
|
var sampleYaml = `zabbix: winner
|
||||||
|
apple: great
|
||||||
|
banana:
|
||||||
|
- {cobra: kai, angus: bob}
|
||||||
|
`
|
||||||
|
|
||||||
|
var expectedJson = `{
|
||||||
|
"zabbix": "winner",
|
||||||
|
"apple": "great",
|
||||||
|
"banana": [
|
||||||
|
{
|
||||||
|
"cobra": "kai",
|
||||||
|
"angus": "bob"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
writer := bufio.NewWriter(&output)
|
writer := bufio.NewWriter(&output)
|
||||||
|
|
||||||
var jsonEncoder = NewJsonEncoder(writer, indent)
|
var jsonEncoder = NewJsonEncoder(writer, 2)
|
||||||
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
|
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -24,33 +42,6 @@ func yamlToJson(sampleYaml string, indent int) string {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
|
test.AssertResult(t, expectedJson, output.String())
|
||||||
|
|
||||||
return strings.TrimSuffix(output.String(), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
|
|
||||||
var sampleYaml = `zabbix: winner
|
|
||||||
apple: great
|
|
||||||
banana:
|
|
||||||
- {cobra: kai, angus: bob}
|
|
||||||
`
|
|
||||||
var expectedJson = `{
|
|
||||||
"zabbix": "winner",
|
|
||||||
"apple": "great",
|
|
||||||
"banana": [
|
|
||||||
{
|
|
||||||
"cobra": "kai",
|
|
||||||
"angus": "bob"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`
|
|
||||||
var actualJson = yamlToJson(sampleYaml, 2)
|
|
||||||
test.AssertResult(t, expectedJson, actualJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJsonEncoderDoesNotEscapeHTMLChars(t *testing.T) {
|
|
||||||
var sampleYaml = `build: "( ./lint && ./format && ./compile ) < src.code"`
|
|
||||||
var expectedJson = `{"build":"( ./lint && ./format && ./compile ) < src.code"}`
|
|
||||||
var actualJson = yamlToJson(sampleYaml, 0)
|
|
||||||
test.AssertResult(t, expectedJson, actualJson)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
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("Bad expression, please check expression syntax")
|
|
||||||
}
|
|
||||||
return stack[0], nil
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
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, "Bad expression, please check expression syntax", err.Error())
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
logging "gopkg.in/op/go-logging.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type expressionPostFixer interface {
|
|
||||||
ConvertToPostfix([]*token) ([]*Operation, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type expressionPostFixerImpl struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func newExpressionPostFixer() expressionPostFixer {
|
|
||||||
return &expressionPostFixerImpl{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) {
|
|
||||||
var newOp *token
|
|
||||||
opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1]
|
|
||||||
log.Debugf("popped %v from opstack to results", newOp.toString(true))
|
|
||||||
return opStack, append(result, newOp.Operation)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) {
|
|
||||||
var result []*Operation
|
|
||||||
// surround the whole thing with brackets
|
|
||||||
var opStack = []*token{{TokenType: openBracket}}
|
|
||||||
var tokens = append(infixTokens, &token{TokenType: closeBracket})
|
|
||||||
|
|
||||||
for _, currentToken := range tokens {
|
|
||||||
log.Debugf("postfix processing currentToken %v", currentToken.toString(true))
|
|
||||||
switch currentToken.TokenType {
|
|
||||||
case openBracket, openCollect, openCollectObject:
|
|
||||||
opStack = append(opStack, currentToken)
|
|
||||||
log.Debugf("put %v onto the opstack", currentToken.toString(true))
|
|
||||||
case closeCollect, closeCollectObject:
|
|
||||||
var opener tokenType = openCollect
|
|
||||||
var collectOperator *operationType = collectOpType
|
|
||||||
if currentToken.TokenType == closeCollectObject {
|
|
||||||
opener = openCollectObject
|
|
||||||
collectOperator = collectObjectOpType
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener {
|
|
||||||
opStack, result = popOpToResult(opStack, result)
|
|
||||||
}
|
|
||||||
if len(opStack) == 0 {
|
|
||||||
return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket")
|
|
||||||
}
|
|
||||||
// now we should have [ as the last element on the opStack, get rid of it
|
|
||||||
opStack = opStack[0 : len(opStack)-1]
|
|
||||||
log.Debugf("deleteing open bracket from opstack")
|
|
||||||
|
|
||||||
//and append a collect to the opStack
|
|
||||||
result = append(result, &Operation{OperationType: collectOperator})
|
|
||||||
log.Debugf("put collect onto the result")
|
|
||||||
result = append(result, &Operation{OperationType: shortPipeOpType})
|
|
||||||
log.Debugf("put shortpipe onto the result")
|
|
||||||
|
|
||||||
case closeBracket:
|
|
||||||
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket {
|
|
||||||
opStack, result = popOpToResult(opStack, result)
|
|
||||||
}
|
|
||||||
if len(opStack) == 0 {
|
|
||||||
return nil, errors.New("Bad path expression, got close brackets without matching opening bracket")
|
|
||||||
}
|
|
||||||
// now we should have ( as the last element on the opStack, get rid of it
|
|
||||||
opStack = opStack[0 : len(opStack)-1]
|
|
||||||
|
|
||||||
default:
|
|
||||||
var currentPrecedence = currentToken.Operation.OperationType.Precedence
|
|
||||||
// pop off higher precedent operators onto the result
|
|
||||||
for len(opStack) > 0 &&
|
|
||||||
opStack[len(opStack)-1].TokenType == operationToken &&
|
|
||||||
opStack[len(opStack)-1].Operation.OperationType.Precedence > currentPrecedence {
|
|
||||||
opStack, result = popOpToResult(opStack, result)
|
|
||||||
}
|
|
||||||
// add this operator to the opStack
|
|
||||||
opStack = append(opStack, currentToken)
|
|
||||||
log.Debugf("put %v onto the opstack", currentToken.toString(true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if log.IsEnabledFor(logging.DEBUG) {
|
|
||||||
log.Debugf("PostFix Result:")
|
|
||||||
for _, currentToken := range result {
|
|
||||||
log.Debugf("> %v", currentToken.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
var pathTests = []struct {
|
|
||||||
path string
|
|
||||||
expectedTokens []interface{}
|
|
||||||
expectedPostFix []interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
`.a as $item ireduce (0; . + $item)`, // note - add code to shuffle reduce to this position for postfix
|
|
||||||
append(make([]interface{}, 0), "a", "ASSIGN_VARIABLE", "GET_VARIABLE", "REDUCE", "(", "0 (int64)", "BLOCK", "SELF", "ADD", "GET_VARIABLE", ")"),
|
|
||||||
append(make([]interface{}, 0), "a", "GET_VARIABLE", "ASSIGN_VARIABLE", "0 (int64)", "SELF", "GET_VARIABLE", "ADD", "BLOCK", "REDUCE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.a | .b | .c`,
|
|
||||||
append(make([]interface{}, 0), "a", "PIPE", "b", "PIPE", "c"),
|
|
||||||
append(make([]interface{}, 0), "a", "b", "c", "PIPE", "PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[]`,
|
|
||||||
append(make([]interface{}, 0), "[", "EMPTY", "]"),
|
|
||||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`{}`,
|
|
||||||
append(make([]interface{}, 0), "{", "EMPTY", "}"),
|
|
||||||
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[{}]`,
|
|
||||||
append(make([]interface{}, 0), "[", "{", "EMPTY", "}", "]"),
|
|
||||||
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE", "COLLECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.realnames as $names | $names["anon"]`,
|
|
||||||
append(make([]interface{}, 0), "realnames", "ASSIGN_VARIABLE", "GET_VARIABLE", "PIPE", "GET_VARIABLE", "TRAVERSE_ARRAY", "[", "anon (string)", "]"),
|
|
||||||
append(make([]interface{}, 0), "realnames", "GET_VARIABLE", "ASSIGN_VARIABLE", "GET_VARIABLE", "anon (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.b[.a]`,
|
|
||||||
append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"),
|
|
||||||
append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.[]`,
|
|
||||||
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
|
|
||||||
append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.a[]`,
|
|
||||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
|
|
||||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.a.[]`,
|
|
||||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
|
|
||||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.a[0]`,
|
|
||||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
|
||||||
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.a.[0]`,
|
|
||||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
|
||||||
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.a[].c`,
|
|
||||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "c"),
|
|
||||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "c", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[3]`,
|
|
||||||
append(make([]interface{}, 0), "[", "3 (int64)", "]"),
|
|
||||||
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.key.array + .key.array2`,
|
|
||||||
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ADD", "key", "SHORT_PIPE", "array2"),
|
|
||||||
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"`,
|
|
||||||
append(make([]interface{}, 0), "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
|
|
||||||
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`(.a | .[].b) == "apple"`,
|
|
||||||
append(make([]interface{}, 0), "(", "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
|
|
||||||
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.[] | select(. == "*at")`,
|
|
||||||
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
|
|
||||||
append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[true]`,
|
|
||||||
append(make([]interface{}, 0), "[", "true (bool)", "]"),
|
|
||||||
append(make([]interface{}, 0), "true (bool)", "COLLECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[true, false]`,
|
|
||||||
append(make([]interface{}, 0), "[", "true (bool)", "UNION", "false (bool)", "]"),
|
|
||||||
append(make([]interface{}, 0), "true (bool)", "false (bool)", "UNION", "COLLECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`"mike": .a`,
|
|
||||||
append(make([]interface{}, 0), "mike (string)", "CREATE_MAP", "a"),
|
|
||||||
append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.a: "mike"`,
|
|
||||||
append(make([]interface{}, 0), "a", "CREATE_MAP", "mike (string)"),
|
|
||||||
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`{"mike": .a}`,
|
|
||||||
append(make([]interface{}, 0), "{", "mike (string)", "CREATE_MAP", "a", "}"),
|
|
||||||
append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`{.a: "mike"}`,
|
|
||||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "mike (string)", "}"),
|
|
||||||
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`{.a: .c, .b.[]: .f.g[]}`,
|
|
||||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "}"),
|
|
||||||
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "f", "g", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`explode(.a.b)`,
|
|
||||||
append(make([]interface{}, 0), "EXPLODE", "(", "a", "SHORT_PIPE", "b", ")"),
|
|
||||||
append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "EXPLODE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.a.b style="folded"`,
|
|
||||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_STYLE", "folded (string)"),
|
|
||||||
append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "folded (string)", "ASSIGN_STYLE"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`tag == "str"`,
|
|
||||||
append(make([]interface{}, 0), "GET_TAG", "EQUALS", "str (string)"),
|
|
||||||
append(make([]interface{}, 0), "GET_TAG", "str (string)", "EQUALS"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`. tag= "str"`,
|
|
||||||
append(make([]interface{}, 0), "SELF", "ASSIGN_TAG", "str (string)"),
|
|
||||||
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_TAG"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`lineComment == "str"`,
|
|
||||||
append(make([]interface{}, 0), "GET_COMMENT", "EQUALS", "str (string)"),
|
|
||||||
append(make([]interface{}, 0), "GET_COMMENT", "str (string)", "EQUALS"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`. lineComment= "str"`,
|
|
||||||
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
|
|
||||||
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"`,
|
|
||||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"),
|
|
||||||
append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "!!str (string)", "ASSIGN_TAG"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`""`,
|
|
||||||
append(make([]interface{}, 0), " (string)"),
|
|
||||||
append(make([]interface{}, 0), " (string)"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`.foo* | (. style="flow")`,
|
|
||||||
append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"),
|
|
||||||
append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var tokeniser = newExpressionTokeniser()
|
|
||||||
var postFixer = newExpressionPostFixer()
|
|
||||||
|
|
||||||
func TestPathParsing(t *testing.T) {
|
|
||||||
for _, tt := range pathTests {
|
|
||||||
tokens, err := tokeniser.Tokenise(tt.path)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(tt.path, err)
|
|
||||||
}
|
|
||||||
var tokenValues []interface{}
|
|
||||||
for _, token := range tokens {
|
|
||||||
tokenValues = append(tokenValues, token.toString(false))
|
|
||||||
}
|
|
||||||
test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, fmt.Sprintf("tokenise: %v", tt.path))
|
|
||||||
|
|
||||||
results, errorP := postFixer.ConvertToPostfix(tokens)
|
|
||||||
|
|
||||||
var readableResults []interface{}
|
|
||||||
for _, token := range results {
|
|
||||||
readableResults = append(readableResults, token.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
if errorP != nil {
|
|
||||||
t.Error(tt.path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
test.AssertResultComplexWithContext(t, tt.expectedPostFix, readableResults, fmt.Sprintf("postfix: %v", tt.path))
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,439 +0,0 @@
|
|||||||
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(detail bool) string {
|
|
||||||
if t.TokenType == operationToken {
|
|
||||||
if detail {
|
|
||||||
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
|
|
||||||
}
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
if strings.Contains(options, "d") {
|
|
||||||
prefs.DeepMergeArrays = 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 getVariableOpToken() lex.Action {
|
|
||||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
|
||||||
value := string(m.Bytes)
|
|
||||||
|
|
||||||
value = value[1:]
|
|
||||||
|
|
||||||
getVarOperation := createValueOperation(value, value)
|
|
||||||
getVarOperation.OperationType = getVariableOpType
|
|
||||||
|
|
||||||
return &token{TokenType: operationToken, Operation: getVarOperation, CheckForPostTraverse: true}, 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(`ireduce`), opToken(reduceOpType))
|
|
||||||
lexer.Add([]byte(`;`), opToken(blockOpType))
|
|
||||||
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*`), opToken(notEqualsOpType))
|
|
||||||
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(`\*[\+|\?d]*`), multiplyWithPrefs())
|
|
||||||
lexer.Add([]byte(`\+`), opToken(addOpType))
|
|
||||||
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
|
|
||||||
lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken())
|
|
||||||
lexer.Add([]byte(`as`), opToken(assignVariableOpType))
|
|
||||||
|
|
||||||
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(true))
|
|
||||||
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
|
|
||||||
|
|
||||||
if index == 0 || tokens[index-1].TokenType != operationToken ||
|
|
||||||
tokens[index-1].Operation.OperationType != traversePathOpType {
|
|
||||||
op := &Operation{OperationType: selfReferenceOpType, StringValue: "SELF"}
|
|
||||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
|
||||||
}
|
|
||||||
|
|
||||||
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.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) ||
|
|
||||||
(currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) {
|
|
||||||
|
|
||||||
op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"}
|
|
||||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
|
||||||
}
|
|
||||||
|
|
||||||
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: traverseArrayOpType}
|
|
||||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
|
||||||
}
|
|
||||||
return postProcessedTokens, skipNextToken
|
|
||||||
}
|
|
||||||
114
pkg/yqlib/lib.go
114
pkg/yqlib/lib.go
@@ -1,5 +1,3 @@
|
|||||||
// Use the top level Evaluator or StreamEvaluator to evaluate expressions and return matches.
|
|
||||||
//
|
|
||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -13,95 +11,77 @@ 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 orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator}
|
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
||||||
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
|
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
||||||
var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 35, Handler: reduceOperator}
|
|
||||||
|
|
||||||
var blockOpType = &operationType{Type: "BLOCK", Precedence: 10, NumArgs: 2, Handler: emptyOperator}
|
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 AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator}
|
||||||
|
|
||||||
var assignOpType = &operationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: assignUpdateOperator}
|
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
||||||
var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator}
|
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
|
||||||
|
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
|
||||||
|
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
|
||||||
|
|
||||||
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
|
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
|
||||||
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
|
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
|
||||||
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator}
|
|
||||||
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
|
|
||||||
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 multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
|
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||||
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
|
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||||
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
|
|
||||||
|
|
||||||
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
|
var ShortPipe = &OperationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
|
||||||
var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator}
|
|
||||||
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator}
|
|
||||||
|
|
||||||
var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: pipeOperator}
|
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
|
||||||
|
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
|
||||||
|
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
|
||||||
|
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
|
||||||
|
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
|
||||||
|
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
|
||||||
|
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
|
||||||
|
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
|
||||||
|
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
|
||||||
|
|
||||||
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
|
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
||||||
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
|
var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator}
|
||||||
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
|
|
||||||
var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator}
|
|
||||||
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
|
|
||||||
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
|
|
||||||
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
|
|
||||||
var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator}
|
|
||||||
var getAliasOptype = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: getAliasOperator}
|
|
||||||
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 explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
||||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||||
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
|
||||||
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
|
|
||||||
|
|
||||||
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
|
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||||
|
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
|
||||||
|
var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator}
|
||||||
|
var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator}
|
||||||
|
var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
|
||||||
|
|
||||||
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
|
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
|
||||||
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 55, Handler: traversePathOperator}
|
|
||||||
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: traverseArrayOperator}
|
|
||||||
|
|
||||||
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator}
|
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
|
||||||
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
|
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
||||||
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
|
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
||||||
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
|
|
||||||
var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
|
|
||||||
|
|
||||||
var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -119,7 +99,7 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Operation{
|
return &Operation{
|
||||||
OperationType: valueOpType,
|
OperationType: ValueOp,
|
||||||
Value: value,
|
Value: value,
|
||||||
StringValue: stringValue,
|
StringValue: stringValue,
|
||||||
CandidateNode: &CandidateNode{Node: &node},
|
CandidateNode: &CandidateNode{Node: &node},
|
||||||
@@ -128,11 +108,13 @@ 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 == traversePathOpType {
|
if p.OperationType == TraversePath {
|
||||||
return fmt.Sprintf("%v", p.Value)
|
return fmt.Sprintf("%v", p.Value)
|
||||||
} else if p.OperationType == selfReferenceOpType {
|
} else if p.OperationType == DocumentFilter {
|
||||||
|
return fmt.Sprintf("d%v", p.Value)
|
||||||
|
} else if p.OperationType == SelfReference {
|
||||||
return "SELF"
|
return "SELF"
|
||||||
} else if p.OperationType == valueOpType {
|
} else if p.OperationType == ValueOp {
|
||||||
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 matchKey(name string, pattern string) (matched bool) {
|
func Match(name string, pattern string) (matched bool) {
|
||||||
if pattern == "" {
|
if pattern == "" {
|
||||||
return name == pattern
|
return name == pattern
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,32 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"strconv"
|
"container/list"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
|
func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
|
||||||
return &ExpressionNode{Operation: &Operation{OperationType: addOpType},
|
return &PathTreeNode{Operation: &Operation{OperationType: Add},
|
||||||
Lhs: lhs,
|
Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}},
|
||||||
Rhs: rhs}
|
Rhs: rhs}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
assignmentOp := &Operation{OperationType: assignOpType}
|
assignmentOp := &Operation{OperationType: Assign}
|
||||||
assignmentOp.UpdateAssign = false
|
assignmentOp.Preferences = &AssignOpPreferences{true}
|
||||||
|
|
||||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)}
|
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
|
||||||
return d.GetMatchingNodes(context, assignmentOpNode)
|
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toNodes(candidate *CandidateNode) []*yaml.Node {
|
func toNodes(candidates *list.List) []*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{}
|
||||||
}
|
}
|
||||||
@@ -36,76 +42,42 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*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)
|
||||||
|
|
||||||
return crossFunction(d, context, expressionNode, add)
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
|
||||||
func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
|
||||||
lhs.Node = unwrapDoc(lhs.Node)
|
|
||||||
rhs.Node = unwrapDoc(rhs.Node)
|
|
||||||
|
|
||||||
lhsNode := lhs.Node
|
|
||||||
|
|
||||||
if lhsNode.Tag == "!!null" {
|
|
||||||
return lhs.CreateChild(nil, rhs.Node), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target := lhs.CreateChild(nil, &yaml.Node{})
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
|
lhsCandidate := el.Value.(*CandidateNode)
|
||||||
|
lhsNode := UnwrapDoc(lhsCandidate.Node)
|
||||||
|
|
||||||
switch lhsNode.Kind {
|
target := &CandidateNode{
|
||||||
case yaml.MappingNode:
|
Path: lhsCandidate.Path,
|
||||||
return nil, fmt.Errorf("Maps not yet supported for addition")
|
Document: lhsCandidate.Document,
|
||||||
case yaml.SequenceNode:
|
Filename: lhsCandidate.Filename,
|
||||||
target.Node.Kind = yaml.SequenceNode
|
Node: &yaml.Node{},
|
||||||
target.Node.Style = lhsNode.Style
|
}
|
||||||
target.Node.Tag = "!!seq"
|
|
||||||
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
switch lhsNode.Kind {
|
||||||
case yaml.ScalarNode:
|
case yaml.MappingNode:
|
||||||
if rhs.Node.Kind != yaml.ScalarNode {
|
return nil, fmt.Errorf("Maps not yet supported for addition")
|
||||||
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
|
case yaml.SequenceNode:
|
||||||
|
target.Node.Kind = yaml.SequenceNode
|
||||||
|
target.Node.Style = lhsNode.Style
|
||||||
|
target.Node.Tag = "!!seq"
|
||||||
|
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||||
|
results.PushBack(target)
|
||||||
|
case yaml.ScalarNode:
|
||||||
|
return nil, fmt.Errorf("Scalars not yet supported for addition")
|
||||||
}
|
}
|
||||||
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,15 +5,6 @@ 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]}}`,
|
||||||
@@ -30,14 +21,6 @@ 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]}`,
|
||||||
@@ -47,11 +30,11 @@ var addOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Add new object to array",
|
description: "Add object to array",
|
||||||
document: `a: [{dog: woof}]`,
|
document: `{a: [1,2], c: {cat: meow}}`,
|
||||||
expression: `.a + {"cat": "meow"}`,
|
expression: `.a + .c`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[a], (!!seq)::[{dog: woof}, {cat: meow}]\n",
|
"D0, P[a], (!!seq)::[1, 2, {cat: meow}]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,56 +53,6 @@ var addOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
|
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
description: "String concatenation",
|
|
||||||
document: `{a: cat, b: meow}`,
|
|
||||||
expression: `.a = .a + .b`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Relative string concatenation",
|
|
||||||
document: `{a: cat, b: meow}`,
|
|
||||||
expression: `.a += .b`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Number addition - float",
|
|
||||||
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
|
|
||||||
document: `{a: 3, b: 4.9}`,
|
|
||||||
expression: `.a = .a + .b`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (doc)::{a: 7.9, b: 4.9}\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Number addition - int",
|
|
||||||
subdescription: "If both the lhs and rhs are ints then the expression will be calculated with ints.",
|
|
||||||
document: `{a: 3, b: 4}`,
|
|
||||||
expression: `.a = .a + .b`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (doc)::{a: 7, b: 4}\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Increment number",
|
|
||||||
document: `{a: 3}`,
|
|
||||||
expression: `.a += 1`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (doc)::{a: 4}\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Add to null",
|
|
||||||
subdescription: "Adding to null simply returns the rhs",
|
|
||||||
expression: `null + "cat"`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (!!str)::cat\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddOperatorScenarios(t *testing.T) {
|
func TestAddOperatorScenarios(t *testing.T) {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
// corssFunction no matches
|
|
||||||
// can boolean use crossfunction
|
|
||||||
|
|
||||||
func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
||||||
log.Debugf("-- alternative")
|
|
||||||
return crossFunction(d, context, expressionNode, alternativeFunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func alternativeFunc(d *dataTreeNavigator, context Context, 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
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/list"
|
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
||||||
|
|
||||||
log.Debugf("AssignAlias operator!")
|
|
||||||
|
|
||||||
aliasName := ""
|
|
||||||
if !expressionNode.Operation.UpdateAssign {
|
|
||||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
if rhs.MatchingNodes.Front() != nil {
|
|
||||||
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for el := lhs.MatchingNodes.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(context.SingleChildContext(candidate), expressionNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
if rhs.MatchingNodes.Front() != nil {
|
|
||||||
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
candidate.Node.Kind = yaml.AliasNode
|
|
||||||
candidate.Node.Value = aliasName
|
|
||||||
}
|
|
||||||
return context, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
||||||
log.Debugf("GetAlias operator!")
|
|
||||||
var results = list.New()
|
|
||||||
|
|
||||||
for el := context.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 context.ChildContext(results), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func assignAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
||||||
|
|
||||||
log.Debugf("AssignAnchor operator!")
|
|
||||||
|
|
||||||
anchorName := ""
|
|
||||||
if !expressionNode.Operation.UpdateAssign {
|
|
||||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rhs.MatchingNodes.Front() != nil {
|
|
||||||
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for el := lhs.MatchingNodes.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(context.SingleChildContext(candidate), expressionNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rhs.MatchingNodes.Front() != nil {
|
|
||||||
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
candidate.Node.Anchor = anchorName
|
|
||||||
}
|
|
||||||
return context, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
||||||
log.Debugf("GetAnchor operator!")
|
|
||||||
var results = list.New()
|
|
||||||
|
|
||||||
for el := context.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 context.ChildContext(results), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func explodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
||||||
log.Debugf("-- ExplodeOperation")
|
|
||||||
|
|
||||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
|
||||||
candidate := el.Value.(*CandidateNode)
|
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
for childEl := rhs.MatchingNodes.Front(); childEl != nil; childEl = childEl.Next() {
|
|
||||||
err = explodeNode(childEl.Value.(*CandidateNode).Node, context)
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return context, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func explodeNode(node *yaml.Node, context Context) error {
|
|
||||||
node.Anchor = ""
|
|
||||||
switch node.Kind {
|
|
||||||
case yaml.SequenceNode, yaml.DocumentNode:
|
|
||||||
for index, contentNode := range node.Content {
|
|
||||||
log.Debugf("exploding index %v", index)
|
|
||||||
errorInContent := explodeNode(contentNode, context)
|
|
||||||
if errorInContent != nil {
|
|
||||||
return errorInContent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case yaml.AliasNode:
|
|
||||||
log.Debugf("its an alias!")
|
|
||||||
if node.Alias != nil {
|
|
||||||
node.Kind = node.Alias.Kind
|
|
||||||
node.Style = node.Alias.Style
|
|
||||||
node.Tag = node.Alias.Tag
|
|
||||||
node.Content = node.Alias.Content
|
|
||||||
node.Value = node.Alias.Value
|
|
||||||
node.Alias = nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case yaml.MappingNode:
|
|
||||||
var newContent = list.New()
|
|
||||||
for index := 0; index < len(node.Content); index = index + 2 {
|
|
||||||
keyNode := node.Content[index]
|
|
||||||
valueNode := node.Content[index+1]
|
|
||||||
log.Debugf("traversing %v", keyNode.Value)
|
|
||||||
if keyNode.Value != "<<" {
|
|
||||||
err := overrideEntry(node, keyNode, valueNode, index, context.ChildContext(newContent))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if valueNode.Kind == yaml.SequenceNode {
|
|
||||||
log.Debugf("an alias merge list!")
|
|
||||||
for index := 0; index < len(valueNode.Content); index = index + 1 {
|
|
||||||
aliasNode := valueNode.Content[index]
|
|
||||||
err := applyAlias(node, aliasNode.Alias, index, context.ChildContext(newContent))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Debugf("an alias merge!")
|
|
||||||
err := applyAlias(node, valueNode.Alias, index, context.ChildContext(newContent))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.Content = make([]*yaml.Node, newContent.Len())
|
|
||||||
index := 0
|
|
||||||
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
|
||||||
node.Content[index] = newEl.Value.(*yaml.Node)
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent Context) error {
|
|
||||||
if alias == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for index := 0; index < len(alias.Content); index = index + 2 {
|
|
||||||
keyNode := alias.Content[index]
|
|
||||||
log.Debugf("applying alias key %v", keyNode.Value)
|
|
||||||
valueNode := alias.Content[index+1]
|
|
||||||
err := overrideEntry(node, keyNode, valueNode, aliasIndex, newContent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent Context) error {
|
|
||||||
|
|
||||||
err := explodeNode(value, newContent)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for newEl := newContent.MatchingNodes.Front(); newEl != nil; newEl = newEl.Next() {
|
|
||||||
valueEl := newEl.Next() // move forward twice
|
|
||||||
keyNode := newEl.Value.(*yaml.Node)
|
|
||||||
log.Debugf("checking new content %v:%v", keyNode.Value, valueEl.Value.(*yaml.Node).Value)
|
|
||||||
if keyNode.Value == key.Value && keyNode.Alias == nil && key.Alias == nil {
|
|
||||||
log.Debugf("overridign new content")
|
|
||||||
valueEl.Value = value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
newEl = valueEl // move forward twice
|
|
||||||
}
|
|
||||||
|
|
||||||
for index := startIndex + 2; index < len(node.Content); index = index + 2 {
|
|
||||||
keyNode := node.Content[index]
|
|
||||||
|
|
||||||
if keyNode.Value == key.Value && keyNode.Alias == nil {
|
|
||||||
log.Debugf("content will be overridden at index %v", index)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = explodeNode(key, newContent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugf("adding %v:%v", key.Value, value.Value)
|
|
||||||
newContent.MatchingNodes.PushBack(key)
|
|
||||||
newContent.MatchingNodes.PushBack(value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,61 +1,70 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
import "container/list"
|
||||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
|
||||||
|
type AssignOpPreferences struct {
|
||||||
|
UpdateAssign bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var rhs Context
|
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
|
||||||
if !expressionNode.Operation.UpdateAssign {
|
|
||||||
rhs, err = d.GetMatchingNodes(context, expressionNode.Rhs)
|
var rhs *list.List
|
||||||
|
if !preferences.UpdateAssign {
|
||||||
|
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
if expressionNode.Operation.UpdateAssign {
|
if preferences.UpdateAssign {
|
||||||
rhs, err = d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// grab the first value
|
// grab the first value
|
||||||
first := rhs.MatchingNodes.Front()
|
first := rhs.Front()
|
||||||
|
|
||||||
if first != nil {
|
if first != nil {
|
||||||
rhsCandidate := first.Value.(*CandidateNode)
|
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
||||||
rhsCandidate.Node = unwrapDoc(rhsCandidate.Node)
|
|
||||||
candidate.UpdateFrom(rhsCandidate)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// // if there was nothing given, perhaps we are creating a new yaml doc
|
||||||
return context, nil
|
// if matchingNodes.Len() == 0 {
|
||||||
|
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
|
||||||
|
// return lhs, nil
|
||||||
|
// }
|
||||||
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// does not update content or values
|
// does not update content or values
|
||||||
func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debug("getting lhs matching nodes for update")
|
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for el := lhs.MatchingNodes.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(context.SingleChildContext(candidate), expressionNode.Rhs)
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// grab the first value
|
// grab the first value
|
||||||
first := rhs.MatchingNodes.Front()
|
first := rhs.Front()
|
||||||
|
|
||||||
if first != nil {
|
if first != nil {
|
||||||
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode))
|
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return context, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,16 +20,6 @@ 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}`,
|
||||||
@@ -90,15 +80,7 @@ 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"`,
|
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"`,
|
|
||||||
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,54 +25,89 @@ func isTruthy(c *CandidateNode) (bool, error) {
|
|||||||
|
|
||||||
type boolOp func(bool, bool) bool
|
type boolOp func(bool, bool) bool
|
||||||
|
|
||||||
func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
|
||||||
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
||||||
lhs.Node = unwrapDoc(lhs.Node)
|
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
||||||
rhs.Node = unwrapDoc(rhs.Node)
|
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
||||||
|
|
||||||
lhsTrue, errDecoding := isTruthy(lhs)
|
|
||||||
if errDecoding != nil {
|
if errDecoding != nil {
|
||||||
return nil, errDecoding
|
return errDecoding
|
||||||
}
|
}
|
||||||
|
|
||||||
rhsTrue, errDecoding := isTruthy(rhs)
|
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
|
||||||
if errDecoding != nil {
|
rhsCandidate := rhsChild.Value.(*CandidateNode)
|
||||||
return nil, errDecoding
|
rhsTrue, errDecoding := isTruthy(rhsCandidate)
|
||||||
|
if errDecoding != nil {
|
||||||
|
return errDecoding
|
||||||
|
}
|
||||||
|
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
|
||||||
|
results.PushBack(boolResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
return createBooleanCandidate(lhs, op(lhsTrue, rhsTrue)), nil
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*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 crossFunction(d, context, expressionNode, performBoolOp(
|
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
||||||
func(b1 bool, b2 bool) bool {
|
return b1 || b2
|
||||||
return b1 || b2
|
})
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debugf("-- AndOp")
|
log.Debugf("-- AndOp")
|
||||||
return crossFunction(d, context, expressionNode, performBoolOp(
|
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
||||||
func(b1 bool, b2 bool) bool {
|
return b1 && b2
|
||||||
return b1 && b2
|
})
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func notOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debugf("-- notOperation")
|
log.Debugf("-- notOperation")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
log.Debug("notOperation checking %v", candidate)
|
log.Debug("notOperation checking %v", candidate)
|
||||||
truthy, errDecoding := isTruthy(candidate)
|
truthy, errDecoding := isTruthy(candidate)
|
||||||
if errDecoding != nil {
|
if errDecoding != nil {
|
||||||
return Context{}, errDecoding
|
return nil, errDecoding
|
||||||
}
|
}
|
||||||
result := createBooleanCandidate(candidate, !truthy)
|
result := createBooleanCandidate(candidate, !truthy)
|
||||||
results.PushBack(result)
|
results.PushBack(result)
|
||||||
}
|
}
|
||||||
return context.ChildContext(results), nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,35 +6,34 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func collectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debugf("-- collectOperation")
|
log.Debugf("-- collectOperation")
|
||||||
|
|
||||||
if context.MatchingNodes.Len() == 0 {
|
if matchMap.Len() == 0 {
|
||||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
|
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
|
||||||
candidate := &CandidateNode{Node: node}
|
candidate := &CandidateNode{Node: node}
|
||||||
return context.SingleChildContext(candidate), nil
|
return nodeToMap(candidate), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
if context.MatchingNodes.Front() != nil {
|
|
||||||
collectC = context.MatchingNodes.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 := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
var document uint = 0
|
||||||
|
var path []interface{}
|
||||||
|
|
||||||
|
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))
|
||||||
node.Content = append(node.Content, unwrapDoc(candidate.Node))
|
if path == nil && candidate.Path != nil && len(candidate.Path) > 1 {
|
||||||
|
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 context.ChildContext(results), nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,83 +17,90 @@ import (
|
|||||||
...
|
...
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func collectObjectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debugf("-- collectObjectOperation")
|
log.Debugf("-- collectObjectOperation")
|
||||||
|
|
||||||
if context.MatchingNodes.Len() == 0 {
|
if matchMap.Len() == 0 {
|
||||||
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"}
|
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"}
|
||||||
candidate := &CandidateNode{Node: node}
|
candidate := &CandidateNode{Node: node}
|
||||||
return context.SingleChildContext(candidate), nil
|
return nodeToMap(candidate), nil
|
||||||
}
|
}
|
||||||
first := context.MatchingNodes.Front().Value.(*CandidateNode)
|
first := matchMap.Front().Value.(*CandidateNode)
|
||||||
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
||||||
|
|
||||||
for i := 0; i < len(first.Node.Content); i++ {
|
for i := 0; i < len(first.Node.Content); i++ {
|
||||||
rotated[i] = list.New()
|
rotated[i] = list.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
for el := context.MatchingNodes.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(candidateNode.CreateChild(i, candidateNode.Node.Content[i]))
|
rotated[i].PushBack(createChildCandidate(candidateNode, i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newObject := list.New()
|
newObject := list.New()
|
||||||
for i := 0; i < len(first.Node.Content); i++ {
|
for i := 0; i < len(first.Node.Content); i++ {
|
||||||
additions, err := collect(d, context.ChildContext(list.New()), rotated[i])
|
additions, err := collect(d, list.New(), rotated[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newObject.PushBackList(additions.MatchingNodes)
|
newObject.PushBackList(additions)
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.ChildContext(newObject), nil
|
return newObject, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func collect(d *dataTreeNavigator, context Context, remainingMatches *list.List) (Context, error) {
|
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) {
|
||||||
if remainingMatches.Len() == 0 {
|
if remainingMatches.Len() == 0 {
|
||||||
return context, 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, context.SingleChildContext(candidate),
|
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||||
traversePreferences{DontFollowAlias: true, IncludeMapKeys: false})
|
|
||||||
|
|
||||||
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
|
||||||
splatEl.Value.(*CandidateNode).Path = nil
|
splatEl.Value.(*CandidateNode).Path = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.MatchingNodes.Len() == 0 {
|
if aggregate.Len() == 0 {
|
||||||
return collect(d, splatted, remainingMatches)
|
return collect(d, splatted, remainingMatches)
|
||||||
}
|
}
|
||||||
|
|
||||||
newAgg := list.New()
|
newAgg := list.New()
|
||||||
|
|
||||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
for el := aggregate.Front(); el != nil; el = el.Next() {
|
||||||
aggCandidate := el.Value.(*CandidateNode)
|
aggCandidate := el.Value.(*CandidateNode)
|
||||||
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||||
splatCandidate := splatEl.Value.(*CandidateNode)
|
splatCandidate := splatEl.Value.(*CandidateNode)
|
||||||
newCandidate, err := aggCandidate.Copy()
|
newCandidate, err := aggCandidate.Copy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
newCandidate.Path = nil
|
newCandidate.Path = nil
|
||||||
|
|
||||||
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, context, newCandidate, splatCandidate)
|
newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
newAgg.PushBack(newCandidate)
|
newAgg.PushBack(newCandidate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return collect(d, context.ChildContext(newAgg), remainingMatches)
|
return collect(d, newAgg, remainingMatches)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, "Create, Collect into Object", collectObjectOperatorScenarios)
|
documentScenarios(t, "Collect into Object", collectObjectOperatorScenarios)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,15 +13,6 @@ 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,53 +4,38 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
"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, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
|
|
||||||
log.Debugf("AssignComments operator!")
|
log.Debugf("AssignComments operator!")
|
||||||
|
|
||||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
comment := ""
|
||||||
|
if rhs.Front() != nil {
|
||||||
|
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||||
|
|
||||||
comment := ""
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
if !expressionNode.Operation.UpdateAssign {
|
|
||||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rhs.MatchingNodes.Front() != nil {
|
|
||||||
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
if expressionNode.Operation.UpdateAssign {
|
|
||||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rhs.MatchingNodes.Front() != nil {
|
|
||||||
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Setting comment of : %v", candidate.GetKey())
|
log.Debugf("Setting comment of : %v", candidate.GetKey())
|
||||||
if preferences.LineComment {
|
if preferences.LineComment {
|
||||||
candidate.Node.LineComment = comment
|
candidate.Node.LineComment = comment
|
||||||
@@ -63,15 +48,15 @@ func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return context, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||||
log.Debugf("GetComments operator!")
|
log.Debugf("GetComments operator!")
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
comment := ""
|
comment := ""
|
||||||
if preferences.LineComment {
|
if preferences.LineComment {
|
||||||
@@ -84,8 +69,8 @@ func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *
|
|||||||
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"}
|
||||||
result := candidate.CreateChild(nil, node)
|
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||||
results.PushBack(result)
|
results.PushBack(lengthCand)
|
||||||
}
|
}
|
||||||
return context.ChildContext(results), nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,39 +13,6 @@ 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`,
|
||||||
@@ -71,12 +38,11 @@ var commentOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Remove all comments",
|
description: "Remove all comments",
|
||||||
subdescription: "Note the use of `...` to ensure key nodes are included.",
|
document: "# hi\n\na: cat # comment\n\n# great\n",
|
||||||
document: "# hi\n\na: cat # comment\n\n# great\n\nb: # key comment",
|
expression: `.. comments=""`,
|
||||||
expression: `... comments=""`,
|
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::a: cat\nb:\n",
|
"D0, P[], (!!map)::a: cat\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*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.
|
||||||
@@ -18,29 +18,29 @@ func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
|
|||||||
|
|
||||||
sequences := list.New()
|
sequences := list.New()
|
||||||
|
|
||||||
if context.MatchingNodes.Len() > 0 {
|
if matchingNodes.Len() > 0 {
|
||||||
|
|
||||||
for matchingNodeEl := context.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, context, matchingNode, expressionNode)
|
sequenceNode, err := sequenceFor(d, matchingNode, pathNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sequences.PushBack(sequenceNode)
|
sequences.PushBack(sequenceNode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sequenceNode, err := sequenceFor(d, context, nil, expressionNode)
|
sequenceNode, err := sequenceFor(d, nil, pathNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Context{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sequences.PushBack(sequenceNode)
|
sequences.PushBack(sequenceNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.SingleChildContext(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
|
return nodeToMap(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, expressionNode *ExpressionNode) (*CandidateNode, error) {
|
func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathTreeNode) (*CandidateNode, error) {
|
||||||
var path []interface{}
|
var path []interface{}
|
||||||
var document uint = 0
|
var document uint = 0
|
||||||
var matches = list.New()
|
var matches = list.New()
|
||||||
@@ -48,17 +48,17 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
|
|||||||
if matchingNode != nil {
|
if matchingNode != nil {
|
||||||
path = matchingNode.Path
|
path = matchingNode.Path
|
||||||
document = matchingNode.Document
|
document = matchingNode.Document
|
||||||
matches.PushBack(matchingNode)
|
matches = nodeToMap(matchingNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapPairs, err := crossFunction(d, context.ChildContext(matches), expressionNode,
|
mapPairs, err := crossFunction(d, matches, pathNode,
|
||||||
func(d *dataTreeNavigator, context Context, 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
|
||||||
@@ -67,7 +67,7 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
innerList := listToNodeSeq(mapPairs.MatchingNodes)
|
innerList := listToNodeSeq(mapPairs)
|
||||||
innerList.Style = yaml.FlowStyle
|
innerList.Style = yaml.FlowStyle
|
||||||
return &CandidateNode{Node: innerList, Document: document, Path: path}, nil
|
return &CandidateNode{Node: innerList, Document: document, Path: path}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,42 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"container/list"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
contextToUse := context.Clone()
|
// for each lhs, splat the node,
|
||||||
contextToUse.DontAutoCreate = true
|
// the intersect it against the rhs expression
|
||||||
nodesToDelete, err := d.GetMatchingNodes(contextToUse, expressionNode.Rhs)
|
// recreate the contents using only the intersection result.
|
||||||
|
|
||||||
if err != nil {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
//need to iterate backwards to ensure correct indices when deleting multiple
|
|
||||||
for el := nodesToDelete.MatchingNodes.Back(); el != nil; el = el.Prev() {
|
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
elMap := list.New()
|
||||||
if len(candidate.Path) > 0 {
|
elMap.PushBack(candidate)
|
||||||
deleteImmediateChildOp := &Operation{
|
nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs)
|
||||||
OperationType: deleteImmediateChildOpType,
|
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
|
||||||
Value: candidate.Path[len(candidate.Path)-1],
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
|
||||||
deleteImmediateChildOpNode := &ExpressionNode{
|
|
||||||
Operation: deleteImmediateChildOp,
|
|
||||||
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}, false),
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := d.GetMatchingNodes(contextToUse, deleteImmediateChildOpNode)
|
|
||||||
if err != nil {
|
|
||||||
return Context{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return context, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteImmediateChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
realNode := UnwrapDoc(candidate.Node)
|
||||||
parents, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
|
||||||
|
|
||||||
if err != nil {
|
if realNode.Kind == yaml.SequenceNode {
|
||||||
return Context{}, err
|
deleteFromArray(candidate, nodesToDelete)
|
||||||
}
|
} else if realNode.Kind == yaml.MappingNode {
|
||||||
|
deleteFromMap(candidate, nodesToDelete)
|
||||||
childPath := expressionNode.Operation.Value
|
|
||||||
|
|
||||||
log.Debug("childPath to remove %v", childPath)
|
|
||||||
|
|
||||||
for el := parents.MatchingNodes.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 {
|
} else {
|
||||||
return Context{}, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
|
log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return context, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||||
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)
|
||||||
|
|
||||||
@@ -74,9 +44,18 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
|||||||
key := contents[index]
|
key := contents[index]
|
||||||
value := contents[index+1]
|
value := contents[index+1]
|
||||||
|
|
||||||
childCandidate := candidate.CreateChild(key.Value, value)
|
childCandidate := &CandidateNode{
|
||||||
|
Node: value,
|
||||||
|
Document: candidate.Document,
|
||||||
|
Path: append(candidate.Path, key.Value),
|
||||||
|
}
|
||||||
|
|
||||||
shouldDelete := key.Value == childPath
|
shouldDelete := false
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -87,16 +66,27 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
|||||||
node.Content = newContents
|
node.Content = newContents
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
|
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||||
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]
|
||||||
|
|
||||||
shouldDelete := fmt.Sprintf("%v", index) == fmt.Sprintf("%v", childPath)
|
childCandidate := &CandidateNode{
|
||||||
|
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,22 +13,6 @@ 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]`,
|
||||||
@@ -37,46 +21,6 @@ var deleteOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (doc)::[1, 3]\n",
|
"D0, P[], (doc)::[1, 3]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
skipDoc: true,
|
|
||||||
document: `a: [1,2,3]`,
|
|
||||||
expression: `del(.a[])`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (doc)::a: []\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
skipDoc: true,
|
|
||||||
document: `a: [10,x,10, 10, x, 10]`,
|
|
||||||
expression: `del(.a[] | select(. == 10))`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (doc)::a: [x, x]\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
skipDoc: true,
|
|
||||||
document: `a: {thing1: yep, thing2: cool, thing3: hi, b: {thing1: cool, great: huh}}`,
|
|
||||||
expression: `del(..)`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (!!map)::{}\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
skipDoc: true,
|
|
||||||
document: `a: {thing1: yep, thing2: cool, thing3: hi, b: {thing1: cool, great: huh}}`,
|
|
||||||
expression: `del(.. | select(tag == "!!map") | (.b.thing1,.thing2))`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[], (!!map)::a: {thing1: yep, thing3: hi, b: {great: huh}}\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}`,
|
||||||
@@ -93,14 +37,6 @@ 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,14 +7,14 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getDocumentIndexOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func GetDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
for el := context.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 := candidate.CreateChild(nil, node)
|
scalar := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||||
results.PushBack(scalar)
|
results.PushBack(scalar)
|
||||||
}
|
}
|
||||||
return context.ChildContext(results), nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,27 +14,10 @@ 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",
|
||||||
},
|
},
|
||||||
@@ -42,7 +25,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",
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type envOpPreferences struct {
|
|
||||||
StringValue bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func envOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, 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 Context{}, 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 Context{}, 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 context.SingleChildContext(target), nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,33 +1,22 @@
|
|||||||
package yqlib
|
package yqlib
|
||||||
|
|
||||||
import "gopkg.in/yaml.v3"
|
import (
|
||||||
|
"container/list"
|
||||||
|
)
|
||||||
|
|
||||||
func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
func EqualsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debugf("-- equalsOperation")
|
log.Debugf("-- equalsOperation")
|
||||||
return crossFunction(d, context, expressionNode, isEquals(false))
|
return crossFunction(d, matchingNodes, pathNode, isEquals)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
value := false
|
||||||
value := false
|
|
||||||
|
|
||||||
lhsNode := unwrapDoc(lhs.Node)
|
if lhs.Node.Tag == "!!null" {
|
||||||
rhsNode := unwrapDoc(rhs.Node)
|
value = (rhs.Node.Tag == "!!null")
|
||||||
|
} else {
|
||||||
if lhsNode.Tag == "!!null" {
|
value = Match(lhs.Node.Value, rhs.Node.Value)
|
||||||
value = (rhsNode.Tag == "!!null")
|
|
||||||
} else if lhsNode.Kind == yaml.ScalarNode && rhsNode.Kind == yaml.ScalarNode {
|
|
||||||
value = matchKey(lhsNode.Value, rhsNode.Value)
|
|
||||||
}
|
|
||||||
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
|
|
||||||
if flip {
|
|
||||||
value = !value
|
|
||||||
}
|
|
||||||
return createBooleanCandidate(lhs, value), nil
|
|
||||||
}
|
}
|
||||||
}
|
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
|
||||||
|
return createBooleanCandidate(lhs, value), nil
|
||||||
func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
||||||
log.Debugf("-- equalsOperation")
|
|
||||||
return crossFunction(d, context, expressionNode, isEquals(true))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,6 @@ 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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
skipDoc: true,
|
|
||||||
document: "{a: { b: {things: \"\"}, f: [1], g: [] }}",
|
|
||||||
expression: ".. | select(. == \"\")",
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[a b things], (!!str)::\"\"\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
description: "Match string",
|
description: "Match string",
|
||||||
document: `[cat,goat,dog]`,
|
document: `[cat,goat,dog]`,
|
||||||
@@ -31,18 +14,7 @@ var equalsOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[1], (!!bool)::true\n",
|
"D0, P[1], (!!bool)::true\n",
|
||||||
"D0, P[2], (!!bool)::false\n",
|
"D0, P[2], (!!bool)::false\n",
|
||||||
},
|
},
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
description: "Don't match string",
|
|
||||||
document: `[cat,goat,dog]`,
|
|
||||||
expression: `.[] | (. != "*at")`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[0], (!!bool)::false\n",
|
|
||||||
"D0, P[1], (!!bool)::false\n",
|
|
||||||
"D0, P[2], (!!bool)::true\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "Match number",
|
description: "Match number",
|
||||||
document: `[3, 4, 5]`,
|
document: `[3, 4, 5]`,
|
||||||
expression: `.[] | (. == 4)`,
|
expression: `.[] | (. == 4)`,
|
||||||
@@ -51,18 +23,7 @@ var equalsOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[1], (!!bool)::true\n",
|
"D0, P[1], (!!bool)::true\n",
|
||||||
"D0, P[2], (!!bool)::false\n",
|
"D0, P[2], (!!bool)::false\n",
|
||||||
},
|
},
|
||||||
},
|
}, {
|
||||||
{
|
|
||||||
description: "Dont match number",
|
|
||||||
document: `[3, 4, 5]`,
|
|
||||||
expression: `.[] | (. != 4)`,
|
|
||||||
expected: []string{
|
|
||||||
"D0, P[0], (!!bool)::true\n",
|
|
||||||
"D0, P[1], (!!bool)::false\n",
|
|
||||||
"D0, P[2], (!!bool)::true\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
|
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
|
||||||
expression: `.a | (.[].b == "apple")`,
|
expression: `.a | (.[].b == "apple")`,
|
||||||
|
|||||||
151
pkg/yqlib/operator_explode.go
Normal file
151
pkg/yqlib/operator_explode.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
|
log.Debugf("-- ExplodeOperation")
|
||||||
|
|
||||||
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
|
||||||
|
err = explodeNode(childEl.Value.(*CandidateNode).Node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func explodeNode(node *yaml.Node) error {
|
||||||
|
node.Anchor = ""
|
||||||
|
switch node.Kind {
|
||||||
|
case yaml.SequenceNode, yaml.DocumentNode:
|
||||||
|
for index, contentNode := range node.Content {
|
||||||
|
log.Debugf("exploding index %v", index)
|
||||||
|
errorInContent := explodeNode(contentNode)
|
||||||
|
if errorInContent != nil {
|
||||||
|
return errorInContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case yaml.AliasNode:
|
||||||
|
log.Debugf("its an alias!")
|
||||||
|
if node.Alias != nil {
|
||||||
|
node.Kind = node.Alias.Kind
|
||||||
|
node.Style = node.Alias.Style
|
||||||
|
node.Tag = node.Alias.Tag
|
||||||
|
node.Content = node.Alias.Content
|
||||||
|
node.Value = node.Alias.Value
|
||||||
|
node.Alias = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case yaml.MappingNode:
|
||||||
|
var newContent = list.New()
|
||||||
|
for index := 0; index < len(node.Content); index = index + 2 {
|
||||||
|
keyNode := node.Content[index]
|
||||||
|
valueNode := node.Content[index+1]
|
||||||
|
log.Debugf("traversing %v", keyNode.Value)
|
||||||
|
if keyNode.Value != "<<" {
|
||||||
|
err := overrideEntry(node, keyNode, valueNode, index, newContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if valueNode.Kind == yaml.SequenceNode {
|
||||||
|
log.Debugf("an alias merge list!")
|
||||||
|
for index := 0; index < len(valueNode.Content); index = index + 1 {
|
||||||
|
aliasNode := valueNode.Content[index]
|
||||||
|
err := applyAlias(node, aliasNode.Alias, index, newContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debugf("an alias merge!")
|
||||||
|
err := applyAlias(node, valueNode.Alias, index, newContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.Content = make([]*yaml.Node, newContent.Len())
|
||||||
|
index := 0
|
||||||
|
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
||||||
|
node.Content[index] = newEl.Value.(*yaml.Node)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent *list.List) error {
|
||||||
|
if alias == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for index := 0; index < len(alias.Content); index = index + 2 {
|
||||||
|
keyNode := alias.Content[index]
|
||||||
|
log.Debugf("applying alias key %v", keyNode.Value)
|
||||||
|
valueNode := alias.Content[index+1]
|
||||||
|
err := overrideEntry(node, keyNode, valueNode, aliasIndex, newContent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent *list.List) error {
|
||||||
|
|
||||||
|
err := explodeNode(value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
||||||
|
valueEl := newEl.Next() // move forward twice
|
||||||
|
keyNode := newEl.Value.(*yaml.Node)
|
||||||
|
log.Debugf("checking new content %v:%v", keyNode.Value, valueEl.Value.(*yaml.Node).Value)
|
||||||
|
if keyNode.Value == key.Value && keyNode.Alias == nil && key.Alias == nil {
|
||||||
|
log.Debugf("overridign new content")
|
||||||
|
valueEl.Value = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
newEl = valueEl // move forward twice
|
||||||
|
}
|
||||||
|
|
||||||
|
for index := startIndex + 2; index < len(node.Content); index = index + 2 {
|
||||||
|
keyNode := node.Content[index]
|
||||||
|
|
||||||
|
if keyNode.Value == key.Value && keyNode.Alias == nil {
|
||||||
|
log.Debugf("content will be overridden at index %v", index)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = explodeNode(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("adding %v:%v", key.Value, value.Value)
|
||||||
|
newContent.PushBack(key)
|
||||||
|
newContent.PushBack(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -4,55 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var anchorOperatorScenarios = []expressionScenario{
|
var explodeTest = []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}}`,
|
||||||
@@ -130,9 +82,9 @@ foobar:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnchorAliaseOperatorScenarios(t *testing.T) {
|
func TestExplodeOperatorScenarios(t *testing.T) {
|
||||||
for _, tt := range anchorOperatorScenarios {
|
for _, tt := range explodeTest {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
documentScenarios(t, "Anchor and Alias Operators", anchorOperatorScenarios)
|
documentScenarios(t, "Explode", explodeTest)
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user