1
0
mirror of https://github.com/taigrr/yq synced 2025-01-18 04:53:17 -08:00

Compare commits

..

115 Commits

Author SHA1 Message Date
Mike Farah
5a55869745 Bump version 2021-01-09 12:23:20 +11:00
Mike Farah
15e18bb98b Error when passing files and using null-input flag 2021-01-09 12:23:06 +11:00
Mike Farah
7798a141cf Bumped go yaml for comment hanlding fixes 2021-01-08 20:56:54 +11:00
Mike Farah
b7583a538d Added webi 2021-01-08 12:14:36 +11:00
Mike Farah
529e88b630 Added recurse examples 2021-01-08 12:11:29 +11:00
Mike Farah
658006eb93 Added another delete example 2021-01-08 11:59:49 +11:00
Mike Farah
2743a7e058 Can assign-update tag 2021-01-06 20:44:28 +11:00
Mike Farah
f95862fba3 Can assign-update style 2021-01-06 20:37:53 +11:00
Mike Farah
3f58c4bc38 Can assign-update aliases and anchors 2021-01-06 20:30:48 +11:00
Mike Farah
a7975df7cd Can assign-update comments 2021-01-06 20:22:50 +11:00
Mike Farah
3d5a5e0360 updating readme 2021-01-06 15:33:20 +11:00
Mike Farah
155755ae2f brew v3! 2021-01-06 15:22:29 +11:00
Mike Farah
601c13531c Updated collect objcet doc 2021-01-06 15:20:54 +11:00
Mike Farah
69d00c89df Added shorthand document index selection 2021-01-05 13:28:37 +11:00
Mike Farah
2faff7b05f Unwrap node in get tag to return proper tag at root level 2021-01-05 13:23:03 +11:00
Mike Farah
165949041d Added v3 snap instructions 2021-01-05 13:05:03 +11:00
Mike Farah
dbd7ab0f13 Refactored doc generation, add fi fileIndex alias 2021-01-02 10:49:33 +11:00
Mike Farah
6d512ad718 Fixed updating yaml from other files 2021-01-02 10:27:32 +11:00
Mike Farah
4fef4a7ab1 update issue template, instruct questions to be raised in disussion 2020-12-31 13:43:44 +11:00
Mike Farah
dcb17b51a9 fixed heading 2020-12-31 09:33:15 +11:00
Mike Farah
385417556d added tar.gz instructions 2020-12-31 09:30:59 +11:00
Mike Farah
edb5f213d7 updating readme 2020-12-31 09:22:22 +11:00
Mike Farah
5cfd9b05ee updating readme 2020-12-31 09:22:18 +11:00
jonatasrenan
0e764c59ce wget version var missing a 'v' prefix. 2020-12-31 08:59:38 +11:00
Mike Farah
1b887e23b3 scripts/check works for local and docker build 2020-12-30 10:40:41 +11:00
Chris Warth
a76b72e691 find golangci_lint through PATH 2020-12-30 10:30:53 +11:00
Mike Farah
9509831cff Updated docs 2020-12-29 22:35:57 +11:00
Mike Farah
e92180e89d updated release instructions 2020-12-29 09:59:16 +11:00
Mike Farah
a6ae33c3f1 Cleaning up release process, fixed github action version 2020-12-29 09:50:21 +11:00
Mike Farah
94a563dfd8 Updated docs 2020-12-28 11:57:20 +11:00
Mike Farah
0328cfd619 Added prettyPrint flag 2020-12-28 11:40:41 +11:00
Mike Farah
88663a6ce3 Added recurse keys operator 2020-12-28 11:24:42 +11:00
Mike Farah
b10a9ccfc6 Removed TraversePrefs 2020-12-28 10:29:43 +11:00
Mike Farah
9e9e15df73 More scenarios 2020-12-27 23:00:46 +11:00
Mike Farah
6cc6fdf322 Cleaning code 2020-12-27 22:56:15 +11:00
Mike Farah
a88c2dc5d3 Traverse Array Operator 2020-12-27 22:48:20 +11:00
Mike Farah
ea231006ed Refactoring traverse 2020-12-27 09:55:08 +11:00
Mike Farah
80f187f1a4 Refactoring traverse 2020-12-27 09:51:34 +11:00
Mike Farah
98e8b3479f Fixed nested array splat path 2020-12-25 12:49:05 +11:00
Mike Farah
eb539ff326 Fixed doc links 2020-12-23 10:37:51 +11:00
Mike Farah
c09f7aa707 Cleaning up docs 2020-12-23 10:30:13 +11:00
Mike Farah
7f5c380d16 v4.1.0 2020-12-23 10:23:54 +11:00
Mike Farah
6a05e517f1 Updated release instructions, remove gate for release 2020-12-23 10:23:09 +11:00
Mike Farah
8ee6f7dc1a fixing xcompile for git action 2020-12-22 22:50:01 +11:00
Mike Farah
8bd54cd603 fixing xcompile for git action 2020-12-22 22:31:28 +11:00
Mike Farah
f2f7b6db0f only tar executable files 2020-12-22 20:50:52 +11:00
Mike Farah
e082fee5d4 Fixed rhash call 2020-12-22 20:37:35 +11:00
Mike Farah
412911561f Fixed xcompile.sh 2020-12-22 20:23:13 +11:00
Mike Farah
52ae633cb7 trialing github release actions 2020-12-22 20:05:23 +11:00
Mike Farah
9356ca59bb trialing github release actions 2020-12-22 20:03:01 +11:00
Mike Farah
6dec167f74 trialing github release actions 2020-12-22 20:01:21 +11:00
Mike Farah
00f6981314 trialing github release actions 2020-12-22 19:56:56 +11:00
Mike Farah
4c60a2a967 trialing github release actions 2020-12-22 19:52:44 +11:00
Mike Farah
f4529614c4 trialing github release actions 2020-12-22 19:19:48 +11:00
Mike Farah
f059e13f94 automated docker releases! 2020-12-22 16:16:31 +11:00
Mike Farah
4dbf158505 playing with release action 2020-12-22 16:01:00 +11:00
Mike Farah
39a93a2836 playing with release action 2020-12-22 16:00:15 +11:00
Mike Farah
7c158fce26 playing with release action 2020-12-22 15:58:56 +11:00
Mike Farah
0bf43d2a55 playing with release action 2020-12-22 15:47:59 +11:00
Mike Farah
1b0bce5da6 Added alias operator;
alias, anchor and explode ops are now all documented together
2020-12-22 12:23:13 +11:00
Mike Farah
f112bde5fe Added anchor operator 2020-12-22 11:57:41 +11:00
Mike Farah
e5aa4a87a4 fixed test name 2020-12-22 11:47:58 +11:00
Mike Farah
f305e8fa12 Fixed delete full path 2020-12-22 11:45:51 +11:00
Mike Farah
d2d0c2c111 Added missing flag 2020-12-22 10:40:20 +11:00
Mike Farah
2aab79431c moved string space test to op values test cases 2020-12-22 10:38:52 +11:00
djajcevic
540d4953f5 #607 Fix string value with spaces error 2020-12-22 10:29:21 +11:00
Mike Farah
7849232255 tar files to keep permissions of exectuable 2020-12-22 10:25:15 +11:00
Mike Farah
57cd67f055 Added compressed binaries for download managers and better file size 2020-12-21 21:40:08 +11:00
Mike Farah
6b17fd4fc1 Added trivy to docker build, bumped alpine image 2020-12-21 15:48:25 +11:00
Mike Farah
ca8cd78616 Add now uses crossFunction 2020-12-21 11:54:03 +11:00
Mike Farah
9876b0ce8f Boolean operators now use the crossFunction util func 2020-12-21 11:42:35 +11:00
Mike Farah
a23272727d Added Alternative op 2020-12-21 11:32:34 +11:00
Mike Farah
1fb37785d6 Better readme 2020-12-20 13:34:58 +11:00
Mike Farah
efb9027540 Merge branch 'v4' 2020-12-20 13:31:23 +11:00
Mike Farah
2577fe5425 Increment version 2020-12-20 13:29:12 +11:00
Mike Farah
f39c57fed9 Updating readme for imminent v4 release 2020-12-20 12:39:42 +11:00
Mike Farah
1e8f755e7c Updating readme for imminent v4 release 2020-12-20 12:37:15 +11:00
Mike Farah
bb088f6aa2 Added better error reporting 2020-12-17 14:19:46 +11:00
Mike Farah
a96b74e779 Added better error reporting 2020-12-17 14:02:54 +11:00
Mike Farah
09a9e1e7f0 handle multiple document streams 2020-12-15 14:33:50 +11:00
Mike Farah
db60746e4e Can now properly handle .a[] expressions 2020-12-09 12:15:14 +11:00
Shashank Veerapaneni
8846255d1c Update README.md
Fix the wget download command
2020-12-02 19:42:05 +11:00
Mike Farah
a3e422ff76 added another test 2020-12-01 18:10:10 +11:00
Mike Farah
2c3357702d clarified pipe parsing tests 2020-12-01 18:08:41 +11:00
Mike Farah
c9dbf04da3 Added pipe and length docs, fix pipe precedence 2020-12-01 17:58:07 +11:00
Mike Farah
fbe53885ae Update README.md 2020-12-01 17:24:14 +11:00
Mike Farah
da027f69d7 updated cobra package 2020-12-01 16:16:20 +11:00
Mike Farah
8cd290c00b incrementing version 2020-12-01 15:15:12 +11:00
Mike Farah
363fe5d283 Added sort keys operator 2020-12-01 15:06:54 +11:00
Mike Farah
773b1a3517 fixed create doc for eval-all 2020-12-01 14:23:27 +11:00
Mike Farah
cf4915d786 improved acceptance tests 2020-12-01 14:14:16 +11:00
Mike Farah
08f579f4e3 Fixed create yaml 2020-12-01 14:06:49 +11:00
Mike Farah
c9229439f7 added exit status 2020-11-30 16:35:21 +11:00
Mike Farah
9bc66c80b6 Added write-inlplace flag 2020-11-30 16:05:07 +11:00
Mike Farah
8de10e550d wip - write in place 2020-11-29 20:25:47 +11:00
Mike Farah
1258fa199e Updated lib todo list 2020-11-28 11:25:10 +11:00
Mike Farah
3a030651a3 Added append equals, merge append. Fixed creating numeric arrays 2020-11-28 11:24:16 +11:00
Mike Farah
3f48201a19 wip 2020-11-28 10:46:04 +11:00
Mike Farah
3cecb4e383 wip 2020-11-28 10:41:09 +11:00
Mike Farah
13679e51e2 Added get key examples 2020-11-26 11:20:53 +11:00
Mike Farah
f42728eef7 updated issue templates 2020-11-25 22:33:50 +11:00
Mike Farah
93136ba840 updated issue templates 2020-11-25 22:32:47 +11:00
Mike Farah
5665dc7b71 updated issue templates 2020-11-25 22:31:55 +11:00
Mike Farah
9302279dd2 updated issue templates 2020-11-25 22:22:16 +11:00
Mike Farah
5205f01248 Fixed recursive decent on empty objects/arrays 2020-11-25 15:01:12 +11:00
Mike Farah
5972bb2f23 attempt to fix pipeline 2020-11-25 11:17:28 +11:00
Mike Farah
ec43f5e7c3 go mod tidy 2020-11-25 11:06:43 +11:00
Mike Farah
62f262147c Attempt to fix git pipeline 2020-11-20 14:00:24 +11:00
Mike Farah
0259bb44c1 Updated readme 2020-11-20 13:55:18 +11:00
bahetiamit
e07a5b6065 Adding github action on release to publish multi-arch image 2020-11-20 13:48:35 +11:00
Mike Farah
f69a81b79b Updated readme re v4 2020-11-19 22:56:13 +11:00
Mike Farah
ccb718cd0f Moved macports to community, announced v4 2020-10-20 13:58:45 +11:00
Herby Gillot
815edef86a README: add instructions for installing with MacPorts 2020-10-20 13:50:10 +11:00
Mike Farah
cd83d94b6a updating release instructions 2020-10-19 09:01:52 +11:00
Mike Farah
6afc2e9189 3.4.1 2020-10-19 08:40:59 +11:00
115 changed files with 3594 additions and 1751 deletions

View File

@@ -1,8 +1,8 @@
---
name: Bug report
name: Bug report - V3
about: Create a report to help us improve
title: ''
labels: bug
labels: bug, v3
assignees: ''
---
@@ -10,11 +10,14 @@ assignees: ''
**Describe the bug**
A clear and concise description of what the bug is.
version of yq:
operating system:
Note that any how to questions should be posted in the discussion board and not raised as an issue.
Version of yq: 3.X.X
Operating system: mac/linux/windows/....
Installed via: docker/binary release/homebrew/snap/...
**Input Yaml**
Concise yaml document(s) (as simple as possible to show the bug)
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

51
.github/ISSUE_TEMPLATE/bug_report_v4.md vendored Normal file
View File

@@ -0,0 +1,51 @@
---
name: Bug report - V4
about: Create a report to help us improve
title: ''
labels: bug, v4
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
Note that any how to questions should be posted in the discussion board and not raised as an issue.
Version of yq: 4.X.X
Operating system: mac/linux/windows/....
Installed via: docker/binary release/homebrew/snap/...
**Input Yaml**
Concise yaml document(s) (as simple as possible to show the bug, please keep it to 10 lines or less)
data1.yml:
```yaml
this: should really work
```
data2.yml:
```yaml
but: it strangely didn't
```
**Command**
The command you ran:
```
yq eval-all 'select(fileIndex==0) | .a.b.c' data1.yml data2.yml
```
**Actual behavior**
```yaml
cat: meow
```
**Expected behavior**
```yaml
this: should really work
but: it strangely didn't
```
**Additional context**
Add any other context about the problem here.

View File

