Compare commits

..
62 Commits
Author SHA1 Message Date
Mike Farah d612897e89 Incrementing version 2021-01-14 19:50:37 +11:00
Mike Farah 806f906041 Added keys operator 2021-01-14 15:45:07 +11:00
Mike Farah e8b2f6e383 Added split string operator 2021-01-14 15:05:50 +11:00
Mike Farah e419b5a39d Added join strings operator 2021-01-14 14:46:50 +11:00
Mike Farah a5b6e08282 Split doc operator 2021-01-14 14:25:31 +11:00
Mike Farah 2417c0ee8f Fixing add,multiply,alternative operator precendences 2021-01-14 11:16:04 +11:00
Mike Farah d3b2e37b66 Fixed remove comments example 2021-01-14 09:12:14 +11:00
Mike Farah 54723c1a36 Dont use pointer for env prefs (avoid nil) 2021-01-13 17:00:53 +11:00
Mike Farah 0318c80d33 Dont use pointer for recursive prefs (avoid nil) 2021-01-13 17:00:03 +11:00
Mike Farah 8980846632 Dont use pointer for multiply prefs (avoid nil) 2021-01-13 16:59:01 +11:00
Mike Farah 0fb62d3ae1 Dont use pointer for commment prefs (avoid nil) 2021-01-13 16:56:24 +11:00
Mike Farah 85398727ad Added merge if empty 2021-01-13 16:54:28 +11:00
Mike Farah 0bf3d781f6 Added operator level doc 2021-01-13 15:23:26 +11:00
Mike Farah 5c15936bf3 Incrementing version 2021-01-13 10:29:06 +11:00
Mike Farah b0735d8152 Renaming pathtree to expression 2021-01-13 10:25:26 +11:00
Mike Farah f17cbfd007 Removed global vars 2021-01-13 10:04:52 +11:00
Mike Farah 7b7ab70286 UnwrapDoc now private 2021-01-13 10:00:51 +11:00
Mike Farah 43165fa340 Moved eval function to eval interface 2021-01-13 09:35:57 +11:00
Mikhail KatychevandMike Farah feda9f044d added lib_test.go 2021-01-13 09:22:40 +11:00
Mikhail KatychevandMike Farah 85629af59c added EvaluateNodes and EvaluateCandidateNodes to yqlib 2021-01-13 09:22:40 +11:00
Mike Farah 5fc26e9453 Merge now copies anchor names 2021-01-13 09:21:16 +11:00
Mike Farah 07a6fa4df5 Fixed creation of candidateNode in operators to include file metadata 2021-01-12 19:36:28 +11:00
Mike Farah ec29ee7c14 Cleaning up exposed public api 2021-01-12 09:58:50 +11:00
Mike Farah 24538c0cc7 Fixed tag operator for top level node 2021-01-12 09:45:57 +11:00
Mike Farah 61398bfd3a Fixed equals operator for top level node 2021-01-12 09:40:37 +11:00
Mike Farah f7d95021c1 Fixed has operator for top level node 2021-01-12 09:30:24 +11:00
Mike Farah 6bb8b1fb77 fixing exposed functions and interfaces 2021-01-11 17:13:48 +11:00
Mike Farah 5e2c19cc86 fixing exposed functions and interfaces 2021-01-11 16:46:28 +11:00
Mike Farah 30c269a66c Better add documentation 2021-01-11 15:52:06 +11:00
Mike Farah 70a1c60d7b Added scalar addition 2021-01-11 15:43:50 +11:00
Mike Farah e4d48bbc0d Fixed collect at document level 2021-01-11 14:44:53 +11:00
Mike Farah 369ff94ad0 Better error handling will empty env 2021-01-11 14:38:53 +11:00
Mike Farah 1c7fb14631 Better recursive decent docs 2021-01-10 11:27:18 +11:00
Mike Farah f4de5c4300 Better docIndex docs 2021-01-10 11:18:13 +11:00
Mike Farah a72c14f06b Better env docs 2021-01-10 11:09:59 +11:00
Mike Farah 5ed52aca66 Merged env commands in :eye-roll: 2021-01-10 10:51:23 +11:00
Mike Farah 5a5ac0dfef Merge branch 'env_var' 2021-01-10 10:50:31 +11:00
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 644063646e Env Ops! 2021-01-09 12:06:19 +11:00
Mike Farah aabed1a237 strenv 2021-01-09 11:38:08 +11:00
Mike Farah c12764dba8 wip 2021-01-08 21:09:43 +11:00
Mike Farah 1d5ecb244d wip 2021-01-08 21:07:46 +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
193 changed files with 1218 additions and 11012 deletions
+1 -1
View File
@@ -1 +1 @@
bin/*
bin
-1
View File
@@ -1 +0,0 @@
github: mikefarah
+12 -28
View File
@@ -11,37 +11,11 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.17.2'
- name: Compile man page markup
id: gen-man-page-md
run: |
./scripts/generate-man-page-md.sh
echo "::set-output name=man-page-md::man.md"
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF##*/}
- name: Generate man page
uses: docker://pandoc/core:2.14.2
id: gen-man-page
with:
args: >-
--standalone
--to man
--variable=title:"YQ"
--variable=section:"1"
--variable=header:"yq (https://github.com/mikefarah/yq/) version ${{ steps.get_version.outputs.VERSION }}"
--variable=author:"Mike Farah"
--output=yq.1
${{ steps.gen-man-page-md.outputs.man-page-md }}
go-version: '^1.15'
- name: Cross compile
run: |
sudo apt-get install rhash -y
go get github.com/mitchellh/gox
mkdir -p build
cp yq.1 build/yq.1
./scripts/xcompile.sh
- name: Create Release
@@ -66,7 +40,17 @@ jobs:
IMAGE_NAME: mikefarah/yq
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- 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
-19
View File
@@ -1,19 +0,0 @@
name: Test Yq Action
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Get test
uses: mikefarah/yq@master
with:
cmd: yq eval '.a' examples/data1.yaml
- name: Write inplace test
id: lookupSdkVersion
uses: mikefarah/yq@master
with:
cmd: yq eval -i '.a.b = 5' examples/data1.yaml
-11
View File
@@ -38,14 +38,3 @@ parts/
prime/
.snapcraft/
yq*.snap
test.yml
test*.yml
0.yml
1.yml
2.yml
# man page
man.md
yq.1
+7 -12
View File
@@ -1,21 +1,18 @@
FROM golang:1.17 as builder
FROM golang:1.15 as builder
WORKDIR /go/src/mikefarah/yq
# cache devtools
COPY ./scripts/devtools.sh /go/src/mikefarah/yq/scripts/devtools.sh
RUN ./scripts/devtools.sh
COPY . /go/src/mikefarah/yq
RUN CGO_ENABLED=0 go build .
RUN ./scripts/test.sh
RUN ./scripts/acceptance.sh
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 as production
RUN mkdir /home/yq/
RUN addgroup -g 1000 yq && \
adduser -u 1000 -G yq -s /bin/bash -h /home/yq -D yq
RUN chown -R yq:yq /home/yq/
FROM alpine:3.12.3 as production
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
RUN chmod +x /usr/bin/yq
@@ -23,8 +20,6 @@ RUN chmod +x /usr/bin/yq
ARG VERSION=none
LABEL version=${VERSION}
USER yq
WORKDIR /workdir
ENTRYPOINT ["/usr/bin/yq"]
+22 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.17
FROM golang:1.15
COPY scripts/devtools.sh /opt/devtools.sh
@@ -6,5 +6,26 @@ RUN set -e -x \
&& /opt/devtools.sh
ENV PATH=/go/bin:$PATH
# install mkdocs
RUN set -ex \
&& buildDeps=' \
build-essential \
python3-dev \
' \
&& apt-get update && apt-get install -y --no-install-recommends \
$buildDeps \
python3 \
python3-setuptools \
python3-wheel \
python3-pip \
&& pip3 install --upgrade \
pip \
'Markdown>=2.6.9' \
'mkdocs>=0.16.3' \
'mkdocs-material>=1.10.1' \
'markdown-include>=0.5.1' \
&& apt-get purge -y --auto-remove $buildDeps \
&& rm -rf /var/lib/apt/lists/*
ENV CGO_ENABLED 0
ENV GOPATH /go:/yq
+5 -5
View File
@@ -17,7 +17,6 @@ help:
@echo ' make vendor Install dependencies to vendor directory.'
@echo ' make format Run code formatter.'
@echo ' make check Run static code analysis (lint).'
@echo ' make secure Run gosec.'
@echo ' make test Run tests on project.'
@echo ' make cover Run tests and capture code coverage metrics on project.'
@echo ' make clean Clean the directory tree of produced artifacts.'
@@ -85,10 +84,6 @@ format: vendor
check: format
${DOCKRUN} bash ./scripts/check.sh
.PHONY: secure
secure:
${DOCKRUN} bash ./scripts/secure.sh
.PHONY: test
test: check
${DOCKRUN} bash ./scripts/test.sh
@@ -101,6 +96,11 @@ cover: check
@find cover -type d -exec chmod 755 {} \; || :
@find cover -type f -exec chmod 644 {} \; || :
.PHONY: build-docs
build-docs: prepare mkdocs.yml mkdocs/*
${DOCKRUN} mkdocs build
@find docs -type d -exec chmod 755 {} \; || :
@find docs -type f -exec chmod 644 {} \; || :
.PHONY: release
release: xcompile
+31 -106
View File
@@ -7,43 +7,12 @@ a lightweight and portable command-line YAML processor. `yq` uses [jq](https://g
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.
## Quick Usage Guide
## 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!
Read a value:
```bash
yq e '.a.b[0].c' file.yaml
```
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).
Pipe from STDIN:
```bash
cat file.yaml | yq e '.a.b[0].c' -
```
Update a yaml file, inplace
```bash
yq e -i '.a.b[0].c = "cool"' file.yaml
```
Update using environment variables
```bash
NAME=mike yq e -i '.a.b[0].c = strenv(NAME)' file.yaml
```
Merge multiple files
```
yq ea '. as $item ireduce ({}; . * $item )' path/to/*.yml
```
Multiple updates to a yaml file
```bash
yq e -i '
.a.b[0].c = "cool" |
.x.y.z = "foobar" |
.person.name = strenv(NAME)
' file.yaml
```
See the [documentation](https://mikefarah.gitbook.io/yq/) for more.
Support for v3 will cease August 2021, until then, critical bug and security fixes will still get applied if required.
## Install
@@ -118,18 +87,10 @@ rm /etc/myfile.tmp
docker run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression ]FILE...
```
#### Pipe in via STDIN:
You'll need to pass the `-i\--interactive` flag to docker:
```bash
cat myfile.yml | docker run -i --rm mikefarah/yq e . -
```
#### Run commands interactively:
```bash
docker run --rm -it -v "${PWD}":/workdir --entrypoint sh mikefarah/yq
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
```
It can be useful to have a bash function to avoid typing the whole docker command:
@@ -140,39 +101,9 @@ yq() {
}
```
#### Running as root:
`yq`'s docker image no longer runs under root (https://github.com/mikefarah/yq/pull/860). If you'd like to install more things in the docker image, or you're having permissions issues when attempting to read/write files you'll need to either:
```
docker run --user="root" -it --entrypoint sh mikefarah/yq
```
Or, in your docker file:
```
FROM mikefarah/yq
USER root
RUN apk add bash
USER yq
```
### GitHub Action
```
- name: Set foobar to cool
uses: mikefarah/yq@master
with:
cmd: yq eval -i '.foo.bar = "cool"' 'config.yml'
```
See https://mikefarah.gitbook.io/yq/usage/github-action for more.
### Go Get:
```
GO111MODULE=on go get github.com/mikefarah/yq/v4
GO111MODULE=on go get github.com/mikefarah/yq
```
## Community Supported Installation methods
@@ -188,8 +119,6 @@ See [webi](https://webinstall.dev/)
Supported by @adithyasunil26 (https://github.com/webinstall/webi-installers/tree/master/yq)
### Windows:
[![Chocolatey](https://img.shields.io/chocolatey/v/yq.svg)](https://chocolatey.org/packages/yq)
[![Chocolatey](https://img.shields.io/chocolatey/dt/yq.svg)](https://chocolatey.org/packages/yq)
```
choco install yq
```
@@ -222,24 +151,19 @@ sudo apt install yq -y
Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
## Features
- [Detailed documentation with many examples](https://mikefarah.gitbook.io/yq/)
- Written in portable go, so you can download a lovely dependency free binary
- Uses similar syntax as `jq` but works with YAML and JSON files
- Fully supports multi document yaml files
- Supports yaml [front matter](https://mikefarah.gitbook.io/yq/usage/front-matter) blocks (e.g. jekyll/assemble)
- Colorized yaml output
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/operators/traverse-read)
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/operators/sort-keys)
- Manipulate yaml [comments](https://mikefarah.gitbook.io/yq/operators/comment-operators), [styling](https://mikefarah.gitbook.io/yq/operators/style), [tags](https://mikefarah.gitbook.io/yq/operators/tag) and [anchors and aliases](https://mikefarah.gitbook.io/yq/operators/anchor-and-alias-operators).
- [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/operators/select#select-and-update-matching-values-in-map)
- [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](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
- [Convert to properties](https://mikefarah.gitbook.io/yq/v/v4.x/usage/properties)
- [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)
- [Reduce](https://mikefarah.gitbook.io/yq/operators/reduce) to merge multiple files or sum an array or other fancy things.
- [Github Action](https://mikefarah.gitbook.io/yq/usage/github-action) to use in your automated pipeline (thanks @devorbitus)
## [Usage](https://mikefarah.gitbook.io/yq/)
@@ -251,32 +175,33 @@ Usage:
yq [command]
Available Commands:
eval Apply the expression to each document in each yaml file in sequence
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
shell-completion Generate completion script
Flags:
-C, --colors force print with colors
-e, --exit-status set exit status if there are no matches or null or false is returned
-f, --front-matter string (extract|process) first input as yaml front-matter. Extract will pull out the yaml content, process will run the expression against the yaml content, leaving the remaining data intact
--header-preprocess Slurp any header comments and seperators before processing expression. This is a workaround for go-yaml to persist header content (default true)
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-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.
-o, --output-format string [yaml|y|json|j|props|p] output format type. (default "yaml")
-P, --prettyPrint pretty print, shorthand for '... style = ""'
--unwrapScalar unwrap scalar, print the value with no quotes, colors or comments (default true)
-v, --verbose verbose mode
-V, --version Print version information and quit
-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)
-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.
```
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)
- Powershell has its own...opinions: https://mikefarah.gitbook.io/yq/usage/tips-and-tricks#quotes-in-windows-powershell
See [tips and tricks](https://mikefarah.gitbook.io/yq/usage/tips-and-tricks) for more common problems and solutions.
-72
View File
@@ -1,72 +0,0 @@
#!/bin/bash
setUp() {
rm test*.yml || true
}
testBasicEvalRoundTrip() {
./yq e -n ".a = 123" > test.yml
X=$(./yq e '.a' test.yml)
assertEquals 123 "$X"
}
testBasicUpdateInPlaceSequence() {
cat >test.yml <<EOL
a: 0
EOL
./yq e -i ".a = 10" test.yml
X=$(./yq e '.a' test.yml)
assertEquals "10" "$X"
}
testBasicUpdateInPlaceSequenceEvalAll() {
cat >test.yml <<EOL
a: 0
EOL
./yq ea -i ".a = 10" test.yml
X=$(./yq e '.a' test.yml)
assertEquals "10" "$X"
}
testBasicNoExitStatus() {
echo "a: cat" > test.yml
X=$(./yq e '.z' test.yml)
assertEquals "null" "$X"
}
testBasicExitStatus() {
echo "a: cat" > test.yml
X=$(./yq e -e '.z' test.yml 2&>/dev/null)
assertEquals 1 "$?"
}
testBasicExtractFieldWithSeperator() {
cat >test.yml <<EOL
---
name: chart-name
version: 1.2.3
EOL
X=$(./yq e '.name' test.yml)
assertEquals "chart-name" "$X"
}
testBasicExtractMultipleFieldWithSeperator() {
cat >test.yml <<EOL
---
name: chart-name
version: 1.2.3
---
name: thing
version: 1.2.3
EOL
read -r -d '' expected << EOM
chart-name
---
thing
EOM
X=$(./yq e '.name' test.yml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
-83
View File
@@ -1,83 +0,0 @@
#!/bin/bash
setUp() {
rm test*.yml || true
cat >test.yml <<EOL
# comment
EOL
}
testEmptyEval() {
X=$(./yq e test.yml)
expected=$(cat test.yml)
assertEquals 0 $?
assertEquals "$expected" "$X"
}
testEmptyEvalNoNewLine() {
echo -n "#comment" >test.yml
X=$(./yq e test.yml)
expected=$(cat test.yml)
assertEquals 0 $?
assertEquals "$expected" "$X"
}
testEmptyEvalNoNewLineWithExpression() {
echo -n "# comment" >test.yml
X=$(./yq e '.apple = "tree"' test.yml)
read -r -d '' expected << EOM
# comment
apple: tree
EOM
assertEquals "$expected" "$X"
}
testEmptyEvalPipe() {
X=$(./yq e - < test.yml)
assertEquals 0 $?
}
testEmptyCommentsWithExpressionEval() {
read -r -d '' expected << EOM
# comment
apple: tree
EOM
X=$(./yq e '.apple="tree"' test.yml)
assertEquals "$expected" "$X"
}
testEmptyCommentsWithExpressionEvalAll() {
read -r -d '' expected << EOM
# comment
apple: tree
EOM
X=$(./yq ea '.apple="tree"' test.yml)
assertEquals "$expected" "$X"
}
testEmptyWithExpressionEval() {
rm test.yml
touch test.yml
expected="apple: tree"
X=$(./yq e '.apple="tree"' test.yml)
assertEquals "$expected" "$X"
}
testEmptyWithExpressionEvalAll() {
rm test.yml
touch test.yml
expected="apple: tree"
X=$(./yq ea '.apple="tree"' test.yml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
-76
View File
@@ -1,76 +0,0 @@
#!/bin/bash
setUp() {
rm test*.yml || true
cat >test.yml <<EOL
---
a: apple
b: cat
---
not yaml
c: at
EOL
}
testFrontMatterProcessEval() {
read -r -d '' expected << EOM
---
a: apple
b: dog
---
not yaml
c: at
EOM
./yq e --front-matter="process" '.b = "dog"' test.yml -i
assertEquals "$expected" "$(cat test.yml)"
}
testFrontMatterProcessEvalAll() {
read -r -d '' expected << EOM
---
a: apple
b: dog
---
not yaml
c: at
EOM
./yq ea --front-matter="process" '.b = "dog"' test.yml -i
assertEquals "$expected" "$(cat test.yml)"
}
testFrontMatterExtractEval() {
cat >test.yml <<EOL
a: apple
b: cat
---
not yaml
c: at
EOL
read -r -d '' expected << EOM
a: apple
b: dog
EOM
./yq e --front-matter="extract" '.b = "dog"' test.yml -i
assertEquals "$expected" "$(cat test.yml)"
}
testFrontMatterExtractEvalAll() {
cat >test.yml <<EOL
a: apple
b: cat
---
not yaml
c: at
EOL
read -r -d '' expected << EOM
a: apple
b: dog
EOM
./yq ea --front-matter="extract" '.b = "dog"' test.yml -i
assertEquals "$expected" "$(cat test.yml)"
}
source ./scripts/shunit2
-450
View File
@@ -1,450 +0,0 @@
#!/bin/bash
setUp() {
rm test*.yml || true
cat >test.yml <<EOL
---
a: test
EOL
}
testLeadingSeperatorWithDoc() {
cat >test.yml <<EOL
# hi peeps
# cool
---
a: test
---
b: cool
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
---
a: thing
---
b: cool
EOM
X=$(./yq e '(select(di == 0) | .a) = "thing"' - < test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorPipeIntoEvalSeq() {
X=$(./yq e - < test.yml)
expected=$(cat test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorExtractField() {
X=$(./yq e '.a' - < test.yml)
assertEquals "test" "$X"
}
testLeadingSeperatorExtractFieldWithCommentsAfterSep() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
EOL
X=$(./yq e '.a' test.yml)
assertEquals "test" "$X"
}
testLeadingSeperatorExtractFieldWithCommentsBeforeSep() {
cat >test.yml <<EOL
# hi peeps
# cool
---
a: test
EOL
X=$(./yq e '.a' test.yml)
assertEquals "test" "$X"
}
testLeadingSeperatorExtractFieldMultiDoc() {
cat >test.yml <<EOL
---
a: test
---
a: test2
EOL
read -r -d '' expected << EOM
test
---
test2
EOM
X=$(./yq e '.a' test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorExtractFieldMultiDocWithComments() {
cat >test.yml <<EOL
# here
---
# there
a: test
# whereever
---
# you are
a: test2
# woop
EOL
read -r -d '' expected << EOM
test
---
test2
EOM
X=$(./yq e '.a' test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorEvalSeq() {
X=$(./yq e test.yml)
expected=$(cat test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorPipeIntoEvalAll() {
X=$(./yq ea - < test.yml)
expected=$(cat test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorEvalAll() {
X=$(./yq ea test.yml)
expected=$(cat test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalSimple() {
read -r -d '' expected << EOM
---
a: test
---
version: 3
application: MyApp
EOM
X=$(./yq e '.' test.yml examples/order.yaml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocInOneFile() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
---
b: things
EOL
expected=$(cat test.yml)
X=$(./yq e '.' test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocInOneFileEvalAll() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
---
b: things
EOL
expected=$(cat test.yml)
X=$(./yq ea '.' test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalComments() {
cat >test.yml <<EOL
# hi peeps
# cool
a: test
EOL
cat >test2.yml <<EOL
# this is another doc
# great
b: sane
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
a: test
---
# this is another doc
# great
b: sane
EOM
X=$(./yq e '.' test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalCommentsTrailingSep() {
cat >test.yml <<EOL
# hi peeps
# cool
---
a: test
EOL
cat >test2.yml <<EOL
# this is another doc
# great
---
b: sane
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
---
a: test
---
# this is another doc
# great
---
b: sane
EOM
X=$(./yq e '.' test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiMultiDocEvalCommentsTrailingSep() {
cat >test.yml <<EOL
# hi peeps
# cool
---
a: test
---
a1: test2
EOL
cat >test2.yml <<EOL
# this is another doc
# great
---
b: sane
---
b2: cool
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
---
a: test
---
a1: test2
---
# this is another doc
# great
---
b: sane
---
b2: cool
EOM
X=$(./yq e '.' test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalCommentsLeadingSep() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
EOL
cat >test2.yml <<EOL
---
# this is another doc
# great
b: sane
EOL
read -r -d '' expected << EOM
---
# hi peeps
# cool
a: test
---
# this is another doc
# great
b: sane
EOM
X=$(./yq e '.' test.yml test2.yml)
assertEquals "$expected" "$X"
}
# https://github.com/mikefarah/yq/issues/919
testLeadingSeparatorDoesNotBreakCommentsOnOtherFiles() {
cat >test.yml <<EOL
# a1
a: 1
# a2
EOL
cat >test2.yml <<EOL
# b1
b: 2
# b2
EOL
read -r -d '' expected << EOM
# a1
a: 1
# a2
# b1
b: 2
# b2
EOM
X=$(./yq ea 'select(fi == 0) * select(fi == 1)' test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalCommentsStripComments() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
---
# this is another doc
# great
b: sane
EOL
# it will be hard to remove that top level separator
read -r -d '' expected << EOM
a: test
---
b: sane
EOM
X=$(./yq e '... comments=""' test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalCommentsLeadingSepNoDocFlag() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
---
# this is another doc
# great
b: sane
EOL
read -r -d '' expected << EOM
# hi peeps
# cool
a: test
# this is another doc
# great
b: sane
EOM
X=$(./yq e '.' --no-doc test.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalJsonFlag() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
EOL
cat >test2.yml <<EOL
---
# this is another doc
# great
b: sane
EOL
read -r -d '' expected << EOM
{
"a": "test"
}
{
"b": "sane"
}
EOM
X=$(./yq e '.' -j test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalAllJsonFlag() {
cat >test.yml <<EOL
---
# hi peeps
# cool
a: test
EOL
cat >test2.yml <<EOL
---
# this is another doc
# great
b: sane
EOL
read -r -d '' expected << EOM
{
"a": "test"
}
{
"b": "sane"
}
EOM
X=$(./yq ea '.' -j test.yml test2.yml)
assertEquals "$expected" "$X"
}
testLeadingSeperatorMultiDocEvalAll() {
read -r -d '' expected << EOM
---
a: test
---
version: 3
application: MyApp
EOM
X=$(./yq ea '.' test.yml examples/order.yaml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
-105
View File
@@ -1,105 +0,0 @@
#!/bin/bash
setUp() {
rm test*.yml || true
}
testOutputJsonDeprecated() {
cat >test.yml <<EOL
a: {b: ["cat"]}
EOL
read -r -d '' expected << EOM
{
"a": {
"b": [
"cat"
]
}
}
EOM
X=$(./yq e -j test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea -j test.yml)
assertEquals "$expected" "$X"
}
testOutputJson() {
cat >test.yml <<EOL
a: {b: ["cat"]}
EOL
read -r -d '' expected << EOM
{
"a": {
"b": [
"cat"
]
}
}
EOM
X=$(./yq e --output-format=json test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --output-format=json test.yml)
assertEquals "$expected" "$X"
}
testOutputJsonShort() {
cat >test.yml <<EOL
a: {b: ["cat"]}
EOL
read -r -d '' expected << EOM
{
"a": {
"b": [
"cat"
]
}
}
EOM
X=$(./yq e -o=j test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea -o=j test.yml)
assertEquals "$expected" "$X"
}
testOutputProperties() {
cat >test.yml <<EOL
a: {b: {c: ["cat"]}}
EOL
read -r -d '' expected << EOM
a.b.c.0 = cat
EOM
X=$(./yq e --output-format=props test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --output-format=props test.yml)
assertEquals "$expected" "$X"
}
testOutputPropertiesShort() {
cat >test.yml <<EOL
a: {b: {c: ["cat"]}}
EOL
read -r -d '' expected << EOM
a.b.c.0 = cat
EOM
X=$(./yq e -o=p test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea -o=p test.yml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
-176
View File
@@ -1,176 +0,0 @@
#!/bin/bash
setUp() {
rm test*.yml || true
}
testPrettyPrintWithBooleans() {
cat >test.yml <<EOL
leaveUnquoted: [yes, no, on, off, y, n, true, false]
leaveQuoted: ["yes", "no", "on", "off", "y", "n", "true", "false"]
EOL
read -r -d '' expected << EOM
leaveUnquoted:
- yes
- no
- on
- off
- y
- n
- true
- false
leaveQuoted:
- "yes"
- "no"
- "on"
- "off"
- "y"
- "n"
- "true"
- "false"
EOM
X=$(./yq e --prettyPrint test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --prettyPrint test.yml)
assertEquals "$expected" "$X"
}
testPrettyPrintWithBooleansCapitals() {
cat >test.yml <<EOL
leaveUnquoted: [YES, NO, ON, OFF, Y, N, TRUE, FALSE]
leaveQuoted: ["YES", "NO", "ON", "OFF", "Y", "N", "TRUE", "FALSE"]
EOL
read -r -d '' expected << EOM
leaveUnquoted:
- YES
- NO
- ON
- OFF
- Y
- N
- TRUE
- FALSE
leaveQuoted:
- "YES"
- "NO"
- "ON"
- "OFF"
- "Y"
- "N"
- "TRUE"
- "FALSE"
EOM
X=$(./yq e --prettyPrint test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --prettyPrint test.yml)
assertEquals "$expected" "$X"
}
testPrettyPrintOtherStringValues() {
cat >test.yml <<EOL
leaveUnquoted: [yesSir, hellno, bonapite]
makeUnquoted: ["yesSir", "hellno", "bonapite"]
EOL
read -r -d '' expected << EOM
leaveUnquoted:
- yesSir
- hellno
- bonapite
makeUnquoted:
- yesSir
- hellno
- bonapite
EOM
X=$(./yq e --prettyPrint test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --prettyPrint test.yml)
assertEquals "$expected" "$X"
}
testPrettyPrintKeys() {
cat >test.yml <<EOL
"removeQuotes": "please"
EOL
read -r -d '' expected << EOM
removeQuotes: please
EOM
X=$(./yq e --prettyPrint test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --prettyPrint test.yml)
assertEquals "$expected" "$X"
}
testPrettyPrintOtherStringValues() {
cat >test.yml <<EOL
leaveUnquoted: [yesSir, hellno, bonapite]
makeUnquoted: ["yesSir", "hellno", "bonapite"]
EOL
read -r -d '' expected << EOM
leaveUnquoted:
- yesSir
- hellno
- bonapite
makeUnquoted:
- yesSir
- hellno
- bonapite
EOM
X=$(./yq e --prettyPrint test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --prettyPrint test.yml)
assertEquals "$expected" "$X"
}
testPrettyPrintStringBlocks() {
cat >test.yml <<EOL
"removeQuotes": |
"please"
EOL
read -r -d '' expected << EOM
removeQuotes: |
"please"
EOM
X=$(./yq e --prettyPrint test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --prettyPrint test.yml)
assertEquals "$expected" "$X"
}
testPrettyPrintWithExpression() {
cat >test.yml <<EOL
a: {b: {c: ["cat"]}}
EOL
read -r -d '' expected << EOM
b:
c:
- cat
EOM
X=$(./yq e '.a' --prettyPrint test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea '.a' --prettyPrint test.yml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
-157
View File
@@ -1,157 +0,0 @@
#!/bin/bash
setUp() {
rm test*.yml || true
}
testBasicSplitWithName() {
cat >test.yml <<EOL
a: test_doc1
---
a: test_doc2
EOL
./yq e test.yml -s ".a"
doc1=$(cat test_doc1.yml)
assertEquals "a: test_doc1" "$doc1"
doc2=$(cat test_doc2.yml)
read -r -d '' expectedDoc2 << EOM
---
a: test_doc2
EOM
assertEquals "$expectedDoc2" "$doc2"
}
testBasicSplitWithNameEvalAll() {
cat >test.yml <<EOL
a: test_doc1
---
a: test_doc2
EOL
./yq ea test.yml -s ".a"
doc1=$(cat test_doc1.yml)
assertEquals "a: test_doc1" "$doc1"
doc2=$(cat test_doc2.yml)
read -r -d '' expectedDoc2 << EOM
---
a: test_doc2
EOM
assertEquals "$expectedDoc2" "$doc2"
}
testBasicSplitWithIndex() {
cat >test.yml <<EOL
a: test_doc1
---
a: test_doc2
EOL
./yq e test.yml -s '"test_" + $index'
doc1=$(cat test_0.yml)
assertEquals "a: test_doc1" "$doc1"
doc2=$(cat test_1.yml)
read -r -d '' expectedDoc2 << EOM
---
a: test_doc2
EOM
assertEquals "$expectedDoc2" "$doc2"
}
testBasicSplitWithIndexEvalAll() {
cat >test.yml <<EOL
a: test_doc1
---
a: test_doc2
EOL
./yq ea test.yml -s '"test_" + $index'
doc1=$(cat test_0.yml)
assertEquals "a: test_doc1" "$doc1"
doc2=$(cat test_1.yml)
read -r -d '' expectedDoc2 << EOM
---
a: test_doc2
EOM
assertEquals "$expectedDoc2" "$doc2"
}
testArraySplitWithNameNoSeparators() {
cat >test.yml <<EOL
- name: test_fred
age: 35
- name: test_catherine
age: 37
EOL
./yq e --no-doc -s ".name" ".[]" test.yml
doc1=$(cat test_fred.yml)
read -r -d '' expectedDoc1 << EOM
name: test_fred
age: 35
EOM
assertEquals "$expectedDoc1" "$doc1"
doc2=$(cat test_catherine.yml)
read -r -d '' expectedDoc2 << EOM
name: test_catherine
age: 37
EOM
assertEquals "$expectedDoc2" "$doc2"
}
testArraySplitWithNameNoSeparatorsEvalAll() {
cat >test.yml <<EOL
- name: test_fred
age: 35
- name: test_catherine
age: 37
EOL
cat >test2.yml <<EOL
- name: test_mike
age: 564
EOL
./yq ea --no-doc -s ".name" ".[]" test.yml test2.yml
doc1=$(cat test_fred.yml)
read -r -d '' expectedDoc1 << EOM
name: test_fred
age: 35
EOM
assertEquals "$expectedDoc1" "$doc1"
doc2=$(cat test_catherine.yml)
read -r -d '' expectedDoc2 << EOM
name: test_catherine
age: 37
EOM
assertEquals "$expectedDoc2" "$doc2"
doc3=$(cat test_mike.yml)
read -r -d '' expectedDoc3 << EOM
name: test_mike
age: 564
EOM
assertEquals "$expectedDoc3" "$doc3"
}
source ./scripts/shunit2
+2 -6
View File
@@ -1,15 +1,11 @@
name: 'yq - portable yaml processor'
description: 'create, read, update, delete, merge, validate and do more with yaml'
branding:
icon: command
color: gray-dark
icon: command
color: gray-dark
inputs:
cmd:
description: 'The Command which should be run'
required: true
outputs:
result:
description: "The complete result from the yq command being run"
runs:
using: 'docker'
image: 'github-action/Dockerfile'
-7
View File
@@ -1,11 +1,9 @@
package cmd
var leadingContentPreProcessing = true
var unwrapScalar = true
var writeInplace = false
var outputToJSON = false
var outputFormat = "yaml"
var exitStatus = false
var forceColor = false
@@ -18,9 +16,4 @@ var verbose = false
var version = false
var prettyPrint = false
// can be either "" (off), "extract" or "process"
var frontMatter = ""
var splitFileExp = ""
var completedSuccessfully = false
+10 -65
View File
@@ -15,27 +15,10 @@ func createEvaluateAllCommand() *cobra.Command {
Aliases: []string{"ea"},
Short: "Loads _all_ yaml documents of _all_ yaml files and runs expression once",
Example: `
# Merge f2.yml into f1.yml (inplace)
yq eval-all --inplace 'select(fileIndex == 0) * select(fileIndex == 1)' f1.yml f2.yml
## the same command and expression using shortened names:
yq ea -i 'select(fi == 0) * select(fi == 1)' f1.yml f2.yml
# Merge all given files
yq ea '. as $item ireduce ({}; . * $item )' file1.yml file2.yml ...
# Pipe from STDIN
## use '-' as a filename to pipe from STDIN
cat file2.yml | yq ea '.a.b' file1.yml - file3.yml
`,
Long: `yq is a portable command-line YAML processor (https://github.com/mikefarah/yq/)
See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.
## Evaluate All ##
This command loads _all_ yaml documents of _all_ yaml files and runs expression once
Useful when you need to run an expression across several yaml documents or files (like merge).
Note that it consumes more memory than eval.
# 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,
}
return cmdEvalAll
@@ -59,25 +42,14 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
colorsEnabled = true
}
firstFileIndex := -1
if !nullInput && len(args) == 1 {
firstFileIndex = 0
} else if len(args) > 1 {
firstFileIndex = 1
}
if writeInplace && (firstFileIndex == -1) {
if writeInplace && len(args) < 2 {
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
}
if writeInplace && splitFileExp != "" {
return fmt.Errorf("Write inplace cannot be used with split file")
}
if writeInplace {
// only use colors if its forced
colorsEnabled = forceColor
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[firstFileIndex])
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
out, err = writeInPlaceHandler.CreateTempFile()
if err != nil {
return err
@@ -90,53 +62,26 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
if nullInput && len(args) > 1 {
return errors.New("Cannot pass files in when using null-input flag")
}
// backwards compatibilty
if outputToJSON {
outputFormat = "json"
}
format, err := yqlib.OutputFormatFromString(outputFormat)
if err != nil {
return err
}
printerWriter := configurePrinterWriter(format, out)
printer := yqlib.NewPrinter(printerWriter, format, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
if frontMatter != "" {
frontMatterHandler := yqlib.NewFrontMatterHandler(args[firstFileIndex])
err = frontMatterHandler.Split()
if err != nil {
return err
}
args[firstFileIndex] = frontMatterHandler.GetYamlFrontMatterFilename()
if frontMatter == "process" {
reader := frontMatterHandler.GetContentReader()
printer.SetAppendix(reader)
defer yqlib.SafelyCloseReader(reader)
}
defer frontMatterHandler.CleanUp()
}
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
switch len(args) {
case 0:
if pipingStdIn {
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer, leadingContentPreProcessing)
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
} else {
cmd.Println(cmd.UsageString())
return nil
}
case 1:
if nullInput {
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer, "")
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer)
} else {
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer, leadingContentPreProcessing)
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
}
default:
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer, leadingContentPreProcessing)
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
}
completedSuccessfully = err == nil
+17 -66
View File
@@ -13,42 +13,32 @@ func createEvaluateSequenceCommand() *cobra.Command {
var cmdEvalSequence = &cobra.Command{
Use: "eval [expression] [yaml_file1]...",
Aliases: []string{"e"},
Short: "Apply the expression to each document in each yaml file in sequence",
Short: "Apply expression to each document in each yaml file given in sequence",
Example: `
# Reads field under the given path for each file
yq e '.a.b' f1.yml f2.yml
# runs the expression against each file, in series
yq e '.a.b | length' f1.yml f2.yml
# Prints out the file
# prints out the file
yq e sample.yaml
# Pipe from STDIN
## use '-' as a filename to pipe from STDIN
cat file2.yml | yq e '.a.b' file1.yml - file3.yml
# Creates a new yaml document
## Note that editing an empty file does not work.
# prints a new yaml document
yq e -n '.a.b.c = "cat"'
# Update a file inplace
# updates file.yaml directly
yq e '.a.b = "cool"' -i file.yaml
`,
Long: `yq is a portable command-line YAML processor (https://github.com/mikefarah/yq/)
See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.
## Evaluate Sequence ##
This command iterates over each yaml document from each given file, applies the
expression and prints the result in sequence.`,
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 {
var prettyPrintExp = `(... | (select(tag != "!!str"), select(tag == "!!str") | select(test("(?i)^(y|yes|n|no|on|off)$") | not)) ) style=""`
if prettyPrint && expression == "" {
return prettyPrintExp
return `... style=""`
} else if prettyPrint {
return fmt.Sprintf("%v | %v", expression, prettyPrintExp)
return fmt.Sprintf("%v | ... style= \"\"", expression)
}
return expression
}
@@ -72,25 +62,14 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
colorsEnabled = true
}
firstFileIndex := -1
if !nullInput && len(args) == 1 {
firstFileIndex = 0
} else if len(args) > 1 {
firstFileIndex = 1
}
if writeInplace && (firstFileIndex == -1) {
if writeInplace && len(args) < 2 {
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
}
if writeInplace && splitFileExp != "" {
return fmt.Errorf("Write inplace cannot be used with split file")
}
if writeInplace {
// only use colors if its forced
colorsEnabled = forceColor
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[firstFileIndex])
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
out, err = writeInPlaceHandler.CreateTempFile()
if err != nil {
return err
@@ -100,19 +79,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
}
// backwards compatibilty
if outputToJSON {
outputFormat = "json"
}
format, err := yqlib.OutputFormatFromString(outputFormat)
if err != nil {
return err
}
printerWriter := configurePrinterWriter(format, out)
printer := yqlib.NewPrinter(printerWriter, format, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
streamEvaluator := yqlib.NewStreamEvaluator()
@@ -120,38 +87,22 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
return errors.New("Cannot pass files in when using null-input flag")
}
if frontMatter != "" {
frontMatterHandler := yqlib.NewFrontMatterHandler(args[firstFileIndex])
err = frontMatterHandler.Split()
if err != nil {
return err
}
args[firstFileIndex] = frontMatterHandler.GetYamlFrontMatterFilename()
if frontMatter == "process" {
reader := frontMatterHandler.GetContentReader()
printer.SetAppendix(reader)
defer yqlib.SafelyCloseReader(reader)
}
defer frontMatterHandler.CleanUp()
}
switch len(args) {
case 0:
if pipingStdIn {
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer, leadingContentPreProcessing)
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
} else {
cmd.Println(cmd.UsageString())
return nil
}
case 1:
if nullInput {
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer, "")
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer)
} else {
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer, leadingContentPreProcessing)
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
}
default:
err = streamEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer, leadingContentPreProcessing)
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
}
completedSuccessfully = err == nil
-28
View File
@@ -1,28 +0,0 @@
package cmd
import (
"io"
"github.com/mikefarah/yq/v4/pkg/yqlib"
)
func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) yqlib.PrinterWriter {
var printerWriter yqlib.PrinterWriter
if splitFileExp == "i" {
colorsEnabled = forceColor
printerWriter = yqlib.NewMultiPrinterWriter(nil, format)
} else if splitFileExp != "" {
colorsEnabled = forceColor
splitExp, err := yqlib.NewExpressionParser().ParseExpression(splitFileExp)
if err != nil {
return nil
}
printerWriter = yqlib.NewMultiPrinterWriter(splitExp, format)
} else {
printerWriter = yqlib.NewSinglePrinterWriter(out)
}
return printerWriter
}
+2 -15
View File
@@ -11,8 +11,7 @@ func New() *cobra.Command {
var rootCmd = &cobra.Command{
Use: "yq",
Short: "yq is a lightweight and portable command-line YAML processor.",
Long: `yq is a portable command-line YAML processor (https://github.com/mikefarah/yq/)
See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.`,
Long: `yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.`,
RunE: func(cmd *cobra.Command, args []string) error {
if version {
cmd.Print(GetVersionDisplay())
@@ -41,14 +40,7 @@ See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.`,
}
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "(deprecated) output as json. Set indent to 0 to print json in one line.")
err := rootCmd.PersistentFlags().MarkDeprecated("tojson", "please use -o=json instead")
if err != nil {
panic(err)
}
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "yaml", "[yaml|y|json|j|props|p] output format type.")
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json. Set indent to 0 to print json in one line.")
rootCmd.PersistentFlags().BoolVarP(&nullInput, "null-input", "n", false, "Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.")
rootCmd.PersistentFlags().BoolVarP(&noDocSeparators, "no-doc", "N", false, "Don't print document separators (---)")
@@ -61,11 +53,6 @@ See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.`,
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors")
rootCmd.PersistentFlags().StringVarP(&frontMatter, "front-matter", "f", "", "(extract|process) first input as yaml front-matter. Extract will pull out the yaml content, process will run the expression against the yaml content, leaving the remaining data intact")
rootCmd.PersistentFlags().BoolVarP(&leadingContentPreProcessing, "header-preprocess", "", true, "Slurp any header comments and separators before processing expression. This is a workaround for go-yaml to persist header content properly.")
rootCmd.PersistentFlags().StringVarP(&splitFileExp, "split-exp", "s", "", "print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter.")
rootCmd.AddCommand(
createEvaluateSequenceCommand(),
createEvaluateAllCommand(),
+2 -2
View File
@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "4.13.5"
Version = "4.4.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
@@ -25,7 +25,7 @@ const ProductName = "yq"
// GetVersionDisplay composes the parts of the version in a way that's suitable
// for displaying to humans.
func GetVersionDisplay() string {
return fmt.Sprintf("yq (https://github.com/mikefarah/yq/) version %s\n", getHumanVersion())
return fmt.Sprintf("%s version %s\n", ProductName, getHumanVersion())
}
func getHumanVersion() string {
+1 -1
View File
@@ -3,7 +3,7 @@ package cmd
import "testing"
func TestGetVersionDisplay(t *testing.T) {
var expectedVersion = ProductName + " (https://github.com/mikefarah/yq/) version " + Version
var expectedVersion = ProductName + " version " + Version
if VersionPrerelease != "" {
expectedVersion = expectedVersion + "-" + VersionPrerelease
}
-25
View File
@@ -1,28 +1,3 @@
yq (4.13.0) focal; urgency=medium
* New `with` operator for making multiple changes to a given path
* New `contains` operator, works like the `jq` equivalent
* Subtract operator now supports subtracting elements from arrays!
* Fixed Swapping values using variables #934
* Github Action now properly supports multiline output #936, thanks @pjxiao
* Fixed missing closing bracket validation #932
* Fix processing of hex numbers #929
* Fixed alternative and union operator issues #930
* Can now convert yaml to properties properties format (`-o=props`), See [docs](https://mikefarah.gitbook.io/yq/v/v4.x/usage/properties) for more info.
* Fixed document header/footer comment handling when merging (https://github.com/mikefarah/yq/issues/919)
* pretty print yaml 1.1 compatibility (https://github.com/mikefarah/yq/issues/914)
-- Roberto Mier Escandon <rmescandon@gmail.com> Thu, 16 Sep 2021 20:58:30 +0200
yq (4.9.6) focal; urgency=medium
* Added darwin/arm64 build, thanks @alecthomas
* Incremented docker alpine base version, thanks @da6d6i7-bronga
* Bug fix: multine expression
* Bug fix: special character
-- Roberto Mier Escandon <rmescandon@gmail.com> Tue, 29 Jun 2021 21:32:14 +0200
yq (3.3.2) focal; urgency=medium
* Bug fix: existStatus bug (#459)
+2 -1
View File
@@ -3,7 +3,8 @@ Section: devel
Priority: optional
Maintainer: Roberto Mier Escandón <rmescandon@gmail.com>
Build-Depends: debhelper (>=10),
golang-1.15,
dh-golang (>=1.34),
golang-1.13,
rsync
Standards-Version: 4.1.4
Homepage: https://github.com/mikefarah/yq.git
+2 -4
View File
@@ -17,7 +17,7 @@
PROJECT := yq
OWNER := mikefarah
REPO := github.com
GOVERSION := 1.15
GOVERSION := 1.13
export DH_OPTIONS
export DH_GOPKG := ${REPO}/${OWNER}/${PROJECT}
@@ -34,7 +34,7 @@ BINDIR := /usr/bin
ASSETSDIR := /usr/share/${PROJECT}
%:
dh $@
dh $@ --builddirectory=${GOPATH} --buildsystem=golang
override_dh_auto_build:
mkdir -p ${SRCDIR}
@@ -45,9 +45,7 @@ override_dh_auto_build:
(cd ${SRCDIR} && go install -buildmode=pie ./...)
override_dh_auto_test:
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
(cd ${SRCDIR} && go test -v ./...)
endif
override_dh_auto_install:
cp ${GOBIN}/yq ${DESTDIR}/${BINDIR}
+4 -5
View File
@@ -1,5 +1,4 @@
Resources:
S3Bucket:
Type: AWS::CloudFormation::Stack
Properties:
BucketName: !Ref MyBucketNameA
a: simple # just the best
b: [1, 2]
c:
test: 1
+7 -3
View File
@@ -1,3 +1,7 @@
#b1
b: 2
#b2
a: other # better than the original
b: [3, 4]
c:
toast: leave
test: 1
tell: 1
tasty.taco: cool
+2 -1
View File
@@ -1 +1,2 @@
# comment
# a: apple
# b: cat
-6
View File
@@ -1,6 +0,0 @@
---
a: apple
b: bannana
---
hello there
apples: great
-2
View File
@@ -1,2 +0,0 @@
---
a: test
+1 -4
View File
@@ -1,8 +1,5 @@
FROM mikefarah/yq:4.13.5
FROM mikefarah/yq:4.4.0
COPY entrypoint.sh /entrypoint.sh
# this seems to be the default user in github actions
USER 1001
ENTRYPOINT ["/entrypoint.sh"]
+3 -8
View File
@@ -1,9 +1,4 @@
#!/bin/sh -l
set -e
echo "::debug::\$cmd: $1"
RESULT=$(eval "$1")
RESULT="${RESULT//'%'/'%25'}"
RESULT="${RESULT//$'\n'/'%0A'}"
RESULT="${RESULT//$'\r'/'%0D'}"
echo "::debug::\$RESULT: $RESULT"
echo ::set-output name=result::"$RESULT"
echo "$1"
eval $1
+7 -16
View File
@@ -1,25 +1,16 @@
module github.com/mikefarah/yq/v4
require (
github.com/elliotchance/orderedmap v1.4.0
github.com/elliotchance/orderedmap v1.3.0
github.com/fatih/color v1.10.0
github.com/goccy/go-yaml v1.8.9
github.com/jinzhu/copier v0.2.8
github.com/magiconair/properties v1.8.5
github.com/spf13/cobra v1.1.3
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-20210105210732-16f7687f5001 // indirect
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/timtadh/data-structures v0.5.3 // indirect
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
go 1.17
go 1.15
+19 -16
View File
@@ -36,8 +36,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A=
github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
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.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
@@ -52,8 +52,8 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM
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.9 h1:4AEXg2qx+/w29jXnXpMY6mTckmYu1TMoHteKuMf0HFg=
github.com/goccy/go-yaml v1.8.9/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
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=
@@ -101,8 +101,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/copier v0.2.8 h1:N8MbL5niMwE3P4dOwurJixz5rMkKfujmMRFmAanSzWE=
github.com/jinzhu/copier v0.2.8/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
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=
@@ -119,8 +119,6 @@ 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.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
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=
@@ -170,9 +168,10 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
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.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
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=
@@ -181,9 +180,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
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/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ=
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
@@ -257,9 +255,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
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-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
@@ -269,6 +268,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
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/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -281,6 +281,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-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 h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
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=
@@ -307,6 +308,7 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
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=
@@ -317,10 +319,11 @@ gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
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.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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=
+4 -27
View File
@@ -8,7 +8,7 @@ import (
// A yaml expression evaluator that runs the expression once against all files/nodes in memory.
type Evaluator interface {
EvaluateFiles(expression string, filenames []string, printer Printer, leadingContentPreProcessing bool) error
EvaluateFiles(expression string, filenames []string, printer Printer) error
// EvaluateNodes takes an expression and one or more yaml nodes, returning a list of matching candidate nodes
EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error)
@@ -39,28 +39,18 @@ func (e *allAtOnceEvaluator) EvaluateCandidateNodes(expression string, inputCand
if err != nil {
return nil, err
}
context, err := e.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputCandidates}, node)
if err != nil {
return nil, err
}
return context.MatchingNodes, nil
return e.treeNavigator.GetMatchingNodes(inputCandidates, node)
}
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer, leadingContentPreProcessing bool) error {
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
fileIndex := 0
firstFileLeadingContent := ""
var allDocuments *list.List = list.New()
for _, filename := range filenames {
reader, leadingContent, err := readStream(filename, fileIndex == 0 && leadingContentPreProcessing)
reader, err := readStream(filename)
if err != nil {
return err
}
if fileIndex == 0 {
firstFileLeadingContent = leadingContent
}
fileDocuments, err := readDocuments(reader, filename, fileIndex)
if err != nil {
return err
@@ -68,19 +58,6 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
allDocuments.PushBackList(fileDocuments)
fileIndex = fileIndex + 1
}
if allDocuments.Len() == 0 {
candidateNode := &CandidateNode{
Document: 0,
Filename: "",
Node: &yaml.Node{Kind: yaml.DocumentNode, HeadComment: firstFileLeadingContent, Content: []*yaml.Node{{Tag: "!!null", Kind: yaml.ScalarNode}}},
FileIndex: 0,
}
allDocuments.PushBack(candidateNode)
} else {
allDocuments.Front().Value.(*CandidateNode).Node.HeadComment = firstFileLeadingContent
}
matches, err := e.EvaluateCandidateNodes(expression, allDocuments)
if err != nil {
return err
+52 -32
View File
@@ -1,45 +1,30 @@
package yqlib
import (
"container/list"
"fmt"
"strconv"
"strings"
"github.com/jinzhu/copier"
yaml "gopkg.in/yaml.v3"
)
type CandidateNode struct {
Node *yaml.Node // the actual node
Parent *CandidateNode // parent node
Path []interface{} /// the path we took to get to this node
Document uint // the document index of this node
Node *yaml.Node // the actual node
Path []interface{} /// the path we took to get to this node
Document uint // the document index of this node
Filename string
FileIndex int
// when performing op against all nodes given, this will treat all the nodes as one
// (e.g. top level cross document merge). This property does not propegate to child nodes.
EvaluateTogether bool
IsMapKey bool
}
func (n *CandidateNode) GetKey() string {
keyPrefix := ""
if n.IsMapKey {
keyPrefix = "key-"
}
return fmt.Sprintf("%v%v - %v", keyPrefix, n.Document, n.Path)
}
func (n *CandidateNode) AsList() *list.List {
elMap := list.New()
elMap.PushBack(n)
return elMap
return fmt.Sprintf("%v - %v", n.Document, n.Path)
}
func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode {
return &CandidateNode{
Node: node,
Path: n.createChildPath(path),
Parent: n,
Document: n.Document,
Filename: n.Filename,
FileIndex: n.FileIndex,
@@ -75,10 +60,11 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
n.UpdateAttributesFrom(other)
n.Node.Content = other.Node.Content
n.Node.Value = other.Node.Value
n.Node.Alias = other.Node.Alias
n.Node.Anchor = other.Node.Anchor
}
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
log.Debug("UpdateAttributesFrom: n: %v other: %v", n.GetKey(), other.GetKey())
if n.Node.Kind != other.Node.Kind {
// clear out the contents when switching to a different type
// e.g. map to array
@@ -87,22 +73,56 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
}
n.Node.Kind = other.Node.Kind
n.Node.Tag = other.Node.Tag
n.Node.Alias = other.Node.Alias
n.Node.Anchor = other.Node.Anchor
// merge will pickup the style of the new thing
// when autocreating nodes
if n.Node.Style == 0 {
n.Node.Style = other.Node.Style
}
n.Node.FootComment = n.Node.FootComment + other.Node.FootComment
n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment
n.Node.LineComment = n.Node.LineComment + other.Node.LineComment
}
if other.Node.FootComment != "" {
n.Node.FootComment = other.Node.FootComment
}
if other.Node.HeadComment != "" {
n.Node.HeadComment = other.Node.HeadComment
}
if other.Node.LineComment != "" {
n.Node.LineComment = other.Node.LineComment
func (n *CandidateNode) PathStackToString() string {
return mergePathStackToString(n.Path)
}
func mergePathStackToString(pathStack []interface{}) string {
var sb strings.Builder
for index, path := range pathStack {
switch path.(type) {
case int, int64:
// if arrayMergeStrategy == AppendArrayMergeStrategy {
// sb.WriteString("[+]")
// } else {
sb.WriteString(fmt.Sprintf("[%v]", path))
// }
default:
s := fmt.Sprintf("%v", path)
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
hasDoubleQuotes := strings.Contains(s, "\"")
wrappingCharacterStart := "\""
wrappingCharacterEnd := "\""
if hasDoubleQuotes {
wrappingCharacterStart = "("
wrappingCharacterEnd = ")"
}
if hasSpecial || errParsingInt == nil {
sb.WriteString(wrappingCharacterStart)
}
sb.WriteString(s)
if hasSpecial || errParsingInt == nil {
sb.WriteString(wrappingCharacterEnd)
}
}
if index < len(pathStack)-1 {
sb.WriteString(".")
}
}
return sb.String()
}
-109
View File
@@ -1,109 +0,0 @@
package yqlib
import (
"container/list"
"fmt"
"github.com/jinzhu/copier"
logging "gopkg.in/op/go-logging.v1"
)
type Context struct {
MatchingNodes *list.List
Variables map[string]*list.List
DontAutoCreate bool
}
func (n *Context) SingleReadonlyChildContext(candidate *CandidateNode) Context {
list := list.New()
list.PushBack(candidate)
newContext := n.ChildContext(list)
newContext.DontAutoCreate = true
return newContext
}
func (n *Context) SingleChildContext(candidate *CandidateNode) Context {
list := list.New()
list.PushBack(candidate)
return n.ChildContext(list)
}
func (n *Context) GetVariable(name string) *list.List {
if n.Variables == nil {
return nil
}
return n.Variables[name]
}
func (n *Context) SetVariable(name string, value *list.List) {
if n.Variables == nil {
n.Variables = make(map[string]*list.List)
}
n.Variables[name] = value
}
func (n *Context) ChildContext(results *list.List) Context {
clone := Context{DontAutoCreate: n.DontAutoCreate}
clone.Variables = make(map[string]*list.List)
if len(n.Variables) > 0 {
err := copier.Copy(&clone.Variables, n.Variables)
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
}
clone.MatchingNodes = results
return clone
}
func (n *Context) ToString() string {
if !log.IsEnabledFor(logging.DEBUG) {
return ""
}
result := fmt.Sprintf("Context\nDontAutoCreate: %v\n", n.DontAutoCreate)
return result + NodesToString(n.MatchingNodes)
}
func (n *Context) DeepClone() Context {
clone := Context{}
err := copier.Copy(&clone, n)
// copier doesn't do lists properly for some reason
clone.MatchingNodes = list.New()
for el := n.MatchingNodes.Front(); el != nil; el = el.Next() {
clonedNode, err := el.Value.(*CandidateNode).Copy()
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
clone.MatchingNodes.PushBack(clonedNode)
}
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
return clone
}
func (n *Context) Clone() Context {
clone := Context{}
err := copier.Copy(&clone, n)
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
return clone
}
func (n *Context) ReadOnlyClone() Context {
clone := n.Clone()
clone.DontAutoCreate = true
return clone
}
func (n *Context) WritableClone() Context {
clone := n.Clone()
clone.DontAutoCreate = false
return clone
}
+11 -9
View File
@@ -3,14 +3,16 @@ package yqlib
import (
"fmt"
"container/list"
logging "gopkg.in/op/go-logging.v1"
)
type DataTreeNavigator interface {
// given the context and a expressionNode,
// this will process the against the given expressionNode and return
// a new context of matching candidates
GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error)
// given a list of CandidateEntities and a expressionNode,
// this will process the list against the given expressionNode and return
// a new list of matching candidates
GetMatchingNodes(matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error)
}
type dataTreeNavigator struct {
@@ -20,22 +22,22 @@ func NewDataTreeNavigator() DataTreeNavigator {
return &dataTreeNavigator{}
}
func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
if expressionNode == nil {
log.Debugf("getMatchingNodes - nothing to do")
return context, nil
return matchingNodes, nil
}
log.Debugf("Processing Op: %v", expressionNode.Operation.toString())
if log.IsEnabledFor(logging.DEBUG) {
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
log.Debug(NodeToString(el.Value.(*CandidateNode)))
}
}
log.Debug(">>")
handler := expressionNode.Operation.OperationType.Handler
if handler != nil {
return handler(d, context, expressionNode)
return handler(d, matchingNodes, expressionNode)
}
return Context{}, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
return nil, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
}
+19 -73
View File
@@ -101,7 +101,7 @@ will output
- hello
```
## Append to array
## Update array (append)
Given a sample.yml file of:
```yaml
a:
@@ -127,62 +127,6 @@ b:
- 4
```
## Append another array using +=
Given a sample.yml file of:
```yaml
a:
- 1
- 2
b:
- 3
- 4
```
then
```bash
yq eval '.a += .b' sample.yml
```
will output
```yaml
a:
- 1
- 2
- 3
- 4
b:
- 3
- 4
```
## Relative append
Given a sample.yml file of:
```yaml
a:
a1:
b:
- cat
a2:
b:
- dog
a3: {}
```
then
```bash
yq eval '.a[].b += ["mouse"]' sample.yml
```
will output
```yaml
a:
a1:
b:
- cat
- mouse
a2:
b:
- dog
- mouse
a3: {b: [mouse]}
```
## String concatenation
Given a sample.yml file of:
```yaml
@@ -199,6 +143,22 @@ a: catmeow
b: meow
```
## Relative string concatenation
Given a sample.yml file of:
```yaml
a: cat
b: meow
```
then
```bash
yq eval '.a += .b' sample.yml
```
will output
```yaml
a: catmeow
b: meow
```
## Number addition - float
If the lhs or rhs are floats then the expression will be calculated with floats.
@@ -235,31 +195,17 @@ a: 7
b: 4
```
## Increment numbers
## Increment number
Given a sample.yml file of:
```yaml
a: 3
b: 5
```
then
```bash
yq eval '.[] += 1' sample.yml
yq eval '.a += 1' sample.yml
```
will output
```yaml
a: 4
b: 6
```
## Add to null
Adding to null simply returns the rhs
Running
```bash
yq eval --null-input 'null + "cat"'
```
will output
```yaml
cat
```
+3 -140
View File
@@ -1,99 +1,8 @@
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
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.
## Merge one map
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<: *CENTER
r: 10
```
then
```bash
yq eval '.[4] | explode(.)' sample.yml
```
will output
```yaml
x: 1
y: 2
r: 10
```
## Merge multiple maps
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *CENTER
- *BIG
```
then
```bash
yq eval '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## Override
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *BIG
- *LEFT
- *SMALL
x: 1
```
then
```bash
yq eval '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## Get anchor
Given a sample.yml file of:
```yaml
@@ -169,22 +78,6 @@ b: &meow purr
a: *meow
```
## Set alias to blank does nothing
Given a sample.yml file of:
```yaml
b: &meow purr
a: cat
```
then
```bash
yq eval '.a alias = ""' sample.yml
```
will output
```yaml
b: &meow purr
a: cat
```
## Set alias relatively using assign-update
Given a sample.yml file of:
```yaml
@@ -290,42 +183,12 @@ bar:
c: bar_c
foobarList:
b: bar_b
thing: foo_thing
c: foobarList_c
a: foo_a
thing: bar_thing
c: foobarList_c
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
```
## Dereference and update a field
`Use explode with multiply to dereference an object
Given a sample.yml file of:
```yaml
item_value: &item_value
value: true
thingOne:
name: item_1
!!merge <<: *item_value
thingTwo:
name: item_2
!!merge <<: *item_value
```
then
```bash
yq eval '.thingOne |= explode(.) * {"value": false}' sample.yml
```
will output
```yaml
item_value: &item_value
value: true
thingOne:
name: item_1
value: false
thingTwo:
name: item_2
!!merge <<: *item_value
```
@@ -1,4 +1,10 @@
This operator is used to update node values. It can be used in either the:
### plain form: `=`
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
### 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
@@ -28,24 +34,6 @@ a:
g: foof
```
## Double elements in an array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq eval '.[] |= . * 2' sample.yml
```
will output
```yaml
- 2
- 4
- 6
```
## Update node from another file
Note this will also work when the second file is a scalar (string/number)
@@ -93,7 +81,7 @@ c: fieldC
```
then
```bash
yq eval '(.a, .c) = "potatoe"' sample.yml
yq eval '(.a, .c) |= "potatoe"' sample.yml
```
will output
```yaml
@@ -136,9 +124,7 @@ a:
b: frog
```
## Update deeply selected results
Note that the LHS is wrapped in brackets! This is to ensure we dont first filter out the yaml and then update the snippet.
## Update selected results
Given a sample.yml file of:
```yaml
a:
+3 -110
View File
@@ -1,14 +1,5 @@
The `or` and `and` operators take two parameters and return a boolean result.
`not` flips a boolean from true to false, or vice versa.
`any` will return `true` if there are any `true` values in a array sequence, and `all` will return true if _all_ elements in an array are true.
`any_c(condition)` and `all_c(condition)` are like `any` and `all` but they take a condition expression that is used against each element to determine if it's `true`. Note: in `jq` you can simply pass a condition to `any` or `all` and it simply works - `yq` isn't that clever..yet
These are most commonly used with the `select` operator to filter particular nodes.
## `or` example
The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes.
## OR example
Running
```bash
yq eval --null-input 'true or false'
@@ -18,7 +9,7 @@ will output
true
```
## `and` example
## AND example
Running
```bash
yq eval --null-input 'true and false'
@@ -50,104 +41,6 @@ will output
b: fly
```
## `any` returns true if any boolean in a given array is true
Given a sample.yml file of:
```yaml
- false
- true
```
then
```bash
yq eval 'any' sample.yml
```
will output
```yaml
true
```
## `any` returns false for an empty array
Given a sample.yml file of:
```yaml
[]
```
then
```bash
yq eval 'any' sample.yml
```
will output
```yaml
false
```
## `any_c` returns true if any element in the array is true for the given condition.
Given a sample.yml file of:
```yaml
a:
- rad
- awesome
b:
- meh
- whatever
```
then
```bash
yq eval '.[] |= any_c(. == "awesome")' sample.yml
```
will output
```yaml
a: true
b: false
```
## `all` returns true if all booleans in a given array are true
Given a sample.yml file of:
```yaml
- true
- true
```
then
```bash
yq eval 'all' sample.yml
```
will output
```yaml
true
```
## `all` returns true for an empty array
Given a sample.yml file of:
```yaml
[]
```
then
```bash
yq eval 'all' sample.yml
```
will output
```yaml
true
```
## `all_c` returns true if all elements in the array are true for the given condition.
Given a sample.yml file of:
```yaml
a:
- rad
- awesome
b:
- meh
- 12
```
then
```bash
yq eval '.[] |= all_c(tag == "!!str")' sample.yml
```
will output
```yaml
a: true
b: false
```
## Not true is false
Running
```bash
+1 -12
View File
@@ -1,15 +1,4 @@
Use these comment operators to set or retrieve comments.
Like the `=` and `|=` assign operators, the same syntax applies when updating comments:
### plain form: `=`
This will assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
### relative form: `|=`
Similar to the plain form, however the RHS evaluates against each matching LHS node! This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
## Set line comment
Given a sample.yml file of:
```yaml
@@ -88,7 +77,7 @@ a: cat
b: dog # leave this
```
## Remove (strip) all comments
## Remove all comments
Note the use of `...` to ensure key nodes are included.
Given a sample.yml file of:
-85
View File
@@ -1,85 +0,0 @@
## Array contains array
Array is equal or subset of
Given a sample.yml file of:
```yaml
- foobar
- foobaz
- blarp
```
then
```bash
yq eval 'contains(["baz", "bar"])' sample.yml
```
will output
```yaml
true
```
## Object included in array
Given a sample.yml file of:
```yaml
"foo": 12
"bar":
- 1
- 2
- "barp": 12
"blip": 13
```
then
```bash
yq eval 'contains({"bar": [{"barp": 12}]})' sample.yml
```
will output
```yaml
true
```
## Object not included in array
Given a sample.yml file of:
```yaml
"foo": 12
"bar":
- 1
- 2
- "barp": 12
"blip": 13
```
then
```bash
yq eval 'contains({"foo": 12, "bar": [{"barp": 15}]})' sample.yml
```
will output
```yaml
false
```
## String contains substring
Given a sample.yml file of:
```yaml
foobar
```
then
```bash
yq eval 'contains("bar")' sample.yml
```
will output
```yaml
true
```
## String equals string
Given a sample.yml file of:
```yaml
meow
```
then
```bash
yq eval 'contains("meow")' sample.yml
```
will output
```yaml
true
```
-177
View File
@@ -1,177 +0,0 @@
Encode operators will take the piped in object structure and encode it as a string in the desired format. The decode operators do the opposite, they take a formatted string and decode it into the relevant object structure.
Note that you can optionally pass an indent value to the encode functions (see below).
These operators are useful to process yaml documents that have stringified embeded yaml/json/props in them.
## Encode value as yaml string
Indent defaults to 2
Given a sample.yml file of:
```yaml
a:
cool:
bob: dylan
```
then
```bash
yq eval '.b = (.a | to_yaml)' sample.yml
```
will output
```yaml
a:
cool:
bob: dylan
b: |
cool:
bob: dylan
```
## Encode value as yaml string, with custom indentation
You can specify the indentation level as the first parameter.
Given a sample.yml file of:
```yaml
a:
cool:
bob: dylan
```
then
```bash
yq eval '.b = (.a | to_yaml(8))' sample.yml
```
will output
```yaml
a:
cool:
bob: dylan
b: |
cool:
bob: dylan
```
## Encode value as yaml string, using toyaml
Does the same thing as to_yaml, matching jq naming convention.
Given a sample.yml file of:
```yaml
a:
cool: thing
```
then
```bash
yq eval '.b = (.a | to_yaml)' sample.yml
```
will output
```yaml
a:
cool: thing
b: |
cool: thing
```
## Encode value as json string
Given a sample.yml file of:
```yaml
a:
cool: thing
```
then
```bash
yq eval '.b = (.a | to_json)' sample.yml
```
will output
```yaml
a:
cool: thing
b: |
{
"cool": "thing"
}
```
## Encode value as json string, on one line
Pass in a 0 indent to print json on a single line.
Given a sample.yml file of:
```yaml
a:
cool: thing
```
then
```bash
yq eval '.b = (.a | to_json(0))' sample.yml
```
will output
```yaml
a:
cool: thing
b: '{"cool":"thing"}'
```
## Encode value as props string
Given a sample.yml file of:
```yaml
a:
cool: thing
```
then
```bash
yq eval '.b = (.a | to_props)' sample.yml
```
will output
```yaml
a:
cool: thing
b: |
cool = thing
```
## Decode a yaml encoded string
Given a sample.yml file of:
```yaml
a: 'foo: bar'
```
then
```bash
yq eval '.b = (.a | from_yaml)' sample.yml
```
will output
```yaml
a: 'foo: bar'
b:
foo: bar
```
## Update a multiline encoded yaml string
Given a sample.yml file of:
```yaml
a: |
foo: bar
baz: dog
```
then
```bash
yq eval '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml
```
will output
```yaml
a: |
foo: cat
baz: dog
```
## Update a single line encoded yaml string
Given a sample.yml file of:
```yaml
a: 'foo: bar'
```
then
```bash
yq eval '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml
```
will output
```yaml
a: 'foo: cat'
```
View File
-118
View File
@@ -1,118 +0,0 @@
Similar to the same named functions in `jq` these functions convert to/from an object and an array of key-value pairs. This is most useful for performing operations on keys of maps.
## to_entries Map
Given a sample.yml file of:
```yaml
a: 1
b: 2
```
then
```bash
yq eval 'to_entries' sample.yml
```
will output
```yaml
- key: a
value: 1
- key: b
value: 2
```
## to_entries Array
Given a sample.yml file of:
```yaml
- a
- b
```
then
```bash
yq eval 'to_entries' sample.yml
```
will output
```yaml
- key: 0
value: a
- key: 1
value: b
```
## to_entries null
Given a sample.yml file of:
```yaml
null
```
then
```bash
yq eval 'to_entries' sample.yml
```
will output
```yaml
```
## from_entries map
Given a sample.yml file of:
```yaml
a: 1
b: 2
```
then
```bash
yq eval 'to_entries | from_entries' sample.yml
```
will output
```yaml
a: 1
b: 2
```
## from_entries with numeric key indexes
from_entries always creates a map, even for numeric keys
Given a sample.yml file of:
```yaml
- a
- b
```
then
```bash
yq eval 'to_entries | from_entries' sample.yml
```
will output
```yaml
0: a
1: b
```
## Use with_entries to update keys
Given a sample.yml file of:
```yaml
a: 1
b: 2
```
then
```bash
yq eval 'with_entries(.key |= "KEY_" + .)' sample.yml
```
will output
```yaml
KEY_a: 1
KEY_b: 2
```
## Use with_entries to filter the map
Given a sample.yml file of:
```yaml
a:
b: bird
c:
d: dog
```
then
```bash
yq eval 'with_entries(select(.value | has("b")))' sample.yml
```
will output
```yaml
a:
b: bird
```
-64
View File
@@ -29,24 +29,6 @@ true
false
```
## Don't match string
Given a sample.yml file of:
```yaml
- cat
- goat
- dog
```
then
```bash
yq eval '.[] | (. != "*at")' sample.yml
```
will output
```yaml
false
false
true
```
## Match number
Given a sample.yml file of:
```yaml
@@ -65,24 +47,6 @@ true
false
```
## Dont match number
Given a sample.yml file of:
```yaml
- 3
- 4
- 5
```
then
```bash
yq eval '.[] | (. != 4)' sample.yml
```
will output
```yaml
true
false
true
```
## Match nulls
Running
```bash
@@ -93,31 +57,3 @@ will output
true
```
## Non exisitant key doesn't equal a value
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq eval 'select(.b != "thing")' sample.yml
```
will output
```yaml
a: frog
```
## Two non existant keys are equal
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq eval 'select(.b == .c)' sample.yml
```
will output
```yaml
a: frog
```
-20
View File
@@ -35,26 +35,6 @@ will output
0
```
## Get file indices of multiple documents
Given a sample.yml file of:
```yaml
a: cat
```
And another sample another.yml file of:
```yaml
a: cat
```
then
```bash
yq eval-all 'fileIndex' sample.yml another.yml
```
will output
```yaml
0
---
1
```
## Get file index alias
Given a sample.yml file of:
```yaml
-70
View File
@@ -1,70 +0,0 @@
This recursively flattens arrays.
## Flatten
Recursively flattens all arrays
Given a sample.yml file of:
```yaml
- 1
- - 2
- - - 3
```
then
```bash
yq eval 'flatten' sample.yml
```
will output
```yaml
- 1
- 2
- 3
```
## Flatten with depth of one
Given a sample.yml file of:
```yaml
- 1
- - 2
- - - 3
```
then
```bash
yq eval 'flatten(1)' sample.yml
```
will output
```yaml
- 1
- 2
- - 3
```
## Flatten empty array
Given a sample.yml file of:
```yaml
- []
```
then
```bash
yq eval 'flatten' sample.yml
```
will output
```yaml
[]
```
## Flatten array of objects
Given a sample.yml file of:
```yaml
- foo: bar
- - foo: baz
```
then
```bash
yq eval 'flatten' sample.yml
```
will output
```yaml
- foo: bar
- foo: baz
```
-54
View File
@@ -1,54 +0,0 @@
This is used to group items in an array by an expression.
## Group by field
Given a sample.yml file of:
```yaml
- foo: 1
bar: 10
- foo: 3
bar: 100
- foo: 1
bar: 1
```
then
```bash
yq eval 'group_by(.foo)' sample.yml
```
will output
```yaml
- - foo: 1
bar: 10
- foo: 1
bar: 1
- - foo: 3
bar: 100
```
## Group by field, with nuls
Given a sample.yml file of:
```yaml
- cat: dog
- foo: 1
bar: 10
- foo: 3
bar: 100
- no: foo for you
- foo: 1
bar: 1
```
then
```bash
yq eval 'group_by(.foo)' sample.yml
```
will output
```yaml
- - cat: dog
- no: foo for you
- - foo: 1
bar: 10
- foo: 1
bar: 1
- - foo: 3
bar: 100
```
-23
View File
@@ -19,29 +19,6 @@ true
false
```
## Select, checking for existence of deep paths
Simply pipe in parent expressions into `has`
Given a sample.yml file of:
```yaml
- a:
b:
c: cat
- a:
b:
d: dog
```
then
```bash
yq eval '.[] | select(.a.b | has("c"))' sample.yml
```
will output
```yaml
a:
b:
c: cat
```
## Has array index
Given a sample.yml file of:
```yaml
-14
View File
@@ -16,20 +16,6 @@ will output
3
```
## null length
Given a sample.yml file of:
```yaml
a: null
```
then
```bash
yq eval '.a | length' sample.yml
```
will output
```yaml
0
```
## Map length
returns number of entries
@@ -1,34 +1,18 @@
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
## Objects and arrays - merging
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
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.
### Merge Flags
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
- `+` to append arrays
- `?` to only merge existing fields
- `d` to deeply merge arrays
### Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory.
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
```
## Multiply integers
Running
```bash
yq eval --null-input '3 * 4'
```
will output
```yaml
12
```
## Merge objects together, returning merged result only
Given a sample.yml file of:
```yaml
@@ -86,6 +70,7 @@ Given a sample.yml file of:
a: {things: great}
b:
also: "me"
```
then
```bash
@@ -205,72 +190,6 @@ thing:
- 4
```
## Merge, deeply merging arrays
Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.
Given a sample.yml file of:
```yaml
a:
- name: fred
age: 12
- name: bob
age: 32
b:
- name: fred
age: 34
```
then
```bash
yq eval '.a *d .b' sample.yml
```
will output
```yaml
- name: fred
age: 34
- name: bob
age: 32
```
## Merge arrays of objects together, matching on a key
It's a complex command, the trickyness comes from needing to have the right context in the expressions.
First we save the second array into a variable '$two' which lets us reference it later.
We then need to update the first array. We will use the relative update (|=) because we need to update relative to the current element of the array in the LHS in the RHS expression.
We set the current element of the first array as $cur. Now we multiply (merge) $cur with the matching entry in $two, by passing $two through a select filter.
Given a sample.yml file of:
```yaml
- a: apple
b: appleB
- a: kiwi
b: kiwiB
- a: banana
b: bananaB
```
And another sample another.yml file of:
```yaml
- a: banana
c: bananaC
- a: apple
b: appleB2
- a: dingo
c: dingoC
```
then
```bash
yq eval-all '(select(fi==1) | .[]) as $two | select(fi==0) | .[] |= (. as $cur | $cur * ($two | select(.a == $cur.a)))' sample.yml another.yml
```
will output
```yaml
- a: apple
b: appleB2
- a: kiwi
b: kiwiB
- a: banana
b: bananaB
c: bananaC
```
## Merge to prefix an element
Given a sample.yml file of:
```yaml
@@ -357,7 +276,7 @@ yq eval '.foobar * .foobarList' sample.yml
will output
```yaml
c: foobarList_c
!!merge <<:
<<:
- *foo
- *bar
thing: foobar_thing
-76
View File
@@ -1,76 +0,0 @@
Reduce is a powerful way to process a collection of data into a new form.
```
<exp> as $<name> ireduce (<init>; <block>)
```
e.g.
```
.[] as $item ireduce (0; . + $item)
```
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
## yq vs jq syntax
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.
## Sum numbers
Given a sample.yml file of:
```yaml
- 10
- 2
- 5
- 3
```
then
```bash
yq eval '.[] as $item ireduce (0; . + $item)' sample.yml
```
will output
```yaml
20
```
## Merge all yaml files together
Given a sample.yml file of:
```yaml
a: cat
```
And another sample another.yml file of:
```yaml
b: dog
```
then
```bash
yq eval-all '. as $item ireduce ({}; . * $item )' sample.yml another.yml
```
will output
```yaml
a: cat
b: dog
```
## Convert an array to an object
Given a sample.yml file of:
```yaml
- name: Cathy
has: apples
- name: Bob
has: bananas
```
then
```bash
yq eval '.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )' sample.yml
```
will output
```yaml
Cathy: apples
Bob: bananas
```
-243
View File
@@ -1,47 +1,5 @@
# String Operators
## RegEx
This uses golangs native regex functions under the hood - See https://github.com/google/re2/wiki/Syntax for the supported syntax.
# String blocks, bash and newlines
Bash is notorious for chomping on precious trailing newline characters, making it tricky to set strings with newlines properly. In particular, the `$( exp )` _will trim trailing newlines_.
For instance to get this yaml:
```
a: |
cat
```
Using `$( exp )` wont work, as it will trim the trailing new line.
```
m=$(echo "cat\n") yq e -n '.a = strenv(m)'
a: cat
```
However, using printf works:
```
printf -v m "cat\n" ; m="$m" yq e -n '.a = strenv(m)'
a: |
cat
```
As well as having multiline expressions:
```
m="cat
" yq e -n '.a = strenv(m)'
a: |
cat
```
Similarly, if you're trying to set the content from a file, and want a trailing new line:
```
IFS= read -rd '' output < <(cat my_file)
output=$output ./yq e '.data.values = strenv(output)' first.yml
```
## Join strings
Given a sample.yml file of:
```yaml
@@ -60,207 +18,6 @@ will output
cat; meow; 1; ; true
```
## Match string
Given a sample.yml file of:
```yaml
foo bar foo
```
then
```bash
yq eval 'match("foo")' sample.yml
```
will output
```yaml
string: foo
offset: 0
length: 3
captures: []
```
## Match string, case insensitive
Given a sample.yml file of:
```yaml
foo bar FOO
```
then
```bash
yq eval '[match("(?i)foo"; "g")]' sample.yml
```
will output
```yaml
- string: foo
offset: 0
length: 3
captures: []
- string: FOO
offset: 8
length: 3
captures: []
```
## Match with capture groups
Given a sample.yml file of:
```yaml
abc abc
```
then
```bash
yq eval '[match("(abc)+"; "g")]' sample.yml
```
will output
```yaml
- string: abc
offset: 0
length: 3
captures:
- string: abc
offset: 0
length: 3
- string: abc
offset: 4
length: 3
captures:
- string: abc
offset: 4
length: 3
```
## Match with named capture groups
Given a sample.yml file of:
```yaml
foo bar foo foo foo
```
then
```bash
yq eval '[match("foo (?P<bar123>bar)? foo"; "g")]' sample.yml
```
will output
```yaml
- string: foo bar foo
offset: 0
length: 11
captures:
- string: bar
offset: 4
length: 3
name: bar123
- string: foo foo
offset: 12
length: 8
captures:
- string: null
offset: -1
length: 0
name: bar123
```
## Capture named groups into a map
Given a sample.yml file of:
```yaml
xyzzy-14
```
then
```bash
yq eval 'capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")' sample.yml
```
will output
```yaml
a: xyzzy
n: "14"
```
## Match without global flag
Given a sample.yml file of:
```yaml
cat cat
```
then
```bash
yq eval 'match("cat")' sample.yml
```
will output
```yaml
string: cat
offset: 0
length: 3
captures: []
```
## Match with global flag
Given a sample.yml file of:
```yaml
cat cat
```
then
```bash
yq eval '[match("cat"; "g")]' sample.yml
```
will output
```yaml
- string: cat
offset: 0
length: 3
captures: []
- string: cat
offset: 4
length: 3
captures: []
```
## Test using regex
Like jq'q equivalant, this works like match but only returns true/false instead of full match details
Given a sample.yml file of:
```yaml
- cat
- dog
```
then
```bash
yq eval '.[] | test("at")' sample.yml
```
will output
```yaml
true
false
```
## Substitute / Replace string
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
Note the use of `|=` to run in context of the current string value.
Given a sample.yml file of:
```yaml
a: dogs are great
```
then
```bash
yq eval '.a |= sub("dogs", "cats")' sample.yml
```
will output
```yaml
a: cats are great
```
## Substitute / Replace string with regex
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
Note the use of `|=` to run in context of the current string value.
Given a sample.yml file of:
```yaml
a: cat
b: heat
```
then
```bash
yq eval '.[] |= sub("(a)", "${1}r")' sample.yml
```
will output
```yaml
a: cart
b: heart
```
## Split strings
Given a sample.yml file of:
```yaml
-36
View File
@@ -1,40 +1,4 @@
The style operator can be used to get or set the style of nodes (e.g. string style, yaml style)
## Update and set style of a particular node (simple)
Given a sample.yml file of:
```yaml
a:
b: thing
c: something
```
then
```bash
yq eval '.a.b = "new" | .a.b style="double"' sample.yml
```
will output
```yaml
a:
b: "new"
c: something
```
## Update and set style of a particular node using path variables
Given a sample.yml file of:
```yaml
a:
b: thing
c: something
```
then
```bash
yq eval 'with(.a.b ; . = "new" | . style="double")' sample.yml
```
will output
```yaml
a:
b: "new"
c: something
```
## Set tagged style
Given a sample.yml file of:
```yaml
-110
View File
@@ -1,110 +0,0 @@
## Array subtraction
Running
```bash
yq eval --null-input '[1,2] - [2,3]'
```
will output
```yaml
- 1
```
## Array subtraction with nested array
Running
```bash
yq eval --null-input '[[1], 1, 2] - [[1], 3]'
```
will output
```yaml
- 1
- 2
```
## Array subtraction with nested object
Note that order of the keys does not matter
Given a sample.yml file of:
```yaml
- a: b
c: d
- a: b
```
then
```bash
yq eval '. - [{"c": "d", "a": "b"}]' sample.yml
```
will output
```yaml
- a: b
```
## Number subtraction - float
If the lhs or rhs are floats then the expression will be calculated with floats.
Given a sample.yml file of:
```yaml
a: 3
b: 4.5
```
then
```bash
yq eval '.a = .a - .b' sample.yml
```
will output
```yaml
a: -1.5
b: 4.5
```
## Number subtraction - float
If the lhs or rhs are floats then the expression will be calculated with floats.
Given a sample.yml file of:
```yaml
a: 3
b: 4.5
```
then
```bash
yq eval '.a = .a - .b' sample.yml
```
will output
```yaml
a: -1.5
b: 4.5
```
## Number subtraction - int
If both the lhs and rhs are ints then the expression will be calculated with ints.
Given a sample.yml file of:
```yaml
a: 3
b: 4
```
then
```bash
yq eval '.a = .a - .b' sample.yml
```
will output
```yaml
a: -1
b: 4
```
## Decrement numbers
Given a sample.yml file of:
```yaml
a: 3
b: 5
```
then
```bash
yq eval '.[] -= 1' sample.yml
```
will output
```yaml
a: 2
b: 4
```
@@ -1,4 +1,4 @@
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structures.
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
## Simple map navigation
Given a sample.yml file of:
```yaml
@@ -32,23 +32,8 @@ b: apple
c: banana
```
## Optional Splat
Just like splat, but won't error if you run it against scalars
Given a sample.yml file of:
```yaml
cat
```
then
```bash
yq eval '.[]' sample.yml
```
will output
```yaml
```
## Special characters
Use quotes with brackets around path elements with special characters
Use quotes around path elements with special characters
Given a sample.yml file of:
```yaml
@@ -56,39 +41,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.["{}"]' sample.yml
```
will output
```yaml
frog
```
## Nested special characters
Given a sample.yml file of:
```yaml
a:
"key.withdots":
"another.key": apple
```
then
```bash
yq eval '.a["key.withdots"]["another.key"]' sample.yml
```
will output
```yaml
apple
```
## Keys with spaces
Use quotes with brackets around path elements with special characters
Given a sample.yml file of:
```yaml
"red rabbit": frog
```
then
```bash
yq eval '.["red rabbit"]' sample.yml
yq eval '."{}"' sample.yml
```
will output
```yaml
@@ -129,23 +82,6 @@ will output
null
```
## Optional identifier
Like jq, does not output an error when the yaml is not an array or object as expected
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq eval '.a?' sample.yml
```
will output
```yaml
```
## Wildcard matching
Given a sample.yml file of:
```yaml
@@ -227,20 +163,6 @@ will output
1
```
## Traversing nested arrays by index
Given a sample.yml file of:
```yaml
[[], [cat]]
```
then
```bash
yq eval '.[1][0]' sample.yml
```
will output
```yaml
cat
```
## Maps with numeric keys
Given a sample.yml file of:
```yaml
-82
View File
@@ -1,82 +0,0 @@
This is used to filter out duplicated items in an array.
## Unique array of scalars (string/numbers)
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
- 2
```
then
```bash
yq eval 'unique' sample.yml
```
will output
```yaml
- 1
- 2
- 3
```
## Unique nulls
Unique works on the node value, so it considers different representations of nulls to be different
Given a sample.yml file of:
```yaml
- ~
- null
- ~
- null
```
then
```bash
yq eval 'unique' sample.yml
```
will output
```yaml
- ~
- null
```
## Unique all nulls
Run against the node tag to unique all the nulls
Given a sample.yml file of:
```yaml
- ~
- null
- ~
- null
```
then
```bash
yq eval 'unique_by(tag)' sample.yml
```
will output
```yaml
- ~
```
## Unique array object fields
Given a sample.yml file of:
```yaml
- name: harry
pet: cat
- name: billy
pet: dog
- name: harry
pet: dog
```
then
```bash
yq eval 'unique_by(.name)' sample.yml
```
will output
```yaml
- name: harry
pet: cat
- name: billy
pet: dog
```
-96
View File
@@ -1,96 +0,0 @@
Like the `jq` equivalents, variables are sometimes required for the more complex expressions (or swapping values between fields).
Note that there is also an additional `ref` operator that holds a reference (instead of a copy) of the path, allowing you to make multiple changes to the same path.
## Single value variable
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval '.a as $foo | $foo' sample.yml
```
will output
```yaml
cat
```
## Multi value variable
Given a sample.yml file of:
```yaml
- cat
- dog
```
then
```bash
yq eval '.[] as $foo | $foo' sample.yml
```
will output
```yaml
cat
dog
```
## Using variables as a lookup
Example taken from [jq](https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...)
Given a sample.yml file of:
```yaml
"posts":
- "title": Frist psot
"author": anon
- "title": A well-written article
"author": person1
"realnames":
"anon": Anonymous Coward
"person1": Person McPherson
```
then
```bash
yq eval '.realnames as $names | .posts[] | {"title":.title, "author": $names[.author]}' sample.yml
```
will output
```yaml
title: Frist psot
author: Anonymous Coward
title: A well-written article
author: Person McPherson
```
## Using variables to swap values
Given a sample.yml file of:
```yaml
a: a_value
b: b_value
```
then
```bash
yq eval '.a as $x | .b as $y | .b = $x | .a = $y' sample.yml
```
will output
```yaml
a: b_value
b: a_value
```
## Use ref to reference a path repeatedly
Note: You may find the `with` operator more useful.
Given a sample.yml file of:
```yaml
a:
b: thing
c: something
```
then
```bash
yq eval '.a.b ref $x | $x = "new" | $x style="double"' sample.yml
```
will output
```yaml
a:
b: "new"
c: something
```
-62
View File
@@ -1,62 +0,0 @@
Use the `with` operator to conveniently make multiple updates to a deeply nested path, or to update array elements relatively to each other. The first argument expression sets the root context, and the second expression runs against that root context.
## Update and style
Given a sample.yml file of:
```yaml
a:
deeply:
nested: value
```
then
```bash
yq eval 'with(.a.deeply.nested; . = "newValue" | . style="single")' sample.yml
```
will output
```yaml
a:
deeply:
nested: 'newValue'
```
## Update multiple deeply nested properties
Given a sample.yml file of:
```yaml
a:
deeply:
nested: value
other: thing
```
then
```bash
yq eval 'with(.a.deeply; .nested = "newValue" | .other= "newThing")' sample.yml
```
will output
```yaml
a:
deeply:
nested: newValue
other: newThing
```
## Update array elements relatively
The second expression runs with each element of the array as it's contextual root. This allows you to make updates relative to the element.
Given a sample.yml file of:
```yaml
myArray:
- a: apple
- a: banana
```
then
```bash
yq eval 'with(.myArray[]; .b = .a + " yum")' sample.yml
```
will output
```yaml
myArray:
- a: apple
b: apple yum
- a: banana
b: banana yum
```
+6 -108
View File
@@ -1,114 +1,12 @@
In `yq` expressions are made up of operators and pipes. A context of nodes is passed through the expression and each operation takes the context as input and returns a new context as output. That output is piped in as input for the next operation in the expression. To begin with, the context is set to the first yaml document of the first yaml file (if processing in sequence using eval).
# Operators
In `yq` expressions are made up of operators. Operators have 0-2 arguments and run against the current 'matching' nodes in the expression tree.
Lets look at a couple of examples.
## Example with a simple operator
The `length` operator take no arguments, and will simply return the length of _each_ matching node. So if there were 2 nodes, one string and one array, length will update the 'matching' nodes context to be two new numeric scalar nodes representing the lengths of the orignal 'matching' nodes.
Given a document like:
The `=` operator takes two arguments, a `lhs` expression and `rhs` expression. It runs the 'matching' nodes context against the `lhs` expression to find the nodes to update, lets call it `lhsNodes`, and then runs the matching nodes against the `rhs` to find the new values, lets call that `rhsNodes`. It updates the `lhsNodes` values with the `rhsNodes` values and _returns the original matching nodes_. This is important, where length changed the matching nodes to be new nodes with the length values, `=` returns the original matching nodes, albeit with some of the nodes values updated. So `.a = 3` will still return the parent matching node, but with the matching child updated.
```yaml
- [a]
- "cat"
```
Please see the individual operator docs for more information and examples.
with an expression:
```
.[] | length
```
`yq` will initially set the context as single node of the entire yaml document, an array of two elements.
```yaml
- [a]
- "cat"
```
This gets piped into the splat operator `.[]` which will split out the context into a collection of two nodes `[a]` and `"cat"`. Note that this is _not_ a yaml array.
The `length` operator take no arguments, and will simply return the length of _each_ matching node in the context. So for the context of `[a]` and `"cat"`, it will return a new context of `1` and `3`.
This being the last operation in the expression, the results will be printed out:
```
1
3
```
# Example with an operator that takes arguments.
Given a document like:
```yaml
a: cat
b: dog
```
with an expression:
```
.a = .b
```
The `=` operator takes two arguments, a `lhs` expression, which in this case is `.a` and `rhs` expression which is `.b`.
It pipes the current, lets call it 'root' context through the `lhs` expression of `.a` to return the node
```yaml
cat
```
Note that this node holds not only its value 'cat', but comments and metadata too, including path and parent information.
The `=` operator then pipes the 'root' context through the `rhs` expression of `.b` to return the node
```yaml
dog
```
Both sides have now been evaluated, so now the operator copies across the value from the RHS to the value on the LHS, and it returns the now updated context:
```yaml
a: dog
b: dog
```
# Relative update (e.g. `|=`)
There is another form of the `=` operator which we call the relative form. It's very similar to `=` but with one key difference when evaluating the RHS expression.
In the plain form, we pass in the 'root' level context to the RHS expression. In relative form, we pass in _each result of the LHS_ to the RHS expression. Let's go through an example.
Given a document like:
```yaml
a: 1
b: thing
```
with an expression:
```
.a |= . + 1
```
Similar to the `=` operator, `|=` takes two operands, the LHS and RHS.
It pipes the current context (the whole document) through the LHS expression of `.a` to get the node value:
```
1
```
Now it pipes _that LHS context_ into the RHS expression `. + 1` (whereas in the `=` plain form it piped the original document context into the RHS) to yield:
```
2
```
The assignment operator then copies across the value from the RHS to the value on the LHS, and it returns the now updated 'root' context:
```yaml
a: 2
b: thing
```
@@ -1,4 +1,4 @@
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
+1 -9
View File
@@ -1,9 +1 @@
The `or` and `and` operators take two parameters and return a boolean result.
`not` flips a boolean from true to false, or vice versa.
`any` will return `true` if there are any `true` values in a array sequence, and `all` will return true if _all_ elements in an array are true.
`any_c(condition)` and `all_c(condition)` are like `any` and `all` but they take a condition expression that is used against each element to determine if it's `true`. Note: in `jq` you can simply pass a condition to `any` or `all` and it simply works - `yq` isn't that clever..yet
These are most commonly used with the `select` operator to filter particular nodes.
The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes.
+1 -11
View File
@@ -1,11 +1 @@
Use these comment operators to set or retrieve comments.
Like the `=` and `|=` assign operators, the same syntax applies when updating comments:
### plain form: `=`
This will assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
### relative form: `|=`
Similar to the plain form, however the RHS evaluates against each matching LHS node! This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
Use these comment operators to set or retrieve comments.
@@ -1,5 +0,0 @@
Encode operators will take the piped in object structure and encode it as a string in the desired format. The decode operators do the opposite, they take a formatted string and decode it into the relevant object structure.
Note that you can optionally pass an indent value to the encode functions (see below).
These operators are useful to process yaml documents that have stringified embeded yaml/json/props in them.
-1
View File
@@ -1 +0,0 @@
Similar to the same named functions in `jq` these functions convert to/from an object and an array of key-value pairs. This is most useful for performing operations on keys of maps.
-1
View File
@@ -1 +0,0 @@
This recursively flattens arrays.
-1
View File
@@ -1 +0,0 @@
This is used to group items in an array by an expression.
-61
View File
@@ -1,61 +0,0 @@
# NAME
*yq* is a portable command-line YAML processor
# SYNOPSIS
yq [eval/eval-all] [expression] files..
eval/e - Apply the expression to each document in each yaml file in sequence
eval-all/ea - Loads all yaml documents of all yaml files and runs expression once
# DESCRIPTION
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.
This documentation is also available at https://mikefarah.gitbook.io/yq/
# QUICK GUIDE
## Read a value:
```bash
yq e '.a.b[0].c' file.yaml
```
## Pipe from STDIN:
```bash
cat file.yaml | yq e '.a.b[0].c' -
```
## Update a yaml file, inplace
```bash
yq e -i '.a.b[0].c = "cool"' file.yaml
```
## Update using environment variables
```bash
NAME=mike yq e -i '.a.b[0].c = strenv(NAME)' file.yaml
```
## Merge multiple files
```
yq ea '. as $item ireduce ({}; . * $item )' path/to/*.yml
```
## Multiple updates to a yaml file
```bash
yq e -i '
.a.b[0].c = "cool" |
.x.y.z = "foobar" |
.person.name = strenv(NAME)
' file.yaml
```
See the [documentation](https://mikefarah.gitbook.io/yq/) for more.
# 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)
- Powershell has its own...opinions: https://mikefarah.gitbook.io/yq/usage/tips-and-tricks#quotes-in-windows-powershell
# BUGS / ISSUES / FEATURE REQUESTS
Please visit the GitHub page https://github.com/mikefarah/yq/.
-20
View File
@@ -1,20 +0,0 @@
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
## Objects and arrays - merging
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
### Merge Flags
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
- `+` to append arrays
- `?` to only merge existing fields
- `d` to deeply merge arrays
### Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
```
+14
View File
@@ -0,0 +1,14 @@
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
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
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
```
-21
View File
@@ -1,21 +0,0 @@
Reduce is a powerful way to process a collection of data into a new form.
```
<exp> as $<name> ireduce (<init>; <block>)
```
e.g.
```
.[] as $item ireduce (0; . + $item)
```
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
## yq vs jq syntax
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.
-43
View File
@@ -1,44 +1 @@
# String Operators
## RegEx
This uses golangs native regex functions under the hood - See https://github.com/google/re2/wiki/Syntax for the supported syntax.
# String blocks, bash and newlines
Bash is notorious for chomping on precious trailing newline characters, making it tricky to set strings with newlines properly. In particular, the `$( exp )` _will trim trailing newlines_.
For instance to get this yaml:
```
a: |
cat
```
Using `$( exp )` wont work, as it will trim the trailing new line.
```
m=$(echo "cat\n") yq e -n '.a = strenv(m)'
a: cat
```
However, using printf works:
```
printf -v m "cat\n" ; m="$m" yq e -n '.a = strenv(m)'
a: |
cat
```
As well as having multiline expressions:
```
m="cat
" yq e -n '.a = strenv(m)'
a: |
cat
```
Similarly, if you're trying to set the content from a file, and want a trailing new line:
```
IFS= read -rd '' output < <(cat my_file)
output=$output ./yq e '.data.values = strenv(output)' first.yml
```
@@ -1 +1 @@
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structures.
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
-1
View File
@@ -1 +0,0 @@
This is used to filter out duplicated items in an array.
@@ -1,3 +0,0 @@
Like the `jq` equivalents, variables are sometimes required for the more complex expressions (or swapping values between fields).
Note that there is also an additional `ref` operator that holds a reference (instead of a copy) of the path, allowing you to make multiple changes to the same path.
-1
View File
@@ -1 +0,0 @@
Use the `with` operator to conveniently make multiple updates to a deeply nested path, or to update array elements relatively to each other. The first argument expression sets the root context, and the second expression runs against that root context.
+3 -9
View File
@@ -76,8 +76,6 @@ func mapKeysToStrings(node *yaml.Node) {
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
var encoder = json.NewEncoder(destination)
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
var indentString = ""
for index := 0; index < indent; index++ {
@@ -155,15 +153,11 @@ func (o *orderedMap) UnmarshalJSON(data []byte) error {
}
func (o orderedMap) MarshalJSON() ([]byte, error) {
if o.kv == nil {
return json.Marshal(o.altVal)
}
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
if o.kv == nil {
if err := enc.Encode(o.altVal); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
buf.WriteByte('{')
for idx, el := range o.kv {
if err := enc.Encode(el.K); err != nil {
-80
View File
@@ -1,80 +0,0 @@
package yqlib
import (
"fmt"
"io"
"strings"
"github.com/magiconair/properties"
yaml "gopkg.in/yaml.v3"
)
type propertiesEncoder struct {
destination io.Writer
}
func NewPropertiesEncoder(destination io.Writer) Encoder {
return &propertiesEncoder{destination}
}
func (pe *propertiesEncoder) Encode(node *yaml.Node) error {
mapKeysToStrings(node)
p := properties.NewProperties()
err := pe.doEncode(p, node, "")
if err != nil {
return err
}
_, err = p.WriteComment(pe.destination, "#", properties.UTF8)
return err
}
func (pe *propertiesEncoder) doEncode(p *properties.Properties, node *yaml.Node, path string) error {
p.SetComment(path,
strings.Replace(node.HeadComment, "#", "", 1)+
strings.Replace(node.LineComment, "#", "", 1))
switch node.Kind {
case yaml.ScalarNode:
_, _, err := p.Set(path, node.Value)
return err
case yaml.DocumentNode:
return pe.doEncode(p, node.Content[0], path)
case yaml.SequenceNode:
return pe.encodeArray(p, node.Content, path)
case yaml.MappingNode:
return pe.encodeMap(p, node.Content, path)
case yaml.AliasNode:
return pe.doEncode(p, node.Alias, path)
default:
return fmt.Errorf("Unsupported node %v", node.Tag)
}
}
func (pe *propertiesEncoder) appendPath(path string, key interface{}) string {
if path == "" {
return fmt.Sprintf("%v", key)
}
return fmt.Sprintf("%v.%v", path, key)
}
func (pe *propertiesEncoder) encodeArray(p *properties.Properties, kids []*yaml.Node, path string) error {
for index, child := range kids {
err := pe.doEncode(p, child, pe.appendPath(path, index))
if err != nil {
return err
}
}
return nil
}
func (pe *propertiesEncoder) encodeMap(p *properties.Properties, kids []*yaml.Node, path string) error {
for index := 0; index < len(kids); index = index + 2 {
key := kids[index]
value := kids[index+1]
err := pe.doEncode(p, value, pe.appendPath(path, key.Value))
if err != nil {
return err
}
}
return nil
}
-78
View File
@@ -1,78 +0,0 @@
package yqlib
import (
"bufio"
"bytes"
"strings"
"testing"
"github.com/mikefarah/yq/v4/test"
)
func yamlToProps(sampleYaml string) string {
var output bytes.Buffer
writer := bufio.NewWriter(&output)
var propsEncoder = NewPropertiesEncoder(writer)
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
if err != nil {
panic(err)
}
node := inputs.Front().Value.(*CandidateNode).Node
err = propsEncoder.Encode(node)
if err != nil {
panic(err)
}
writer.Flush()
return strings.TrimSuffix(output.String(), "\n")
}
func TestPropertiesEncoderSimple(t *testing.T) {
var sampleYaml = `a: 'bob cool'`
var expectedJson = `a = bob cool`
var actualProps = yamlToProps(sampleYaml)
test.AssertResult(t, expectedJson, actualProps)
}
func TestPropertiesEncoderSimpleWithComments(t *testing.T) {
var sampleYaml = `a: 'bob cool' # line`
var expectedJson = `# line
a = bob cool`
var actualProps = yamlToProps(sampleYaml)
test.AssertResult(t, expectedJson, actualProps)
}
func TestPropertiesEncoderDeep(t *testing.T) {
var sampleYaml = `a:
b: "bob cool"
`
var expectedJson = `a.b = bob cool`
var actualProps = yamlToProps(sampleYaml)
test.AssertResult(t, expectedJson, actualProps)
}
func TestPropertiesEncoderDeepWithComments(t *testing.T) {
var sampleYaml = `a: # a thing
b: "bob cool" # b thing
`
var expectedJson = `# b thing
a.b = bob cool`
var actualProps = yamlToProps(sampleYaml)
test.AssertResult(t, expectedJson, actualProps)
}
func TestPropertiesEncoderArray(t *testing.T) {
var sampleYaml = `a:
b: [{c: dog}, {c: cat}]
`
var expectedJson = `a.b.0.c = dog
a.b.1.c = cat`
var actualProps = yamlToProps(sampleYaml)
test.AssertResult(t, expectedJson, actualProps)
}
+21 -30
View File
@@ -9,11 +9,29 @@ import (
"github.com/mikefarah/yq/v4/test"
)
func yamlToJson(sampleYaml string, indent int) string {
var sampleYaml = `zabbix: winner
apple: great
banana:
- {cobra: kai, angus: bob}
`
var expectedJson = `{
"zabbix": "winner",
"apple": "great",
"banana": [
{
"cobra": "kai",
"angus": "bob"
}
]
}
`
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
var output bytes.Buffer
writer := bufio.NewWriter(&output)
var jsonEncoder = NewJsonEncoder(writer, indent)
var jsonEncoder = NewJsonEncoder(writer, 2)
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
if err != nil {
panic(err)
@@ -24,33 +42,6 @@ func yamlToJson(sampleYaml string, indent int) string {
panic(err)
}
writer.Flush()
test.AssertResult(t, expectedJson, output.String())
return strings.TrimSuffix(output.String(), "\n")
}
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
var sampleYaml = `zabbix: winner
apple: great
banana:
- {cobra: kai, angus: bob}
`
var expectedJson = `{
"zabbix": "winner",
"apple": "great",
"banana": [
{
"cobra": "kai",
"angus": "bob"
}
]
}`
var actualJson = yamlToJson(sampleYaml, 2)
test.AssertResult(t, expectedJson, actualJson)
}
func TestJsonEncoderDoesNotEscapeHTMLChars(t *testing.T) {
var sampleYaml = `build: "( ./lint && ./format && ./compile ) < src.code"`
var expectedJson = `{"build":"( ./lint && ./format && ./compile ) < src.code"}`
var actualJson = yamlToJson(sampleYaml, 0)
test.AssertResult(t, expectedJson, actualJson)
}
+7 -6
View File
@@ -5,6 +5,9 @@ import (
"strings"
)
var myPathTokeniser = newExpressionTokeniser()
var myPathPostfixer = newExpressionPostFixer()
type ExpressionNode struct {
Operation *Operation
Lhs *ExpressionNode
@@ -16,21 +19,19 @@ type ExpressionParser interface {
}
type expressionParserImpl struct {
pathTokeniser expressionTokeniser
pathPostFixer expressionPostFixer
}
func NewExpressionParser() ExpressionParser {
return &expressionParserImpl{newExpressionTokeniser(), newExpressionPostFixer()}
return &expressionParserImpl{}
}
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
tokens, err := p.pathTokeniser.Tokenise(expression)
tokens, err := myPathTokeniser.Tokenise(expression)
if err != nil {
return nil, err
}
var Operations []*Operation
Operations, err = p.pathPostFixer.ConvertToPostfix(tokens)
Operations, err = myPathPostfixer.ConvertToPostfix(tokens)
if err != nil {
return nil, err
}
@@ -69,7 +70,7 @@ func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*
stack = append(stack, &newNode)
}
if len(stack) != 1 {
return nil, fmt.Errorf("Bad expression, please check expression syntax")
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
}
+8 -37
View File
@@ -6,66 +6,37 @@ import (
"github.com/mikefarah/yq/v4/test"
)
func TestParserNoMatchingCloseCollect(t *testing.T) {
_, err := NewExpressionParser().ParseExpression("[1,2")
test.AssertResultComplex(t, "Bad expression, could not find matching `]`", err.Error())
}
func TestParserNoMatchingCloseObjectInCollect(t *testing.T) {
_, err := NewExpressionParser().ParseExpression(`[{"b": "c"]`)
test.AssertResultComplex(t, "Bad expression, could not find matching `}`", err.Error())
}
func TestParserNoMatchingCloseInCollect(t *testing.T) {
_, err := NewExpressionParser().ParseExpression(`[(.a]`)
test.AssertResultComplex(t, "Bad expression, could not find matching `)`", err.Error())
}
func TestParserNoMatchingCloseCollectObject(t *testing.T) {
_, err := NewExpressionParser().ParseExpression(`{"a": "b"`)
test.AssertResultComplex(t, "Bad expression, could not find matching `}`", err.Error())
}
func TestParserNoMatchingCloseCollectInCollectObject(t *testing.T) {
_, err := NewExpressionParser().ParseExpression(`{"b": [1}`)
test.AssertResultComplex(t, "Bad expression, could not find matching `]`", err.Error())
}
func TestParserNoMatchingCloseBracketInCollectObject(t *testing.T) {
_, err := NewExpressionParser().ParseExpression(`{"b": (1}`)
test.AssertResultComplex(t, "Bad expression, could not find matching `)`", err.Error())
}
func TestParserNoArgsForTwoArgOp(t *testing.T) {
func TestPathTreeNoArgsForTwoArgOp(t *testing.T) {
_, err := NewExpressionParser().ParseExpression("=")
test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error())
}
func TestParserOneLhsArgsForTwoArgOp(t *testing.T) {
func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) {
_, err := NewExpressionParser().ParseExpression(".a =")
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
}
func TestParserOneRhsArgsForTwoArgOp(t *testing.T) {
func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) {
_, err := NewExpressionParser().ParseExpression("= .a")
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
}
func TestParserTwoArgsForTwoArgOp(t *testing.T) {
func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) {
_, err := NewExpressionParser().ParseExpression(".a = .b")
test.AssertResultComplex(t, nil, err)
}
func TestParserNoArgsForOneArgOp(t *testing.T) {
func TestPathTreeNoArgsForOneArgOp(t *testing.T) {
_, err := NewExpressionParser().ParseExpression("explode")
test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error())
}
func TestParserOneArgForOneArgOp(t *testing.T) {
func TestPathTreeOneArgForOneArgOp(t *testing.T) {
_, err := NewExpressionParser().ParseExpression("explode(.)")
test.AssertResultComplex(t, nil, err)
}
func TestParserExtraArgs(t *testing.T) {
func TestPathTreeExtraArgs(t *testing.T) {
_, err := NewExpressionParser().ParseExpression("sortKeys(.) explode(.)")
test.AssertResultComplex(t, "Bad expression, please check expression syntax", err.Error())
test.AssertResultComplex(t, "expected end of expression but found 'explode', please check expression syntax", err.Error())
}
+14 -54
View File
@@ -2,7 +2,6 @@ package yqlib
import (
"errors"
"fmt"
logging "gopkg.in/op/go-logging.v1"
)
@@ -21,33 +20,20 @@ func newExpressionPostFixer() expressionPostFixer {
func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) {
var newOp *token
opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1]
log.Debugf("popped %v from opstack to results", newOp.toString(true))
return opStack, append(result, newOp.Operation)
}
func validateNoOpenTokens(token *token) error {
if token.TokenType == openCollect {
return fmt.Errorf(("Bad expression, could not find matching `]`"))
} else if token.TokenType == openCollectObject {
return fmt.Errorf(("Bad expression, could not find matching `}`"))
} else if token.TokenType == openBracket {
return fmt.Errorf(("Bad expression, could not find matching `)`"))
}
return nil
}
func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) {
var result []*Operation
// surround the whole thing with brackets
var opStack = []*token{{TokenType: openBracket}}
// surround the whole thing with quotes
var opStack = []*token{&token{TokenType: openBracket}}
var tokens = append(infixTokens, &token{TokenType: closeBracket})
for _, currentToken := range tokens {
log.Debugf("postfix processing currentToken %v", currentToken.toString(true))
log.Debugf("postfix processing currentToken %v, %v", currentToken.toString(), currentToken.Operation)
switch currentToken.TokenType {
case openBracket, openCollect, openCollectObject:
opStack = append(opStack, currentToken)
log.Debugf("put %v onto the opstack", currentToken.toString(true))
case closeCollect, closeCollectObject:
var opener tokenType = openCollect
var collectOperator *operationType = collectOpType
@@ -55,48 +41,25 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
opener = openCollectObject
collectOperator = collectObjectOpType
}
itemsInMiddle := false
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener {
missingClosingTokenErr := validateNoOpenTokens(opStack[len(opStack)-1])
if missingClosingTokenErr != nil {
return nil, missingClosingTokenErr
}
opStack, result = popOpToResult(opStack, result)
itemsInMiddle = true
}
if !itemsInMiddle {
// must be an empty collection, add the empty object as a LHS parameter
result = append(result, &Operation{OperationType: emptyOpType})
}
if len(opStack) == 0 {
return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket")
}
// now we should have [ as the last element on the opStack, get rid of it
// now we should have [] as the last element on the opStack, get rid of it
opStack = opStack[0 : len(opStack)-1]
log.Debugf("deleteing open bracket from opstack")
//and append a collect to the result
// hack - see if there's the optional traverse flag
// on the close op - move it to the collect op.
// allows for .["cat"]?
prefs := traversePreferences{}
closeTokenMatch := string(currentToken.Match.Bytes)
if closeTokenMatch[len(closeTokenMatch)-1:] == "?" {
prefs.OptionalTraverse = true
}
result = append(result, &Operation{OperationType: collectOperator, Preferences: prefs})
log.Debugf("put collect onto the result")
result = append(result, &Operation{OperationType: shortPipeOpType})
log.Debugf("put shortpipe onto the result")
//traverseArrayCollect is a sneaky op that needs to be included too
//when closing a []
if len(opStack) > 0 && opStack[len(opStack)-1].Operation != nil && opStack[len(opStack)-1].Operation.OperationType == traverseArrayOpType {
opStack, result = popOpToResult(opStack, result)
}
//and append a collect to the opStack
opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: shortPipeOpType}})
opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: collectOperator}})
case closeBracket:
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket {
missingClosingTokenErr := validateNoOpenTokens(opStack[len(opStack)-1])
if missingClosingTokenErr != nil {
return nil, missingClosingTokenErr
}
opStack, result = popOpToResult(opStack, result)
}
if len(opStack) == 0 {
@@ -110,17 +73,14 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
// pop off higher precedent operators onto the result
for len(opStack) > 0 &&
opStack[len(opStack)-1].TokenType == operationToken &&
opStack[len(opStack)-1].Operation.OperationType.Precedence > currentPrecedence {
opStack[len(opStack)-1].Operation.OperationType.Precedence >= currentPrecedence {
opStack, result = popOpToResult(opStack, result)
}
// add this operator to the opStack
opStack = append(opStack, currentToken)
log.Debugf("put %v onto the opstack", currentToken.toString(true))
}
}
log.Debugf("opstackLen: %v", len(opStack))
if log.IsEnabledFor(logging.DEBUG) {
log.Debugf("PostFix Result:")
for _, currentToken := range result {
+28 -136
View File
@@ -7,158 +7,45 @@ import (
"github.com/mikefarah/yq/v4/test"
)
var variableWithNewLine = `"cat
"`
var pathTests = []struct {
path string
expectedTokens []interface{}
expectedPostFix []interface{}
}{
{
`.["cat"].["dog"]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "cat (string)", "]", "SHORT_PIPE", "SELF", "TRAVERSE_ARRAY", "[", "dog (string)", "]"),
append(make([]interface{}, 0), "SELF", "cat (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "dog (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.["cat"]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "cat (string)", "]"),
append(make([]interface{}, 0), "SELF", "cat (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
"with(.a;.=3)",
append(make([]interface{}, 0), "WITH", "(", "a", "BLOCK", "SELF", "ASSIGN", "3 (int64)", ")"),
append(make([]interface{}, 0), "a", "SELF", "3 (int64)", "ASSIGN", "BLOCK", "WITH"),
},
{
"0x12",
append(make([]interface{}, 0), "18 (int64)"),
append(make([]interface{}, 0), "18 (int64)"),
},
{
"0X12",
append(make([]interface{}, 0), "18 (int64)"),
append(make([]interface{}, 0), "18 (int64)"),
},
{
".a\n",
append(make([]interface{}, 0), "a"),
append(make([]interface{}, 0), "a"),
},
{
variableWithNewLine,
append(make([]interface{}, 0), "cat\n (string)"),
append(make([]interface{}, 0), "cat\n (string)"),
},
{
`.[0]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "SELF", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.[0][1]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "0 (int64)", "]", "TRAVERSE_ARRAY", "[", "1 (int64)", "]"),
append(make([]interface{}, 0), "SELF", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "1 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`"\""`,
append(make([]interface{}, 0), "\" (string)"),
append(make([]interface{}, 0), "\" (string)"),
},
{
`[]|join(".")`,
append(make([]interface{}, 0), "[", "EMPTY", "]", "PIPE", "JOIN", "(", ". (string)", ")"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", ". (string)", "JOIN", "PIPE"),
},
{
`{"cool": .b or .c}`,
append(make([]interface{}, 0), "{", "cool (string)", "CREATE_MAP", "b", "OR", "c", "}"),
append(make([]interface{}, 0), "cool (string)", "b", "c", "OR", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`{"cool": []|join(".")}`,
append(make([]interface{}, 0), "{", "cool (string)", "CREATE_MAP", "[", "EMPTY", "]", "PIPE", "JOIN", "(", ". (string)", ")", "}"),
append(make([]interface{}, 0), "cool (string)", "EMPTY", "COLLECT", "SHORT_PIPE", ". (string)", "JOIN", "PIPE", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`.a as $item ireduce (0; . + $item)`, // note - add code to shuffle reduce to this position for postfix
append(make([]interface{}, 0), "a", "ASSIGN_VARIABLE", "GET_VARIABLE", "REDUCE", "(", "0 (int64)", "BLOCK", "SELF", "ADD", "GET_VARIABLE", ")"),
append(make([]interface{}, 0), "a", "GET_VARIABLE", "ASSIGN_VARIABLE", "0 (int64)", "SELF", "GET_VARIABLE", "ADD", "BLOCK", "REDUCE"),
},
{
`.a | .b | .c`,
append(make([]interface{}, 0), "a", "PIPE", "b", "PIPE", "c"),
append(make([]interface{}, 0), "a", "b", "c", "PIPE", "PIPE"),
},
{
`[]`,
append(make([]interface{}, 0), "[", "EMPTY", "]"),
append(make([]interface{}, 0), "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
},
{
`{}`,
append(make([]interface{}, 0), "{", "EMPTY", "}"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`[{}]`,
append(make([]interface{}, 0), "[", "{", "EMPTY", "}", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE", "COLLECT", "SHORT_PIPE"),
},
{
`.realnames as $names | $names["anon"]`,
append(make([]interface{}, 0), "realnames", "ASSIGN_VARIABLE", "GET_VARIABLE", "PIPE", "GET_VARIABLE", "TRAVERSE_ARRAY", "[", "anon (string)", "]"),
append(make([]interface{}, 0), "realnames", "GET_VARIABLE", "ASSIGN_VARIABLE", "GET_VARIABLE", "anon (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "PIPE"),
},
{
`.b[.a]`,
append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"),
append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.b[.a]?`,
append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"),
append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.[]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.a[]`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.a[]?`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
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", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
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", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.a[0]?`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
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", "SELF", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "SELF", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
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", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "c"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "c", "SHORT_PIPE"),
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]`,
@@ -182,18 +69,18 @@ var pathTests = []struct {
},
{
`.a | .[].b == "apple"`,
append(make([]interface{}, 0), "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
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", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
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), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
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]`,
@@ -226,9 +113,9 @@ var pathTests = []struct {
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", "SHORT_PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "}"),
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "f", "g", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
`{.a: .c, .b.[]: .f.g.[]}`,
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)`,
@@ -280,6 +167,11 @@ var pathTests = []struct {
append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"),
append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"),
},
{
`{}`,
append(make([]interface{}, 0), "{", "}"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"),
},
}
var tokeniser = newExpressionTokeniser()
@@ -293,7 +185,7 @@ func TestPathParsing(t *testing.T) {
}
var tokenValues []interface{}
for _, token := range tokens {
tokenValues = append(tokenValues, token.toString(false))
tokenValues = append(tokenValues, token.toString())
}
test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, fmt.Sprintf("tokenise: %v", tt.path))
+28 -171
View File
@@ -2,7 +2,6 @@ package yqlib
import (
"fmt"
"regexp"
"strconv"
"strings"
@@ -30,17 +29,14 @@ const (
type token struct {
TokenType tokenType
Operation *Operation
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
Match *machines.Match // match that created this token
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
}
func (t *token) toString(detail bool) string {
func (t *token) toString() string {
if t.TokenType == operationToken {
if detail {
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
}
log.Debug("toString, its an op")
return t.Operation.toString()
} else if t.TokenType == openBracket {
return "("
@@ -65,19 +61,12 @@ func (t *token) toString(detail bool) string {
func pathToken(wrapped bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
prefs := traversePreferences{}
if value[len(value)-1:] == "?" {
prefs.OptionalTraverse = true
value = value[:len(value)-1]
}
value = value[1:]
if wrapped {
value = unwrap(value)
}
log.Debug("PathToken %v", value)
op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value, Preferences: prefs}
op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value, Preferences: traversePreferences{}}
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
}
}
@@ -109,10 +98,6 @@ func multiplyWithPrefs() lex.Action {
if strings.Contains(options, "?") {
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
}
if strings.Contains(options, "d") {
prefs.DeepMergeArrays = true
}
prefs.TraversePrefs.DontFollowAlias = true
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil
}
@@ -131,44 +116,6 @@ func opTokenWithPrefs(op *operationType, assignOpType *operationType, preference
}
}
func extractNumberParamter(value string) (int, error) {
parameterParser := regexp.MustCompile(`.*\(([0-9]+)\)`)
matches := parameterParser.FindStringSubmatch(value)
var indent, errParsingInt = strconv.ParseInt(matches[1], 10, 32) // nolint
if errParsingInt != nil {
return 0, errParsingInt
}
return int(indent), nil
}
func flattenWithDepth() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
var depth, errParsingInt = extractNumberParamter(value)
if errParsingInt != nil {
return nil, errParsingInt
}
prefs := flattenPreferences{depth: depth}
op := &Operation{OperationType: flattenOpType, Value: flattenOpType.Type, StringValue: value, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil
}
}
func encodeWithIndent(outputFormat PrinterOutputFormat) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
var indent, errParsingInt = extractNumberParamter(value)
if errParsingInt != nil {
return nil, errParsingInt
}
prefs := encoderPreferences{format: outputFormat, indent: indent}
op := &Operation{OperationType: encodeOpType, Value: encodeOpType.Type, StringValue: value, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil
}
}
func assignAllCommentsOp(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignAllCommentsOp %v", string(m.Bytes))
@@ -186,7 +133,7 @@ func assignAllCommentsOp(updateAssign bool) lex.Action {
func literalToken(pType tokenType, checkForPost bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
return &token{TokenType: pType, CheckForPostTraverse: checkForPost, Match: m}, nil
return &token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil
}
}
@@ -206,20 +153,6 @@ func numberValue() lex.Action {
}
}
func hexValue() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var originalString = string(m.Bytes)
var numberString = originalString[2:]
log.Debugf("numberString: %v", numberString)
var number, errParsingInt = strconv.ParseInt(numberString, 16, 64) // nolint
if errParsingInt != nil {
return nil, errParsingInt
}
return &token{TokenType: operationToken, Operation: createValueOperation(number, originalString)}, nil
}
}
func floatValue() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var numberString = string(m.Bytes)
@@ -243,24 +176,10 @@ func stringValue(wrapped bool) lex.Action {
if wrapped {
value = unwrap(value)
}
value = strings.ReplaceAll(value, "\\\"", "\"")
return &token{TokenType: operationToken, Operation: createValueOperation(value, value)}, nil
}
}
func getVariableOpToken() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
value = value[1:]
getVarOperation := createValueOperation(value, value)
getVarOperation.OperationType = getVariableOpType
return &token{TokenType: operationToken, Operation: getVarOperation, CheckForPostTraverse: true}, nil
}
}
func envOp(strenv bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
@@ -311,42 +230,13 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`,`), opToken(unionOpType))
lexer.Add([]byte(`:\s*`), opToken(createMapOpType))
lexer.Add([]byte(`length`), opToken(lengthOpType))
lexer.Add([]byte(`flatten\([0-9]+\)`), flattenWithDepth())
lexer.Add([]byte(`flatten`), opTokenWithPrefs(flattenOpType, nil, flattenPreferences{depth: -1}))
lexer.Add([]byte(`toyaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat))
lexer.Add([]byte(`to_yaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat))
lexer.Add([]byte(`tojson\([0-9]+\)`), encodeWithIndent(JsonOutputFormat))
lexer.Add([]byte(`to_json\([0-9]+\)`), encodeWithIndent(JsonOutputFormat))
lexer.Add([]byte(`toyaml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: YamlOutputFormat, indent: 2}))
lexer.Add([]byte(`tojson`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: JsonOutputFormat, indent: 2}))
lexer.Add([]byte(`toprops`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: PropsOutputFormat, indent: 2}))
lexer.Add([]byte(`to_yaml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: YamlOutputFormat, indent: 2}))
lexer.Add([]byte(`to_json`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: JsonOutputFormat, indent: 2}))
lexer.Add([]byte(`to_props`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: PropsOutputFormat, indent: 2}))
lexer.Add([]byte(`fromyaml`), opToken(decodeOpType))
lexer.Add([]byte(`fromjson`), opToken(decodeOpType))
lexer.Add([]byte(`from_yaml`), opToken(decodeOpType))
lexer.Add([]byte(`from_json`), opToken(decodeOpType))
lexer.Add([]byte(`sortKeys`), opToken(sortKeysOpType))
lexer.Add([]byte(`select`), opToken(selectOpType))
lexer.Add([]byte(`has`), opToken(hasOpType))
lexer.Add([]byte(`unique`), opToken(uniqueOpType))
lexer.Add([]byte(`unique_by`), opToken(uniqueByOpType))
lexer.Add([]byte(`group_by`), opToken(groupByOpType))
lexer.Add([]byte(`explode`), opToken(explodeOpType))
lexer.Add([]byte(`or`), opToken(orOpType))
lexer.Add([]byte(`and`), opToken(andOpType))
lexer.Add([]byte(`not`), opToken(notOpType))
lexer.Add([]byte(`ireduce`), opToken(reduceOpType))
lexer.Add([]byte(`;`), opToken(blockOpType))
lexer.Add([]byte(`\/\/`), opToken(alternativeOpType))
lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType))
@@ -354,17 +244,6 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType))
lexer.Add([]byte(`join`), opToken(joinStringOpType))
lexer.Add([]byte(`sub`), opToken(subStringOpType))
lexer.Add([]byte(`match`), opToken(matchOpType))
lexer.Add([]byte(`capture`), opToken(captureOpType))
lexer.Add([]byte(`test`), opToken(testOpType))
lexer.Add([]byte(`any`), opToken(anyOpType))
lexer.Add([]byte(`any_c`), opToken(anyConditionOpType))
lexer.Add([]byte(`all`), opToken(allOpType))
lexer.Add([]byte(`all_c`), opToken(allConditionOpType))
lexer.Add([]byte(`contains`), opToken(containsOpType))
lexer.Add([]byte(`split`), opToken(splitStringOpType))
lexer.Add([]byte(`keys`), opToken(keysOpType))
@@ -377,11 +256,6 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`fileIndex`), opToken(getFileIndexOpType))
lexer.Add([]byte(`fi`), opToken(getFileIndexOpType))
lexer.Add([]byte(`path`), opToken(getPathOpType))
lexer.Add([]byte(`to_entries`), opToken(toEntriesOpType))
lexer.Add([]byte(`from_entries`), opToken(fromEntriesOpType))
lexer.Add([]byte(`with_entries`), opToken(withEntriesOpType))
lexer.Add([]byte(`with`), opToken(withOpType))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{LineComment: true}))
@@ -395,7 +269,6 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`collect`), opToken(collectOpType))
lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType))
lexer.Add([]byte(`\s*!=\s*`), opToken(notEqualsOpType))
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
lexer.Add([]byte(`del`), opToken(deleteChildOpType))
@@ -404,13 +277,12 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
lexer.Add([]byte(`\."[^ "]+"\??`), pathToken(true))
lexer.Add([]byte(`\.[^ ;\}\{\:\[\],\|\.\[\(\)=\n]+\??`), pathToken(false))
lexer.Add([]byte(`\."[^ "]+"`), pathToken(true))
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false))
lexer.Add([]byte(`\.`), selfToken())
lexer.Add([]byte(`\|`), opToken(pipeOpType))
lexer.Add([]byte(`0[xX][0-9A-Fa-f]+`), hexValue())
lexer.Add([]byte(`-?\d+(\.\d+)`), floatValue())
lexer.Add([]byte(`-?[1-9](\.\d+)?[Ee][-+]?\d+`), floatValue())
lexer.Add([]byte(`-?\d+`), numberValue())
@@ -421,24 +293,19 @@ 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(`strenv\([^\)]+\)`), envOp(true))
lexer.Add([]byte(`env\([^\)]+\)`), envOp(false))
lexer.Add([]byte(`\[`), literalToken(openCollect, false))
lexer.Add([]byte(`\]\??`), literalToken(closeCollect, true))
lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
lexer.Add([]byte(`\*[\+|\?d]*`), multiplyWithPrefs())
lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs())
lexer.Add([]byte(`\+`), opToken(addOpType))
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
lexer.Add([]byte(`\-`), opToken(subtractOpType))
lexer.Add([]byte(`\-=`), opToken(subtractAssignOpType))
lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken())
lexer.Add([]byte(`as`), opTokenWithPrefs(assignVariableOpType, nil, assignVarPreferences{}))
lexer.Add([]byte(`ref`), opTokenWithPrefs(assignVariableOpType, nil, assignVarPreferences{IsReference: true}))
err := lexer.CompileNFA()
err := lexer.Compile()
if err != nil {
return nil, err
}
@@ -472,7 +339,7 @@ func (p *expressionTokeniserImpl) Tokenise(expression string) ([]*token, error)
if tok != nil {
currentToken := tok.(*token)
log.Debugf("Tokenising %v", currentToken.toString(true))
log.Debugf("Tokenising %v", currentToken.toString())
tokens = append(tokens, currentToken)
}
if err != nil {
@@ -498,19 +365,11 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
skipNextToken = false
currentToken := tokens[index]
log.Debug("processing %v", currentToken.toString(true))
if currentToken.TokenType == traverseArrayCollect {
// `.[exp]`` works by creating a traversal array of [self, exp] and piping that into the traverse array operator
//need to put a traverse array then a collect currentToken
// do this by adding traverse then converting currentToken to collect
log.Debug(" adding self")
op := &Operation{OperationType: selfReferenceOpType, StringValue: "SELF"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
log.Debug(" adding traverse array")
op = &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
op := &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
currentToken = &token{TokenType: openCollect}
@@ -520,36 +379,34 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
if index != len(tokens)-1 && currentToken.AssignOperation != nil &&
tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == assignOpType {
log.Debug(" its an update assign")
currentToken.Operation = currentToken.AssignOperation
currentToken.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
skipNextToken = true
}
log.Debug(" adding token to the fixed list")
postProcessedTokens = append(postProcessedTokens, currentToken)
if index != len(tokens)-1 &&
((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) ||
(currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) {
log.Debug(" adding empty")
op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
((tokens[index+1].TokenType == operationToken && (tokens[index+1].Operation.OperationType == traversePathOpType)) ||
(tokens[index+1].TokenType == traverseArrayCollect)) {
log.Debug(" adding pipe because the next thing is traverse")
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE", StringValue: "."}
tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == traversePathOpType {
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == openCollect {
log.Debug(" adding traverArray because next is opencollect")
op := &Operation{OperationType: traverseArrayOpType}
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
op = &Operation{OperationType: traverseArrayOpType}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == traverseArrayCollect {
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
return postProcessedTokens, skipNextToken
}
+1 -39
View File
@@ -2,7 +2,6 @@ package yqlib
import (
"io"
"io/ioutil"
"os"
)
@@ -24,20 +23,9 @@ func safelyRenameFile(from string, to string) {
}
}
func tryRemoveFile(filename string) {
log.Debug("Removing temp file: %v", filename)
removeErr := os.Remove(filename)
if removeErr != nil {
log.Errorf("Failed to remove temp file: %v", filename)
}
}
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
func copyFileContents(src, dst string) (err error) {
// ignore CWE-22 gosec issue - that's more targetted for http based apps that run in a public directory,
// and ensuring that it's not possible to give a path to a file outside thar directory.
in, err := os.Open(src) // #nosec
in, err := os.Open(src) // nolint gosec
if err != nil {
return err
}
@@ -53,13 +41,6 @@ func copyFileContents(src, dst string) (err error) {
return out.Sync()
}
func SafelyCloseReader(reader io.Reader) {
switch reader := reader.(type) {
case *os.File:
safelyCloseFile(reader)
}
}
func safelyCloseFile(file *os.File) {
err := file.Close()
if err != nil {
@@ -67,22 +48,3 @@ func safelyCloseFile(file *os.File) {
log.Error(err.Error())
}
}
func createTempFile() (*os.File, error) {
_, err := os.Stat(os.TempDir())
if os.IsNotExist(err) {
err = os.Mkdir(os.TempDir(), 0700)
if err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
file, err := ioutil.TempFile("", "temp")
if err != nil {
return nil, err
}
return file, err
}
-93
View File
@@ -1,93 +0,0 @@
package yqlib
import (
"bufio"
"io"
"os"
)
type frontMatterHandler interface {
Split() error
GetYamlFrontMatterFilename() string
GetContentReader() io.Reader
CleanUp()
}
type frontMatterHandlerImpl struct {
originalFilename string
yamlFrontMatterFilename string
contentReader io.Reader
}
func NewFrontMatterHandler(originalFilename string) frontMatterHandler {
return &frontMatterHandlerImpl{originalFilename, "", nil}
}
func (f *frontMatterHandlerImpl) GetYamlFrontMatterFilename() string {
return f.yamlFrontMatterFilename
}
func (f *frontMatterHandlerImpl) GetContentReader() io.Reader {
return f.contentReader
}
func (f *frontMatterHandlerImpl) CleanUp() {
tryRemoveFile(f.yamlFrontMatterFilename)
}
// Splits the given file by yaml front matter
// yaml content will be saved to first temporary file
// remaining content will be saved to second temporary file
func (f *frontMatterHandlerImpl) Split() error {
var reader *bufio.Reader
var err error
if f.originalFilename == "-" {
reader = bufio.NewReader(os.Stdin)
} else {
file, err := os.Open(f.originalFilename) // #nosec
if err != nil {
return err
}
reader = bufio.NewReader(file)
}
f.contentReader = reader
yamlTempFile, err := createTempFile()
if err != nil {
return err
}
f.yamlFrontMatterFilename = yamlTempFile.Name()
log.Debug("yamlTempFile: %v", yamlTempFile.Name())
lineCount := 0
for {
peekBytes, err := reader.Peek(3)
if err == io.EOF {
// we've finished reading the yaml content..I guess
break
} else if err != nil {
return err
}
if lineCount > 0 && string(peekBytes) == "---" {
// we've finished reading the yaml content..
break
}
line, errReading := reader.ReadString('\n')
lineCount = lineCount + 1
if errReading != nil && errReading != io.EOF {
return errReading
}
_, errWriting := yamlTempFile.Write([]byte(line))
if errWriting != nil {
return errWriting
}
}
safelyCloseFile(yamlTempFile)
return nil
}
-142
View File
@@ -1,142 +0,0 @@
package yqlib
import (
"io/ioutil"
"testing"
"github.com/mikefarah/yq/v4/test"
)
func createTestFile(content string) string {
tempFile, err := createTempFile()
if err != nil {
panic(err)
}
_, err = tempFile.Write([]byte(content))
if err != nil {
panic(err)
}
safelyCloseFile(tempFile)
return tempFile.Name()
}
func readFile(filename string) string {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
return string(bytes)
}
func TestFrontMatterSplitWithLeadingSep(t *testing.T) {
file := createTestFile(`---
a: apple
b: banana
---
not a
yaml: doc
`)
expectedYamlFm := `---
a: apple
b: banana
`
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := ioutil.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveFile(file)
fmHandler.CleanUp()
}
func TestFrontMatterSplitWithNoLeadingSep(t *testing.T) {
file := createTestFile(`a: apple
b: banana
---
not a
yaml: doc
`)
expectedYamlFm := `a: apple
b: banana
`
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := ioutil.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveFile(file)
fmHandler.CleanUp()
}
func TestFrontMatterSplitWithArray(t *testing.T) {
file := createTestFile(`[1,2,3]
---
not a
yaml: doc
`)
expectedYamlFm := "[1,2,3]\n"
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := ioutil.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveFile(file)
fmHandler.CleanUp()
}
+14 -125
View File
@@ -6,8 +6,6 @@ import (
"bytes"
"container/list"
"fmt"
"strconv"
"strings"
logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
@@ -22,11 +20,11 @@ type operationType struct {
Handler operatorHandler
}
// operators TODO:
// - mergeEmpty (sets only if the document is empty, do I do that now?)
var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator}
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 35, Handler: reduceOperator}
var blockOpType = &operationType{Type: "BLOCK", Precedence: 10, NumArgs: 2, Handler: emptyOperator}
var unionOpType = &operationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: unionOperator}
@@ -34,11 +32,9 @@ var pipeOpType = &operationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handle
var assignOpType = &operationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: assignUpdateOperator}
var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator}
var subtractAssignOpType = &operationType{Type: "SUBTRACT_ASSIGN", NumArgs: 2, Precedence: 40, Handler: subtractAssignOperator}
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator}
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
@@ -46,36 +42,16 @@ var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precede
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
var subtractOpType = &operationType{Type: "SUBTRACT", NumArgs: 2, Precedence: 42, Handler: subtractOperator}
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator}
//createmap needs to be above union, as we use union to build the components of the objects
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 15, Handler: createMapOperator}
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator}
var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: pipeOperator}
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
var encodeOpType = &operationType{Type: "ENCODE", NumArgs: 0, Precedence: 50, Handler: encodeOperator}
var decodeOpType = &operationType{Type: "DECODE", NumArgs: 0, Precedence: 50, Handler: decodeOperator}
var anyOpType = &operationType{Type: "ANY", NumArgs: 0, Precedence: 50, Handler: anyOperator}
var allOpType = &operationType{Type: "ALL", NumArgs: 0, Precedence: 50, Handler: allOperator}
var containsOpType = &operationType{Type: "CONTAINS", NumArgs: 1, Precedence: 50, Handler: containsOperator}
var anyConditionOpType = &operationType{Type: "ANY_CONDITION", NumArgs: 1, Precedence: 50, Handler: anyOperator}
var allConditionOpType = &operationType{Type: "ALL_CONDITION", NumArgs: 1, Precedence: 50, Handler: allOperator}
var toEntriesOpType = &operationType{Type: "TO_ENTRIES", NumArgs: 0, Precedence: 50, Handler: toEntriesOperator}
var fromEntriesOpType = &operationType{Type: "FROM_ENTRIES", NumArgs: 0, Precedence: 50, Handler: fromEntriesOperator}
var withEntriesOpType = &operationType{Type: "WITH_ENTRIES", NumArgs: 1, Precedence: 50, Handler: withEntriesOperator}
var withOpType = &operationType{Type: "WITH", NumArgs: 1, Precedence: 50, Handler: withOperator}
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator}
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
@@ -89,33 +65,26 @@ var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50,
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
var subStringOpType = &operationType{Type: "SUBSTR", NumArgs: 1, Precedence: 50, Handler: substituteStringOperator}
var matchOpType = &operationType{Type: "MATCH", NumArgs: 1, Precedence: 50, Handler: matchOperator}
var captureOpType = &operationType{Type: "CAPTURE", NumArgs: 1, Precedence: 50, Handler: captureOperator}
var testOpType = &operationType{Type: "TEST", NumArgs: 1, Precedence: 50, Handler: testOperator}
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 55, Handler: traversePathOperator}
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: traverseArrayOperator}
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator}
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator}
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator}
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: selfOperator}
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
var emptyOpType = &operationType{Type: "EMPTY", NumArgs: 50, Handler: emptyOperator}
var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}
var selectOpType = &operationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: selectOperator}
var hasOpType = &operationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: hasOperator}
var uniqueOpType = &operationType{Type: "UNIQUE", NumArgs: 0, Precedence: 50, Handler: unique}
var uniqueByOpType = &operationType{Type: "UNIQUE_BY", NumArgs: 1, Precedence: 50, Handler: uniqueBy}
var groupByOpType = &operationType{Type: "GROUP_BY", NumArgs: 1, Precedence: 50, Handler: groupBy}
var flattenOpType = &operationType{Type: "FLATTEN_BY", NumArgs: 0, Precedence: 50, Handler: flattenOp}
var deleteChildOpType = &operationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: deleteChildOperator}
var deleteImmediateChildOpType = &operationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: deleteImmediateChildOperator}
type Operation struct {
OperationType *operationType
@@ -126,77 +95,8 @@ type Operation struct {
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs
}
func recurseNodeArrayEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
if len(lhs.Content) != len(rhs.Content) {
return false
}
for index := 0; index < len(lhs.Content); index = index + 1 {
if !recursiveNodeEqual(lhs.Content[index], rhs.Content[index]) {
return false
}
}
return true
}
func findInArray(array *yaml.Node, item *yaml.Node) int {
for index := 0; index < len(array.Content); index = index + 1 {
if recursiveNodeEqual(array.Content[index], item) {
return index
}
}
return -1
}
func recurseNodeObjectEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
if len(lhs.Content) != len(rhs.Content) {
return false
}
for index := 0; index < len(lhs.Content); index = index + 2 {
key := lhs.Content[index]
value := lhs.Content[index+1]
indexInRhs := findInArray(rhs, key)
if indexInRhs == -1 || !recursiveNodeEqual(value, rhs.Content[indexInRhs+1]) {
return false
}
}
return true
}
func recursiveNodeEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
if lhs.Kind != rhs.Kind || lhs.Tag != rhs.Tag {
return false
} else if lhs.Tag == "!!null" {
return true
} else if lhs.Kind == yaml.ScalarNode {
return lhs.Value == rhs.Value
} else if lhs.Kind == yaml.SequenceNode {
return recurseNodeArrayEqual(lhs, rhs)
} else if lhs.Kind == yaml.MappingNode {
return recurseNodeObjectEqual(lhs, rhs)
}
return false
}
// yaml numbers can be hex encoded...
func parseInt(numberString string) (string, int64, error) {
if strings.HasPrefix(numberString, "0x") ||
strings.HasPrefix(numberString, "0X") {
num, err := strconv.ParseInt(numberString[2:], 16, 64) // nolint
return "0x%X", num, err
}
num, err := strconv.ParseInt(numberString, 10, 64) // nolint
return "%v", num, err
}
func createScalarNode(value interface{}, stringValue string) *yaml.Node {
var node = &yaml.Node{Kind: yaml.ScalarNode}
func createValueOperation(value interface{}, stringValue string) *Operation {
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
node.Value = stringValue
switch value.(type) {
@@ -211,25 +111,17 @@ func createScalarNode(value interface{}, stringValue string) *yaml.Node {
case nil:
node.Tag = "!!null"
}
return node
}
func createValueOperation(value interface{}, stringValue string) *Operation {
var node *yaml.Node = createScalarNode(value, stringValue)
return &Operation{
OperationType: valueOpType,
Value: value,
StringValue: stringValue,
CandidateNode: &CandidateNode{Node: node},
CandidateNode: &CandidateNode{Node: &node},
}
}
// debugging purposes only
func (p *Operation) toString() string {
if p == nil {
return "OP IS NIL"
}
if p.OperationType == traversePathOpType {
return fmt.Sprintf("%v", p.Value)
} else if p.OperationType == selfReferenceOpType {
@@ -247,7 +139,7 @@ func NodesToString(collection *list.List) string {
return ""
}
result := fmt.Sprintf("%v results\n", collection.Len())
result := ""
for el := collection.Front(); el != nil; el = el.Next() {
result = result + "\n" + NodeToString(el.Value.(*CandidateNode))
}
@@ -268,10 +160,7 @@ func NodeToString(node *CandidateNode) string {
if errorEncoding != nil {
log.Error("Error debugging node, %v", errorEncoding.Error())
}
errorClosingEncoder := encoder.Close()
if errorClosingEncoder != nil {
log.Error("Error closing encoder: ", errorClosingEncoder.Error())
}
encoder.Close()
tag := value.Tag
if value.Kind == yaml.DocumentNode {
tag = "doc"
+17 -41
View File
@@ -9,50 +9,26 @@ func matchKey(name string, pattern string) (matched bool) {
log.Debug("wild!")
return true
}
return deepMatch(name, pattern)
return deepMatch([]rune(name), []rune(pattern))
}
// deepMatch reports whether the name matches the pattern in linear time.
// Source https://research.swtch.com/glob
func deepMatch(name, pattern string) bool {
px := 0
nx := 0
nextPx := 0
nextNx := 0
for px < len(pattern) || nx < len(name) {
if px < len(pattern) {
c := pattern[px]
switch c {
default: // ordinary character
if nx < len(name) && name[nx] == c {
px++
nx++
continue
}
case '?': // single-character wildcard
if nx < len(name) {
px++
nx++
continue
}
case '*': // zero-or-more-character wildcard
// Try to match at nx.
// If that doesn't work out,
// restart at nx+1 next.
nextPx = px
nextNx = nx + 1
px++
continue
func deepMatch(str, pattern []rune) bool {
for len(pattern) > 0 {
switch pattern[0] {
default:
if len(str) == 0 || str[0] != pattern[0] {
return false
}
case '?':
if len(str) == 0 {
return false
}
case '*':
return deepMatch(str, pattern[1:]) ||
(len(str) > 0 && deepMatch(str[1:], pattern))
}
// Mismatch. Maybe restart.
if 0 < nextNx && nextNx <= len(name) {
px = nextPx
nx = nextNx
continue
}
return false
str = str[1:]
pattern = pattern[1:]
}
// Matched all of pattern to all of name. Success.
return true
return len(str) == 0 && len(pattern) == 0
}
-40
View File
@@ -1,40 +0,0 @@
package yqlib
import (
"strings"
"testing"
)
func TestDeepMatch(t *testing.T) {
var tests = []struct {
name string
pattern string
ok bool
}{
{"", "", true},
{"", "x", false},
{"x", "", false},
{"abc", "abc", true},
{"abc", "*", true},
{"abc", "*c", true},
{"abc", "*b", false},
{"abc", "a*", true},
{"abc", "b*", false},
{"a", "a*", true},
{"a", "*a", true},
{"axbxcxdxe", "a*b*c*d*e*", true},
{"axbxcxdxexxx", "a*b*c*d*e*", true},
{"abxbbxdbxebxczzx", "a*b?c*x", true},
{"abxbbxdbxebxczzy", "a*b?c*x", false},
{strings.Repeat("a", 100), "a*a*a*a*b", false},
{"xxx", "*x", true},
}
for _, tt := range tests {
t.Run(tt.name+" "+tt.pattern, func(t *testing.T) {
if want, got := tt.ok, deepMatch(tt.name, tt.pattern); want != got {
t.Errorf("Expected %v got %v", want, got)
}
})
}
}
+14 -14
View File
@@ -3,6 +3,7 @@ package yqlib
import (
"fmt"
"container/list"
"strconv"
yaml "gopkg.in/yaml.v3"
@@ -14,8 +15,12 @@ func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
Rhs: rhs}
}
func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
return compoundAssignFunction(d, context, expressionNode, createAddOp)
func addAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
assignmentOp := &Operation{OperationType: assignOpType}
assignmentOp.UpdateAssign = false
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)}
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
}
func toNodes(candidate *CandidateNode) []*yaml.Node {
@@ -32,23 +37,18 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
}
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func addOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("Add operator")
return crossFunction(d, context.ReadOnlyClone(), expressionNode, add, false)
return crossFunction(d, matchingNodes, expressionNode, add)
}
func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node)
lhsNode := lhs.Node
if lhsNode.Tag == "!!null" {
return lhs.CreateChild(nil, rhs.Node), nil
}
target := lhs.CreateChild(nil, &yaml.Node{})
lhsNode := lhs.Node
switch lhsNode.Kind {
case yaml.MappingNode:
@@ -76,17 +76,17 @@ func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*Candida
target.Node.Tag = "!!str"
target.Node.Value = lhs.Value + rhs.Value
} else if lhs.Tag == "!!int" && rhs.Tag == "!!int" {
format, lhsNum, err := parseInt(lhs.Value)
lhsNum, err := strconv.Atoi(lhs.Value)
if err != nil {
return nil, err
}
_, rhsNum, err := parseInt(rhs.Value)
rhsNum, err := strconv.Atoi(rhs.Value)
if err != nil {
return nil, err
}
sum := lhsNum + rhsNum
target.Node.Tag = "!!int"
target.Node.Value = fmt.Sprintf(format, sum)
target.Node.Value = fmt.Sprintf("%v", sum)
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") {
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
if err != nil {
+13 -54
View File
@@ -5,31 +5,6 @@ import (
)
var addOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `[{a: foo, b: bar}, {a: 1, b: 2}]`,
expression: ".[] | .a + .b",
expected: []string{
"D0, P[0 a], (!!str)::foobar\n",
"D0, P[1 a], (!!int)::3\n",
},
},
{
skipDoc: true,
document: `{}`,
expression: "(.a + .b) as $x",
expected: []string{
"D0, P[], (doc)::{}\n",
},
},
{
skipDoc: true,
document: `a: 0`,
expression: ".a += .b.c",
expected: []string{
"D0, P[], (doc)::a: 0\n",
},
},
{
description: "Concatenate and assign arrays",
document: `{a: {val: thing, b: [cat,dog]}}`,
@@ -79,29 +54,13 @@ var addOperatorScenarios = []expressionScenario{
},
},
{
description: "Append to array",
description: "Update array (append)",
document: `{a: [1,2], b: [3,4]}`,
expression: `.a = .a + .b`,
expected: []string{
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
},
},
{
description: "Append another array using +=",
document: `{a: [1,2], b: [3,4]}`,
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
},
},
{
description: "Relative append",
document: `a: { a1: {b: [cat]}, a2: {b: [dog]}, a3: {} }`,
expression: `.a[].b += ["mouse"]`,
expected: []string{
"D0, P[], (doc)::a: {a1: {b: [cat, mouse]}, a2: {b: [dog, mouse]}, a3: {b: [mouse]}}\n",
},
},
{
description: "String concatenation",
document: `{a: cat, b: meow}`,
@@ -110,6 +69,14 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
},
},
{
description: "Relative string concatenation",
document: `{a: cat, b: meow}`,
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
},
},
{
description: "Number addition - float",
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
@@ -129,19 +96,11 @@ var addOperatorScenarios = []expressionScenario{
},
},
{
description: "Increment numbers",
document: `{a: 3, b: 5}`,
expression: `.[] += 1`,
description: "Increment number",
document: `{a: 3}`,
expression: `.a += 1`,
expected: []string{
"D0, P[], (doc)::{a: 4, b: 6}\n",
},
},
{
description: "Add to null",
subdescription: "Adding to null simply returns the rhs",
expression: `null + "cat"`,
expected: []string{
"D0, P[], (!!str)::cat\n",
"D0, P[], (doc)::{a: 4}\n",
},
},
}
+10 -9
View File
@@ -1,17 +1,18 @@
package yqlib
func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
import (
"container/list"
)
// corssFunction no matches
// can boolean use crossfunction
func alternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- alternative")
return crossFunction(d, context, expressionNode, alternativeFunc, true)
return crossFunction(d, matchingNodes, expressionNode, alternativeFunc)
}
func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
if lhs == nil {
return rhs, nil
}
if rhs == nil {
return lhs, nil
}
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)

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