@@ -1,17 +1,23 @@
---
name: Feature request
name: Feature request - V4
about: Suggest an idea for this project
title: ''
labels: enhancement
labels: enhancement, v4
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Please describe your feature request.**
A clear and concise description of what the request is and what it would solve.
Eg. I wish I could use yq to [...]
Note:
- how to questions should be posted in the discussion board and not raised as an issue.
- V3 will no longer have any enhancements.
**Describe the solution you'd like**
If we have data1.yml like:
(please keep to around 10 lines )
```yaml
country: Australia
@@ -20,7 +26,7 @@ country: Australia
And we run a command:
```bash
yq predictWeather data1.yml
yq eval 'predictWeatherOf(.country)'
```
it could output

View File

@@ -28,7 +28,4 @@ jobs:
run: |
export PATH=${PATH}:`go env GOPATH`/bin
scripts/devtools.sh
- name: Build
run: |
export PATH=${PATH}:`go env GOPATH`/bin
make local build

80
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: Release YQ
on:
push:
tags:
- 'v*'
jobs:
publishGitRelease:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.15'
- name: Cross compile
run: |
sudo apt-get install rhash -y
go get github.com/mitchellh/gox
./scripts/xcompile.sh
- name: Create Release
id: create_release
uses: actions/create-release@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: false
- uses: shogo82148/actions-upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/*
publishDocker:
environment: dockerhub
env:
IMAGE_NAME: mikefarah/yq
runs-on: ubuntu-latest
steps:
- name: Get latest release tag
uses: oprypin/find-latest-tag@v1
with:
repository: mikefarah/yq # The repository to scan.
releases-only: true # We know that all relevant tags have a GitHub release for them.
id: yq
- name: Clone source code
uses: actions/checkout@v2
with:
ref: ${{ steps.yq.outputs.tag }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }} && docker version
- name: Build and push image
run: |
IMAGE_V_VERSION="$(git describe --tags --abbrev=0)"
IMAGE_VERSION=${IMAGE_V_VERSION:1}
SHORT_SHA1=$(git rev-parse --short HEAD)
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64"
echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}"
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker buildx build --platform "${PLATFORMS}" -t "${IMAGE_NAME}:${IMAGE_VERSION}" -t "${IMAGE_NAME}:latest" -t "${IMAGE_NAME}:4" \
--push .

View File

@@ -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
# CI tools expect an interactive shell inside the container
FROM alpine:3.12 as production
FROM alpine:3.12.3 as production
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
RUN chmod +x /usr/bin/yq

View File

@@ -4,6 +4,7 @@ COPY scripts/devtools.sh /opt/devtools.sh
RUN set -e -x \
&& /opt/devtools.sh
ENV PATH=/go/bin:$PATH
# install mkdocs
RUN set -ex \

147
README.md
View File

@@ -3,60 +3,88 @@
![Build](https://github.com/mikefarah/yq/workflows/Build/badge.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq)
a lightweight and portable command-line YAML processor
a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
yq is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as docker, all listed below.
## V4 released!
V4 is now officially released, it's quite different from V3 (sorry for the migration), however it is much more similar to ```jq```, using a similar expression syntax and therefore support much more complex functionality!
If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3).
Support for v3 will cease August 2021, until then, critical bug and security fixes will still get applied if required.
## Install
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
### MacOS:
### wget
Use wget to download the pre-compiled binaries:
#### Compressed via tar.gz
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\
tar xz && mv ${BINARY} /usr/bin/yq
```
#### Plain binary
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
chmod +x /usr/bin/yq
```
For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
### MacOS / Linux via Homebrew:
Using [Homebrew](https://brew.sh/)
```
brew install yq
```
### Ubuntu and other Linux distros supporting `snap` packages:
or, for the (deprecated) v3 version:
```
brew install yq@3
```
Note that for v3, as it is a versioned brew it will not add the `yq` command to your path automatically. Please follow the instructions given by brew upon installation.
### Linux via snap:
```
snap install yq
```
or, for the (deprecated) v3 version:
```
snap install yq --channel=v3/stable
```
#### 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:
```
sudo cat /etc/myfile | yq r - a.path
sudo cat /etc/myfile | yq e '.a.path' -
```
And to write to a root file you can either use [sponge](https://linux.die.net/man/1/sponge):
```
sudo cat /etc/myfile | yq w - a.path value | sudo sponge /etc/myfile
sudo cat /etc/myfile | yq e '.a.path = "value"' - | sudo sponge /etc/myfile
```
or write to a temporary file:
```
sudo cat /etc/myfile | yq w - a.path value | sudo tee /etc/myfile.tmp
sudo cat /etc/myfile | yq e '.a.path = "value"' | sudo tee /etc/myfile.tmp
sudo mv /etc/myfile.tmp /etc/myfile
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=3.4.0 and BINARY=yq_linux_amd64
### Run with Docker
#### Oneshot use:
```bash
docker run --rm -v "${PWD}":/workdir mikefarah/yq yq [flags] <command> FILE...
docker run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression ]FILE...
```
#### Run commands interactively:
@@ -69,18 +97,26 @@ It can be useful to have a bash function to avoid typing the whole docker comman
```bash
yq() {
docker run --rm -i -v "${PWD}":/workdir mikefarah/yq yq "$@"
docker run --rm -i -v "${PWD}":/workdir mikefarah/yq "$@"
}
```
### Go Get:
```
GO111MODULE=on go get github.com/mikefarah/yq/v3
GO111MODULE=on go get github.com/mikefarah/yq
```
## Community Supported Installation methods
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:
```
@@ -88,6 +124,14 @@ choco install 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
- Enable edge/community repo by adding ```$MIRROR/alpine/edge/community``` to ```/etc/apk/repositories```
- Update database index with ```apk update```
@@ -108,22 +152,18 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
## Features
- Written in portable go, so you can download a lovely dependency free binary
- [Colorize the output](https://mikefarah.gitbook.io/yq/usage/output-format#colorize-output)
- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/commands/read#basic)
- [List matching paths of a given path expression](https://mikefarah.gitbook.io/yq/commands/read#path-only)
- [Return the lengths of arrays/object/scalars](https://mikefarah.gitbook.io/yq/commands/read#printing-length-of-the-results)
- 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 creates any missing entries in the path on the fly
- Deeply [compare](https://mikefarah.gitbook.io/yq/commands/compare) yaml files
- Keeps yaml formatting and comments when updating
- [Validate a yaml file](https://mikefarah.gitbook.io/yq/commands/validate)
- 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)
- [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)
- Uses similar syntax as `jq` but works with YAML and JSON files
- Fully supports multi document yaml files
- Colorized yaml output
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/v/v4.x/sort-keys)
- 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 yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags)
- [Complex expressions to select and update](https://mikefarah.gitbook.io/yq/v/v4.x/select#select-and-update-matching-values-in-map)
- Keeps yaml formatting and comments when updating (though there are issues with whitespace)
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate)
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
## [Usage](https://mikefarah.gitbook.io/yq/)
@@ -135,32 +175,33 @@ Usage:
yq [command]
Available Commands:
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
eval Apply expression to each document in each yaml file given in sequence
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
help Help about any command
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
shell-completion Generate completion script
Flags:
-C, --colors print with colors
-C, --colors force print with colors
-e, --exit-status set exit status if there are no matches or null or false is returned
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-P, --prettyPrint pretty print
-j, --tojson output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc.
-i, --inplace update the yaml file inplace of first yaml file given.
-M, --no-colors force print with no colors
-N, --no-doc Don't print document separators (---)
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.
-P, --prettyPrint pretty print, shorthand for '... style = ""'
-j, --tojson output as json. Set indent to 0 to print json in one line.
-v, --verbose verbose mode
-V, --version Print version information and quit
Use "yq [command] --help" for more information about a command.
```
## 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).
Simple Example:
```bash
yq e '.a.b | length' f1.yml f2.yml
```
## 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)
- You cannot (yet) select multiple paths/keys from the yaml to be printed out (https://github.com/mikefarah/yq/issues/287)

View File

@@ -2,10 +2,10 @@ package cmd
var unwrapScalar = true
// var writeInplace = false
var writeInplace = false
var outputToJSON = false
// var exitStatus = false
var exitStatus = false
var forceColor = false
var forceNoColor = false
var colorsEnabled = false
@@ -14,5 +14,6 @@ var noDocSeparators = false
var nullInput = false
var verbose = false
var version = false
var prettyPrint = false
// var log = logging.MustGetLogger("yq")
var completedSuccessfully = false

View File

@@ -1,6 +1,8 @@
package cmd
import (
"errors"
"fmt"
"os"
"github.com/mikefarah/yq/v4/pkg/yqlib"
@@ -13,9 +15,8 @@ func createEvaluateAllCommand() *cobra.Command {
Aliases: []string{"ea"},
Short: "Loads _all_ yaml documents of _all_ yaml files and runs expression once",
Example: `
yq es '.a.b | length' file1.yml file2.yml
yq es < sample.yaml
yq es -n '{"a": "b"}'
# merges f2.yml into f1.yml (inplace)
yq eval-all --inplace 'select(fileIndex == 0) * select(fileIndex == 1)' f1.yml f2.yml
`,
Long: "Evaluate All:\nUseful when you need to run an expression across several yaml documents or files. Consumes more memory than eval",
RunE: evaluateAll,
@@ -23,6 +24,7 @@ yq es -n '{"a": "b"}'
return cmdEvalAll
}
func evaluateAll(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
// 0 args, read std in
// 1 arg, null input, process expression
// 1 arg, read file in sequence
@@ -39,26 +41,54 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
colorsEnabled = true
}
if writeInplace && len(args) < 2 {
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
}
if writeInplace {
// only use colors if its forced
colorsEnabled = forceColor
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
out, err = writeInPlaceHandler.CreateTempFile()
if err != nil {
return err
}
// need to indirectly call the function so that completedSuccessfully is
// passed when we finish execution as opposed to now
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)
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
switch len(args) {
case 0:
if pipingStdIn {
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
} else {
cmd.Println(cmd.UsageString())
return nil
}
case 1:
if nullInput {
err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer)
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer)
} else {
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
}
default:
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
}
completedSuccessfully = err == nil
if err == nil && exitStatus && !printer.PrintedAnything() {
return errors.New("no matches found")
}
cmd.SilenceUsage = true
return err
}

View File

@@ -1,6 +1,8 @@
package cmd
import (
"errors"
"fmt"
"os"
"github.com/mikefarah/yq/v4/pkg/yqlib"
@@ -13,16 +15,36 @@ func createEvaluateSequenceCommand() *cobra.Command {
Aliases: []string{"e"},
Short: "Apply expression to each document in each yaml file given in sequence",
Example: `
yq es '.a.b | length' file1.yml file2.yml
yq es < sample.yaml
yq es -n '{"a": "b"}'
# runs the expression against each file, in series
yq e '.a.b | length' f1.yml f2.yml
# prints out the file
yq e sample.yaml
# prints a new yaml document
yq e -n '.a.b.c = "cat"'
# updates file.yaml directly
yq e '.a.b = "cool"' -i file.yaml
`,
Long: "Evaluate Sequence:\nIterate over each yaml document, apply the expression and print the results, in sequence.",
RunE: evaluateSequence,
}
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 {
cmd.SilenceUsage = true
// 0 args, read std in
// 1 arg, null input, process expression
// 1 arg, read file in sequence
@@ -39,29 +61,54 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
colorsEnabled = true
}
if writeInplace && len(args) < 2 {
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
}
if writeInplace {
// only use colors if its forced
colorsEnabled = forceColor
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
out, err = writeInPlaceHandler.CreateTempFile()
if err != nil {
return err
}
// need to indirectly call the function so that completedSuccessfully is
// passed when we finish execution as opposed to now
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
}
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
streamEvaluator := yqlib.NewStreamEvaluator()
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
if nullInput && len(args) > 1 {
return errors.New("Cannot pass files in when using null-input flag")
}
switch len(args) {
case 0:
if pipingStdIn {
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
} else {
cmd.Println(cmd.UsageString())
return nil
}
case 1:
if nullInput {
err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer)
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer)
} else {
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
}
default:
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
}
completedSuccessfully = err == nil
if err == nil && exitStatus && !printer.PrintedAnything() {
return errors.New("no matches found")
}
cmd.SilenceUsage = true
return err
}

View File

@@ -46,6 +46,10 @@ func New() *cobra.Command {
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.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(&forceColor, "colors", "C", false, "force print with colors")
rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors")

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "4.0.0-alpha2"
Version = "4.3.0"
// 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

View File

@@ -1,61 +0,0 @@
package cmd
// import (
// "github.com/spf13/cobra"
// )
// func createWriteCmd() *cobra.Command {
// var cmdWrite = &cobra.Command{
// Use: "write [yaml_file] [path_expression] [value]",
// Aliases: []string{"w"},
// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue",
// Example: `
// yq write things.yaml 'a.b.c' true
// yq write things.yaml 'a.*.c' true
// yq write things.yaml 'a.**' true
// yq write things.yaml 'a.(child.subchild==co*).c' true
// yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
// yq write things.yaml 'a.b.c' --tag '!!float' 3
// yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags
// yq w -i things.yaml 'a.b.c' cat
// yq w -i -s update_script.yaml things.yaml
// yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array
// yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file
// `,
// Long: `Updates the yaml file w.r.t the given path and value.
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
// Append value to array adds the value to the end of array.
// Update Scripts:
// Note that you can give an update script to perform more sophisticated update. Update script
// format is list of update commands (update or delete) like so:
// ---
// - command: update
// path: b.c
// value:
// #great
// things: frog # wow!
// - command: delete
// path: b.d
// `,
// RunE: writeProperty,
// }
// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
// cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
// cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)")
// cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
// cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
// return cmdWrite
// }
// func writeProperty(cmd *cobra.Command, args []string) error {
// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
// if updateCommandsError != nil {
// return updateCommandsError
// }
// return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
// }

View File

@@ -1,610 +0,0 @@
package cmd
// import (
// "fmt"
// "runtime"
// "strings"
// "testing"
// "github.com/mikefarah/yq/v3/test"
// )
// func TestWriteCmd(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 7
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteKeepCommentsCmd(t *testing.T) {
// content := `b:
// c: 3 # comment
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 7 # comment
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteWithTaggedStyleCmd(t *testing.T) {
// content := `b:
// c: dog
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: !!str cat
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) {
// content := `b:
// c: dog
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: "cat"
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteUpdateStyleOnlyCmd(t *testing.T) {
// content := `b:
// c: dog
// d: things
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 'dog'
// d: 'things'
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteUpdateTagOnlyCmd(t *testing.T) {
// content := `b:
// c: true
// d: false
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: "true"
// d: "false"
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteWithSingleQuotedStyleCmd(t *testing.T) {
// content := `b:
// c: dog
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 'cat'
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteWithLiteralStyleCmd(t *testing.T) {
// content := `b:
// c: dog
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: |-
// cat
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteWithFoldedStyleCmd(t *testing.T) {
// content := `b:
// c: dog
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: >-
// cat
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteEmptyMultiDocCmd(t *testing.T) {
// content := `# this is empty
// ---
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `c: 7
// # this is empty
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) {
// content := `---
// # empty
// ---
// cat: frog
// ---
// # empty
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `
// # empty
// ---
// cat: frog
// c: 7
// ---
// # empty
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteFromFileCmd(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// source := `kittens: are cute # sure are!`
// fromFilename := test.WriteTempYamlFile(source)
// defer test.RemoveTempYamlFile(fromFilename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c:
// kittens: are cute # sure are!
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteEmptyCmd(t *testing.T) {
// content := ``
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 7
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteAutoCreateCmd(t *testing.T) {
// content := `applications:
// - name: app
// env:`
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `applications:
// - name: app
// env:
// hello: world
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmdScript(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// updateScript := `- command: update
// path: b.c
// value: 7`
// scriptFilename := test.WriteTempYamlFile(updateScript)
// defer test.RemoveTempYamlFile(scriptFilename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 7
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmdEmptyScript(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// updateScript := ``
// scriptFilename := test.WriteTempYamlFile(updateScript)
// defer test.RemoveTempYamlFile(scriptFilename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteMultiCmd(t *testing.T) {
// content := `b:
// c: 3
// ---
// apples: great
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 3
// ---
// apples: ok
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteInvalidDocumentIndexCmd(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename))
// if result.Error == nil {
// t.Error("Expected command to fail due to invalid path")
// }
// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
// func TestWriteBadDocumentIndexCmd(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
// if result.Error == nil {
// t.Error("Expected command to fail due to invalid path")
// }
// expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
// func TestWriteMultiAllCmd(t *testing.T) {
// content := `b:
// c: 3
// ---
// apples: great
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 3
// apples: ok
// ---
// apples: ok`
// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
// }
// func TestWriteCmd_EmptyArray(t *testing.T) {
// content := `b: 3`
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b: 3
// a: []
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmd_Error(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "write")
// if result.Error == nil {
// t.Error("Expected command to fail due to missing arg")
// }
// expectedOutput := `Must provide <filename> <path_to_update> <value>`
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
// func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "write fake-unknown a.b 3")
// if result.Error == nil {
// t.Error("Expected command to fail due to unknown file")
// }
// var expectedOutput string
// if runtime.GOOS == "windows" {
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
// } else {
// expectedOutput = `open fake-unknown: no such file or directory`
// }
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
// func TestWriteCmd_Inplace(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// gotOutput := test.ReadTempYamlFile(filename)
// expectedOutput := `b:
// c: 7`
// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
// }
// func TestWriteCmd_InplaceError(t *testing.T) {
// content := `b: cat
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
// if result.Error == nil {
// t.Error("Expected Error to occur!")
// }
// gotOutput := test.ReadTempYamlFile(filename)
// test.AssertResult(t, content, gotOutput)
// }
// func TestWriteCmd_Append(t *testing.T) {
// content := `b:
// - foo
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// - foo
// - 7
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmd_AppendInline(t *testing.T) {
// content := `b: [foo]`
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b: [foo, 7]
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmd_AppendInlinePretty(t *testing.T) {
// content := `b: [foo]`
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// - foo
// - 7
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmd_AppendEmptyArray(t *testing.T) {
// content := `a: 2
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `a: 2
// b:
// - v
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmd_SplatArray(t *testing.T) {
// content := `b:
// - c: thing
// - c: another thing
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// - c: new
// - c: new
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmd_SplatMap(t *testing.T) {
// content := `b:
// c: thing
// d: another thing
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: new
// d: new
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestWriteCmd_SplatMapEmpty(t *testing.T) {
// content := `b:
// c: thing
// d: another thing
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: {}
// d: another thing
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }

View File

@@ -1,4 +1,4 @@
FROM mikefarah/yq:3
FROM mikefarah/yq:4.3.0
COPY entrypoint.sh /entrypoint.sh

15
go.mod
View File

@@ -2,18 +2,15 @@ module github.com/mikefarah/yq/v4
require (
github.com/elliotchance/orderedmap v1.3.0
github.com/fatih/color v1.9.0
github.com/goccy/go-yaml v1.8.1
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5 // indirect
github.com/fatih/color v1.10.0
github.com/goccy/go-yaml v1.8.4
github.com/jinzhu/copier v0.1.0
github.com/spf13/cobra v1.1.1
github.com/timtadh/data-structures v0.5.3 // indirect
github.com/timtadh/lexmachine v0.2.2
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
go 1.15

222
go.sum
View File

@@ -1,16 +1,33 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -22,37 +39,74 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/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-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.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/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/goccy/go-yaml v1.8.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.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/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
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/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88=
github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -64,26 +118,34 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
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.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/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.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/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.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -94,88 +156,163 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
github.com/timtadh/lexmachine v0.2.2 h1:g55RnjdYazm5wnKv59pwFcBJHOyvTPfDEoz21s4PHmY=
github.com/timtadh/lexmachine v0.2.2/go.mod h1:GBJvD5OAfRn/gnp92zb9KTgHLB7akKyxmVivoYCcjQI=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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-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-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-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-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-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-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-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-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-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-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
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/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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-20191012152004-8de300cfc20a/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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
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.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/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/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
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/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
@@ -183,6 +320,13 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
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-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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View File

@@ -21,6 +21,14 @@ func (n *CandidateNode) GetKey() string {
return fmt.Sprintf("%v - %v", n.Document, n.Path)
}
func (n *CandidateNode) CreateChildPath(path interface{}) []interface{} {
//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) {
clone := &CandidateNode{}
err := copier.Copy(clone, n)
@@ -32,6 +40,7 @@ func (n *CandidateNode) Copy() (*CandidateNode, error) {
// updates this candidate from the given candidate node
func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
n.UpdateAttributesFrom(other)
n.Node.Content = other.Node.Content
n.Node.Value = other.Node.Value

View File

@@ -1,5 +0,0 @@
package yqlib
import "gopkg.in/op/go-logging.v1"
var log = logging.MustGetLogger("yq-lib")

1
pkg/yqlib/doc/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.zip

View File

@@ -2,6 +2,32 @@ Add behaves differently according to the type of the LHS:
- arrays: concatenate
- number scalars: arithmetic addition (soon)
- string scalars: concatenate (soon)
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
## Concatenate and assign arrays
Given a sample.yml file of:
```yaml
a:
val: thing
b:
- cat
- dog
```
then
```bash
yq eval '.a.b += ["cow"]' sample.yml
```
will output
```yaml
a:
val: thing
b:
- cat
- dog
- cow
```
## Concatenate arrays
Given a sample.yml file of:
```yaml

View File

@@ -0,0 +1,73 @@
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
## LHS is defined
Given a sample.yml file of:
```yaml
a: bridge
```
then
```bash
yq eval '.a // "hello"' sample.yml
```
will output
```yaml
bridge
```
## LHS is not defined
Given a sample.yml file of:
```yaml
{}
```
then
```bash
yq eval '.a // "hello"' sample.yml
```
will output
```yaml
hello
```
## LHS is null
Given a sample.yml file of:
```yaml
a: ~
```
then
```bash
yq eval '.a // "hello"' sample.yml
```
will output
```yaml
hello
```
## LHS is false
Given a sample.yml file of:
```yaml
a: false
```
then
```bash
yq eval '.a // "hello"' sample.yml
```
will output
```yaml
hello
```
## RHS is an expression
Given a sample.yml file of:
```yaml
a: false
b: cat
```
then
```bash
yq eval '.a // .b' sample.yml
```
will output
```yaml
cat
```

View File

@@ -0,0 +1,194 @@
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
## Get anchor
Given a sample.yml file of:
```yaml
a: &billyBob cat
```
then
```bash
yq eval '.a | anchor' sample.yml
```
will output
```yaml
billyBob
```
## Set anchor
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval '.a anchor = "foobar"' sample.yml
```
will output
```yaml
a: &foobar cat
```
## Set anchor relatively using assign-update
Given a sample.yml file of:
```yaml
a:
b: cat
```
then
```bash
yq eval '.a anchor |= .b' sample.yml
```
will output
```yaml
a: &cat
b: cat
```
## Get alias
Given a sample.yml file of:
```yaml
b: &billyBob meow
a: *billyBob
```
then
```bash
yq eval '.a | alias' sample.yml
```
will output
```yaml
billyBob
```
## Set alias
Given a sample.yml file of:
```yaml
b: &meow purr
a: cat
```
then
```bash
yq eval '.a alias = "meow"' sample.yml
```
will output
```yaml
b: &meow purr
a: *meow
```
## Set alias relatively using assign-update
Given a sample.yml file of:
```yaml
b: &meow purr
a:
f: meow
```
then
```bash
yq eval '.a alias |= .f' sample.yml
```
will output
```yaml
b: &meow purr
a: *meow
```
## Explode alias and anchor
Given a sample.yml file of:
```yaml
f:
a: &a cat
b: *a
```
then
```bash
yq eval 'explode(.f)' sample.yml
```
will output
```yaml
f:
a: cat
b: cat
```
## Explode with no aliases or anchors
Given a sample.yml file of:
```yaml
a: mike
```
then
```bash
yq eval 'explode(.a)' sample.yml
```
will output
```yaml
a: mike
```
## Explode with alias keys
Given a sample.yml file of:
```yaml
f:
a: &a cat
*a: b
```
then
```bash
yq eval 'explode(.f)' sample.yml
```
will output
```yaml
f:
a: cat
cat: b
```
## Explode with merge anchors
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval 'explode(.)' sample.yml
```
will output
```yaml
foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
a: foo_a
thing: bar_thing
c: foobarList_c
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
```

View File

@@ -5,6 +5,18 @@ Which will assign the LHS node values to the RHS node values. The RHS expression
### relative form: `|=`
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
## Create yaml file
Running
```bash
yq eval --null-input '.a.b = "cat" | .x = "frog"'
```
will output
```yaml
a:
b: cat
x: frog
```
## Update node to be the child value
Given a sample.yml file of:
```yaml
@@ -22,6 +34,27 @@ a:
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
Given a sample.yml file of:
```yaml
@@ -100,7 +133,7 @@ a:
```
then
```bash
yq eval '.a.[] | select(. == "apple") |= "frog"' sample.yml
yq eval '(.a[] | select(. == "apple")) = "frog"' sample.yml
```
will output
```yaml
@@ -118,7 +151,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | select(. == "*andy") |= "bogs"' sample.yml
yq eval '(.[] | select(. == "*andy")) = "bogs"' sample.yml
```
will output
```yaml
@@ -148,7 +181,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.a.b[0] |= "bogs"' sample.yml
yq eval '.a.b.[0] |= "bogs"' sample.yml
```
will output
```yaml

View File

@@ -13,6 +13,22 @@ will output
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
Given a sample.yml file of:
```yaml

View File

@@ -14,6 +14,23 @@ will output
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
Given a sample.yml file of:
```yaml
@@ -31,6 +48,21 @@ will output
- 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
Given a sample.yml file of:
```yaml
@@ -63,3 +95,23 @@ will output
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
```

View File

@@ -17,6 +17,24 @@ will output
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
Given a sample.yml file of:
```yaml
@@ -26,7 +44,23 @@ a: frog
```
then
```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
```yaml
@@ -42,7 +76,7 @@ a: frog
```
then
```bash
yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml
yq eval '.a | ({"match": ., "doc": documentIndex})' sample.yml
```
will output
```yaml

View File

@@ -1,98 +0,0 @@
Explodes (or dereferences) aliases and anchors.
## Explode alias and anchor
Given a sample.yml file of:
```yaml
f:
a: &a cat
b: *a
```
then
```bash
yq eval 'explode(.f)' sample.yml
```
will output
```yaml
f:
a: cat
b: cat
```
## Explode with no aliases or anchors
Given a sample.yml file of:
```yaml
a: mike
```
then
```bash
yq eval 'explode(.a)' sample.yml
```
will output
```yaml
a: mike
```
## Explode with alias keys
Given a sample.yml file of:
```yaml
f:
a: &a cat
*a: b
```
then
```bash
yq eval 'explode(.f)' sample.yml
```
will output
```yaml
f:
a: cat
cat: b
```
## Explode with merge anchors
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval 'explode(.)' sample.yml
```
will output
```yaml
foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
a: foo_a
thing: bar_thing
c: foobarList_c
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
```

View File

@@ -1,9 +1,11 @@
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
Note that the `fileIndex` operator has a short alias of `fi`.
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
```
## Get filename
Given a sample.yml file of:
@@ -16,7 +18,7 @@ yq eval 'filename' sample.yml
```
will output
```yaml
sample.yaml
sample.yml
```
## Get file index
@@ -33,3 +35,17 @@ will output
0
```
## Get file index alias
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval 'fi' sample.yml
```
will output
```yaml
0
```

54
pkg/yqlib/doc/Length.md Normal file
View File

@@ -0,0 +1,54 @@
Returns the lengths of the nodes. Length is defined according to the type of the node.
## String length
returns length of string
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval '.a | length' sample.yml
```
will output
```yaml
3
```
## Map length
returns number of entries
Given a sample.yml file of:
```yaml
a: cat
c: dog
```
then
```bash
yq eval 'length' sample.yml
```
will output
```yaml
2
```
## Array length
returns number of elements
Given a sample.yml file of:
```yaml
- 2
- 4
- 6
- 8
```
then
```bash
yq eval 'length' sample.yml
```
will output
```yaml
4
```

View File

@@ -2,6 +2,8 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
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.
## Merging files
@@ -109,6 +111,38 @@ b:
- 5
```
## Merge, appending arrays
Given a sample.yml file of:
```yaml
a:
array:
- 1
- 2
- animal: dog
value: coconut
b:
array:
- 3
- 4
- animal: cat
value: banana
```
then
```bash
yq eval '.a *+ .b' sample.yml
```
will output
```yaml
array:
- 1
- 2
- animal: dog
- 3
- 4
- animal: cat
value: banana
```
## Merge to prefix an element
Given a sample.yml file of:
```yaml

View File

@@ -1,4 +1,7 @@
The path operator can be used to get the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.
You can get the key/index of matching nodes by using the `path` operator to return the path array then piping that through `.[-1]` to get the last element of that array, the key.
## Map path
Given a sample.yml file of:
```yaml
@@ -15,6 +18,21 @@ will output
- b
```
## Get map key
Given a sample.yml file of:
```yaml
a:
b: cat
```
then
```bash
yq eval '.a.b | path | .[-1]' sample.yml
```
will output
```yaml
b
```
## Array path
Given a sample.yml file of:
```yaml
@@ -32,6 +50,22 @@ will output
- 1
```
## Get array index
Given a sample.yml file of:
```yaml
a:
- cat
- dog
```
then
```bash
yq eval '.a.[] | select(. == "dog") | path | .[-1]' sample.yml
```
will output
```yaml
1
```
## Print path and value
Given a sample.yml file of:
```yaml

35
pkg/yqlib/doc/Pipe.md Normal file
View File

@@ -0,0 +1,35 @@
Pipe the results of an expression into another. Like the bash operator.
## Simple Pipe
Given a sample.yml file of:
```yaml
a:
b: cat
```
then
```bash
yq eval '.a | .b' sample.yml
```
will output
```yaml
cat
```
## Multiple updates
Given a sample.yml file of:
```yaml
a: cow
b: sheep
c: same
```
then
```bash
yq eval '.a = "cat" | .b = "dog"' sample.yml
```
will output
```yaml
a: cat
b: dog
c: same
```

View File

@@ -1,8 +1,97 @@
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:
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
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

View File

@@ -0,0 +1,68 @@
The Sort Keys operator sorts maps by their keys (based on their string value). This operator does not do anything to arrays or scalars (so you can easily recursively apply it to all maps).
Sort is particularly useful for diffing two different yaml documents:
```bash
yq eval -i 'sortKeys(..)' file1.yml
yq eval -i 'sortKeys(..)' file2.yml
diff file1.yml file2.yml
```
## Sort keys of map
Given a sample.yml file of:
```yaml
c: frog
a: blah
b: bing
```
then
```bash
yq eval 'sortKeys(.)' sample.yml
```
will output
```yaml
a: blah
b: bing
c: frog
```
## Sort keys recursively
Note the array elements are left unsorted, but maps inside arrays are sorted
Given a sample.yml file of:
```yaml
bParent:
c: dog
array:
- 3
- 1
- 2
aParent:
z: donkey
x:
- c: yum
b: delish
- b: ew
a: apple
```
then
```bash
yq eval 'sortKeys(..)' sample.yml
```
will output
```yaml
aParent:
x:
- b: delish
c: yum
- a: apple
b: ew
z: donkey
bParent:
array:
- 3
- 1
- 2
c: dog
```

View File

@@ -40,6 +40,26 @@ c: "3.2"
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
Given a sample.yml file of:
```yaml
@@ -125,19 +145,19 @@ will output
{a: cat, b: 5, c: 3.2, e: true}
```
## Pretty print
Set empty (default) quote style
## Reset style - or pretty print
Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
"b": 5
'c': 3.2
"e": true
```
then
```bash
yq eval '.. style=""' sample.yml
yq eval '... style=""' sample.yml
```
will output
```yaml
@@ -147,6 +167,22 @@ c: 3.2
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
Given a sample.yml file of:
```yaml

View File

@@ -22,7 +22,21 @@ will output
!!seq
```
## Convert numbers to strings
## Set custom tag
Given a sample.yml file of:
```yaml
a: str
```
then
```bash
yq eval '.a tag = "!!mikefarah"' sample.yml
```
will output
```yaml
a: !!mikefarah str
```
## Find numbers and convert them to strings
Given a sample.yml file of:
```yaml
a: cat

View File

@@ -106,7 +106,7 @@ b: *cat
```
then
```bash
yq eval '.b.[]' sample.yml
yq eval '.b[]' sample.yml
```
will output
```yaml
@@ -138,7 +138,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '[0]' sample.yml
yq eval '.[0]' sample.yml
```
will output
```yaml
@@ -152,7 +152,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '[2]' sample.yml
yq eval '.[2]' sample.yml
```
will output
```yaml
@@ -166,7 +166,7 @@ a: b
```
then
```bash
yq eval '[0]' sample.yml
yq eval '.[0]' sample.yml
```
will output
```yaml
@@ -290,7 +290,7 @@ foobar:
```
then
```bash
yq eval '.foobar.[]' sample.yml
yq eval '.foobar[]' sample.yml
```
will output
```yaml
@@ -356,7 +356,7 @@ foobar:
```
then
```bash
yq eval '.foobarList.[]' sample.yml
yq eval '.foobarList[]' sample.yml
```
will output
```yaml
@@ -366,3 +366,21 @@ bar_thing
foobarList_c
```
## Select multiple indices
Given a sample.yml file of:
```yaml
a:
- a
- b
- c
```
then
```bash
yq eval '.a[0, 2]' sample.yml
```
will output
```yaml
a
c
```

0
pkg/yqlib/doc/aa.md Normal file
View File

View File

@@ -1,4 +1,6 @@
Add behaves differently according to the type of the LHS:
- arrays: concatenate
- number scalars: arithmetic addition (soon)
- string scalars: concatenate (soon)
- string scalars: concatenate (soon)
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.

View File

@@ -0,0 +1 @@
This operator is used to provide alternative (or default) values when a particular expression is either null or false.

View File

@@ -0,0 +1,4 @@
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.

View File

@@ -1 +0,0 @@
Explodes (or dereferences) aliases and anchors.

View File

@@ -1,7 +1,9 @@
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
Note that the `fileIndex` operator has a short alias of `fi`.
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
```

View File

@@ -0,0 +1 @@
Returns the lengths of the nodes. Length is defined according to the type of the node.

View File

@@ -2,6 +2,8 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
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.
## Merging files

View File

@@ -1 +1,3 @@
The path operator can be used to get the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.
The path operator can be used to get the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.
You can get the key/index of matching nodes by using the `path` operator to return the path array then piping that through `.[-1]` to get the last element of that array, the key.

View File

@@ -0,0 +1 @@
Pipe the results of an expression into another. Like the bash operator.

View File

@@ -1,5 +1,19 @@
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:
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
```

View File

@@ -0,0 +1,9 @@
The Sort Keys operator sorts maps by their keys (based on their string value). This operator does not do anything to arrays or scalars (so you can easily recursively apply it to all maps).
Sort is particularly useful for diffing two different yaml documents:
```bash
yq eval -i 'sortKeys(..)' file1.yml
yq eval -i 'sortKeys(..)' file2.yml
diff file1.yml file2.yml
```

50
pkg/yqlib/file_utils.go Normal file
View File

@@ -0,0 +1,50 @@
package yqlib
import (
"io"
"os"
)
func safelyRenameFile(from string, to string) {
if renameError := os.Rename(from, to); renameError != nil {
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
log.Debug(renameError.Error())
// can't do this rename when running in docker to a file targeted in a mounted volume,
// so gracefully degrade to copying the entire contents.
if copyError := copyFileContents(from, to); copyError != nil {
log.Errorf("Failed copying from %v to %v", from, to)
log.Error(copyError.Error())
} else {
removeErr := os.Remove(from)
if removeErr != nil {
log.Errorf("failed removing original file: %s", from)
}
}
}
}
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src) // nolint gosec
if err != nil {
return err
}
defer safelyCloseFile(in)
out, err := os.Create(dst)
if err != nil {
return err
}
defer safelyCloseFile(out)
if _, err = io.Copy(out, in); err != nil {
return err
}
return out.Sync()
}
func safelyCloseFile(file *os.File) {
err := file.Close()
if err != nil {
log.Error("Error closing file!")
log.Error(err.Error())
}
}

View File

@@ -9,6 +9,8 @@ import (
yaml "gopkg.in/yaml.v3"
)
var log = logging.MustGetLogger("yq-lib")
type OperationType struct {
Type string
NumArgs uint // number of arguments to the op
@@ -17,46 +19,53 @@ type OperationType struct {
}
// operators TODO:
// - write in place
// - mergeAppend (merges and appends arrays)
// - keys operator for controlling key metadata (particularly anchors/aliases)
// - mergeEmpty (sets only if the document is empty, do I do that now?)
// - compare ??
// - validate ??
// - exists
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
var Pipe = &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 AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
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 AssignAnchor = &OperationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: AssignAnchorOperator}
var AssignAlias = &OperationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: AssignAliasOperator}
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
var Alternative = &OperationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: AlternativeOperator}
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
var ShortPipe = &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 GetAnchor = &OperationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: GetAnchorOperator}
var GetAlias = &OperationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: GetAliasOperator}
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 Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator}
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var TraverseArray = &OperationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: TraverseArrayOperator}
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
@@ -69,9 +78,7 @@ var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Pre
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}
// filters matches if they have the existing path
var DeleteImmediateChild = &OperationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: DeleteImmediateChildOperator}
type Operation struct {
OperationType *OperationType
@@ -79,6 +86,7 @@ type Operation struct {
StringValue string
CandidateNode *CandidateNode // used for Value Path elements
Preferences interface{}
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs (instead of matching nodes)
}
func CreateValueOperation(value interface{}, stringValue string) *Operation {

View File

@@ -8,13 +8,21 @@ import (
yaml "gopkg.in/yaml.v3"
)
func toNodes(candidates *list.List) []*yaml.Node {
func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
return &PathTreeNode{Operation: &Operation{OperationType: Add},
Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}},
Rhs: rhs}
}
if candidates.Len() == 0 {
return []*yaml.Node{}
}
candidate := candidates.Front().Value.(*CandidateNode)
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
assignmentOp := &Operation{OperationType: Assign}
assignmentOp.UpdateAssign = true
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
}
func toNodes(candidate *CandidateNode) []*yaml.Node {
if candidate.Node.Tag == "!!null" {
return []*yaml.Node{}
}
@@ -30,40 +38,33 @@ func toNodes(candidates *list.List) []*yaml.Node {
func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("Add operator")
var results = list.New()
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
for el := lhs.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode)
lhsNode := UnwrapDoc(lhsCandidate.Node)
var newBlank = &CandidateNode{
Path: lhsCandidate.Path,
Document: lhsCandidate.Document,
Filename: lhsCandidate.Filename,
Node: &yaml.Node{},
}
switch lhsNode.Kind {
case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for addition")
case yaml.SequenceNode:
newBlank.Node.Kind = yaml.SequenceNode
newBlank.Node.Style = lhsNode.Style
newBlank.Node.Tag = "!!seq"
newBlank.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
results.PushBack(newBlank)
case yaml.ScalarNode:
return nil, fmt.Errorf("Scalars not yet supported for addition")
}
}
return results, nil
return crossFunction(d, matchingNodes, pathNode, add)
}
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
target := &CandidateNode{
Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &yaml.Node{},
}
lhsNode := lhs.Node
switch lhsNode.Kind {
case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for addition")
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)...)
case yaml.ScalarNode:
return nil, fmt.Errorf("Scalars not yet supported for addition")
}
return target, nil
}

View File

@@ -5,6 +5,14 @@ import (
)
var addOperatorScenarios = []expressionScenario{
{
description: "Concatenate and assign arrays",
document: `{a: {val: thing, b: [cat,dog]}}`,
expression: ".a.b += [\"cow\"]",
expected: []string{
"D0, P[], (doc)::{a: {val: thing, b: [cat, dog, cow]}}\n",
},
},
{
description: "Concatenate arrays",
document: `{a: [1,2], b: [3,4]}`,
@@ -13,6 +21,14 @@ var addOperatorScenarios = []expressionScenario{
"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",
document: `{a: [1,2]}`,

View File

@@ -0,0 +1,28 @@
package yqlib
import (
"container/list"
)
// corssFunction no matches
// can boolean use crossfunction
func AlternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- alternative")
return crossFunction(d, matchingNodes, pathNode, alternativeFunc)
}
func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag)
isTrue, err := isTruthy(lhs)
if err != nil {
return nil, err
} else if isTrue {
return lhs, nil
}
return rhs, nil
}

View File

@@ -0,0 +1,62 @@
package yqlib
import (
"testing"
)
var alternativeOperatorScenarios = []expressionScenario{
{
description: "LHS is defined",
expression: `.a // "hello"`,
document: `{a: bridge}`,
expected: []string{
"D0, P[a], (!!str)::bridge\n",
},
},
{
description: "LHS is not defined",
expression: `.a // "hello"`,
document: `{}`,
expected: []string{
"D0, P[], (!!str)::hello\n",
},
},
{
description: "LHS is null",
expression: `.a // "hello"`,
document: `{a: ~}`,
expected: []string{
"D0, P[], (!!str)::hello\n",
},
},
{
description: "LHS is false",
expression: `.a // "hello"`,
document: `{a: false}`,
expected: []string{
"D0, P[], (!!str)::hello\n",
},
},
{
description: "RHS is an expression",
expression: `.a // .b`,
document: `{a: false, b: cat}`,
expected: []string{
"D0, P[b], (!!str)::cat\n",
},
},
{
skipDoc: true,
expression: `false // true`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
}
func TestAlternativeOperatorScenarios(t *testing.T) {
for _, tt := range alternativeOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Alternative (Default value)", alternativeOperatorScenarios)
}

View File

@@ -3,9 +3,119 @@ package yqlib
import (
"container/list"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignAlias operator!")
aliasName := ""
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting aliasName : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Kind = yaml.AliasNode
candidate.Node.Value = aliasName
}
return matchingNodes, nil
}
func GetAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetAlias operator!")
var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}
func AssignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignAnchor operator!")
anchorName := ""
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting anchorName of : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Anchor = anchorName
}
return matchingNodes, nil
}
func GetAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetAnchor operator!")
var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
anchor := candidate.Node.Anchor
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}
func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- ExplodeOperation")

View File

@@ -4,7 +4,55 @@ import (
"testing"
)
var explodeTest = []expressionScenario{
var anchorOperatorScenarios = []expressionScenario{
{
description: "Get anchor",
document: `a: &billyBob cat`,
expression: `.a | anchor`,
expected: []string{
"D0, P[a], (!!str)::billyBob\n",
},
},
{
description: "Set anchor",
document: `a: cat`,
expression: `.a anchor = "foobar"`,
expected: []string{
"D0, P[], (doc)::a: &foobar cat\n",
},
},
{
description: "Set anchor relatively using assign-update",
document: `a: {b: cat}`,
expression: `.a anchor |= .b`,
expected: []string{
"D0, P[], (doc)::a: &cat {b: cat}\n",
},
},
{
description: "Get alias",
document: `{b: &billyBob meow, a: *billyBob}`,
expression: `.a | alias`,
expected: []string{
"D0, P[a], (!!str)::billyBob\n",
},
},
{
description: "Set alias",
document: `{b: &meow purr, a: cat}`,
expression: `.a alias = "meow"`,
expected: []string{
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
},
},
{
description: "Set alias relatively using assign-update",
document: `{b: &meow purr, a: {f: meow}}`,
expression: `.a alias |= .f`,
expected: []string{
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
},
},
{
description: "Explode alias and anchor",
document: `{f : {a: &a cat, b: *a}}`,
@@ -82,9 +130,9 @@ foobar:
},
}
func TestExplodeOperatorScenarios(t *testing.T) {
for _, tt := range explodeTest {
func TestAnchorAliaseOperatorScenarios(t *testing.T) {
for _, tt := range anchorOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Explode", explodeTest)
documentScenarios(t, "Anchor and Alias Operators", anchorOperatorScenarios)
}

View File

@@ -2,26 +2,20 @@ package yqlib
import "container/list"
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 {
return nil, err
}
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
var rhs *list.List
if !preferences.UpdateAssign {
if !pathNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
if preferences.UpdateAssign {
if pathNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
}
@@ -33,9 +27,16 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
first := rhs.Front()
if first != nil {
candidate.UpdateFrom(first.Value.(*CandidateNode))
rhsCandidate := first.Value.(*CandidateNode)
rhsCandidate.Node = UnwrapDoc(rhsCandidate.Node)
candidate.UpdateFrom(rhsCandidate)
}
}
// // if there was nothing given, perhaps we are creating a new yaml doc
// if matchingNodes.Len() == 0 {
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
// return lhs, nil
// }
return matchingNodes, nil
}

View File

@@ -5,6 +5,13 @@ import (
)
var assignOperatorScenarios = []expressionScenario{
{
description: "Create yaml file",
expression: `.a.b = "cat" | .x = "frog"`,
expected: []string{
"D0, P[], ()::a:\n b: cat\nx: frog\n",
},
},
{
description: "Update node to be the child value",
document: `{a: {b: {g: foof}}}`,
@@ -13,6 +20,16 @@ var assignOperatorScenarios = []expressionScenario{
"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",
document: `{a: {b: child}, b: sibling}`,
@@ -73,7 +90,15 @@ var assignOperatorScenarios = []expressionScenario{
{
description: "Update selected results",
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{
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
},
@@ -81,7 +106,7 @@ var assignOperatorScenarios = []expressionScenario{
{
description: "Update array values",
document: `[candy, apple, sandy]`,
expression: `.[] | select(. == "*andy") |= "bogs"`,
expression: `(.[] | select(. == "*andy")) = "bogs"`,
expected: []string{
"D0, P[], (doc)::[bogs, apple, bogs]\n",
},
@@ -99,7 +124,7 @@ var assignOperatorScenarios = []expressionScenario{
description: "Update empty object and array",
dontFormatInputForDoc: true,
document: `{}`,
expression: `.a.b[0] |= "bogs"`,
expression: `.a.b.[0] |= "bogs"`,
expected: []string{
"D0, P[], (doc)::{a: {b: [bogs]}}\n",
},
@@ -107,7 +132,7 @@ var assignOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{}`,
expression: `.a.b[1].c |= "bogs"`,
expression: `.a.b.[1].c |= "bogs"`,
expected: []string{
"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n",
},

View File

@@ -25,74 +25,39 @@ func isTruthy(c *CandidateNode) (bool, error) {
type boolOp func(bool, bool) bool
func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
lhsCandidate := lhsChild.Value.(*CandidateNode)
lhsTrue, errDecoding := isTruthy(lhsCandidate)
func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
lhsTrue, errDecoding := isTruthy(lhs)
if errDecoding != nil {
return errDecoding
return nil, errDecoding
}
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
rhsCandidate := rhsChild.Value.(*CandidateNode)
rhsTrue, errDecoding := isTruthy(rhsCandidate)
if errDecoding != nil {
return errDecoding
}
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
results.PushBack(boolResult)
rhsTrue, errDecoding := isTruthy(rhs)
if errDecoding != nil {
return nil, errDecoding
}
return createBooleanCandidate(lhs, op(lhsTrue, rhsTrue)), nil
}
return nil
}
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")
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
return b1 || b2
})
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
func(b1 bool, b2 bool) bool {
return b1 || b2
}))
}
func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- AndOp")
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
return b1 && b2
})
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
func(b1 bool, b2 bool) bool {
return b1 && b2
}))
}
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {

View File

@@ -55,7 +55,7 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode {
return &CandidateNode{
Document: candidate.Document,
Path: append(candidate.Path, index),
Path: candidate.CreateChildPath(index),
Filename: candidate.Filename,
Node: candidate.Node.Content[index],
}
@@ -67,7 +67,9 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
}
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
splatted, err := Splat(d, nodeToMap(candidate))
splatted, err := Splat(d, nodeToMap(candidate),
&TraversePreferences{FollowAlias: false, IncludeMapKeys: false})
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatEl.Value.(*CandidateNode).Path = nil
@@ -94,7 +96,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
newCandidate.Path = nil
newCandidate, err = multiply(d, newCandidate, splatCandidate)
newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
if err != nil {
return nil, err
}

View File

@@ -110,5 +110,5 @@ func TestCollectObjectOperatorScenarios(t *testing.T) {
for _, tt := range collectObjectOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Collect into Object", collectObjectOperatorScenarios)
documentScenarios(t, "Create, Collect into Object", collectObjectOperatorScenarios)
}

View File

@@ -13,6 +13,14 @@ var collectOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: ``,
expression: `[3]`,
expected: []string{
"D0, P[], (!!seq)::- 3\n",
},
},
{
description: "Collect single",
document: ``,

View File

@@ -4,7 +4,7 @@ import (
"container/list"
"strings"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
type CommentOpPreferences struct {
@@ -17,15 +17,6 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
log.Debugf("AssignComments operator!")
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 {
@@ -34,8 +25,32 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
comment := ""
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
log.Debugf("Setting comment of : %v", candidate.GetKey())
if preferences.LineComment {
candidate.Node.LineComment = comment

View File

@@ -13,6 +13,39 @@ var commentOperatorScenarios = []expressionScenario{
"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",
document: `a: cat`,

View File

@@ -2,39 +2,67 @@ package yqlib
import (
"container/list"
"fmt"
yaml "gopkg.in/yaml.v3"
)
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
// for each lhs, splat the node,
// the intersect it against the rhs expression
// recreate the contents using only the intersection result.
for el := matchingNodes.Front(); el != nil; el = el.Next() {
nodesToDelete, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
for el := nodesToDelete.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
elMap := list.New()
elMap.PushBack(candidate)
nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs)
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
if err != nil {
return nil, err
deleteImmediateChildOp := &Operation{
OperationType: DeleteImmediateChild,
Value: candidate.Path[len(candidate.Path)-1],
}
realNode := UnwrapDoc(candidate.Node)
deleteImmediateChildOpNode := &PathTreeNode{
Operation: deleteImmediateChildOp,
Rhs: createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]),
}
if realNode.Kind == yaml.SequenceNode {
deleteFromArray(candidate, nodesToDelete)
} else if realNode.Kind == yaml.MappingNode {
deleteFromMap(candidate, nodesToDelete)
} else {
log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate))
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
if err != nil {
return nil, err
}
}
return matchingNodes, nil
}
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
func DeleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
parents, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
childPath := pathNode.Operation.Value
log.Debug("childPath to remove %v", childPath)
for el := parents.Front(); el != nil; el = el.Next() {
parent := el.Value.(*CandidateNode)
parentNode := UnwrapDoc(parent.Node)
if parentNode.Kind == yaml.MappingNode {
deleteFromMap(parent, childPath)
} else if parentNode.Kind == yaml.SequenceNode {
deleteFromArray(parent, childPath)
} else {
return nil, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
}
}
return matchingNodes, nil
}
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
log.Debug("deleteFromMap")
node := UnwrapDoc(candidate.Node)
contents := node.Content
@@ -47,15 +75,10 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
childCandidate := &CandidateNode{
Node: value,
Document: candidate.Document,
Path: append(candidate.Path, key.Value),
Path: candidate.CreateChildPath(key.Value),
}
shouldDelete := false
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
shouldDelete = true
}
}
shouldDelete := key.Value == childPath
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
@@ -66,7 +89,7 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
node.Content = newContents
}
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
log.Debug("deleteFromArray")
node := UnwrapDoc(candidate.Node)
contents := node.Content
@@ -75,18 +98,7 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
for index := 0; index < len(contents); index = index + 1 {
value := contents[index]
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
}
}
shouldDelete := fmt.Sprintf("%v", index) == fmt.Sprintf("%v", childPath)
if !shouldDelete {
newContents = append(newContents, value)

View File

@@ -13,6 +13,22 @@ var deleteOperatorScenarios = []expressionScenario{
"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",
document: `[1,2,3]`,
@@ -21,6 +37,14 @@ var deleteOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::[1, 3]\n",
},
},
{
description: "Delete nested entry in array",
document: `[{a: cat, b: dog}]`,
expression: `del(.[0].a)`,
expected: []string{
"D0, P[], (doc)::[{b: dog}]\n",
},
},
{
description: "Delete no matches",
document: `{a: cat, b: dog}`,
@@ -37,6 +61,14 @@ var deleteOperatorScenarios = []expressionScenario{
"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) {

View File

@@ -14,10 +14,27 @@ var documentIndexScenarios = []expressionScenario{
"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",
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{
"D1, P[], (doc)::a: frog\n",
},
@@ -25,7 +42,7 @@ var documentIndexScenarios = []expressionScenario{
{
description: "Print Document Index with matches",
document: "a: cat\n---\na: frog\n",
expression: `.a | ({"match": ., "doc": (. | documentIndex)})`,
expression: `.a | ({"match": ., "doc": documentIndex})`,
expected: []string{
"D0, P[], (!!map)::match: cat\ndoc: 0\n",
"D0, P[], (!!map)::match: frog\ndoc: 1\n",

View File

@@ -21,6 +21,14 @@ var fileOperatorScenarios = []expressionScenario{
"D0, P[], (!!int)::0\n",
},
},
{
description: "Get file index alias",
document: `{a: cat}`,
expression: `fi`,
expected: []string{
"D0, P[], (!!int)::0\n",
},
},
}
func TestFileOperatorsScenarios(t *testing.T) {

View File

@@ -0,0 +1,35 @@
package yqlib
import (
"container/list"
"fmt"
yaml "gopkg.in/yaml.v3"
)
func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- lengthOperation")
var results = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
targetNode := UnwrapDoc(candidate.Node)
var length int
switch targetNode.Kind {
case yaml.ScalarNode:
length = len(targetNode.Value)
case yaml.MappingNode:
length = len(targetNode.Content) / 2
case yaml.SequenceNode:
length = len(targetNode.Content)
default:
length = 0
}
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}

View File

@@ -0,0 +1,42 @@
package yqlib
import (
"testing"
)
var lengthOperatorScenarios = []expressionScenario{
{
description: "String length",
subdescription: "returns length of string",
document: `{a: cat}`,
expression: `.a | length`,
expected: []string{
"D0, P[a], (!!int)::3\n",
},
},
{
description: "Map length",
subdescription: "returns number of entries",
document: `{a: cat, c: dog}`,
expression: `length`,
expected: []string{
"D0, P[], (!!int)::2\n",
},
},
{
description: "Array length",
subdescription: "returns number of elements",
document: `[2,4,6,8]`,
expression: `length`,
expected: []string{
"D0, P[], (!!int)::4\n",
},
},
}
func TestLengthOperatorScenarios(t *testing.T) {
for _, tt := range lengthOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Length", lengthOperatorScenarios)
}

View File

@@ -43,39 +43,51 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
return results, nil
}
type MultiplyPreferences struct {
AppendArrays bool
}
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- MultiplyOperator")
return crossFunction(d, matchingNodes, pathNode, multiply)
return crossFunction(d, matchingNodes, pathNode, multiply(pathNode.Operation.Preferences.(*MultiplyPreferences)))
}
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag)
func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag)
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
shouldAppendArrays := preferences.AppendArrays
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
var newBlank = &CandidateNode{
Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &yaml.Node{},
}
var newThing, err = mergeObjects(d, newBlank, lhs, false)
if err != nil {
return nil, err
}
return mergeObjects(d, newThing, rhs, shouldAppendArrays)
var newBlank = &CandidateNode{
Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &yaml.Node{},
}
var newThing, err = mergeObjects(d, newBlank, lhs)
if err != nil {
return nil, err
}
return mergeObjects(d, newThing, rhs)
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
}
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
}
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
var results = list.New()
err := recursiveDecent(d, results, nodeToMap(rhs))
// shouldn't recurse arrays if appending
prefs := &RecursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
TraversePreferences: &TraversePreferences{FollowAlias: false}}
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
if err != nil {
return nil, err
}
@@ -86,7 +98,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode)
}
for el := results.Front(); el != nil; el = el.Next() {
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode))
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
if err != nil {
return nil, err
}
@@ -94,20 +106,8 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode)
return lhs, nil
}
func createTraversalTree(path []interface{}) *PathTreeNode {
if len(path) == 0 {
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
} else if len(path) == 1 {
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
}
return &PathTreeNode{
Operation: &Operation{OperationType: Pipe},
Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])}
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
}
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode) error {
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
lhsPath := rhs.Path[pathIndexToStartFrom:]
@@ -115,7 +115,9 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
assignmentOp := &Operation{OperationType: AssignAttributes}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = Assign
assignmentOp.Preferences = &AssignOpPreferences{false}
assignmentOp.UpdateAssign = false
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = AddAssign
}
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}

View File

@@ -13,6 +13,13 @@ var multiplyOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
},
},
{
skipDoc: true,
expression: `{} * {"cat":"dog"}`,
expected: []string{
"D0, P[], (!!map)::cat: dog\n",
},
},
{
skipDoc: true,
document: `{a: {also: me}, b: {also: [1]}}`,
@@ -92,6 +99,22 @@ b:
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
},
},
{
skipDoc: true,
document: `{a: [1], b: [2]}`,
expression: `.a *+ .b`,
expected: []string{
"D0, P[a], (!!seq)::[1, 2]\n",
},
},
{
description: "Merge, appending arrays",
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
expression: `.a *+ .b`,
expected: []string{
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
},
},
{
description: "Merge to prefix an element",
document: `{a: cat, b: dog}`,

View File

@@ -13,6 +13,31 @@ var pathOperatorScenarios = []expressionScenario{
"D0, P[a b], (!!seq)::- a\n- b\n",
},
},
{
skipDoc: true,
document: `a:
b:
c:
- 0
- 1
- 2
- 3`,
expression: `.a.b.c.[]`,
expected: []string{
"D0, P[a b c 0], (!!int)::0\n",
"D0, P[a b c 1], (!!int)::1\n",
"D0, P[a b c 2], (!!int)::2\n",
"D0, P[a b c 3], (!!int)::3\n",
},
},
{
description: "Get map key",
document: `{a: {b: cat}}`,
expression: `.a.b | path | .[-1]`,
expected: []string{
"D0, P[a b -1], (!!str)::b\n",
},
},
{
description: "Array path",
document: `{a: [cat, dog]}`,
@@ -21,6 +46,14 @@ var pathOperatorScenarios = []expressionScenario{
"D0, P[a 1], (!!seq)::- a\n- 1\n",
},
},
{
description: "Get array index",
document: `{a: [cat, dog]}`,
expression: `.a.[] | select(. == "dog") | path | .[-1]`,
expected: []string{
"D0, P[a 1 -1], (!!int)::1\n",
},
},
{
description: "Print path and value",
document: `{a: [cat, dog, frog]}`,

View File

@@ -0,0 +1,11 @@
package yqlib
import "container/list"
func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
return d.GetMatchingNodes(lhs, pathNode.Rhs)
}

View File

@@ -0,0 +1,31 @@
package yqlib
import (
"testing"
)
var pipeOperatorScenarios = []expressionScenario{
{
description: "Simple Pipe",
document: `{a: {b: cat}}`,
expression: `.a | .b`,
expected: []string{
"D0, P[a b], (!!str)::cat\n",
},
},
{
description: "Multiple updates",
document: `{a: cow, b: sheep, c: same}`,
expression: `.a = "cat" | .b = "dog"`,
expected: []string{
"D0, P[], (doc)::{a: cat, b: dog, c: same}\n",
},
},
}
func TestPipeOperatorScenarios(t *testing.T) {
for _, tt := range pipeOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Pipe", pipeOperatorScenarios)
}

View File

@@ -3,13 +3,19 @@ package yqlib
import (
"container/list"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
type RecursiveDescentPreferences struct {
TraversePreferences *TraversePreferences
RecurseArray bool
}
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
var results = list.New()
err := recursiveDecent(d, results, matchMap)
preferences := pathNode.Operation.Preferences.(*RecursiveDescentPreferences)
err := recursiveDecent(d, results, matchMap, preferences)
if err != nil {
return nil, err
}
@@ -17,7 +23,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
return results, nil
}
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error {
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences *RecursiveDescentPreferences) error {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
@@ -26,14 +32,15 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li
log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
results.PushBack(candidate)
if candidate.Node.Kind != yaml.AliasNode {
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
(preferences.RecurseArray || candidate.Node.Kind != yaml.SequenceNode) {
children, err := Splat(d, nodeToMap(candidate))
children, err := Splat(d, nodeToMap(candidate), preferences.TraversePreferences)
if err != nil {
return err
}
err = recursiveDecent(d, results, children)
err = recursiveDecent(d, results, children, preferences)
if err != nil {
return err
}

View File

@@ -5,6 +5,38 @@ import (
)
var recursiveDescentOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{}`,
expression: `..`,
expected: []string{
"D0, P[], (!!map)::{}\n",
},
},
{
skipDoc: true,
document: `{}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{}\n",
},
},
{
skipDoc: true,
document: `[]`,
expression: `..`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `[]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `cat`,
@@ -15,13 +47,50 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
{
skipDoc: true,
document: `{a: frog}`,
expression: `..`,
document: `cat`,
expression: `...`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
},
{
description: "Recurse map (values only)",
document: `{a: frog}`,
expression: `..`,
expected: []string{
"D0, P[], (!!map)::{a: frog}\n",
"D0, P[a], (!!str)::frog\n",
},
},
{
description: "Recursively find nodes with keys",
document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
expression: `.. | select(has("name"))`,
expected: []string{
"D0, P[a], (!!map)::{name: frog, b: {name: blog, age: 12}}\n",
"D0, P[a b], (!!map)::{name: blog, age: 12}\n",
},
},
{
description: "Recursively find nodes with values",
document: `{a: {nameA: frog, b: {nameB: frog, age: 12}}}`,
expression: `.. | select(. == "frog")`,
expected: []string{
"D0, P[a nameA], (!!str)::frog\n",
"D0, P[a b nameB], (!!str)::frog\n",
},
},
{
description: "Recurse map (values and keys)",
subdescription: "Note that the map key appears in the results",
document: `{a: frog}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: frog}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `{a: {b: apple}}`,
@@ -32,6 +101,18 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[a b], (!!str)::apple\n",
},
},
{
skipDoc: true,
document: `{a: {b: apple}}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: {b: apple}}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!map)::{b: apple}\n",
"D0, P[a b], (!!str)::b\n",
"D0, P[a b], (!!str)::apple\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
@@ -43,6 +124,17 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[2], (!!int)::3\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[1, 2, 3]\n",
"D0, P[0], (!!int)::1\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!int)::3\n",
},
},
{
skipDoc: true,
document: `[{a: cat},2,true]`,
@@ -55,6 +147,19 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[2], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: `[{a: cat},2,true]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[{a: cat}, 2, true]\n",
"D0, P[0], (!!map)::{a: cat}\n",
"D0, P[0 a], (!!str)::a\n",
"D0, P[0 a], (!!str)::cat\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!bool)::true\n",
},
},
{
description: "Aliases are not traversed",
document: `{a: &cat {c: frog}, b: *cat}`,
@@ -63,6 +168,20 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
},
},
{
skipDoc: true,
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!map)::&cat {c: frog}\n",
"D0, P[a c], (!!str)::c\n",
"D0, P[a c], (!!str)::frog\n",
"D0, P[b], (!!str)::b\n",
"D0, P[b], (alias)::*cat\n",
},
},
{
description: "Merge docs are not traversed",
document: mergeDocSample,
@@ -71,6 +190,14 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobar | [...]`,
expected: []string{
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- c\n- foobar_c\n- !!merge <<\n- *foo\n- thing\n- foobar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
@@ -84,6 +211,22 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList | ...`,
expected: []string{
"D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n",
"D0, P[foobarList b], (!!str)::b\n",
"D0, P[foobarList b], (!!str)::foobarList_b\n",
"D0, P[foobarList <<], (!!merge)::!!merge <<\n",
"D0, P[foobarList <<], (!!seq)::[*foo, *bar]\n",
"D0, P[foobarList << 0], (alias)::*foo\n",
"D0, P[foobarList << 1], (alias)::*bar\n",
"D0, P[foobarList c], (!!str)::c\n",
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
}
func TestRecursiveDescentOperatorScenarios(t *testing.T) {

View File

@@ -0,0 +1,53 @@
package yqlib
import (
"container/list"
"sort"
yaml "gopkg.in/yaml.v3"
)
func SortKeysOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
for el := matchingNodes.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() {
node := UnwrapDoc(childEl.Value.(*CandidateNode).Node)
if node.Kind == yaml.MappingNode {
sortKeys(node)
}
if err != nil {
return nil, err
}
}
}
return matchingNodes, nil
}
func sortKeys(node *yaml.Node) {
keys := make([]string, len(node.Content)/2)
keyBucket := map[string]*yaml.Node{}
valueBucket := map[string]*yaml.Node{}
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
key := contents[index]
value := contents[index+1]
keys[index/2] = key.Value
keyBucket[key.Value] = key
valueBucket[key.Value] = value
}
sort.Strings(keys)
sortedContent := make([]*yaml.Node, len(node.Content))
for index := 0; index < len(keys); index = index + 1 {
keyString := keys[index]
sortedContent[index*2] = keyBucket[keyString]
sortedContent[1+(index*2)] = valueBucket[keyString]
}
node.Content = sortedContent
}

View File

@@ -0,0 +1,32 @@
package yqlib
import (
"testing"
)
var sortKeysOperatorScenarios = []expressionScenario{
{
description: "Sort keys of map",
document: `{c: frog, a: blah, b: bing}`,
expression: `sortKeys(.)`,
expected: []string{
"D0, P[], (doc)::{a: blah, b: bing, c: frog}\n",
},
},
{
description: "Sort keys recursively",
subdescription: "Note the array elements are left unsorted, but maps inside arrays are sorted",
document: `{bParent: {c: dog, array: [3,1,2]}, aParent: {z: donkey, x: [{c: yum, b: delish}, {b: ew, a: apple}]}}`,
expression: `sortKeys(..)`,
expected: []string{
"D0, P[], (!!map)::{aParent: {x: [{b: delish, c: yum}, {a: apple, b: ew}], z: donkey}, bParent: {array: [3, 1, 2], c: dog}}\n",
},
},
}
func TestSortKeysOperatorScenarios(t *testing.T) {
for _, tt := range sortKeysOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Sort Keys", sortKeysOperatorScenarios)
}

View File

@@ -4,39 +4,46 @@ import (
"container/list"
"fmt"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
func parseStyle(customStyle string) (yaml.Style, error) {
if customStyle == "tagged" {
return yaml.TaggedStyle, nil
} else if customStyle == "double" {
return yaml.DoubleQuotedStyle, nil
} else if customStyle == "single" {
return yaml.SingleQuotedStyle, nil
} else if customStyle == "literal" {
return yaml.LiteralStyle, nil
} else if customStyle == "folded" {
return yaml.FoldedStyle, nil
} else if customStyle == "flow" {
return yaml.FlowStyle, nil
} else if customStyle != "" {
return 0, fmt.Errorf("Unknown style %v", customStyle)
}
return 0, nil
}
func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignStyleOperator: %v")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
customStyle := ""
if rhs.Front() != nil {
customStyle = rhs.Front().Value.(*CandidateNode).Node.Value
}
var style yaml.Style
if customStyle == "tagged" {
style = yaml.TaggedStyle
} else if customStyle == "double" {
style = yaml.DoubleQuotedStyle
} else if customStyle == "single" {
style = yaml.SingleQuotedStyle
} else if customStyle == "literal" {
style = yaml.LiteralStyle
} else if customStyle == "folded" {
style = yaml.FoldedStyle
} else if customStyle == "flow" {
style = yaml.FlowStyle
} else if customStyle != "" {
return nil, fmt.Errorf("Unknown style %v", customStyle)
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil {
return nil, err
}
}
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
@@ -46,6 +53,20 @@ func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting style of : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil {
return nil, err
}
}
}
candidate.Node.Style = style
}

View File

@@ -21,6 +21,22 @@ var styleOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::a: \"cat\"\nb: \"5\"\nc: \"3.2\"\ne: \"true\"\n",
},
},
{
description: "Set double quote style on map keys too",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `... style="double"`,
expected: []string{
"D0, P[], (!!map)::\"a\": \"cat\"\n\"b\": \"5\"\n\"c\": \"3.2\"\n\"e\": \"true\"\n",
},
},
{
skipDoc: true,
document: "bing: &foo frog\na:\n c: cat\n <<: [*foo]",
expression: `(... | select(tag=="!!str")) style="single"`,
expected: []string{
"D0, P[], (!!map)::'bing': &foo 'frog'\n'a':\n 'c': 'cat'\n !!merge <<: [*foo]\n",
},
},
{
description: "Set single quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
@@ -70,14 +86,22 @@ e: >-
},
},
{
description: "Pretty print",
subdescription: "Set empty (default) quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style=""`,
description: "Reset style - or pretty print",
subdescription: "Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.",
document: `{a: cat, "b": 5, 'c': 3.2, "e": true}`,
expression: `... style=""`,
expected: []string{
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
},
},
{
description: "Set style relatively with assign-update",
document: `{a: single, b: double}`,
expression: `.[] style |= .`,
expected: []string{
"D0, P[], (doc)::{a: 'single', b: \"double\"}\n",
},
},
{
skipDoc: true,
document: `{a: cat, b: double}`,

View File

@@ -3,21 +3,23 @@ package yqlib
import (
"container/list"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignTagOperator: %v")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
tag := ""
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
@@ -29,6 +31,16 @@ func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting tag of : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Tag = tag
}
@@ -42,7 +54,7 @@ func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pa
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Tag, Tag: "!!str"}
node := &yaml.Node{Kind: yaml.ScalarNode, Value: UnwrapDoc(candidate.Node).Tag, Tag: "!!str"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}

View File

@@ -19,13 +19,37 @@ var tagOperatorScenarios = []expressionScenario{
},
},
{
description: "Convert numbers to strings",
skipDoc: true,
document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`,
expression: `tag`,
expected: []string{
"D0, P[], (!!str)::'!!map'\n",
},
},
{
description: "Set custom tag",
document: `{a: str}`,
expression: `.a tag = "!!mikefarah"`,
expected: []string{
"D0, P[], (doc)::{a: !!mikefarah str}\n",
},
},
{
description: "Find numbers and convert them to strings",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
expected: []string{
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
},
},
{
skipDoc: true,
document: `{a: "!!frog", b: "!!customTag"}`,
expression: `.[] tag |= .`,
expected: []string{
"D0, P[], (doc)::{a: !!frog \"!!frog\", b: !!customTag \"!!customTag\"}\n",
},
},
}
func TestTagOperatorScenarios(t *testing.T) {

View File

@@ -1,46 +1,39 @@
package yqlib
import (
"fmt"
"container/list"
"fmt"
"strconv"
"github.com/elliotchance/orderedmap"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
type TraversePreferences struct {
DontFollowAlias bool
FollowAlias bool
IncludeMapKeys bool
}
func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) {
preferences := &TraversePreferences{DontFollowAlias: true}
splatOperation := &Operation{OperationType: TraversePath, Value: "[]", Preferences: preferences}
splatTreeNode := &PathTreeNode{Operation: splatOperation}
return TraversePathOperator(d, matches, splatTreeNode)
func Splat(d *dataTreeNavigator, matches *list.List, prefs *TraversePreferences) (*list.List, error) {
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
}
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- Traversing")
var matchingNodeMap = list.New()
var newNodes []*CandidateNode
var err error
for el := matchMap.Front(); el != nil; el = el.Next() {
newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
newNodes, err := traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
if err != nil {
return nil, err
}
for _, n := range newNodes {
matchingNodeMap.PushBack(n)
}
matchingNodeMap.PushBackList(newNodes)
}
return matchingNodeMap, nil
}
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
log.Debug("Traversing %v", NodeToString(matchingNode))
value := matchingNode.Node
@@ -61,34 +54,8 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
switch value.Kind {
case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2)
var newMatches = orderedmap.NewOrderedMap()
err := traverseMap(newMatches, matchingNode, operation)
if err != nil {
return nil, err
}
if newMatches.Len() == 0 {
//no matches, create one automagically
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
node := matchingNode.Node
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: operation.StringValue}, valueNode)
candidateNode := &CandidateNode{
Node: valueNode,
Path: append(matchingNode.Path, operation.StringValue),
Document: matchingNode.Document,
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
arrayMatches := make([]*CandidateNode, newMatches.Len())
i := 0
for el := newMatches.Front(); el != nil; el = el.Next() {
arrayMatches[i] = el.Value.(*CandidateNode)
i++
}
return arrayMatches, nil
prefs := &TraversePreferences{FollowAlias: true}
return traverseMap(matchingNode, operation.StringValue, prefs, false)
case yaml.SequenceNode:
log.Debug("its a sequence of %v things!", len(value.Content))
@@ -101,105 +68,118 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
case yaml.DocumentNode:
log.Debug("digging into doc node")
return traverse(d, &CandidateNode{
Node: matchingNode.Node.Content[0],
Document: matchingNode.Document}, operation)
Node: matchingNode.Node.Content[0],
Filename: matchingNode.Filename,
FileIndex: matchingNode.FileIndex,
Document: matchingNode.Document}, operation)
default:
return nil, nil
return list.New(), nil
}
}
func keyMatches(key *yaml.Node, pathNode *Operation) bool {
return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue)
}
func TraverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
// rhs is a collect expression that will yield indexes to retreive of the arrays
func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, operation *Operation) error {
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
//TODO ALIASES, auto creation?
node := candidate.Node
followAlias := true
if operation.Preferences != nil {
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
key := contents[index]
value := contents[index+1]
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
prefs := &TraversePreferences{FollowAlias: true}
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs)
}
log.Debug("checking %v (%v)", key.Value, key.Tag)
//skip the 'merge' tag, find a direct match first
if key.Tag == "!!merge" && followAlias {
log.Debug("Merge anchor")
err := traverseMergeAnchor(newMatches, candidate, value, operation)
if err != nil {
return err
}
} else if keyMatches(key, operation) {
log.Debug("MATCHED")
candidateNode := &CandidateNode{
Node: value,
Path: append(candidate.Path, key.Value),
Document: candidate.Document,
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
var matchingNodeMap = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs)
if err != nil {
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
}
return nil
return matchingNodeMap, nil
}
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) error {
switch value.Kind {
case yaml.AliasNode:
candidateNode := &CandidateNode{
Node: value.Alias,
Path: originalCandidate.Path,
Document: originalCandidate.Document,
}
return traverseMap(newMatches, candidateNode, operation)
case yaml.SequenceNode:
for _, childValue := range value.Content {
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, operation)
if err != nil {
return err
}
}
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
node := matchingNode.Node
if node.Tag == "!!null" {
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
// auto vivification, make it into an empty array
node.Tag = ""
node.Kind = yaml.SequenceNode
}
return nil
if node.Kind == yaml.AliasNode {
matchingNode.Node = node.Alias
return traverseArrayIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.SequenceNode {
return traverseArrayWithIndices(matchingNode, indicesToTraverse)
} else if node.Kind == yaml.MappingNode {
return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.DocumentNode {
return traverseArrayIndices(&CandidateNode{
Node: matchingNode.Node.Content[0],
Filename: matchingNode.Filename,
FileIndex: matchingNode.FileIndex,
Document: matchingNode.Document}, indicesToTraverse, prefs)
}
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
return list.New(), nil
}
func traverseArray(candidate *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
log.Debug("operation Value %v", operation.Value)
if operation.Value == "[]" {
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
if len(indices) == 0 {
return traverseMap(candidate, "", prefs, true)
}
var contents = candidate.Node.Content
var newMatches = make([]*CandidateNode, len(contents))
var matchingNodeMap = list.New()
for _, indexNode := range indices {
log.Debug("traverseMapWithIndices: %v", indexNode.Value)
newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false)
if err != nil {
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
}
return matchingNodeMap, nil
}
func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) {
log.Debug("traverseArrayWithIndices")
var newMatches = list.New()
node := UnwrapDoc(candidate.Node)
if len(indices) == 0 {
log.Debug("splatting")
var index int64
for index = 0; index < int64(len(contents)); index = index + 1 {
newMatches[index] = &CandidateNode{
for index = 0; index < int64(len(node.Content)); index = index + 1 {
newMatches.PushBack(&CandidateNode{
Document: candidate.Document,
Path: append(candidate.Path, index),
Node: contents[index],
}
Path: candidate.CreateChildPath(index),
Node: node.Content[index],
})
}
return newMatches, nil
}
switch operation.Value.(type) {
case int64:
index := operation.Value.(int64)
for _, indexNode := range indices {
log.Debug("traverseArrayWithIndices: '%v'", indexNode.Value)
index, err := strconv.ParseInt(indexNode.Value, 10, 64)
if err != nil {
return nil, fmt.Errorf("Cannot index array with '%v' (%v)", indexNode.Value, err)
}
indexToUse := index
contentLength := int64(len(candidate.Node.Content))
contentLength := int64(len(node.Content))
for contentLength <= index {
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
contentLength = int64(len(candidate.Node.Content))
node.Content = append(node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
contentLength = int64(len(node.Content))
}
if indexToUse < 0 {
@@ -210,14 +190,115 @@ func traverseArray(candidate *CandidateNode, operation *Operation) ([]*Candidate
return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
}
return []*CandidateNode{&CandidateNode{
Node: candidate.Node.Content[indexToUse],
newMatches.PushBack(&CandidateNode{
Node: node.Content[indexToUse],
Document: candidate.Document,
Path: append(candidate.Path, index),
}}, nil
default:
log.Debug("argument not an int (%v), no array matches", operation.Value)
return nil, nil
Path: candidate.CreateChildPath(index),
})
}
return newMatches, nil
}
func keyMatches(key *yaml.Node, wantedKey string) bool {
return Match(key.Value, wantedKey)
}
func traverseMap(matchingNode *CandidateNode, key string, prefs *TraversePreferences, splat bool) (*list.List, error) {
var newMatches = orderedmap.NewOrderedMap()
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
if err != nil {
return nil, err
}
if newMatches.Len() == 0 {
//no matches, create one automagically
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
node := matchingNode.Node
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode)
candidateNode := &CandidateNode{
Node: valueNode,
Path: append(matchingNode.Path, key),
Document: matchingNode.Document,
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
results := list.New()
i := 0
for el := newMatches.Front(); el != nil; el = el.Next() {
results.PushBack(el.Value)
i++
}
return results, nil
}
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs *TraversePreferences, splat bool) error {
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
node := candidate.Node
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
key := contents[index]
value := contents[index+1]
log.Debug("checking %v (%v)", key.Value, key.Tag)
//skip the 'merge' tag, find a direct match first
if key.Tag == "!!merge" && prefs.FollowAlias {
log.Debug("Merge anchor")
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
if err != nil {
return err
}
} else if splat || keyMatches(key, wantedKey) {
log.Debug("MATCHED")
if prefs.IncludeMapKeys {
candidateNode := &CandidateNode{
Node: key,
Path: candidate.CreateChildPath(key.Value),
Document: candidate.Document,
}
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
}
candidateNode := &CandidateNode{
Node: value,
Path: candidate.CreateChildPath(key.Value),
Document: candidate.Document,
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
}
return nil
}
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *TraversePreferences, splat bool) error {
switch value.Kind {
case yaml.AliasNode:
candidateNode := &CandidateNode{
Node: value.Alias,
Path: originalCandidate.Path,
Document: originalCandidate.Document,
}
return doTraverseMap(newMatches, candidateNode, wantedKey, prefs, splat)
case yaml.SequenceNode:
for _, childValue := range value.Content {
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey, prefs, splat)
if err != nil {
return err
}
}
}
return nil
}
func traverseArray(candidate *CandidateNode, operation *Operation) (*list.List, error) {
log.Debug("operation Value %v", operation.Value)
indices := []*yaml.Node{&yaml.Node{Value: operation.StringValue}}
return traverseArrayWithIndices(candidate, indices)
}

View File

@@ -65,7 +65,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
{
skipDoc: true,
document: `{}`,
document: ``,
expression: `.[1].a`,
expected: []string{
"D0, P[1 a], (!!null)::null\n",
@@ -137,7 +137,15 @@ var traversePathOperatorScenarios = []expressionScenario{
{
description: "Traversing aliases with splat",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b.[]`,
expression: `.b[]`,
expected: []string{
"D0, P[b c], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b.[]`,
expected: []string{
"D0, P[b c], (!!str)::frog\n",
},
@@ -150,16 +158,10 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[b c], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
expression: `.b`,
expected: []string{},
},
{
description: "Traversing arrays by index",
document: `[1,2,3]`,
expression: `[0]`,
expression: `.[0]`,
expected: []string{
"D0, P[0], (!!int)::1\n",
},
@@ -167,7 +169,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
description: "Maps with numeric keys",
document: `{2: cat}`,
expression: `[2]`,
expression: `.[2]`,
expected: []string{
"D0, P[2], (!!str)::cat\n",
},
@@ -175,7 +177,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
description: "Maps with non existing numeric keys",
document: `{a: b}`,
expression: `[0]`,
expression: `.[0]`,
expected: []string{
"D0, P[0], (!!null)::null\n",
},
@@ -215,7 +217,17 @@ var traversePathOperatorScenarios = []expressionScenario{
{
description: "Splatting merge anchors",
document: mergeDocSample,
expression: `.foobar.[]`,
expression: `.foobar[]`,
expected: []string{
"D0, P[foobar c], (!!str)::foo_c\n",
"D0, P[foobar a], (!!str)::foo_a\n",
"D0, P[foobar thing], (!!str)::foobar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobar.[]`,
expected: []string{
"D0, P[foobar c], (!!str)::foo_c\n",
"D0, P[foobar a], (!!str)::foo_a\n",
@@ -266,7 +278,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
description: "Splatting merge anchor lists",
document: mergeDocSample,
expression: `.foobarList.[]`,
expression: `.foobarList[]`,
expected: []string{
"D0, P[foobarList b], (!!str)::bar_b\n",
"D0, P[foobarList a], (!!str)::foo_a\n",
@@ -274,6 +286,123 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList.[]`,
expected: []string{
"D0, P[foobarList b], (!!str)::bar_b\n",
"D0, P[foobarList a], (!!str)::foo_a\n",
"D0, P[foobarList thing], (!!str)::bar_thing\n",
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
{
skipDoc: true,
document: `[a,b,c]`,
expression: `.[]`,
expected: []string{
"D0, P[0], (!!str)::a\n",
"D0, P[1], (!!str)::b\n",
"D0, P[2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `[a,b,c]`,
expression: `[]`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[0]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
},
},
{
description: "Select multiple indices",
document: `{a: [a,b,c]}`,
expression: `.a[0, 2]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a.[0, 2]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[-1]`,
expected: []string{
"D0, P[a -1], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a.[-1]`,
expected: []string{
"D0, P[a -1], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[-2]`,
expected: []string{
"D0, P[a -2], (!!str)::b\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a.[-2]`,
expected: []string{
"D0, P[a -2], (!!str)::b\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a.[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a | .[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
}
func TestTraversePathOperatorScenarios(t *testing.T) {

View File

@@ -36,13 +36,22 @@ var valueOperatorScenarios = []expressionScenario{
expected: []string{
"D0, P[], (!!float)::5e-10\n",
},
}, {
},
{
document: ``,
expression: `"cat"`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
}, {
},
{
document: ``,
expression: `"frog jumps"`,
expected: []string{
"D0, P[], (!!str)::frog jumps\n",
},
},
{
document: ``,
expression: `"1.3"`,
expected: []string{

View File

@@ -20,14 +20,6 @@ func EmptyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
return list.New(), nil
}
func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
return d.GetMatchingNodes(lhs, pathNode.Rhs)
}
func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
valString := "true"
if !value {
@@ -43,28 +35,14 @@ func nodeToMap(candidate *CandidateNode) *list.List {
return elMap
}
func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- lengthOperation")
var results = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
var length int
switch candidate.Node.Kind {
case yaml.ScalarNode:
length = len(candidate.Node.Value)
case yaml.MappingNode:
length = len(candidate.Node.Content) / 2
case yaml.SequenceNode:
length = len(candidate.Node.Content)
default:
length = 0
}
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
func createTraversalTree(path []interface{}) *PathTreeNode {
if len(path) == 0 {
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
} else if len(path) == 1 {
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
}
return results, nil
return &PathTreeNode{
Operation: &Operation{OperationType: ShortPipe},
Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])}
}

View File

@@ -11,12 +11,14 @@ import (
"testing"
"github.com/mikefarah/yq/v4/test"
yaml "gopkg.in/yaml.v3"
)
type expressionScenario struct {
description string
subdescription string
document string
document2 string
expression string
expected []string
skipDoc bool
@@ -37,15 +39,32 @@ func testScenario(t *testing.T, s *expressionScenario) {
if s.document != "" {
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
if err != nil {
t.Error(err)
t.Error(err, s.document, s.expression)
return
}
if s.document2 != "" {
moreInputs, err := readDocuments(strings.NewReader(s.document2), "another.yml", 1)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
inputs.PushBackList(moreInputs)
}
} else {
candidateNode := &CandidateNode{
Document: 0,
Filename: "",
Node: &yaml.Node{Tag: "!!null"},
FileIndex: 0,
}
inputs.PushBack(candidateNode)
}
results, err = treeNavigator.GetMatchingNodes(inputs, node)
if err != nil {
t.Error(err)
t.Error(fmt.Errorf("%v: %v", err, s.expression))
return
}
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
@@ -82,7 +101,7 @@ func copyFromHeader(title string, out *os.File) error {
return err
}
func formatYaml(yaml string) string {
func formatYaml(yaml string, filename string) string {
var output bytes.Buffer
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
@@ -91,7 +110,7 @@ func formatYaml(yaml string) string {
panic(err)
}
streamEvaluator := NewStreamEvaluator()
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(yaml), node, printer)
err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer)
if err != nil {
panic(err)
}
@@ -118,63 +137,116 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
for _, s := range scenarios {
if !s.skipDoc {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
formattedDoc := ""
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document + "\n"
} else {
formattedDoc = formatYaml(s.document)
}
//TODO: pretty here
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
writeOrPanic(w, "then\n")
if s.expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval '%v' sample.yml\n```\n", s.expression))
} else {
writeOrPanic(w, "```bash\nyq eval sample.yml\n```\n")
}
} else {
writeOrPanic(w, "Running\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval --null-input '%v'\n```\n", s.expression))
}
writeOrPanic(w, "will output\n")
var output bytes.Buffer
var err error
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
if s.document != "" {
node, err := treeCreator.ParsePath(s.expression)
if err != nil {
t.Error(err)
}
streamEvaluator := NewStreamEvaluator()
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
if err != nil {
t.Error(err)
}
} else {
allAtOnceEvaluator := NewAllAtOnceEvaluator()
err = allAtOnceEvaluator.EvaluateFiles(s.expression, []string{}, printer)
if err != nil {
t.Error(err)
}
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
documentScenario(t, w, s)
}
}
w.Flush()
}
func documentScenario(t *testing.T, w *bufio.Writer, s expressionScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
formattedDoc, formattedDoc2 := documentInput(w, s)
writeOrPanic(w, "will output\n")
documentOutput(t, w, s, formattedDoc, formattedDoc2)
}
func documentInput(w *bufio.Writer, s expressionScenario) (string, string) {
formattedDoc := ""
formattedDoc2 := ""
command := "eval"
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document + "\n"
} else {
formattedDoc = formatYaml(s.document, "sample.yml")
}
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
files := "sample.yml"
if s.document2 != "" {
if s.dontFormatInputForDoc {
formattedDoc2 = s.document2 + "\n"
} else {
formattedDoc2 = formatYaml(s.document2, "another.yml")
}
writeOrPanic(w, "And another sample another.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc2))
files = "sample.yml another.yml"
command = "eval-all"
}
writeOrPanic(w, "then\n")
if s.expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v '%v' %v\n```\n", command, s.expression, files))
} else {
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v %v\n```\n", command, files))
}
} else {
writeOrPanic(w, "Running\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v --null-input '%v'\n```\n", command, s.expression))
}
return formattedDoc, formattedDoc2
}
func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formattedDoc string, formattedDoc2 string) {
var output bytes.Buffer
var err error
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
node, err := treeCreator.ParsePath(s.expression)
if err != nil {
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
return
}
inputs := list.New()
if s.document != "" {
inputs, err = readDocuments(strings.NewReader(formattedDoc), "sample.yml", 0)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
if s.document2 != "" {
moreInputs, err := readDocuments(strings.NewReader(formattedDoc2), "another.yml", 1)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
inputs.PushBackList(moreInputs)
}
} else {
candidateNode := &CandidateNode{
Document: 0,
Filename: "",
Node: &yaml.Node{Tag: "!!null"},
FileIndex: 0,
}
inputs.PushBack(candidateNode)
}
results, err := treeNavigator.GetMatchingNodes(inputs, node)
if err != nil {
t.Error(err, s.expression)
}
err = printer.PrintResults(results)
if err != nil {
t.Error(err, s.expression)
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
}

View File

@@ -11,68 +11,76 @@ var pathTests = []struct {
path string
expectedTokens []interface{}
expectedPostFix []interface{}
}{ // TODO: Ensure ALL documented examples have tests! sheesh
// {"len(.)", append(make([]interface{}, 0), "LENGTH", "(", "SELF", ")")},
// {"\"len\"(.)", append(make([]interface{}, 0), "len", "TRAVERSE", "(", "SELF", ")")},
// {".a OR (.b OR .c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")},
// {"a OR (b OR c)", append(make([]interface{}, 0), "a", "OR", "(", "b", "OR", "c", ")")},
// {"a .- (b OR c)", append(make([]interface{}, 0), "a", " .- ", "(", "b", "OR", "c", ")")},
// {"(animal==3)", append(make([]interface{}, 0), "(", "animal", "==", int64(3), ")")},
// {"(animal==f3)", append(make([]interface{}, 0), "(", "animal", "==", "f3", ")")},
// {"apples.BANANAS", append(make([]interface{}, 0), "apples", "TRAVERSE", "BANANAS")},
// {"appl*.BANA*", append(make([]interface{}, 0), "appl*", "TRAVERSE", "BANA*")},
// {"a.b.**", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "**")},
// {"a.\"=\".frog", append(make([]interface{}, 0), "a", "TRAVERSE", "=", "TRAVERSE", "frog")},
// {"a.b.*", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "*")},
// {"a.b.thin*", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "thin*")},
// {".a.b.[0]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", int64(0))},
// {".a.b.[]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[]")},
// {".a.b.[+]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[+]")},
// {".a.b.[-12]", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", int64(-12))},
// {".a.b.0", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "0")},
// {".a", append(make([]interface{}, 0), "a")},
// {".\"a.b\".c", append(make([]interface{}, 0), "a.b", "TRAVERSE", "c")},
// {`.b."foo.bar"`, append(make([]interface{}, 0), "b", "TRAVERSE", "foo.bar")},
// {`f | . == *og | length`, append(make([]interface{}, 0), "f", "TRAVERSE", "SELF", "EQUALS", "*og", "TRAVERSE", "LENGTH")},
// {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")},
// {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")},
// {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")},
// {`.a.[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")},
// {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")},
// {
// `["cat"]`,
// append(make([]interface{}, 0), "[", "cat (string)", "]"),
// append(make([]interface{}, 0), "cat (string)", "COLLECT", "PIPE"),
// },
}{
{
`[]`,
append(make([]interface{}, 0), "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
},
{
`.[]`,
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.a[]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.a.[]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.a[0]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.a.[0]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.a[].c`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "c"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "c", "SHORT_PIPE"),
},
{
`[3]`,
append(make([]interface{}, 0), "[", "3 (int64)", "]"),
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"),
},
{
`d0.a`,
append(make([]interface{}, 0), "d0", "PIPE", "a"),
append(make([]interface{}, 0), "d0", "a", "PIPE"),
append(make([]interface{}, 0), "d0", "SHORT_PIPE", "a"),
append(make([]interface{}, 0), "d0", "a", "SHORT_PIPE"),
},
{
`.a | (.[].b == "apple")`,
append(make([]interface{}, 0), "a", "PIPE", "(", "[]", "PIPE", "b", "EQUALS", "apple (string)", ")"),
append(make([]interface{}, 0), "a", "[]", "b", "PIPE", "apple (string)", "EQUALS", "PIPE"),
`.a | .[].b == "apple"`,
append(make([]interface{}, 0), "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
},
{
`(.a | .[].b) == "apple"`,
append(make([]interface{}, 0), "(", "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
},
{
`.[] | select(. == "*at")`,
append(make([]interface{}, 0), "[]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
append(make([]interface{}, 0), "[]", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
append(make([]interface{}, 0), "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", "PIPE"),
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", "PIPE"),
append(make([]interface{}, 0), "true (bool)", "false (bool)", "UNION", "COLLECT", "SHORT_PIPE"),
},
{
`"mike": .a`,
@@ -87,27 +95,27 @@ var pathTests = []struct {
{
`{"mike": .a}`,
append(make([]interface{}, 0), "{", "mike (string)", "CREATE_MAP", "a", "}"),
append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"),
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", "PIPE"),
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", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"),
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"),
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "}"),
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`explode(.a.b)`,
append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"),
append(make([]interface{}, 0), "a", "b", "PIPE", "EXPLODE"),
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", "PIPE", "b", "ASSIGN_STYLE", "folded (string)"),
append(make([]interface{}, 0), "a", "b", "PIPE", "folded (string)", "ASSIGN_STYLE"),
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"`,
@@ -129,12 +137,16 @@ var pathTests = []struct {
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), "EXPLODE", "(", "a", "PIPE", "b", ")"),
// append(make([]interface{}, 0), "a", "b", "PIPE", "EXPLODE"),
// },
{
`. 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)"),
@@ -148,27 +160,8 @@ var pathTests = []struct {
{
`{}`,
append(make([]interface{}, 0), "{", "}"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "PIPE"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"),
},
// {".animals | .==cat", append(make([]interface{}, 0), "animals", "TRAVERSE", "SELF", "EQUALS", "cat")},
// {".animals | (. == cat)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "cat", ")")},
// {".animals | (.==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "SELF", "EQUALS", "c*", ")")},
// {"animals(a.b==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "a", "TRAVERSE", "b", "==", "c*", ")")},
// {"animals.(a.b==c*)", append(make([]interface{}, 0), "animals", "TRAVERSE", "(", "a", "TRAVERSE", "b", "==", "c*", ")")},
// {"(a.b==c*).animals", append(make([]interface{}, 0), "(", "a", "TRAVERSE", "b", "==", "c*", ")", "TRAVERSE", "animals")},
// {"(a.b==c*)animals", append(make([]interface{}, 0), "(", "a", "TRAVERSE", "b", "==", "c*", ")", "TRAVERSE", "animals")},
// {"[1].a.d", append(make([]interface{}, 0), int64(1), "TRAVERSE", "a", "TRAVERSE", "d")},
// {"[1]a.d", append(make([]interface{}, 0), int64(1), "TRAVERSE", "a", "TRAVERSE", "d")},
// {"a[0]c", append(make([]interface{}, 0), "a", "TRAVERSE", int64(0), "TRAVERSE", "c")},
// {"a.[0].c", append(make([]interface{}, 0), "a", "TRAVERSE", int64(0), "TRAVERSE", "c")},
// {"[0]", append(make([]interface{}, 0), int64(0))},
// {"0", append(make([]interface{}, 0), int64(0))},
// {"a.b[+]c", append(make([]interface{}, 0), "a", "TRAVERSE", "b", "TRAVERSE", "[+]", "TRAVERSE", "c")},
// {"a.cool(s.d.f == cool)", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", " == ", "cool", ")")},
// {"a.cool.(s.d.f==cool OR t.b.h==frog).caterpillar", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "OR", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "caterpillar")},
// {"a.cool(s.d.f==cool and t.b.h==frog)*", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "and", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "*")},
// {"a.cool(s.d.f==cool and t.b.h==frog).th*", append(make([]interface{}, 0), "a", "TRAVERSE", "cool", "TRAVERSE", "(", "s", "TRAVERSE", "d", "TRAVERSE", "f", "==", "cool", "and", "t", "TRAVERSE", "b", "TRAVERSE", "h", "==", "frog", ")", "TRAVERSE", "th*")},
}
var tokeniser = NewPathTokeniser()

View File

@@ -56,7 +56,7 @@ func (p *pathPostFixer) ConvertToPostfix(infixTokens []*Token) ([]*Operation, er
// now we should have [] as the last element on the opStack, get rid of it
opStack = opStack[0 : len(opStack)-1]
//and append a collect to the opStack
opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: Pipe}})
opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: ShortPipe}})
opStack = append(opStack, &Token{TokenType: OperationToken, Operation: &Operation{OperationType: collectOperator}})
case CloseBracket:
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != OpenBracket {

View File

@@ -1,6 +1,7 @@
package yqlib
import (
"fmt"
"strconv"
lex "github.com/timtadh/lexmachine"
@@ -21,6 +22,7 @@ const (
CloseCollect
OpenCollectObject
CloseCollectObject
TraverseArrayCollect
)
type Token struct {
@@ -47,6 +49,9 @@ func (t *Token) toString() string {
return "{"
} else if t.TokenType == CloseCollectObject {
return "}"
} else if t.TokenType == TraverseArrayCollect {
return ".["
} else {
return "NFI"
}
@@ -87,6 +92,15 @@ func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.A
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: Assign, Value: Assign.Type, StringValue: value, UpdateAssign: updateAssign}
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))
@@ -100,6 +114,21 @@ func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preference
}
}
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: AssignComment,
Value: AssignComment.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
@@ -110,23 +139,6 @@ func unwrap(value string) string {
return value[1 : len(value)-1]
}
func arrayIndextoken(precedingDot bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var numberString = string(m.Bytes)
startIndex := 1
if precedingDot {
startIndex = 2
}
numberString = numberString[startIndex : len(numberString)-1]
var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint
if errParsingInt != nil {
return nil, errParsingInt
}
op := &Operation{OperationType: TraversePath, Value: number, StringValue: numberString}
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
}
}
func numberValue() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var numberString = string(m.Bytes)
@@ -184,26 +196,36 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\(`), literalToken(OpenBracket, false))
lexer.Add([]byte(`\)`), literalToken(CloseBracket, true))
lexer.Add([]byte(`\.\[\]`), pathToken(false))
lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent))
lexer.Add([]byte(`\.\[`), literalToken(TraverseArrayCollect, false))
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(RecursiveDescent, nil, &RecursiveDescentPreferences{RecurseArray: true,
TraversePreferences: &TraversePreferences{FollowAlias: false, IncludeMapKeys: false}}))
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(RecursiveDescent, nil, &RecursiveDescentPreferences{RecurseArray: true,
TraversePreferences: &TraversePreferences{FollowAlias: false, IncludeMapKeys: true}}))
lexer.Add([]byte(`,`), opToken(Union))
lexer.Add([]byte(`:\s*`), opToken(CreateMap))
lexer.Add([]byte(`length`), opToken(Length))
lexer.Add([]byte(`sortKeys`), opToken(SortKeys))
lexer.Add([]byte(`select`), opToken(Select))
lexer.Add([]byte(`has`), opToken(Has))
lexer.Add([]byte(`explode`), opToken(Explode))
lexer.Add([]byte(`or`), opToken(Or))
lexer.Add([]byte(`and`), opToken(And))
lexer.Add([]byte(`not`), opToken(Not))
lexer.Add([]byte(`\/\/`), opToken(Alternative))
lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))
lexer.Add([]byte(`di`), opToken(GetDocumentIndex))
lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle))
lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag))
lexer.Add([]byte(`anchor`), opAssignableToken(GetAnchor, AssignAnchor))
lexer.Add([]byte(`alias`), opAssignableToken(GetAlias, AssignAlias))
lexer.Add([]byte(`filename`), opToken(GetFilename))
lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex))
lexer.Add([]byte(`fi`), opToken(GetFileIndex))
lexer.Add([]byte(`path`), opToken(GetPath))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true}))
@@ -212,19 +234,17 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, nil, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true}))
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
lexer.Add([]byte(`collect`), opToken(Collect))
lexer.Add([]byte(`\s*==\s*`), opToken(Equals))
lexer.Add([]byte(`\s*=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{false}))
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
lexer.Add([]byte(`del`), opToken(DeleteChild))
lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true}))
lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(false))
lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true))
lexer.Add([]byte(`\s*\|=\s*`), assignOpToken(true))
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
@@ -245,14 +265,16 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`[Nn][Uu][Ll][Ll]`), nullValue())
lexer.Add([]byte(`~`), nullValue())
lexer.Add([]byte(`"[^ "]*"`), stringValue(true))
lexer.Add([]byte(`"[^"]*"`), stringValue(true))
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(`\*`), opToken(Multiply))
lexer.Add([]byte(`\*`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: false}))
lexer.Add([]byte(`\*\+`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: true}))
lexer.Add([]byte(`\+`), opToken(Add))
lexer.Add([]byte(`\+=`), opToken(AddAssign))
err := lexer.Compile()
if err != nil {
@@ -281,7 +303,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
scanner, err := p.lexer.Scanner([]byte(path))
if err != nil {
return nil, err
return nil, fmt.Errorf("Parsing expression: %v", err)
}
var tokens []*Token
for tok, err, eof := scanner.Next(); !eof; tok, err, eof = scanner.Next() {
@@ -292,35 +314,70 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
tokens = append(tokens, token)
}
if err != nil {
return nil, err
return nil, fmt.Errorf("Parsing expression: %v", err)
}
}
var postProcessedTokens = make([]*Token, 0)
skipNextToken := false
for index, token := range tokens {
for index := range tokens {
if skipNextToken {
skipNextToken = false
} else {
if index != len(tokens)-1 && token.AssignOperation != nil &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == Assign {
token.Operation = token.AssignOperation
skipNextToken = true
}
postProcessedTokens = append(postProcessedTokens, token)
if index != len(tokens)-1 && token.CheckForPostTraverse &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == TraversePath {
op := &Operation{OperationType: Pipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
}
postProcessedTokens, skipNextToken = p.handleToken(tokens, index, postProcessedTokens)
}
}
return postProcessedTokens, nil
}
func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTokens []*Token) (tokensAccum []*Token, skipNextToken bool) {
skipNextToken = false
token := tokens[index]
if token.TokenType == TraverseArrayCollect {
//need to put a traverse array then a collect token
// do this by adding traverse then converting token to collect
op := &Operation{OperationType: TraverseArray, StringValue: "TRAVERSE_ARRAY"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
token = &Token{TokenType: OpenCollect}
}
if index != len(tokens)-1 && token.AssignOperation != nil &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == Assign {
token.Operation = token.AssignOperation
token.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
skipNextToken = true
}
postProcessedTokens = append(postProcessedTokens, token)
if index != len(tokens)-1 && token.CheckForPostTraverse &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == TraversePath {
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
}
if index != len(tokens)-1 && token.CheckForPostTraverse &&
tokens[index+1].TokenType == OpenCollect {
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
op = &Operation{OperationType: TraverseArray}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
}
if index != len(tokens)-1 && token.CheckForPostTraverse &&
tokens[index+1].TokenType == TraverseArrayCollect {
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
}
return postProcessedTokens, skipNextToken
}

View File

@@ -1,6 +1,9 @@
package yqlib
import "fmt"
import (
"fmt"
"strings"
)
var myPathTokeniser = NewPathTokeniser()
var myPathPostfixer = NewPathPostFixer()
@@ -49,10 +52,16 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*Operation) (*PathTreeNod
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
@@ -62,7 +71,7 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*Operation) (*PathTreeNod
stack = append(stack, &newNode)
}
if len(stack) != 1 {
return nil, fmt.Errorf("expected stack to have 1 thing but its %v", stack)
return nil, fmt.Errorf("expected end of expression but found '%v', please check expression syntax", strings.TrimSpace(stack[1].Operation.StringValue))
}
return stack[0], nil
}

View File

@@ -0,0 +1,42 @@
package yqlib
import (
"testing"
"github.com/mikefarah/yq/v4/test"
)
func TestPathTreeNoArgsForTwoArgOp(t *testing.T) {
_, err := treeCreator.ParsePath("=")
test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error())
}
func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) {
_, err := treeCreator.ParsePath(".a =")
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
}
func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) {
_, err := treeCreator.ParsePath("= .a")
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
}
func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) {
_, err := treeCreator.ParsePath(".a = .b")
test.AssertResultComplex(t, nil, err)
}
func TestPathTreeNoArgsForOneArgOp(t *testing.T) {
_, err := treeCreator.ParsePath("explode")
test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error())
}
func TestPathTreeOneArgForOneArgOp(t *testing.T) {
_, err := treeCreator.ParsePath("explode(.)")
test.AssertResultComplex(t, nil, err)
}
func TestPathTreeExtraArgs(t *testing.T) {
_, err := treeCreator.ParsePath("sortKeys(.) explode(.)")
test.AssertResultComplex(t, "expected end of expression but found 'explode', please check expression syntax", err.Error())
}

View File

@@ -5,11 +5,12 @@ import (
"container/list"
"io"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
type Printer interface {
PrintResults(matchingNodes *list.List) error
PrintedAnything() bool
}
type resultsPrinter struct {
@@ -21,6 +22,8 @@ type resultsPrinter struct {
writer io.Writer
firstTimePrinting bool
previousDocIndex uint
previousFileIndex int
printedMatches bool
}
func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer {
@@ -35,7 +38,14 @@ func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEn
}
}
func (p *resultsPrinter) PrintedAnything() bool {
return p.printedMatches
}
func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error {
p.printedMatches = p.printedMatches || (node.Tag != "!!null" &&
(node.Tag != "!!bool" || node.Value != "false"))
var encoder Encoder
if node.Kind == yaml.ScalarNode && p.unwrapScalar && !p.outputToJSON {
return p.writeString(writer, node.Value+"\n")
@@ -53,6 +63,13 @@ func (p *resultsPrinter) writeString(writer io.Writer, txt string) error {
return errorWriting
}
func (p *resultsPrinter) safelyFlush(writer *bufio.Writer) {
if err := writer.Flush(); err != nil {
log.Error("Error flushing writer!")
log.Error(err.Error())
}
}
func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
log.Debug("PrintResults for %v matches", matchingNodes.Len())
var err error
@@ -66,26 +83,27 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
}
bufferedWriter := bufio.NewWriter(p.writer)
defer safelyFlush(bufferedWriter)
defer p.safelyFlush(bufferedWriter)
if matchingNodes.Len() == 0 {
log.Debug("no matching results, nothing to print")
return nil
}
if p.firstTimePrinting {
p.previousDocIndex = matchingNodes.Front().Value.(*CandidateNode).Document
node := matchingNodes.Front().Value.(*CandidateNode)
p.previousDocIndex = node.Document
p.previousFileIndex = node.FileIndex
p.firstTimePrinting = false
}
for el := matchingNodes.Front(); el != nil; el = el.Next() {
mappedDoc := el.Value.(*CandidateNode)
log.Debug("-- print sep logic: p.firstTimePrinting: %v, previousDocIndex: %v, mappedDoc.Document: %v, printDocSeparators: %v", p.firstTimePrinting, p.previousDocIndex, mappedDoc.Document, p.printDocSeparators)
if (p.previousDocIndex != mappedDoc.Document) && p.printDocSeparators {
if (p.previousDocIndex != mappedDoc.Document || p.previousFileIndex != mappedDoc.FileIndex) && p.printDocSeparators {
log.Debug("-- writing doc sep")
if err := p.writeString(bufferedWriter, "---\n"); err != nil {
return err
}
}
if err := p.printNode(mappedDoc.Node, bufferedWriter); err != nil {

View File

@@ -52,7 +52,53 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) {
writer.Flush()
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleFilesInSequence(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}
el := inputs.Front()
elNode := el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 0
sample1 := nodeToMap(elNode)
el = el.Next()
elNode = el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 1
sample2 := nodeToMap(elNode)
el = el.Next()
elNode = el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 2
sample3 := nodeToMap(elNode)
err = printer.PrintResults(sample1)
if err != nil {
panic(err)
}
err = printer.PrintResults(sample2)
if err != nil {
panic(err)
}
err = printer.PrintResults(sample3)
if err != nil {
panic(err)
}
writer.Flush()
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleDocsInSinglePrint(t *testing.T) {

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