Compare commits

..
Author SHA1 Message Date
Mike Farah 5db42a122a Updating docs 2022-01-27 17:22:27 +11:00
Mike Farah b2a93930d0 Preparing release notes 2022-01-27 16:00:02 +11:00
Mike Farah dedd1ca892 Fixing github build 2022-01-27 15:54:26 +11:00
Mike Farah 01b4917f26 Debug github build 2022-01-27 14:22:01 +11:00
Mike Farah 25e374f4ee Debug github build 2022-01-27 14:00:55 +11:00
Mike Farah 3ba728d40c Working around github strangeness 2022-01-27 13:54:29 +11:00
Mike Farah 8761920f58 Debug github build 2022-01-27 13:24:05 +11:00
Mike Farah 394be5c65a Debug github build 2022-01-27 12:47:06 +11:00
Mike Farah 4ec533bc1b No longer have to specify eval! 2022-01-27 12:07:41 +11:00
Mike Farah fc276ff450 Smarter behaviour when piping data in with one argument 2022-01-27 10:18:46 +11:00
Mike Farah c3d815998a Auto style when adding to empty maps/arrays 2022-01-27 09:58:13 +11:00
Mike Farah 71242b0c8e Fix Seg Fault on bad split expression input #1086 2022-01-27 09:29:41 +11:00
Mike Farah a9e7f669d0 Merged envsubst into env operators 2022-01-27 09:20:53 +11:00
Samuel Cormier-IijimaandGitHub 8195ff8b9b Add a new envsubst operator to replace environment variables in strings (#1082) 2022-01-25 19:33:30 +11:00
Mike Farah 9b1a7bf451 Added support for adding objects 2022-01-23 11:35:44 +11:00
Mike Farah 9b66e0a094 Include zip file checksums in release 2022-01-23 10:45:18 +11:00
Mike Farah 6e33132de9 Added support for *= 2022-01-22 16:40:17 +11:00
Mike Farah a6c79f3410 Multiply, substract with custom types 2022-01-22 16:12:59 +11:00
Mike Farah 50df792e49 Auto cast for add 2022-01-22 13:17:16 +11:00
Mike Farah 6f24e878aa Updated README, better xml docs 2022-01-22 12:35:33 +11:00
Mike Farah a0ba208669 Bumping release 2022-01-22 11:57:19 +11:00
Mike Farah d5b0154747 Fixing manpage path issue 2022-01-22 11:56:21 +11:00
Phil MillerandGitHub 7d2ddc1cde fixes path in scripts/generate-man-page-md.sh (#1075) 2022-01-22 11:51:16 +11:00
Mike Farah 02252e8e69 Bumping version 2022-01-22 11:48:08 +11:00
Mike Farah 2526b03d67 Added JSON conversion tests and doc generation 2022-01-21 20:26:19 +11:00
Eng Zer JunandGitHub e347090571 refactor: move from io/ioutil to io and os packages (#1068)
* refactor: move from io/ioutil to io and os packages

The io/ioutil package has been deprecated as of Go 1.16, see
https://golang.org/doc/go1.16#ioutil. This commit replaces the existing
io/ioutil functions with their new definitions in io and os packages.

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>

* build: update snapcraft `go-channel` to 1.17

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2022-01-21 19:50:53 +11:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f8bcaca4c9 Bump github.com/jinzhu/copier from 0.3.4 to 0.3.5 (#1071)
Bumps [github.com/jinzhu/copier](https://github.com/jinzhu/copier) from 0.3.4 to 0.3.5.
- [Release notes](https://github.com/jinzhu/copier/releases)
- [Commits](https://github.com/jinzhu/copier/compare/v0.3.4...v0.3.5)

---
updated-dependencies:
- dependency-name: github.com/jinzhu/copier
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-21 18:36:33 +11:00
Mike Farah de0716e875 Tweaking select docs 2022-01-15 18:22:26 +11:00
Mike Farah 21cdbab0d7 Improving select docs (#839) 2022-01-15 18:18:52 +11:00
Mike Farah 1fba46f2f7 Updated readme as per #1051 recommendations 2022-01-15 17:54:08 +11:00
Mike Farah dfe4308304 Preparing release notes 2022-01-15 17:45:45 +11:00
Mike Farah 8c94a96ee0 New merge flag (n) to only merge in new fields (#1038) 2022-01-15 15:48:34 +11:00
Mike FarahandGitHub ec8ef312ef Added XML encoding/decoding (#1067)
* Added XML encoding/decoding

* Minor fixes

* Improve yq doc

* Lint
2022-01-15 11:57:59 +11:00
Mike Farah 606ef91cc6 Better fix #1062 (return error instead of panic) 2022-01-14 15:40:24 +11:00
Mike Farah 78b45a3eb0 Fixed #1062 2022-01-14 15:22:55 +11:00
Mike Farah 84ddf1862f Fixed #1048 2022-01-14 14:55:25 +11:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2d7c63e4e4 Bump github.com/goccy/go-yaml from 1.9.4 to 1.9.5 (#1065)
Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.9.4 to 1.9.5.
- [Release notes](https://github.com/goccy/go-yaml/releases)
- [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/goccy/go-yaml/compare/v1.9.4...v1.9.5)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-yaml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-14 13:33:30 +11:00
HélioandGitHub eac75f7dad Arch Linux install instructions (#1059) 2022-01-03 09:20:38 +11:00
robertomierandGitHub 5e30f3ba90 Update changelog for version v4.16.2 (#1046)
* Update changelog for version v4.16.2

* fix: remove useless changelog comments from debian changelog
2022-01-03 09:20:13 +11:00
robertomierandGitHub 0b49bebad9 chore: script to build the deb sources (#1045)
* chore: script to build the deb sources

now it includes the man files. This script makes only sense executing
on the machine having the gpg key needed to sign and push to ppa

* fix: dput dependency and proper configuration
2021-12-23 11:43:21 +11:00
Mike Farah c48b1612df Updating cli docs 2021-12-21 17:07:32 +11:00
Mike Farah 6763ae6e7b Added xml acceptance tests 2021-12-21 17:05:33 +11:00
125 changed files with 3568 additions and 925 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ country: Australia
And we run a command:
```bash
yq eval 'predictWeatherOf(.country)'
yq 'predictWeatherOf(.country)'
```
it could output
+1
View File
@@ -25,6 +25,7 @@ jobs:
fi
- name: Check the build
shell: bash -l {0}
run: |
export PATH=${PATH}:`go env GOPATH`/bin
scripts/devtools.sh
+5 -1
View File
@@ -48,4 +48,8 @@ test*.yml
# man page
man.md
yq.1
yq.1
# debian pkg
_build
debian/files
+8
View File
@@ -18,6 +18,14 @@ linters:
- revive
- unconvert
- unparam
linters-settings:
depguard:
list-type: blacklist
include-go-root: true
packages:
- io/ioutil
packages-with-error-message:
- io/ioutil: "The 'io/ioutil' package is deprecated. Use corresponding 'os' or 'io' functions instead."
issues:
exclude-rules:
- linters:
+62 -35
View File
@@ -3,7 +3,7 @@
![Build](https://github.com/mikefarah/yq/workflows/Build/badge.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq)
a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
a lightweight and portable command-line YAML, JSON and XML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json and xml. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
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 and Podman, all listed below.
@@ -11,32 +11,35 @@ yq is written in go - so you can download a dependency free binary for your plat
Read a value:
```bash
yq e '.a.b[0].c' file.yaml
yq '.a.b[0].c' file.yaml
```
Pipe from STDIN:
```bash
cat file.yaml | yq e '.a.b[0].c' -
cat file.yaml | yq '.a.b[0].c'
```
Update a yaml file, inplace
```bash
yq e -i '.a.b[0].c = "cool"' file.yaml
yq -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
NAME=mike yq -i '.a.b[0].c = strenv(NAME)' file.yaml
```
Merge multiple files
```
```bash
# note the use of `ea` to evaluate all the files at once
# instead of in sequence
yq ea '. as $item ireduce ({}; . * $item )' path/to/*.yml
```
Multiple updates to a yaml file
```bash
yq e -i '
yq -i '
.a.b[0].c = "cool" |
.x.y.z = "foobar" |
.person.name = strenv(NAME)
@@ -82,16 +85,16 @@ snap install yq
`yq` installs with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
```
sudo cat /etc/myfile | yq e '.a.path' -
sudo cat /etc/myfile | yq '.a.path'
```
And to write to a root file you can either use [sponge](https://linux.die.net/man/1/sponge):
```
sudo cat /etc/myfile | yq e '.a.path = "value"' - | sudo sponge /etc/myfile
sudo cat /etc/myfile | yq '.a.path = "value"' | sudo sponge /etc/myfile
```
or write to a temporary file:
```
sudo cat /etc/myfile | yq e '.a.path = "value"' | sudo tee /etc/myfile.tmp
sudo cat /etc/myfile | yq '.a.path = "value"' | sudo tee /etc/myfile.tmp
sudo mv /etc/myfile.tmp /etc/myfile
rm /etc/myfile.tmp
```
@@ -101,11 +104,14 @@ rm /etc/myfile.tmp
#### Oneshot use:
```bash
docker run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression ]FILE...
docker run --rm -v "${PWD}":/workdir mikefarah/yq [command] [flags] [expression ]FILE...
```
Note that you can run `yq` in docker without network access and other privileges if you desire,
namely `--security-opt=no-new-privileges --cap-drop all --network none`.
```bash
podman run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression ]FILE...
podman run --rm -v "${PWD}":/workdir mikefarah/yq [command] [flags] [expression ]FILE...
```
#### Pipe in via STDIN:
@@ -113,11 +119,11 @@ podman run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression
You'll need to pass the `-i\--interactive` flag to docker:
```bash
cat myfile.yml | docker run -i --rm mikefarah/yq e . -
cat myfile.yml | docker run -i --rm mikefarah/yq '.this.thing'
```
```bash
cat myfile.yml | podman run -i --rm mikefarah/yq e . -
cat myfile.yml | podman run -i --rm mikefarah/yq '.this.thing'
```
#### Run commands interactively:
@@ -173,7 +179,7 @@ USER yq
- name: Set foobar to cool
uses: mikefarah/yq@master
with:
cmd: yq eval -i '.foo.bar = "cool"' 'config.yml'
cmd: yq -i '.foo.bar = "cool"' 'config.yml'
```
See https://mikefarah.gitbook.io/yq/usage/github-action for more.
@@ -195,6 +201,12 @@ webi yq
See [webi](https://webinstall.dev/)
Supported by @adithyasunil26 (https://github.com/webinstall/webi-installers/tree/master/yq)
### Arch Linux
```
pacman -S go-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)
@@ -232,19 +244,21 @@ 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
- Uses similar syntax as `jq` but works with YAML, [JSON](https://mikefarah.gitbook.io/yq/usage/convert) and [XML](https://mikefarah.gitbook.io/yq/usage/xml) 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)
- [Deeply data structures](https://mikefarah.gitbook.io/yq/operators/traverse-read)
- [Sort 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).
- [Update yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags)
- [Update 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)
- Keeps yaml formatting and comments when updating (though there are issues with whitespace)
- [Load content from other files](https://mikefarah.gitbook.io/yq/operators/load)
- [Convert to/from json](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
- [Convert to/from xml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/xml)
- [Convert to properties](https://mikefarah.gitbook.io/yq/v/v4.x/usage/properties)
- [Convert to csv/tsv](https://mikefarah.gitbook.io/yq/usage/csv-tsv)
- [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.
@@ -259,28 +273,41 @@ Usage:
yq [flags]
yq [command]
Examples:
# yq defaults to 'eval' command if no command is specified. See "yq eval --help" for more examples.
cat myfile.yml | yq '.stuff' # outputs the data at the "stuff" node from "myfile.yml"
yq -i '.stuff = "foo"' myfile.yml # update myfile.yml inplace
Available Commands:
eval Apply the expression to each document in each yaml file in sequence
completion Generate the autocompletion script for the specified shell
eval (default) Apply the expression to each document in each yaml file 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
-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 separators before processing expression. (default true)
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-i, --inplace update the file inplace of first file given.
-p, --input-format string [yaml|y|xml|x] parse format for input. Note that json is a subset of yaml. (default "yaml")
-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 docs from scratch.
-o, --output-format string [yaml|y|json|j|props|p|xml|x] output format type. (default "yaml")
-P, --prettyPrint pretty print, shorthand for '... style = ""'
-s, --split-exp string 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.
--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
--xml-attribute-prefix string prefix for xml attributes (default "+")
--xml-content-name string name for xml content (if no attribute name is present). (default "+content")
Use "yq [command] --help" for more information about a command.
```
+34 -1
View File
@@ -5,8 +5,26 @@ setUp() {
}
testBasicEvalRoundTrip() {
./yq -n ".a = 123" > test.yml
X=$(./yq '.a' test.yml)
assertEquals 123 "$X"
}
testBasicEvalRoundTripNoEval() {
./yq -n ".a = 123" > test.yml
X=$(./yq '.a' test.yml)
assertEquals 123 "$X"
}
testBasicStdInWithOneArg() {
./yq e -n ".a = 123" > test.yml
X=$(./yq e '.a' test.yml)
X=$(cat test.yml | ./yq e ".a")
assertEquals 123 "$X"
X=$(cat test.yml | ./yq ea ".a")
assertEquals 123 "$X"
X=$(cat test.yml | ./yq ".a")
assertEquals 123 "$X"
}
@@ -19,6 +37,15 @@ EOL
assertEquals "10" "$X"
}
testBasicUpdateInPlaceSequenceNoEval() {
cat >test.yml <<EOL
a: 0
EOL
./yq -i ".a = 10" test.yml
X=$(./yq '.a' test.yml)
assertEquals "10" "$X"
}
testBasicUpdateInPlaceSequenceEvalAll() {
cat >test.yml <<EOL
a: 0
@@ -40,6 +67,12 @@ testBasicExitStatus() {
assertEquals 1 "$?"
}
testBasicExitStatusNoEval() {
echo "a: cat" > test.yml
X=$(./yq -e '.z' test.yml 2&>/dev/null)
assertEquals 1 "$?"
}
testBasicExtractFieldWithSeperator() {
cat >test.yml <<EOL
---
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
setUp() {
rm test*.yml || true
}
testInputXml() {
cat >test.yml <<EOL
<cat legs="4">BiBi</cat>
EOL
read -r -d '' expected << EOM
cat:
+content: BiBi
+legs: "4"
EOM
X=$(./yq e -p=xml test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea -p=xml test.yml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
+61
View File
@@ -102,4 +102,65 @@ EOM
assertEquals "$expected" "$X"
}
testOutputXml() {
cat >test.yml <<EOL
a: {b: {c: ["cat"]}}
EOL
read -r -d '' expected << EOM
<a>
<b>
<c>cat</c>
</b>
</a>
EOM
X=$(./yq e --output-format=xml test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --output-format=xml test.yml)
assertEquals "$expected" "$X"
}
testOutputXmlShort() {
cat >test.yml <<EOL
a: {b: {c: ["cat"]}}
EOL
read -r -d '' expected << EOM
<a>
<b>
<c>cat</c>
</b>
</a>
EOM
X=$(./yq e --output-format=x test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --output-format=x test.yml)
assertEquals "$expected" "$X"
}
testOutputXmComplex() {
cat >test.yml <<EOL
a: {b: {c: ["cat", "dog"], +f: meow}}
EOL
read -r -d '' expected << EOM
<a>
<b f="meow">
<c>cat</c>
<c>dog</c>
</b>
</a>
EOM
X=$(./yq e --output-format=x test.yml)
assertEquals "$expected" "$X"
X=$(./yq ea --output-format=x test.yml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
+70
View File
@@ -0,0 +1,70 @@
#!/bin/bash
setUp() {
rm test*.yml || true
cat >test.yml <<EOL
a: frog
EOL
}
testPipeViaCatWithParam() {
X=$(cat test.yml | ./yq '.a')
assertEquals "frog" "$X"
}
testPipeViaCatWithParamEval() {
X=$(cat test.yml | ./yq e '.a')
assertEquals "frog" "$X"
}
testPipeViaCatWithParamEvalAll() {
X=$(cat test.yml | ./yq ea '.a')
assertEquals "frog" "$X"
}
testPipeViaCatNoParam() {
X=$(cat test.yml | ./yq)
assertEquals "a: frog" "$X"
}
testPipeViaCatNoParamEval() {
X=$(cat test.yml | ./yq e)
assertEquals "a: frog" "$X"
}
testPipeViaCatNoParamEvalAll() {
X=$(cat test.yml | ./yq ea)
assertEquals "a: frog" "$X"
}
testPipeViaFileishWithParam() {
X=$(./yq '.a' < test.yml)
assertEquals "frog" "$X"
}
testPipeViaFileishWithParamEval() {
X=$(./yq e '.a' < test.yml)
assertEquals "frog" "$X"
}
testPipeViaFileishWithParamEvalAll() {
X=$(./yq ea '.a' < test.yml)
assertEquals "frog" "$X"
}
testPipeViaFileishNoParam() {
X=$(./yq < test.yml)
assertEquals "a: frog" "$X"
}
testPipeViaFileishNoParamEval() {
X=$(./yq e < test.yml)
assertEquals "a: frog" "$X"
}
testPipeViaFileishNoParamEvalAll() {
X=$(./yq ea < test.yml)
assertEquals "a: frog" "$X"
}
source ./scripts/shunit2
+32 -4
View File
@@ -39,7 +39,7 @@ Note that it consumes more memory than eval.
}
return cmdEvalAll
}
func evaluateAll(cmd *cobra.Command, args []string) error {
func evaluateAll(cmd *cobra.Command, args []string) (cmdError error) {
// 0 args, read std in
// 1 arg, null input, process expression
// 1 arg, read file in sequence
@@ -54,6 +54,22 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
stat, _ := os.Stdin.Stat()
pipingStdIn := (stat.Mode() & os.ModeCharDevice) == 0
yqlib.GetLogger().Debug("pipingStdIn: %v", pipingStdIn)
yqlib.GetLogger().Debug("stat.Mode(): %v", stat.Mode())
yqlib.GetLogger().Debug("ModeDir: %v", stat.Mode()&os.ModeDir)
yqlib.GetLogger().Debug("ModeAppend: %v", stat.Mode()&os.ModeAppend)
yqlib.GetLogger().Debug("ModeExclusive: %v", stat.Mode()&os.ModeExclusive)
yqlib.GetLogger().Debug("ModeTemporary: %v", stat.Mode()&os.ModeTemporary)
yqlib.GetLogger().Debug("ModeSymlink: %v", stat.Mode()&os.ModeSymlink)
yqlib.GetLogger().Debug("ModeDevice: %v", stat.Mode()&os.ModeDevice)
yqlib.GetLogger().Debug("ModeNamedPipe: %v", stat.Mode()&os.ModeNamedPipe)
yqlib.GetLogger().Debug("ModeSocket: %v", stat.Mode()&os.ModeSocket)
yqlib.GetLogger().Debug("ModeSetuid: %v", stat.Mode()&os.ModeSetuid)
yqlib.GetLogger().Debug("ModeSetgid: %v", stat.Mode()&os.ModeSetgid)
yqlib.GetLogger().Debug("ModeCharDevice: %v", stat.Mode()&os.ModeCharDevice)
yqlib.GetLogger().Debug("ModeSticky: %v", stat.Mode()&os.ModeSticky)
yqlib.GetLogger().Debug("ModeIrregular: %v", stat.Mode()&os.ModeIrregular)
out := cmd.OutOrStdout()
@@ -67,7 +83,11 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
}
// need to indirectly call the function so that completedSuccessfully is
// passed when we finish execution as opposed to now
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
defer func() {
if cmdError == nil {
cmdError = writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully)
}
}()
}
format, err := yqlib.OutputFormatFromString(outputFormat)
@@ -80,9 +100,13 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
return err
}
printerWriter := configurePrinterWriter(format, out)
printerWriter, err := configurePrinterWriter(format, out)
if err != nil {
return err
}
encoder := configureEncoder(format)
printer := yqlib.NewPrinter(printerWriter, format, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
printer := yqlib.NewPrinter(encoder, printerWriter)
if frontMatter != "" {
frontMatterHandler := yqlib.NewFrontMatterHandler(args[firstFileIndex])
@@ -112,6 +136,10 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
case 1:
if nullInput {
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer, "")
} else if pipingStdIn && args[0] != "-" && !maybeFile(args[0]) {
// must have given a single expression and piping input from stdin
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), []string{"-"}, printer, leadingContentPreProcessing, decoder)
} else {
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer, leadingContentPreProcessing, decoder)
}
+40 -8
View File
@@ -13,7 +13,7 @@ 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: "(default) Apply the expression to each document in each yaml file in sequence",
Example: `
# Reads field under the given path for each file
yq e '.a.b' f1.yml f2.yml
@@ -44,16 +44,16 @@ expression and prints the result in sequence.`,
}
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 yqlib.PrettyPrintExp
} else if prettyPrint {
return fmt.Sprintf("%v | %v", expression, prettyPrintExp)
return fmt.Sprintf("%v | %v", expression, yqlib.PrettyPrintExp)
}
return expression
}
func evaluateSequence(cmd *cobra.Command, args []string) error {
func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
// 0 args, read std in
// 1 arg, null input, process expression
// 1 arg, read file in sequence
@@ -67,6 +67,27 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
stat, _ := os.Stdin.Stat()
pipingStdIn := (stat.Mode() & os.ModeCharDevice) == 0
yqlib.GetLogger().Debug("pipingStdIn: %v", pipingStdIn)
yqlib.GetLogger().Debug("stat.Mode(): %v", stat.Mode())
yqlib.GetLogger().Debug("ModeDir: %v", stat.Mode()&os.ModeDir)
yqlib.GetLogger().Debug("ModeAppend: %v", stat.Mode()&os.ModeAppend)
yqlib.GetLogger().Debug("ModeExclusive: %v", stat.Mode()&os.ModeExclusive)
yqlib.GetLogger().Debug("ModeTemporary: %v", stat.Mode()&os.ModeTemporary)
yqlib.GetLogger().Debug("ModeSymlink: %v", stat.Mode()&os.ModeSymlink)
yqlib.GetLogger().Debug("ModeDevice: %v", stat.Mode()&os.ModeDevice)
yqlib.GetLogger().Debug("ModeNamedPipe: %v", stat.Mode()&os.ModeNamedPipe)
yqlib.GetLogger().Debug("ModeSocket: %v", stat.Mode()&os.ModeSocket)
yqlib.GetLogger().Debug("ModeSetuid: %v", stat.Mode()&os.ModeSetuid)
yqlib.GetLogger().Debug("ModeSetgid: %v", stat.Mode()&os.ModeSetgid)
yqlib.GetLogger().Debug("ModeCharDevice: %v", stat.Mode()&os.ModeCharDevice)
yqlib.GetLogger().Debug("ModeSticky: %v", stat.Mode()&os.ModeSticky)
yqlib.GetLogger().Debug("ModeIrregular: %v", stat.Mode()&os.ModeIrregular)
// Mask for the type bits. For regular files, none will be set.
yqlib.GetLogger().Debug("ModeType: %v", stat.Mode()&os.ModeType)
yqlib.GetLogger().Debug("ModePerm: %v", stat.Mode()&os.ModePerm)
out := cmd.OutOrStdout()
@@ -80,7 +101,11 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
}
// need to indirectly call the function so that completedSuccessfully is
// passed when we finish execution as opposed to now
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
defer func() {
if cmdError == nil {
cmdError = writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully)
}
}()
}
format, err := yqlib.OutputFormatFromString(outputFormat)
@@ -88,9 +113,13 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
return err
}
printerWriter := configurePrinterWriter(format, out)
printerWriter, err := configurePrinterWriter(format, out)
if err != nil {
return err
}
encoder := configureEncoder(format)
printer := yqlib.NewPrinter(printerWriter, format, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
printer := yqlib.NewPrinter(encoder, printerWriter)
decoder, err := configureDecoder()
if err != nil {
@@ -126,6 +155,9 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
case 1:
if nullInput {
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer, "")
} else if pipingStdIn && args[0] != "-" && !maybeFile(args[0]) {
// must have given a single expression and piping input from stdin
err = streamEvaluator.EvaluateFiles(processExpression(args[0]), []string{"-"}, printer, leadingContentPreProcessing, decoder)
} else {
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer, leadingContentPreProcessing, decoder)
}
+16 -7
View File
@@ -14,13 +14,22 @@ func New() *cobra.Command {
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.`,
Example: `
# yq defaults to 'eval' command if no command is specified. See "yq eval --help" for more examples.
# read the "stuff" node from "myfile.yml"
cat myfile.yml | yq '.stuff'
# update myfile.yml in place
yq -i '.stuff = "foo"' myfile.yml # update myfile.yml inplace
`,
RunE: func(cmd *cobra.Command, args []string) error {
if version {
cmd.Print(GetVersionDisplay())
return nil
}
cmd.Println(cmd.UsageString())
return nil
return evaluateSequence(cmd, args)
},
PersistentPreRun: func(cmd *cobra.Command, args []string) {
@@ -51,18 +60,18 @@ See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.`,
panic(err)
}
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "yaml", "[yaml|y|json|j|props|p] output format type.")
rootCmd.PersistentFlags().StringVarP(&inputFormat, "input-format", "p", "yaml", "[yaml|y|xml|x] input format type.")
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "yaml", "[yaml|y|json|j|props|p|xml|x] output format type.")
rootCmd.PersistentFlags().StringVarP(&inputFormat, "input-format", "p", "yaml", "[yaml|y|xml|x] parse format for input. Note that json is a subset of yaml.")
rootCmd.PersistentFlags().StringVar(&xmlAttributePrefix, "xml-attribute-prefix", "+", "prefix for xml attributes")
rootCmd.PersistentFlags().StringVar(&xmlContentName, "xml-content-name", "+content", "name for xml content (if no attribute name is present).")
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(&nullInput, "null-input", "n", false, "Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.")
rootCmd.PersistentFlags().BoolVarP(&noDocSeparators, "no-doc", "N", false, "Don't print document separators (---)")
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the file inplace of first file given.")
rootCmd.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print, shorthand for '... style = \"\"'")
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
@@ -70,7 +79,7 @@ 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().BoolVarP(&leadingContentPreProcessing, "header-preprocess", "", true, "Slurp any header comments and separators before processing expression.")
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.")
+31 -3
View File
@@ -57,7 +57,7 @@ func configureDecoder() (yqlib.Decoder, error) {
return yqlib.NewYamlDecoder(), nil
}
func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) yqlib.PrinterWriter {
func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yqlib.PrinterWriter, error) {
var printerWriter yqlib.PrinterWriter
@@ -65,11 +65,39 @@ func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) yql
colorsEnabled = forceColor
splitExp, err := yqlib.NewExpressionParser().ParseExpression(splitFileExp)
if err != nil {
return nil
return nil, fmt.Errorf("bad split document expression: %w", err)
}
printerWriter = yqlib.NewMultiPrinterWriter(splitExp, format)
} else {
printerWriter = yqlib.NewSinglePrinterWriter(out)
}
return printerWriter
return printerWriter, nil
}
func configureEncoder(format yqlib.PrinterOutputFormat) yqlib.Encoder {
switch format {
case yqlib.JsonOutputFormat:
return yqlib.NewJsonEncoder(indent)
case yqlib.PropsOutputFormat:
return yqlib.NewPropertiesEncoder()
case yqlib.CsvOutputFormat:
return yqlib.NewCsvEncoder(',')
case yqlib.TsvOutputFormat:
return yqlib.NewCsvEncoder('\t')
case yqlib.YamlOutputFormat:
return yqlib.NewYamlEncoder(indent, colorsEnabled, !noDocSeparators, unwrapScalar)
case yqlib.XmlOutputFormat:
return yqlib.NewXmlEncoder(indent, xmlAttributePrefix, xmlContentName)
}
panic("invalid encoder")
}
// this is a hack to enable backwards compatibility with githubactions (which pipe /dev/null into everything)
// and being able to call yq with the filename as a single parameter
//
// without this - yq detects there is stdin (thanks githubactions),
// then tries to parse the filename as an expression
func maybeFile(str string) bool {
_, err := os.Stat(str) // #nosec
return err == nil
}
+1 -1
View File
@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "4.16.2"
Version = "4.17.2"
// 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
+109
View File
@@ -1,3 +1,112 @@
yq (4.16.2) focal; urgency=medium
* Fixed with semicolon space issue
* Updating with documentation
* Added STDIN example to the top
* minor readme cleanup
* Help text tweak
* Fixed docker timeout - simplify docker builds
* New release with docker build fixes
* Updating to go 1.17 to fix CVE #944
* Fix a typo in root.go
* Skip the tests if the nocheck Debian build option is specified
* Fixed select bug (#958)
* Sped up explode operator
* Slight performance improvement to context.ChildContext
* Speed up multiply
* Update README with recently added / changed options
* Make deepMatch report in linear time
* Removed leadingContentPreProcessing flag - header preprocessing is stable
* Revert "Removed leadingContentPreProcessing flag - header preprocessing is stable"
* Keep flag, it is needed in corner cases
* Updated Readme
* Man page
* Fixed expression parsing bug #970
* Bumping go-lang, docker versions
* Added test release flow
* Updated github action release to generate man page
* Bumping version
* Removing no longer needed github action
* Added decoder op
* Fixed newline handling when decoding/encoding
* Fixed newline handling in encoder/decoder
* Can specify indent in encode ops
* Added group_by operator
* Added flatten operator
* Fixed flatten error message
* Improving docs
* Split printer
* Refactored command logic
* Fix JSON encoding removing null #985
* Fixed acceptance tests
* gitbook
* Update document generation script
* Updating README
* Updating release instructions
* github action no longer uses data1.yml
* Create dependabot.yml
* Bump actions/create-release from 1.0.0 to 1.1.4
* Bump actions/setup-go from 1 to 2.1.4
* Bump github.com/goccy/go-yaml from 1.8.9 to 1.9.4
* Bump github.com/jinzhu/copier from 0.2.8 to 0.3.2
* Bump github.com/fatih/color from 1.10.0 to 1.13.0
* Bump github.com/spf13/cobra from 1.1.3 to 1.2.1
* Update dependabot.yml
* Update go.yml
* add build check to PRs
* Include secure as part of build process
* Fixing bad label in github action
* fixed printer test
* remove leading content indicator
* Fixed header preprocessing!
* lint : define golangci configuration file
* Update check.sh
* Load file acceptance test
* Minor improvement on handling front matter
* Improved load doc
* feature: detect MANPATh and install there
* Update install-man-page.sh
* simplify prod stage, move version label to action
* add labels, quote some values
* enable errorlint linter
* Added errorlint to devtools
* Added key operator
* Added more tests
* Fixing comments
* Attempt to fix golint problem
* Include version query for tools
* Clean up errored file?
* enable misspell linter
* updated readme
* update Golangci version to v1.43.0
* gci linter
* Better merge array by key example
* Added credit for merge by array example
* Better formatting of merge arrays example
* Better merge example
* Add accessor for the yq logger instance (#1013)
* Fixed collect op when working with multiple nodes
* Added map, map_values
* Add support for Podman as well as Docker (#1026)
* Bump github.com/jinzhu/copier from 0.3.2 to 0.3.4 (#1027)
* Added csv, tsv output formats
* Added encoder tests
* Cleanup test
* Fixed docker permission issue #1014
* Recording release notes for next release
* Assignment op no longer clobbers anchor (#1029)
* Added sort_by operator
* Improved error message
* Improved tips and tricks
* Report while filename failed to parse #1030
* Added script for extracting checksums
* Improved extract-checksum.sh
* Bump github.com/spf13/cobra from 1.2.1 to 1.3.0 (#1039)
* enable more linters (#1043)
* Bump golang compiler #1037
-- Roberto Mier Escandon <rmescandon@gmail.com> Tue, 21 Dec 2021 09:41:44 +0000
yq (4.13.0) focal; urgency=medium
* New `with` operator for making multiple changes to a given path
+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,
golang-1.17-go,
pandoc,
rsync
Standards-Version: 4.1.4
Homepage: https://github.com/mikefarah/yq.git
+19 -4
View File
@@ -1,6 +1,6 @@
#!/usr/bin/make -f
#
# Copyright (C) 2018 Roberto Mier Escandón <rmescandon@gmail.com>
# Copyright (C) 2018-2021 Roberto Mier Escandón <rmescandon@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
@@ -17,20 +17,21 @@
PROJECT := yq
OWNER := mikefarah
REPO := github.com
GOVERSION := 1.15
export DH_OPTIONS
export DH_GOPKG := ${REPO}/${OWNER}/${PROJECT}
export GOROOT := /usr/lib/go-${GOVERSION}
export GOROOT := /usr/local/go
export GOPATH := ${CURDIR}/_build
export GOBIN := ${GOPATH}/bin
export PATH := ${GOROOT}/bin:${GOBIN}:${PATH}
export GOCACHE := /tmp/gocache
export GOFLAGS := -mod=vendor
export GO111MODULE := on
SRCDIR := ${GOPATH}/src/${DH_GOPKG}
DESTDIR := ${CURDIR}/debian/${PROJECT}
BINDIR := /usr/bin
MANDIR := /usr/share/man/man1/
ASSETSDIR := /usr/share/${PROJECT}
%:
@@ -42,7 +43,17 @@ override_dh_auto_build:
# copy project to local srcdir to build from there
rsync -avz --progress --exclude=_build --exclude=debian --exclude=tmp. --exclude=go.mod --exclude=docs . $(SRCDIR)
# build go code
(cd ${SRCDIR} && go install -buildmode=pie ./...)
( \
cd ${SRCDIR} && \
go install -buildmode=pie ./... \
)
# build man page
( \
cd ${SRCDIR} && \
./scripts/generate-man-page-md.sh && \
./scripts/generate-man-page.sh \
)
override_dh_auto_test:
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
@@ -54,6 +65,10 @@ override_dh_auto_install:
cp -f ${SRCDIR}/LICENSE ${DESTDIR}/${ASSETSDIR}
chmod a+x ${DESTDIR}/${BINDIR}/yq
# man
mkdir -p "${DESTDIR}"/"${MANDIR}"
cp "${SRCDIR}"/yq.1 "${DESTDIR}"/"${MANDIR}" \
override_dh_auto_clean:
dh_clean
rm -rf ${CURDIR}/_build
+1 -15
View File
@@ -1,15 +1 @@
strings:
- a: banana
- a: cat
- a: apple
numbers:
- a: 12
- a: 13
- a: 120
obj:
- a: {cat: "adog"}
- a: {cat: "doga"}
- a: apple
cat: purrs
+14 -2
View File
@@ -1,2 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<cat legs="4">BiBi</cat>
<!-- before cat -->
<cat>
<!-- in cat before -->
<x>3<!-- multi
line comment
for x --></x>
<y>
<!-- in y before -->
<d><!-- in d before -->4<!-- in d after --></d>
<!-- in y after -->
</y>
<!-- in_cat_after -->
</cat>
<!-- after cat -->
+4 -2
View File
@@ -1,11 +1,13 @@
module github.com/mikefarah/yq/v4
require (
github.com/a8m/envsubst v1.2.0
github.com/elliotchance/orderedmap v1.4.0
github.com/fatih/color v1.13.0
github.com/goccy/go-yaml v1.9.4
github.com/jinzhu/copier v0.3.4
github.com/goccy/go-yaml v1.9.5
github.com/jinzhu/copier v0.3.5
github.com/magiconair/properties v1.8.5
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
github.com/spf13/cobra v1.3.0
github.com/timtadh/lexmachine v0.2.2
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
+8 -4
View File
@@ -50,6 +50,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/a8m/envsubst v1.2.0 h1:yvzAhJD2QKdo35Ut03wIfXQmg+ta3wC/1bskfZynz+Q=
github.com/a8m/envsubst v1.2.0/go.mod h1:PpvLvNWa+Rvu/10qXmFbFiGICIU5hZvFJNPCCkUaObg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -123,8 +125,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.9.4 h1:S0GCYjwHKVI6IHqio7QWNKNThUl6NLzFd/g8Z65Axw8=
github.com/goccy/go-yaml v1.9.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0=
github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -235,8 +237,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
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.3.4 h1:mfU6jI9PtCeUjkjQ322dlff9ELjGDu975C2p/nrubVI=
github.com/jinzhu/copier v0.3.4/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -292,6 +294,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
+10 -2
View File
@@ -136,7 +136,12 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP
n.Node.Value = ""
}
n.Node.Kind = other.Node.Kind
n.Node.Tag = other.Node.Tag
// don't clobber custom tags...
if strings.HasPrefix(n.Node.Tag, "!!") || n.Node.Tag == "" {
n.Node.Tag = other.Node.Tag
}
n.Node.Alias = other.Node.Alias
if !prefs.DontOverWriteAnchor {
@@ -145,7 +150,10 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP
// merge will pickup the style of the new thing
// when autocreating nodes
if n.Node.Style == 0 {
//
// if this is an empty map or empty array, use the style of other node.
if n.Node.Style == 0 || (n.Node.Kind != yaml.ScalarNode && len(n.Node.Content) == 0) {
n.Node.Style = other.Node.Style
}
+97 -16
View File
@@ -4,6 +4,7 @@ import (
"encoding/xml"
"fmt"
"io"
"strings"
"unicode"
"golang.org/x/net/html/charset"
@@ -31,15 +32,15 @@ func InputFormatFromString(format string) (InputFormat, error) {
type xmlDecoder struct {
reader io.Reader
attributePrefix string
contentPrefix string
contentName string
finished bool
}
func NewXmlDecoder(attributePrefix string, contentPrefix string) Decoder {
if contentPrefix == "" {
contentPrefix = "content"
func NewXmlDecoder(attributePrefix string, contentName string) Decoder {
if contentName == "" {
contentName = "content"
}
return &xmlDecoder{attributePrefix: attributePrefix, contentPrefix: contentPrefix, finished: false}
return &xmlDecoder{attributePrefix: attributePrefix, contentName: contentName, finished: false}
}
func (dec *xmlDecoder) Init(reader io.Reader) {
@@ -60,20 +61,41 @@ func (dec *xmlDecoder) createSequence(nodes []*xmlNode) (*yaml.Node, error) {
return yamlNode, nil
}
func (dec *xmlDecoder) processComment(c string) string {
if c == "" {
return ""
}
return "#" + strings.TrimRight(c, " ")
}
func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
yamlNode := &yaml.Node{Kind: yaml.MappingNode, HeadComment: n.Comment}
log.Debug("createMap: headC: %v, footC: %v", n.HeadComment, n.FootComment)
yamlNode := &yaml.Node{Kind: yaml.MappingNode}
if len(n.Data) > 0 {
label := dec.contentPrefix
yamlNode.Content = append(yamlNode.Content, createScalarNode(label, label), createScalarNode(n.Data, n.Data))
label := dec.contentName
labelNode := createScalarNode(label, label)
labelNode.HeadComment = dec.processComment(n.HeadComment)
labelNode.FootComment = dec.processComment(n.FootComment)
yamlNode.Content = append(yamlNode.Content, labelNode, createScalarNode(n.Data, n.Data))
}
for _, keyValuePair := range n.Children {
for i, keyValuePair := range n.Children {
label := keyValuePair.K
children := keyValuePair.V
labelNode := createScalarNode(label, label)
var valueNode *yaml.Node
var err error
if i == 0 {
labelNode.HeadComment = dec.processComment(n.HeadComment)
}
// if i == len(n.Children)-1 {
labelNode.FootComment = dec.processComment(keyValuePair.FootComment)
// }
log.Debug("len of children in %v is %v", label, len(children))
if len(children) > 1 {
valueNode, err = dec.createSequence(children)
@@ -81,6 +103,13 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
return nil, err
}
} else {
// comment hack for maps of scalars
// if the value is a scalar, the head comment of the scalar needs to go on the key?
// add tests for <z/> as well as multiple <ds> of inputXmlWithComments > yaml
if len(children[0].Children) == 0 && children[0].HeadComment != "" {
labelNode.HeadComment = labelNode.HeadComment + "\n" + strings.TrimSpace(children[0].HeadComment)
children[0].HeadComment = ""
}
valueNode, err = dec.convertToYamlNode(children[0])
if err != nil {
return nil, err
@@ -97,7 +126,11 @@ func (dec *xmlDecoder) convertToYamlNode(n *xmlNode) (*yaml.Node, error) {
return dec.createMap(n)
}
scalar := createScalarNode(n.Data, n.Data)
scalar.HeadComment = n.Comment
log.Debug("scalar headC: %v, footC: %v", n.HeadComment, n.FootComment)
scalar.HeadComment = dec.processComment(n.HeadComment)
scalar.LineComment = dec.processComment(n.LineComment)
scalar.FootComment = dec.processComment(n.FootComment)
return scalar, nil
}
@@ -124,14 +157,17 @@ func (dec *xmlDecoder) Decode(rootYamlNode *yaml.Node) error {
}
type xmlNode struct {
Children []*xmlChildrenKv
Comment string
Data string
Children []*xmlChildrenKv
HeadComment string
FootComment string
LineComment string
Data string
}
type xmlChildrenKv struct {
K string
V []*xmlNode
K string
V []*xmlNode
FootComment string
}
// AddChild appends a node to the list of children
@@ -158,6 +194,7 @@ type element struct {
parent *element
n *xmlNode
label string
state string
}
// this code is heavily based on https://github.com/basgys/goxml2json
@@ -183,6 +220,8 @@ func (dec *xmlDecoder) decodeXml(root *xmlNode) error {
switch se := t.(type) {
case xml.StartElement:
log.Debug("start element %v", se.Name.Local)
elem.state = "started"
// Build new a new current element and link it to its parent
elem = &element{
parent: elem,
@@ -197,7 +236,13 @@ func (dec *xmlDecoder) decodeXml(root *xmlNode) error {
case xml.CharData:
// Extract XML data (if any)
elem.n.Data = trimNonGraphic(string(se))
if elem.n.Data != "" {
elem.state = "chardata"
log.Debug("chardata [%v] for %v", elem.n.Data, elem.label)
}
case xml.EndElement:
log.Debug("end element %v", elem.label)
elem.state = "finished"
// And add it to its parent list
if elem.parent != nil {
elem.parent.n.AddChild(elem.label, elem.n)
@@ -206,13 +251,49 @@ func (dec *xmlDecoder) decodeXml(root *xmlNode) error {
// Then change the current element to its parent
elem = elem.parent
case xml.Comment:
elem.n.Comment = trimNonGraphic(string(xml.CharData(se)))
commentStr := string(xml.CharData(se))
if elem.state == "started" {
applyFootComment(elem, commentStr)
} else if elem.state == "chardata" {
log.Debug("got a line comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
elem.n.LineComment = joinFilter([]string{elem.n.LineComment, commentStr})
} else {
log.Debug("got a head comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
elem.n.HeadComment = joinFilter([]string{elem.n.HeadComment, commentStr})
}
}
}
return nil
}
func applyFootComment(elem *element, commentStr string) {
// first lets try to put the comment on the last child
if len(elem.n.Children) > 0 {
lastChildIndex := len(elem.n.Children) - 1
childKv := elem.n.Children[lastChildIndex]
log.Debug("got a foot comment for %v: [%v]", childKv.K, commentStr)
childKv.FootComment = joinFilter([]string{elem.n.FootComment, commentStr})
} else {
log.Debug("got a foot comment for %v: [%v]", elem.label, commentStr)
elem.n.FootComment = joinFilter([]string{elem.n.FootComment, commentStr})
}
}
func joinFilter(rawStrings []string) string {
stringsToJoin := make([]string, 0)
for _, str := range rawStrings {
if str != "" {
stringsToJoin = append(stringsToJoin, str)
}
}
return strings.Join(stringsToJoin, " ")
}
// trimNonGraphic returns a slice of the string s, with all leading and trailing
// non graphic characters and spaces removed.
//
+87 -14
View File
@@ -4,8 +4,10 @@ Add behaves differently according to the type of the LHS:
* arrays: concatenate
* number scalars: arithmetic addition
* string scalars: concatenate
* maps: shallow merge (use the multiply operator (`*`) to deeply merge)
Use `+=` as a relative append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
## Concatenate and assign arrays
Given a sample.yml file of:
@@ -18,7 +20,7 @@ a:
```
then
```bash
yq eval '.a.b += ["cow"]' sample.yml
yq '.a.b += ["cow"]' sample.yml
```
will output
```yaml
@@ -42,7 +44,7 @@ b:
```
then
```bash
yq eval '.a + .b' sample.yml
yq '.a + .b' sample.yml
```
will output
```yaml
@@ -61,7 +63,7 @@ a:
```
then
```bash
yq eval '.a + null' sample.yml
yq '.a + null' sample.yml
```
will output
```yaml
@@ -77,7 +79,7 @@ a:
```
then
```bash
yq eval '.a + {"cat": "meow"}' sample.yml
yq '.a + {"cat": "meow"}' sample.yml
```
will output
```yaml
@@ -94,7 +96,7 @@ a:
```
then
```bash
yq eval '.a + "hello"' sample.yml
yq '.a + "hello"' sample.yml
```
will output
```yaml
@@ -115,7 +117,7 @@ b:
```
then
```bash
yq eval '.a = .a + .b' sample.yml
yq '.a = .a + .b' sample.yml
```
will output
```yaml
@@ -141,7 +143,7 @@ b:
```
then
```bash
yq eval '.a += .b' sample.yml
yq '.a += .b' sample.yml
```
will output
```yaml
@@ -169,7 +171,7 @@ a:
```
then
```bash
yq eval '.a[].b += ["mouse"]' sample.yml
yq '.a[].b += ["mouse"]' sample.yml
```
will output
```yaml
@@ -193,7 +195,7 @@ b: meow
```
then
```bash
yq eval '.a = .a + .b' sample.yml
yq '.a = .a + .b' sample.yml
```
will output
```yaml
@@ -211,7 +213,7 @@ b: 4.9
```
then
```bash
yq eval '.a = .a + .b' sample.yml
yq '.a = .a + .b' sample.yml
```
will output
```yaml
@@ -229,7 +231,7 @@ b: 4
```
then
```bash
yq eval '.a = .a + .b' sample.yml
yq '.a = .a + .b' sample.yml
```
will output
```yaml
@@ -245,7 +247,7 @@ b: 5
```
then
```bash
yq eval '.[] += 1' sample.yml
yq '.[] += 1' sample.yml
```
will output
```yaml
@@ -258,10 +260,81 @@ Adding to null simply returns the rhs
Running
```bash
yq eval --null-input 'null + "cat"'
yq --null-input 'null + "cat"'
```
will output
```yaml
cat
```
## Add maps to shallow merge
Adding objects together shallow merges them. Use `*` to deeply merge.
Given a sample.yml file of:
```yaml
a:
thing:
name: Astuff
value: x
a1: cool
b:
thing:
name: Bstuff
legs: 3
b1: neat
```
then
```bash
yq '.a += .b' sample.yml
```
will output
```yaml
a:
thing:
name: Bstuff
legs: 3
a1: cool
b1: neat
b:
thing:
name: Bstuff
legs: 3
b1: neat
```
## Custom types: that are really strings
When custom tags are encountered, yq will try to decode the underlying type.
Given a sample.yml file of:
```yaml
a: !horse cat
b: !goat _meow
```
then
```bash
yq '.a += .b' sample.yml
```
will output
```yaml
a: !horse cat_meow
b: !goat _meow
```
## Custom types: that are really numbers
When custom tags are encountered, yq will try to decode the underlying type.
Given a sample.yml file of:
```yaml
a: !horse 1.2
b: !goat 2.3
```
then
```bash
yq '.a += .b' sample.yml
```
will output
```yaml
a: !horse 3.5
b: !goat 2.3
```
@@ -9,7 +9,7 @@ a: bridge
```
then
```bash
yq eval '.a // "hello"' sample.yml
yq '.a // "hello"' sample.yml
```
will output
```yaml
@@ -23,7 +23,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.a // "hello"' sample.yml
yq '.a // "hello"' sample.yml
```
will output
```yaml
@@ -37,7 +37,7 @@ a: ~
```
then
```bash
yq eval '.a // "hello"' sample.yml
yq '.a // "hello"' sample.yml
```
will output
```yaml
@@ -51,7 +51,7 @@ a: false
```
then
```bash
yq eval '.a // "hello"' sample.yml
yq '.a // "hello"' sample.yml
```
will output
```yaml
@@ -66,7 +66,7 @@ b: cat
```
then
```bash
yq eval '.a // .b' sample.yml
yq '.a // .b' sample.yml
```
will output
```yaml
@@ -25,7 +25,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[4] | explode(.)' sample.yml
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
@@ -55,7 +55,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[4] | explode(.)' sample.yml
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
@@ -87,7 +87,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[4] | explode(.)' sample.yml
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
@@ -103,7 +103,7 @@ a: &billyBob cat
```
then
```bash
yq eval '.a | anchor' sample.yml
yq '.a | anchor' sample.yml
```
will output
```yaml
@@ -117,7 +117,7 @@ a: cat
```
then
```bash
yq eval '.a anchor = "foobar"' sample.yml
yq '.a anchor = "foobar"' sample.yml
```
will output
```yaml
@@ -132,7 +132,7 @@ a:
```
then
```bash
yq eval '.a anchor |= .b' sample.yml
yq '.a anchor |= .b' sample.yml
```
will output
```yaml
@@ -148,7 +148,7 @@ a: *billyBob
```
then
```bash
yq eval '.a | alias' sample.yml
yq '.a | alias' sample.yml
```
will output
```yaml
@@ -163,7 +163,7 @@ a: cat
```
then
```bash
yq eval '.a alias = "meow"' sample.yml
yq '.a alias = "meow"' sample.yml
```
will output
```yaml
@@ -179,7 +179,7 @@ a: cat
```
then
```bash
yq eval '.a alias = ""' sample.yml
yq '.a alias = ""' sample.yml
```
will output
```yaml
@@ -196,7 +196,7 @@ a:
```
then
```bash
yq eval '.a alias |= .f' sample.yml
yq '.a alias |= .f' sample.yml
```
will output
```yaml
@@ -213,7 +213,7 @@ f:
```
then
```bash
yq eval 'explode(.f)' sample.yml
yq 'explode(.f)' sample.yml
```
will output
```yaml
@@ -229,7 +229,7 @@ a: mike
```
then
```bash
yq eval 'explode(.a)' sample.yml
yq 'explode(.a)' sample.yml
```
will output
```yaml
@@ -245,7 +245,7 @@ f:
```
then
```bash
yq eval 'explode(.f)' sample.yml
yq 'explode(.f)' sample.yml
```
will output
```yaml
@@ -278,7 +278,7 @@ foobar:
```
then
```bash
yq eval 'explode(.)' sample.yml
yq 'explode(.)' sample.yml
```
will output
```yaml
@@ -317,7 +317,7 @@ thingTwo:
```
then
```bash
yq eval '.thingOne |= explode(.) * {"value": false}' sample.yml
yq '.thingOne |= explode(.) * {"value": false}' sample.yml
```
will output
```yaml
+12 -12
View File
@@ -10,7 +10,7 @@ This will do a similar thing to the plain form, however, the RHS expression is r
## Create yaml file
Running
```bash
yq eval --null-input '.a.b = "cat" | .x = "frog"'
yq --null-input '.a.b = "cat" | .x = "frog"'
```
will output
```yaml
@@ -28,7 +28,7 @@ a:
```
then
```bash
yq eval '.a |= .b' sample.yml
yq '.a |= .b' sample.yml
```
will output
```yaml
@@ -45,7 +45,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] |= . * 2' sample.yml
yq '.[] |= . * 2' sample.yml
```
will output
```yaml
@@ -84,7 +84,7 @@ b: sibling
```
then
```bash
yq eval '.a = .b' sample.yml
yq '.a = .b' sample.yml
```
will output
```yaml
@@ -101,7 +101,7 @@ c: fieldC
```
then
```bash
yq eval '(.a, .c) = "potatoe"' sample.yml
yq '(.a, .c) = "potatoe"' sample.yml
```
will output
```yaml
@@ -118,7 +118,7 @@ a:
```
then
```bash
yq eval '.a.b = "frog"' sample.yml
yq '.a.b = "frog"' sample.yml
```
will output
```yaml
@@ -136,7 +136,7 @@ a:
```
then
```bash
yq eval '.a.b |= "frog"' sample.yml
yq '.a.b |= "frog"' sample.yml
```
will output
```yaml
@@ -155,7 +155,7 @@ a:
```
then
```bash
yq eval '(.a[] | select(. == "apple")) = "frog"' sample.yml
yq '(.a[] | select(. == "apple")) = "frog"' sample.yml
```
will output
```yaml
@@ -173,7 +173,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '(.[] | select(. == "*andy")) = "bogs"' sample.yml
yq '(.[] | select(. == "*andy")) = "bogs"' sample.yml
```
will output
```yaml
@@ -189,7 +189,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.a.b |= "bogs"' sample.yml
yq '.a.b |= "bogs"' sample.yml
```
will output
```yaml
@@ -205,7 +205,7 @@ a: &cool cat
```
then
```bash
yq eval '.a = "dog"' sample.yml
yq '.a = "dog"' sample.yml
```
will output
```yaml
@@ -219,7 +219,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.a.b.[0] |= "bogs"' sample.yml
yq '.a.b.[0] |= "bogs"' sample.yml
```
will output
```yaml
+16 -16
View File
@@ -13,7 +13,7 @@ These are most commonly used with the `select` operator to filter particular nod
## `or` example
Running
```bash
yq eval --null-input 'true or false'
yq --null-input 'true or false'
```
will output
```yaml
@@ -23,7 +23,7 @@ true
## `and` example
Running
```bash
yq eval --null-input 'true and false'
yq --null-input 'true and false'
```
will output
```yaml
@@ -42,7 +42,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '[.[] | select(.a == "cat" or .b == "dog")]' sample.yml
yq '[.[] | select(.a == "cat" or .b == "dog")]' sample.yml
```
will output
```yaml
@@ -60,7 +60,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'any' sample.yml
yq 'any' sample.yml
```
will output
```yaml
@@ -74,7 +74,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'any' sample.yml
yq 'any' sample.yml
```
will output
```yaml
@@ -93,7 +93,7 @@ b:
```
then
```bash
yq eval '.[] |= any_c(. == "awesome")' sample.yml
yq '.[] |= any_c(. == "awesome")' sample.yml
```
will output
```yaml
@@ -109,7 +109,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'all' sample.yml
yq 'all' sample.yml
```
will output
```yaml
@@ -123,7 +123,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'all' sample.yml
yq 'all' sample.yml
```
will output
```yaml
@@ -142,7 +142,7 @@ b:
```
then
```bash
yq eval '.[] |= all_c(tag == "!!str")' sample.yml
yq '.[] |= all_c(tag == "!!str")' sample.yml
```
will output
```yaml
@@ -153,7 +153,7 @@ b: false
## Not true is false
Running
```bash
yq eval --null-input 'true | not'
yq --null-input 'true | not'
```
will output
```yaml
@@ -163,7 +163,7 @@ false
## Not false is true
Running
```bash
yq eval --null-input 'false | not'
yq --null-input 'false | not'
```
will output
```yaml
@@ -173,7 +173,7 @@ true
## String values considered to be true
Running
```bash
yq eval --null-input '"cat" | not'
yq --null-input '"cat" | not'
```
will output
```yaml
@@ -183,7 +183,7 @@ false
## Empty string value considered to be true
Running
```bash
yq eval --null-input '"" | not'
yq --null-input '"" | not'
```
will output
```yaml
@@ -193,7 +193,7 @@ false
## Numbers are considered to be true
Running
```bash
yq eval --null-input '1 | not'
yq --null-input '1 | not'
```
will output
```yaml
@@ -203,7 +203,7 @@ false
## Zero is considered to be true
Running
```bash
yq eval --null-input '0 | not'
yq --null-input '0 | not'
```
will output
```yaml
@@ -213,7 +213,7 @@ false
## Null is considered to be false
Running
```bash
yq eval --null-input '~ | not'
yq --null-input '~ | not'
```
will output
```yaml
@@ -6,7 +6,7 @@ This creates an array using the expression between the square brackets.
## Collect empty
Running
```bash
yq eval --null-input '[]'
yq --null-input '[]'
```
will output
```yaml
@@ -16,7 +16,7 @@ will output
## Collect single
Running
```bash
yq eval --null-input '["cat"]'
yq --null-input '["cat"]'
```
will output
```yaml
@@ -31,7 +31,7 @@ b: dog
```
then
```bash
yq eval '[.a, .b]' sample.yml
yq '[.a, .b]' sample.yml
```
will output
```yaml
+10 -10
View File
@@ -17,7 +17,7 @@ a: cat
```
then
```bash
yq eval '.a lineComment="single"' sample.yml
yq '.a lineComment="single"' sample.yml
```
will output
```yaml
@@ -32,7 +32,7 @@ b: dog
```
then
```bash
yq eval '.. lineComment |= .' sample.yml
yq '.. lineComment |= .' sample.yml
```
will output
```yaml
@@ -47,7 +47,7 @@ a: cat
```
then
```bash
yq eval '. headComment="single"' sample.yml
yq '. headComment="single"' sample.yml
```
will output
```yaml
@@ -63,7 +63,7 @@ a: cat
```
then
```bash
yq eval '. footComment=.a' sample.yml
yq '. footComment=.a' sample.yml
```
will output
```yaml
@@ -80,7 +80,7 @@ b: dog # leave this
```
then
```bash
yq eval '.a lineComment=""' sample.yml
yq '.a lineComment=""' sample.yml
```
will output
```yaml
@@ -99,7 +99,7 @@ b: # key comment
```
then
```bash
yq eval '... comments=""' sample.yml
yq '... comments=""' sample.yml
```
will output
```yaml
@@ -114,7 +114,7 @@ a: cat # meow
```
then
```bash
yq eval '.a | lineComment' sample.yml
yq '.a | lineComment' sample.yml
```
will output
```yaml
@@ -132,7 +132,7 @@ a: cat # meow
```
then
```bash
yq eval '. | headComment' sample.yml
yq '. | headComment' sample.yml
```
will output
```yaml
@@ -151,7 +151,7 @@ a: cat # meow
```
then
```bash
yq eval 'headComment' sample.yml
yq 'headComment' sample.yml
```
will output
```yaml
@@ -171,7 +171,7 @@ a: cat # meow
```
then
```bash
yq eval '. | footComment' sample.yml
yq '. | footComment' sample.yml
```
will output
```yaml
+5 -5
View File
@@ -13,7 +13,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'contains(["baz", "bar"])' sample.yml
yq 'contains(["baz", "bar"])' sample.yml
```
will output
```yaml
@@ -32,7 +32,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'contains({"bar": [{"barp": 12}]})' sample.yml
yq 'contains({"bar": [{"barp": 12}]})' sample.yml
```
will output
```yaml
@@ -51,7 +51,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'contains({"foo": 12, "bar": [{"barp": 15}]})' sample.yml
yq 'contains({"foo": 12, "bar": [{"barp": 15}]})' sample.yml
```
will output
```yaml
@@ -65,7 +65,7 @@ foobar
```
then
```bash
yq eval 'contains("bar")' sample.yml
yq 'contains("bar")' sample.yml
```
will output
```yaml
@@ -79,7 +79,7 @@ meow
```
then
```bash
yq eval 'contains("meow")' sample.yml
yq 'contains("meow")' sample.yml
```
will output
```yaml
@@ -5,7 +5,7 @@ This is used to construct objects (or maps). This can be used against existing y
## Collect empty object
Running
```bash
yq eval --null-input '{}'
yq --null-input '{}'
```
will output
```yaml
@@ -19,7 +19,7 @@ name: Mike
```
then
```bash
yq eval '{"wrap": .}' sample.yml
yq '{"wrap": .}' sample.yml
```
will output
```yaml
@@ -37,7 +37,7 @@ pets:
```
then
```bash
yq eval '{.name: .pets.[]}' sample.yml
yq '{.name: .pets.[]}' sample.yml
```
will output
```yaml
@@ -60,7 +60,7 @@ pets:
```
then
```bash
yq eval '{.name: .pets.[]}' sample.yml
yq '{.name: .pets.[]}' sample.yml
```
will output
```yaml
@@ -73,7 +73,7 @@ Rosey: sheep
## Creating yaml from scratch
Running
```bash
yq eval --null-input '{"wrap": "frog"}'
yq --null-input '{"wrap": "frog"}'
```
will output
```yaml
+7 -7
View File
@@ -10,7 +10,7 @@ b: dog
```
then
```bash
yq eval 'del(.b)' sample.yml
yq 'del(.b)' sample.yml
```
will output
```yaml
@@ -26,7 +26,7 @@ a:
```
then
```bash
yq eval 'del(.a.a1)' sample.yml
yq 'del(.a.a1)' sample.yml
```
will output
```yaml
@@ -43,7 +43,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'del(.[1])' sample.yml
yq 'del(.[1])' sample.yml
```
will output
```yaml
@@ -59,7 +59,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'del(.[0].a)' sample.yml
yq 'del(.[0].a)' sample.yml
```
will output
```yaml
@@ -74,7 +74,7 @@ b: dog
```
then
```bash
yq eval 'del(.c)' sample.yml
yq 'del(.c)' sample.yml
```
will output
```yaml
@@ -91,7 +91,7 @@ c: bat
```
then
```bash
yq eval 'del( .[] | select(. == "*at") )' sample.yml
yq 'del( .[] | select(. == "*at") )' sample.yml
```
will output
```yaml
@@ -109,7 +109,7 @@ a:
```
then
```bash
yq eval 'del(.. | select(has("name")).name)' sample.yml
yq 'del(.. | select(has("name")).name)' sample.yml
```
will output
```yaml
+5 -5
View File
@@ -11,7 +11,7 @@ a: frog
```
then
```bash
yq eval '.a | documentIndex' sample.yml
yq '.a | documentIndex' sample.yml
```
will output
```yaml
@@ -29,7 +29,7 @@ a: frog
```
then
```bash
yq eval '.a | di' sample.yml
yq '.a | di' sample.yml
```
will output
```yaml
@@ -47,7 +47,7 @@ a: frog
```
then
```bash
yq eval 'select(documentIndex == 1)' sample.yml
yq 'select(documentIndex == 1)' sample.yml
```
will output
```yaml
@@ -63,7 +63,7 @@ a: frog
```
then
```bash
yq eval 'select(di == 1)' sample.yml
yq 'select(di == 1)' sample.yml
```
will output
```yaml
@@ -79,7 +79,7 @@ a: frog
```
then
```bash
yq eval '.a | ({"match": ., "doc": documentIndex})' sample.yml
yq '.a | ({"match": ., "doc": documentIndex})' sample.yml
```
will output
```yaml
+17 -17
View File
@@ -30,7 +30,7 @@ a:
```
then
```bash
yq eval '.b = (.a | to_json)' sample.yml
yq '.b = (.a | to_json)' sample.yml
```
will output
```yaml
@@ -52,7 +52,7 @@ a:
```
then
```bash
yq eval '.b = (.a | to_json(0))' sample.yml
yq '.b = (.a | to_json(0))' sample.yml
```
will output
```yaml
@@ -71,7 +71,7 @@ a:
```
then
```bash
yq eval '.b = (.a | @json)' sample.yml
yq '.b = (.a | @json)' sample.yml
```
will output
```yaml
@@ -89,7 +89,7 @@ a: '{"cool":"thing"}'
```
then
```bash
yq eval '.a | from_json | ... style=""' sample.yml
yq '.a | from_json | ... style=""' sample.yml
```
will output
```yaml
@@ -104,7 +104,7 @@ a:
```
then
```bash
yq eval '.b = (.a | @props)' sample.yml
yq '.b = (.a | @props)' sample.yml
```
will output
```yaml
@@ -125,7 +125,7 @@ a:
```
then
```bash
yq eval '.b = (.a | to_yaml)' sample.yml
yq '.b = (.a | to_yaml)' sample.yml
```
will output
```yaml
@@ -148,7 +148,7 @@ a:
```
then
```bash
yq eval '.b = (.a | to_yaml(8))' sample.yml
yq '.b = (.a | to_yaml(8))' sample.yml
```
will output
```yaml
@@ -167,7 +167,7 @@ a: 'foo: bar'
```
then
```bash
yq eval '.b = (.a | from_yaml)' sample.yml
yq '.b = (.a | from_yaml)' sample.yml
```
will output
```yaml
@@ -186,7 +186,7 @@ a: |
```
then
```bash
yq eval '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml
yq '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml
```
will output
```yaml
@@ -202,7 +202,7 @@ a: 'foo: bar'
```
then
```bash
yq eval '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml
yq '.a |= (from_yaml | .foo = "cat" | to_yaml)' sample.yml
```
will output
```yaml
@@ -221,7 +221,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '@csv' sample.yml
yq '@csv' sample.yml
```
will output
```yaml
@@ -242,7 +242,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '@csv' sample.yml
yq '@csv' sample.yml
```
will output
```yaml
@@ -266,7 +266,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '@tsv' sample.yml
yq '@tsv' sample.yml
```
will output
```yaml
@@ -284,7 +284,7 @@ a:
```
then
```bash
yq eval '.a | to_xml' sample.yml
yq '.a | to_xml' sample.yml
```
will output
```yaml
@@ -304,7 +304,7 @@ a:
```
then
```bash
yq eval '.a | @xml' sample.yml
yq '.a | @xml' sample.yml
```
will output
```yaml
@@ -322,7 +322,7 @@ a:
```
then
```bash
yq eval '{"cat": .a | to_xml(1)}' sample.yml
yq '{"cat": .a | to_xml(1)}' sample.yml
```
will output
```yaml
@@ -339,7 +339,7 @@ a: <foo>bar</foo>
```
then
```bash
yq eval '.b = (.a | from_xml)' sample.yml
yq '.b = (.a | from_xml)' sample.yml
```
will output
```yaml
+7 -7
View File
@@ -10,7 +10,7 @@ b: 2
```
then
```bash
yq eval 'to_entries' sample.yml
yq 'to_entries' sample.yml
```
will output
```yaml
@@ -28,7 +28,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'to_entries' sample.yml
yq 'to_entries' sample.yml
```
will output
```yaml
@@ -45,7 +45,7 @@ null
```
then
```bash
yq eval 'to_entries' sample.yml
yq 'to_entries' sample.yml
```
will output
```yaml
@@ -59,7 +59,7 @@ b: 2
```
then
```bash
yq eval 'to_entries | from_entries' sample.yml
yq 'to_entries | from_entries' sample.yml
```
will output
```yaml
@@ -77,7 +77,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'to_entries | from_entries' sample.yml
yq 'to_entries | from_entries' sample.yml
```
will output
```yaml
@@ -93,7 +93,7 @@ b: 2
```
then
```bash
yq eval 'with_entries(.key |= "KEY_" + .)' sample.yml
yq 'with_entries(.key |= "KEY_" + .)' sample.yml
```
will output
```yaml
@@ -111,7 +111,7 @@ c:
```
then
```bash
yq eval 'with_entries(select(.value | has("b")))' sample.yml
yq 'with_entries(select(.value | has("b")))' sample.yml
```
will output
```yaml
@@ -1,11 +1,26 @@
# Env Variable Operators
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.
These operators are used to handle environment variables usage in expressions and documents. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read.
There are three operators:
- `env` which takes a single environment variable name and parse the variable as a yaml node (be it a map, array, string, number of boolean)
- `strenv` which also takes a single environment variable name, and always parses the variable as a string.
- `envsubst` which you pipe strings into and it interpolates environment variables in strings using [envsubst](https://github.com/a8m/envsubst).
## Tip
To replace environment variables across all values in a document, `envsubst` can be used with the recursive descent operator
as follows:
```bash
yq '(.. | select(tag == "!!str")) |= envsubst' file.yaml
```
## Read string environment variable
Running
```bash
myenv="cat meow" yq eval --null-input '.a = env(myenv)'
myenv="cat meow" yq --null-input '.a = env(myenv)'
```
will output
```yaml
@@ -15,7 +30,7 @@ a: cat meow
## Read boolean environment variable
Running
```bash
myenv="true" yq eval --null-input '.a = env(myenv)'
myenv="true" yq --null-input '.a = env(myenv)'
```
will output
```yaml
@@ -25,7 +40,7 @@ a: true
## Read numeric environment variable
Running
```bash
myenv="12" yq eval --null-input '.a = env(myenv)'
myenv="12" yq --null-input '.a = env(myenv)'
```
will output
```yaml
@@ -35,7 +50,7 @@ a: 12
## Read yaml environment variable
Running
```bash
myenv="{b: fish}" yq eval --null-input '.a = env(myenv)'
myenv="{b: fish}" yq --null-input '.a = env(myenv)'
```
will output
```yaml
@@ -45,7 +60,7 @@ a: {b: fish}
## Read boolean environment variable as a string
Running
```bash
myenv="true" yq eval --null-input '.a = strenv(myenv)'
myenv="true" yq --null-input '.a = strenv(myenv)'
```
will output
```yaml
@@ -55,7 +70,7 @@ a: "true"
## Read numeric environment variable as a string
Running
```bash
myenv="12" yq eval --null-input '.a = strenv(myenv)'
myenv="12" yq --null-input '.a = strenv(myenv)'
```
will output
```yaml
@@ -70,10 +85,54 @@ dog: woof
```
then
```bash
myenv="cat" yq eval '.[env(myenv)]' sample.yml
myenv="cat" yq '.[env(myenv)]' sample.yml
```
will output
```yaml
meow
```
## Replace strings with envsubst
Running
```bash
myenv="cat" yq --null-input '"the ${myenv} meows" | envsubst'
```
will output
```yaml
the cat meows
```
## Replace strings with envsubst, missing variables
Running
```bash
myenv="cat" yq --null-input '"the ${myenvnonexisting} meows" | envsubst'
```
will output
```yaml
the meows
```
## Replace strings with envsubst, missing variables with defaults
Running
```bash
myenv="cat" yq --null-input '"the ${myenvnonexisting-dog} meows" | envsubst'
```
will output
```yaml
the dog meows
```
## Replace string environment variable in document
Given a sample.yml file of:
```yaml
v: ${myenv}
```
then
```bash
myenv="cat meow" yq '.v |= envsubst' sample.yml
```
will output
```yaml
v: cat meow
```
+7 -7
View File
@@ -21,7 +21,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | (. == "*at")' sample.yml
yq '.[] | (. == "*at")' sample.yml
```
will output
```yaml
@@ -39,7 +39,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | (. != "*at")' sample.yml
yq '.[] | (. != "*at")' sample.yml
```
will output
```yaml
@@ -57,7 +57,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | (. == 4)' sample.yml
yq '.[] | (. == 4)' sample.yml
```
will output
```yaml
@@ -75,7 +75,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | (. != 4)' sample.yml
yq '.[] | (. != 4)' sample.yml
```
will output
```yaml
@@ -87,7 +87,7 @@ true
## Match nulls
Running
```bash
yq eval --null-input 'null == ~'
yq --null-input 'null == ~'
```
will output
```yaml
@@ -101,7 +101,7 @@ a: frog
```
then
```bash
yq eval 'select(.b != "thing")' sample.yml
yq 'select(.b != "thing")' sample.yml
```
will output
```yaml
@@ -115,7 +115,7 @@ a: frog
```
then
```bash
yq eval 'select(.b == .c)' sample.yml
yq 'select(.b == .c)' sample.yml
```
will output
```yaml
+3 -3
View File
@@ -17,7 +17,7 @@ a: cat
```
then
```bash
yq eval 'filename' sample.yml
yq 'filename' sample.yml
```
will output
```yaml
@@ -31,7 +31,7 @@ a: cat
```
then
```bash
yq eval 'fileIndex' sample.yml
yq 'fileIndex' sample.yml
```
will output
```yaml
@@ -65,7 +65,7 @@ a: cat
```
then
```bash
yq eval 'fi' sample.yml
yq 'fi' sample.yml
```
will output
```yaml
+4 -4
View File
@@ -12,7 +12,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'flatten' sample.yml
yq 'flatten' sample.yml
```
will output
```yaml
@@ -30,7 +30,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'flatten(1)' sample.yml
yq 'flatten(1)' sample.yml
```
will output
```yaml
@@ -46,7 +46,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'flatten' sample.yml
yq 'flatten' sample.yml
```
will output
```yaml
@@ -61,7 +61,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'flatten' sample.yml
yq 'flatten' sample.yml
```
will output
```yaml
+2 -2
View File
@@ -14,7 +14,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'group_by(.foo)' sample.yml
yq 'group_by(.foo)' sample.yml
```
will output
```yaml
@@ -40,7 +40,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'group_by(.foo)' sample.yml
yq 'group_by(.foo)' sample.yml
```
will output
```yaml
+3 -3
View File
@@ -12,7 +12,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | has("a")' sample.yml
yq '.[] | has("a")' sample.yml
```
will output
```yaml
@@ -36,7 +36,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | select(.a.b | has("c"))' sample.yml
yq '.[] | select(.a.b | has("c"))' sample.yml
```
will output
```yaml
@@ -57,7 +57,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | has(1)' sample.yml
yq '.[] | has(1)' sample.yml
```
will output
```yaml
+7 -6
View File
@@ -5,7 +5,7 @@
yq [eval/eval-all] [expression] files..
eval/e - Apply the expression to each document in each yaml file in sequence
eval/e - (default) 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
@@ -18,32 +18,33 @@ This documentation is also available at https://mikefarah.gitbook.io/yq/
## Read a value:
```bash
yq e '.a.b[0].c' file.yaml
yq '.a.b[0].c' file.yaml
```
## Pipe from STDIN:
```bash
cat file.yaml | yq e '.a.b[0].c' -
cat file.yaml | yq '.a.b[0].c'
```
## Update a yaml file, inplace
```bash
yq e -i '.a.b[0].c = "cool"' file.yaml
yq -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
NAME=mike yq -i '.a.b[0].c = strenv(NAME)' file.yaml
```
## Merge multiple files
```
yq ea '. as $item ireduce ({}; . * $item )' path/to/*.yml
```
Note the use of `ea` to evaluate all files at once (instead of in sequence.)
## Multiple updates to a yaml file
```bash
yq e -i '
yq -i '
.a.b[0].c = "cool" |
.x.y.z = "foobar" |
.person.name = strenv(NAME)
+3 -1
View File
@@ -4,5 +4,7 @@ Add behaves differently according to the type of the LHS:
* arrays: concatenate
* number scalars: arithmetic addition
* string scalars: concatenate
* maps: shallow merge (use the multiply operator (`*`) to deeply merge)
Use `+=` as a relative append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
@@ -1,3 +1,18 @@
# Env Variable Operators
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.
These operators are used to handle environment variables usage in expressions and documents. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read.
There are three operators:
- `env` which takes a single environment variable name and parse the variable as a yaml node (be it a map, array, string, number of boolean)
- `strenv` which also takes a single environment variable name, and always parses the variable as a string.
- `envsubst` which you pipe strings into and it interpolates environment variables in strings using [envsubst](https://github.com/a8m/envsubst).
## Tip
To replace environment variables across all values in a document, `envsubst` can be used with the recursive descent operator
as follows:
```bash
yq '(.. | select(tag == "!!str")) |= envsubst' file.yaml
```
@@ -10,9 +10,10 @@ Note that when merging objects, this operator returns the merged object (not the
### 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
- `+` append arrays
- `d` deeply merge arrays
- `?` only merge _existing_ fields
- `n` only merge _new_ fields
### Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory.
@@ -8,7 +8,7 @@ This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
```bash
yq eval '.. style= "flow"' file.yaml
yq '.. style= "flow"' file.yaml
```
## match values and map keys form `...`
@@ -17,5 +17,5 @@ The also includes map keys in the results set. This is particularly useful in YA
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
```bash
yq eval '... style= "flow"' file.yaml
yq '... style= "flow"' file.yaml
```
+4 -2
View File
@@ -5,7 +5,9 @@ The Sort Keys operator sorts maps by their keys (based on their string value). T
Sort is particularly useful for diffing two different yaml documents:
```bash
yq eval -i -P 'sort_keys(..)' file1.yml
yq eval -i -P 'sort_keys(..)' file2.yml
yq -i -P 'sort_keys(..)' file1.yml
yq -i -P 'sort_keys(..)' file2.yml
diff file1.yml file2.yml
```
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if your are using merge anchors.
@@ -17,13 +17,13 @@ a: |
Using `$( exp )` wont work, as it will trim the trailing new line.
```
m=$(echo "cat\n") yq e -n '.a = strenv(m)'
m=$(echo "cat\n") yq -n '.a = strenv(m)'
a: cat
```
However, using printf works:
```
printf -v m "cat\n" ; m="$m" yq e -n '.a = strenv(m)'
printf -v m "cat\n" ; m="$m" yq -n '.a = strenv(m)'
a: |
cat
```
@@ -31,7 +31,7 @@ a: |
As well as having multiline expressions:
```
m="cat
" yq e -n '.a = strenv(m)'
" yq -n '.a = strenv(m)'
a: |
cat
```
@@ -40,5 +40,5 @@ Similarly, if you're trying to set the content from a file, and want a trailing
```
IFS= read -rd '' output < <(cat my_file)
output=$output ./yq e '.data.values = strenv(output)' first.yml
output=$output ./yq '.data.values = strenv(output)' first.yml
```
+7 -7
View File
@@ -10,7 +10,7 @@ cat: meow
```
then
```bash
yq eval 'keys' sample.yml
yq 'keys' sample.yml
```
will output
```yaml
@@ -26,7 +26,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'keys' sample.yml
yq 'keys' sample.yml
```
will output
```yaml
@@ -43,7 +43,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[1] | key' sample.yml
yq '.[1] | key' sample.yml
```
will output
```yaml
@@ -57,7 +57,7 @@ a: thing
```
then
```bash
yq eval '.a | key' sample.yml
yq '.a | key' sample.yml
```
will output
```yaml
@@ -71,7 +71,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'key' sample.yml
yq 'key' sample.yml
```
will output
```yaml
@@ -86,7 +86,7 @@ a:
```
then
```bash
yq eval '(.a.x | key) = "meow"' sample.yml
yq '(.a.x | key) = "meow"' sample.yml
```
will output
```yaml
@@ -105,7 +105,7 @@ a:
```
then
```bash
yq eval '.a.x | key | headComment' sample.yml
yq '.a.x | key | headComment' sample.yml
```
will output
```yaml
+4 -4
View File
@@ -11,7 +11,7 @@ a: cat
```
then
```bash
yq eval '.a | length' sample.yml
yq '.a | length' sample.yml
```
will output
```yaml
@@ -25,7 +25,7 @@ a: null
```
then
```bash
yq eval '.a | length' sample.yml
yq '.a | length' sample.yml
```
will output
```yaml
@@ -42,7 +42,7 @@ c: dog
```
then
```bash
yq eval 'length' sample.yml
yq 'length' sample.yml
```
will output
```yaml
@@ -61,7 +61,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'length' sample.yml
yq 'length' sample.yml
```
will output
```yaml
+4 -4
View File
@@ -20,7 +20,7 @@ myFile: ../../examples/thing.yml
```
then
```bash
yq eval 'load(.myFile)' sample.yml
yq 'load(.myFile)' sample.yml
```
will output
```yaml
@@ -38,7 +38,7 @@ something:
```
then
```bash
yq eval '.something |= load("../../examples/" + .file)' sample.yml
yq '.something |= load("../../examples/" + .file)' sample.yml
```
will output
```yaml
@@ -60,7 +60,7 @@ over:
```
then
```bash
yq eval '(.. | select(has("file"))) |= load("../../examples/" + .file)' sample.yml
yq '(.. | select(has("file"))) |= load("../../examples/" + .file)' sample.yml
```
will output
```yaml
@@ -83,7 +83,7 @@ something:
```
then
```bash
yq eval '.something |= strload("../../examples/" + .file)' sample.yml
yq '.something |= strload("../../examples/" + .file)' sample.yml
```
will output
```yaml
+2 -2
View File
@@ -11,7 +11,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'map(. + 1)' sample.yml
yq 'map(. + 1)' sample.yml
```
will output
```yaml
@@ -29,7 +29,7 @@ c: 3
```
then
```bash
yq eval 'map_values(. + 1)' sample.yml
yq 'map_values(. + 1)' sample.yml
```
will output
```yaml
+87 -18
View File
@@ -10,9 +10,10 @@ Note that when merging objects, this operator returns the merged object (not the
### 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
- `+` append arrays
- `d` deeply merge arrays
- `?` only merge _existing_ fields
- `n` only merge _new_ fields
### Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory.
@@ -22,13 +23,19 @@ yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.y
```
## Multiply integers
Running
Given a sample.yml file of:
```yaml
a: 3
b: 4
```
then
```bash
yq eval --null-input '3 * 4'
yq '.a *= .b' sample.yml
```
will output
```yaml
12
a: 12
b: 4
```
## Merge objects together, returning merged result only
@@ -44,7 +51,7 @@ b:
```
then
```bash
yq eval '.a * .b' sample.yml
yq '.a * .b' sample.yml
```
will output
```yaml
@@ -67,7 +74,7 @@ b:
```
then
```bash
yq eval '. * {"a":.b}' sample.yml
yq '. * {"a":.b}' sample.yml
```
will output
```yaml
@@ -91,7 +98,7 @@ b:
```
then
```bash
yq eval '. * {"a":.b}' sample.yml
yq '. * {"a":.b}' sample.yml
```
will output
```yaml
@@ -114,7 +121,7 @@ b:
```
then
```bash
yq eval '. * {"a":.b}' sample.yml
yq '. * {"a":.b}' sample.yml
```
will output
```yaml
@@ -140,7 +147,7 @@ b:
```
then
```bash
yq eval '.a *? .b' sample.yml
yq '.a *? .b' sample.yml
```
will output
```yaml
@@ -148,6 +155,27 @@ thing: two
cat: frog
```
## Merge, only new fields
Given a sample.yml file of:
```yaml
a:
thing: one
cat: frog
b:
missing: two
thing: two
```
then
```bash
yq '.a *n .b' sample.yml
```
will output
```yaml
thing: one
cat: frog
missing: two
```
## Merge, appending arrays
Given a sample.yml file of:
```yaml
@@ -166,7 +194,7 @@ b:
```
then
```bash
yq eval '.a *+ .b' sample.yml
yq '.a *+ .b' sample.yml
```
will output
```yaml
@@ -196,7 +224,7 @@ b:
```
then
```bash
yq eval '.a *?+ .b' sample.yml
yq '.a *?+ .b' sample.yml
```
will output
```yaml
@@ -223,7 +251,7 @@ b:
```
then
```bash
yq eval '.a *d .b' sample.yml
yq '.a *d .b' sample.yml
```
will output
```yaml
@@ -303,7 +331,7 @@ b: dog
```
then
```bash
yq eval '. * {"a": {"c": .a}}' sample.yml
yq '. * {"a": {"c": .a}}' sample.yml
```
will output
```yaml
@@ -324,7 +352,7 @@ c:
```
then
```bash
yq eval '.c * .b' sample.yml
yq '.c * .b' sample.yml
```
will output
```yaml
@@ -344,7 +372,7 @@ c:
```
then
```bash
yq eval '.c * .a' sample.yml
yq '.c * .a' sample.yml
```
will output
```yaml
@@ -376,7 +404,7 @@ foobar:
```
then
```bash
yq eval '.foobar * .foobarList' sample.yml
yq '.foobar * .foobarList' sample.yml
```
will output
```yaml
@@ -388,3 +416,44 @@ thing: foobar_thing
b: foobarList_b
```
## Custom types: that are really numbers
When custom tags are encountered, yq will try to decode the underlying type.
Given a sample.yml file of:
```yaml
a: !horse 2
b: !goat 3
```
then
```bash
yq '.a = .a * .b' sample.yml
```
will output
```yaml
a: !horse 6
b: !goat 3
```
## Custom types: that are really maps
Custom tags will be maintained.
Given a sample.yml file of:
```yaml
a: !horse
cat: meow
b: !goat
dog: woof
```
then
```bash
yq '.a = .a * .b' sample.yml
```
will output
```yaml
a: !horse
cat: meow
dog: woof
b: !goat
dog: woof
```
+3 -3
View File
@@ -10,7 +10,7 @@ a:
```
then
```bash
yq eval '.a.nested | parent' sample.yml
yq '.a.nested | parent' sample.yml
```
will output
```yaml
@@ -29,7 +29,7 @@ b:
```
then
```bash
yq eval '.. | select(. == "banana") | parent' sample.yml
yq '.. | select(. == "banana") | parent' sample.yml
```
will output
```yaml
@@ -44,7 +44,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'parent' sample.yml
yq 'parent' sample.yml
```
will output
```yaml
+5 -5
View File
@@ -12,7 +12,7 @@ a:
```
then
```bash
yq eval '.a.b | path' sample.yml
yq '.a.b | path' sample.yml
```
will output
```yaml
@@ -28,7 +28,7 @@ a:
```
then
```bash
yq eval '.a.b | path | .[-1]' sample.yml
yq '.a.b | path | .[-1]' sample.yml
```
will output
```yaml
@@ -44,7 +44,7 @@ a:
```
then
```bash
yq eval '.a.[] | select(. == "dog") | path' sample.yml
yq '.a.[] | select(. == "dog") | path' sample.yml
```
will output
```yaml
@@ -61,7 +61,7 @@ a:
```
then
```bash
yq eval '.a.[] | select(. == "dog") | path | .[-1]' sample.yml
yq '.a.[] | select(. == "dog") | path | .[-1]' sample.yml
```
will output
```yaml
@@ -78,7 +78,7 @@ a:
```
then
```bash
yq eval '.a[] | select(. == "*og") | [{"path":path, "value":.}]' sample.yml
yq '.a[] | select(. == "*og") | [{"path":path, "value":.}]' sample.yml
```
will output
```yaml
+2 -2
View File
@@ -10,7 +10,7 @@ a:
```
then
```bash
yq eval '.a | .b' sample.yml
yq '.a | .b' sample.yml
```
will output
```yaml
@@ -26,7 +26,7 @@ c: same
```
then
```bash
yq eval '.a = "cat" | .b = "dog"' sample.yml
yq '.a = "cat" | .b = "dog"' sample.yml
```
will output
```yaml
@@ -8,7 +8,7 @@ This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
```bash
yq eval '.. style= "flow"' file.yaml
yq '.. style= "flow"' file.yaml
```
## match values and map keys form `...`
@@ -17,7 +17,7 @@ The also includes map keys in the results set. This is particularly useful in YA
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
```bash
yq eval '... style= "flow"' file.yaml
yq '... style= "flow"' file.yaml
```
## Recurse map (values only)
Given a sample.yml file of:
@@ -26,7 +26,7 @@ a: frog
```
then
```bash
yq eval '..' sample.yml
yq '..' sample.yml
```
will output
```yaml
@@ -47,7 +47,7 @@ a:
```
then
```bash
yq eval '[.. | select(has("name"))]' sample.yml
yq '[.. | select(has("name"))]' sample.yml
```
will output
```yaml
@@ -70,7 +70,7 @@ a:
```
then
```bash
yq eval '.. | select(. == "frog")' sample.yml
yq '.. | select(. == "frog")' sample.yml
```
will output
```yaml
@@ -87,7 +87,7 @@ a: frog
```
then
```bash
yq eval '...' sample.yml
yq '...' sample.yml
```
will output
```yaml
@@ -105,7 +105,7 @@ b: *cat
```
then
```bash
yq eval '[..]' sample.yml
yq '[..]' sample.yml
```
will output
```yaml
@@ -142,7 +142,7 @@ foobar:
```
then
```bash
yq eval '.foobar | [..]' sample.yml
yq '.foobar | [..]' sample.yml
```
will output
```yaml
+2 -2
View File
@@ -31,7 +31,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] as $item ireduce (0; . + $item)' sample.yml
yq '.[] as $item ireduce (0; . + $item)' sample.yml
```
will output
```yaml
@@ -67,7 +67,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )' sample.yml
yq '.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )' sample.yml
```
will output
```yaml
+96 -4
View File
@@ -2,7 +2,7 @@
Select is used to filter arrays and maps by a boolean expression.
## Select elements from array
## Select elements from array using wildcard prefix
Given a sample.yml file of:
```yaml
- cat
@@ -11,7 +11,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | select(. == "*at")' sample.yml
yq '.[] | select(. == "*at")' sample.yml
```
will output
```yaml
@@ -19,7 +19,99 @@ cat
goat
```
## Select and update matching values in map
## Select elements from array using wildcard suffix
Given a sample.yml file of:
```yaml
- go-kart
- goat
- dog
```
then
```bash
yq '.[] | select(. == "go*")' sample.yml
```
will output
```yaml
go-kart
goat
```
## Select elements from array using wildcard prefix and suffix
Given a sample.yml file of:
```yaml
- ago
- go
- meow
- going
```
then
```bash
yq '.[] | select(. == "*go*")' sample.yml
```
will output
```yaml
ago
go
going
```
## Select elements from array with regular expression
See more regular expression examples under the `string` operator docs.
Given a sample.yml file of:
```yaml
- this_0
- not_this
- nor_0_this
- thisTo_4
```
then
```bash
yq '.[] | select(test("[a-zA-Z]+_[0-9]$"))' sample.yml
```
will output
```yaml
this_0
thisTo_4
```
## Select items from a map
Given a sample.yml file of:
```yaml
things: cat
bob: goat
horse: dog
```
then
```bash
yq '.[] | select(. == "cat" or test("og$"))' sample.yml
```
will output
```yaml
cat
dog
```
## Use select and with_entries to filter map keys
Given a sample.yml file of:
```yaml
name: bob
legs: 2
game: poker
```
then
```bash
yq 'with_entries(select(.key | test("ame$")))' sample.yml
```
will output
```yaml
name: bob
game: poker
```
## Select multiple items in a map and update
Note the brackets around the entire LHS.
Given a sample.yml file of:
```yaml
a:
@@ -29,7 +121,7 @@ a:
```
then
```bash
yq eval '(.a.[] | select(. == "*at")) |= "rabbit"' sample.yml
yq '(.a.[] | select(. == "cat" or . == "goat")) |= "rabbit"' sample.yml
```
will output
```yaml
+6 -4
View File
@@ -5,11 +5,13 @@ The Sort Keys operator sorts maps by their keys (based on their string value). T
Sort is particularly useful for diffing two different yaml documents:
```bash
yq eval -i -P 'sort_keys(..)' file1.yml
yq eval -i -P 'sort_keys(..)' file2.yml
yq -i -P 'sort_keys(..)' file1.yml
yq -i -P 'sort_keys(..)' file2.yml
diff file1.yml file2.yml
```
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if your are using merge anchors.
## Sort keys of map
Given a sample.yml file of:
```yaml
@@ -19,7 +21,7 @@ b: bing
```
then
```bash
yq eval 'sort_keys(.)' sample.yml
yq 'sort_keys(.)' sample.yml
```
will output
```yaml
@@ -49,7 +51,7 @@ aParent:
```
then
```bash
yq eval 'sort_keys(..)' sample.yml
yq 'sort_keys(..)' sample.yml
```
will output
```yaml
+6 -6
View File
@@ -13,7 +13,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'sort_by(.a)' sample.yml
yq 'sort_by(.a)' sample.yml
```
will output
```yaml
@@ -32,7 +32,7 @@ cool:
```
then
```bash
yq eval '.cool |= sort_by(.a)' sample.yml
yq '.cool |= sort_by(.a)' sample.yml
```
will output
```yaml
@@ -54,7 +54,7 @@ cool:
```
then
```bash
yq eval '.cool |= sort_by(keys | .[0])' sample.yml
yq '.cool |= sort_by(keys | .[0])' sample.yml
```
will output
```yaml
@@ -80,7 +80,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'sort_by(.a)' sample.yml
yq 'sort_by(.a)' sample.yml
```
will output
```yaml
@@ -103,7 +103,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'sort_by(.a)' sample.yml
yq 'sort_by(.a)' sample.yml
```
will output
```yaml
@@ -125,7 +125,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'sort' sample.yml
yq 'sort' sample.yml
```
will output
```yaml
@@ -5,7 +5,7 @@ This operator splits all matches into separate documents
## Split empty
Running
```bash
yq eval --null-input 'splitDoc'
yq --null-input 'splitDoc'
```
will output
```yaml
@@ -20,7 +20,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | splitDoc' sample.yml
yq '.[] | splitDoc' sample.yml
```
will output
```yaml
+17 -17
View File
@@ -17,13 +17,13 @@ a: |
Using `$( exp )` wont work, as it will trim the trailing new line.
```
m=$(echo "cat\n") yq e -n '.a = strenv(m)'
m=$(echo "cat\n") yq -n '.a = strenv(m)'
a: cat
```
However, using printf works:
```
printf -v m "cat\n" ; m="$m" yq e -n '.a = strenv(m)'
printf -v m "cat\n" ; m="$m" yq -n '.a = strenv(m)'
a: |
cat
```
@@ -31,7 +31,7 @@ a: |
As well as having multiline expressions:
```
m="cat
" yq e -n '.a = strenv(m)'
" yq -n '.a = strenv(m)'
a: |
cat
```
@@ -40,7 +40,7 @@ Similarly, if you're trying to set the content from a file, and want a trailing
```
IFS= read -rd '' output < <(cat my_file)
output=$output ./yq e '.data.values = strenv(output)' first.yml
output=$output ./yq '.data.values = strenv(output)' first.yml
```
## Join strings
@@ -54,7 +54,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'join("; ")' sample.yml
yq 'join("; ")' sample.yml
```
will output
```yaml
@@ -68,7 +68,7 @@ foo bar foo
```
then
```bash
yq eval 'match("foo")' sample.yml
yq 'match("foo")' sample.yml
```
will output
```yaml
@@ -85,7 +85,7 @@ foo bar FOO
```
then
```bash
yq eval '[match("(?i)foo"; "g")]' sample.yml
yq '[match("(?i)foo"; "g")]' sample.yml
```
will output
```yaml
@@ -106,7 +106,7 @@ abc abc
```
then
```bash
yq eval '[match("(abc)+"; "g")]' sample.yml
yq '[match("(abc)+"; "g")]' sample.yml
```
will output
```yaml
@@ -133,7 +133,7 @@ foo bar foo foo foo
```
then
```bash
yq eval '[match("foo (?P<bar123>bar)? foo"; "g")]' sample.yml
yq '[match("foo (?P<bar123>bar)? foo"; "g")]' sample.yml
```
will output
```yaml
@@ -162,7 +162,7 @@ xyzzy-14
```
then
```bash
yq eval 'capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")' sample.yml
yq 'capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")' sample.yml
```
will output
```yaml
@@ -177,7 +177,7 @@ cat cat
```
then
```bash
yq eval 'match("cat")' sample.yml
yq 'match("cat")' sample.yml
```
will output
```yaml
@@ -194,7 +194,7 @@ cat cat
```
then
```bash
yq eval '[match("cat"; "g")]' sample.yml
yq '[match("cat"; "g")]' sample.yml
```
will output
```yaml
@@ -218,7 +218,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] | test("at")' sample.yml
yq '.[] | test("at")' sample.yml
```
will output
```yaml
@@ -236,7 +236,7 @@ a: dogs are great
```
then
```bash
yq eval '.a |= sub("dogs", "cats")' sample.yml
yq '.a |= sub("dogs", "cats")' sample.yml
```
will output
```yaml
@@ -254,7 +254,7 @@ b: heat
```
then
```bash
yq eval '.[] |= sub("(a)", "${1}r")' sample.yml
yq '.[] |= sub("(a)", "${1}r")' sample.yml
```
will output
```yaml
@@ -269,7 +269,7 @@ cat; meow; 1; ; true
```
then
```bash
yq eval 'split("; ")' sample.yml
yq 'split("; ")' sample.yml
```
will output
```yaml
@@ -287,7 +287,7 @@ word
```
then
```bash
yq eval 'split("; ")' sample.yml
yq 'split("; ")' sample.yml
```
will output
```yaml
+12 -12
View File
@@ -11,7 +11,7 @@ a:
```
then
```bash
yq eval '.a.b = "new" | .a.b style="double"' sample.yml
yq '.a.b = "new" | .a.b style="double"' sample.yml
```
will output
```yaml
@@ -29,7 +29,7 @@ a:
```
then
```bash
yq eval 'with(.a.b ; . = "new" | . style="double")' sample.yml
yq 'with(.a.b ; . = "new" | . style="double")' sample.yml
```
will output
```yaml
@@ -48,7 +48,7 @@ e: true
```
then
```bash
yq eval '.. style="tagged"' sample.yml
yq '.. style="tagged"' sample.yml
```
will output
```yaml
@@ -69,7 +69,7 @@ e: true
```
then
```bash
yq eval '.. style="double"' sample.yml
yq '.. style="double"' sample.yml
```
will output
```yaml
@@ -89,7 +89,7 @@ e: true
```
then
```bash
yq eval '... style="double"' sample.yml
yq '... style="double"' sample.yml
```
will output
```yaml
@@ -109,7 +109,7 @@ e: true
```
then
```bash
yq eval '.. style="single"' sample.yml
yq '.. style="single"' sample.yml
```
will output
```yaml
@@ -129,7 +129,7 @@ e: true
```
then
```bash
yq eval '.. style="literal"' sample.yml
yq '.. style="literal"' sample.yml
```
will output
```yaml
@@ -153,7 +153,7 @@ e: true
```
then
```bash
yq eval '.. style="folded"' sample.yml
yq '.. style="folded"' sample.yml
```
will output
```yaml
@@ -177,7 +177,7 @@ e: true
```
then
```bash
yq eval '.. style="flow"' sample.yml
yq '.. style="flow"' sample.yml
```
will output
```yaml
@@ -196,7 +196,7 @@ a: cat
```
then
```bash
yq eval '... style=""' sample.yml
yq '... style=""' sample.yml
```
will output
```yaml
@@ -214,7 +214,7 @@ b: double
```
then
```bash
yq eval '.[] style |= .' sample.yml
yq '.[] style |= .' sample.yml
```
will output
```yaml
@@ -229,7 +229,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.. | style' sample.yml
yq '.. | style' sample.yml
```
will output
```yaml
+25 -7
View File
@@ -5,7 +5,7 @@ You can use subtract to subtract numbers, as well as removing elements from an a
## Array subtraction
Running
```bash
yq eval --null-input '[1,2] - [2,3]'
yq --null-input '[1,2] - [2,3]'
```
will output
```yaml
@@ -15,7 +15,7 @@ will output
## Array subtraction with nested array
Running
```bash
yq eval --null-input '[[1], 1, 2] - [[1], 3]'
yq --null-input '[[1], 1, 2] - [[1], 3]'
```
will output
```yaml
@@ -34,7 +34,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '. - [{"c": "d", "a": "b"}]' sample.yml
yq '. - [{"c": "d", "a": "b"}]' sample.yml
```
will output
```yaml
@@ -51,7 +51,7 @@ b: 4.5
```
then
```bash
yq eval '.a = .a - .b' sample.yml
yq '.a = .a - .b' sample.yml
```
will output
```yaml
@@ -69,7 +69,7 @@ b: 4.5
```
then
```bash
yq eval '.a = .a - .b' sample.yml
yq '.a = .a - .b' sample.yml
```
will output
```yaml
@@ -87,7 +87,7 @@ b: 4
```
then
```bash
yq eval '.a = .a - .b' sample.yml
yq '.a = .a - .b' sample.yml
```
will output
```yaml
@@ -103,7 +103,7 @@ b: 5
```
then
```bash
yq eval '.[] -= 1' sample.yml
yq '.[] -= 1' sample.yml
```
will output
```yaml
@@ -111,3 +111,21 @@ a: 2
b: 4
```
## Custom types: that are really numbers
When custom tags are encountered, yq will try to decode the underlying type.
Given a sample.yml file of:
```yaml
a: !horse 2
b: !goat 1
```
then
```bash
yq '.a -= .b' sample.yml
```
will output
```yaml
a: !horse 1
b: !goat 1
```
+3 -3
View File
@@ -13,7 +13,7 @@ f: []
```
then
```bash
yq eval '.. | tag' sample.yml
yq '.. | tag' sample.yml
```
will output
```yaml
@@ -32,7 +32,7 @@ a: str
```
then
```bash
yq eval '.a tag = "!!mikefarah"' sample.yml
yq '.a tag = "!!mikefarah"' sample.yml
```
will output
```yaml
@@ -49,7 +49,7 @@ e: true
```
then
```bash
yq eval '(.. | select(tag == "!!int")) tag= "!!str"' sample.yml
yq '(.. | select(tag == "!!int")) tag= "!!str"' sample.yml
```
will output
```yaml
+24 -24
View File
@@ -10,7 +10,7 @@ a:
```
then
```bash
yq eval '.a' sample.yml
yq '.a' sample.yml
```
will output
```yaml
@@ -27,7 +27,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[]' sample.yml
yq '.[]' sample.yml
```
will output
```yaml
@@ -44,7 +44,7 @@ cat
```
then
```bash
yq eval '.[]' sample.yml
yq '.[]' sample.yml
```
will output
```yaml
@@ -59,7 +59,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.["{}"]' sample.yml
yq '.["{}"]' sample.yml
```
will output
```yaml
@@ -75,7 +75,7 @@ a:
```
then
```bash
yq eval '.a["key.withdots"]["another.key"]' sample.yml
yq '.a["key.withdots"]["another.key"]' sample.yml
```
will output
```yaml
@@ -91,7 +91,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.["red rabbit"]' sample.yml
yq '.["red rabbit"]' sample.yml
```
will output
```yaml
@@ -109,7 +109,7 @@ banana: soft yum
```
then
```bash
yq eval '.[.b]' sample.yml
yq '.[.b]' sample.yml
```
will output
```yaml
@@ -125,7 +125,7 @@ c: banana
```
then
```bash
yq eval '.a.b' sample.yml
yq '.a.b' sample.yml
```
will output
```yaml
@@ -143,7 +143,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.a?' sample.yml
yq '.a?' sample.yml
```
will output
```yaml
@@ -158,7 +158,7 @@ a:
```
then
```bash
yq eval '.a."*a*"' sample.yml
yq '.a."*a*"' sample.yml
```
will output
```yaml
@@ -175,7 +175,7 @@ b: *cat
```
then
```bash
yq eval '.b' sample.yml
yq '.b' sample.yml
```
will output
```yaml
@@ -191,7 +191,7 @@ b: *cat
```
then
```bash
yq eval '.b[]' sample.yml
yq '.b[]' sample.yml
```
will output
```yaml
@@ -207,7 +207,7 @@ b: *cat
```
then
```bash
yq eval '.b.c' sample.yml
yq '.b.c' sample.yml
```
will output
```yaml
@@ -223,7 +223,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[0]' sample.yml
yq '.[0]' sample.yml
```
will output
```yaml
@@ -237,7 +237,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[1][0]' sample.yml
yq '.[1][0]' sample.yml
```
will output
```yaml
@@ -251,7 +251,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[2]' sample.yml
yq '.[2]' sample.yml
```
will output
```yaml
@@ -265,7 +265,7 @@ a: b
```
then
```bash
yq eval '.[0]' sample.yml
yq '.[0]' sample.yml
```
will output
```yaml
@@ -296,7 +296,7 @@ foobar:
```
then
```bash
yq eval '.foobar.a' sample.yml
yq '.foobar.a' sample.yml
```
will output
```yaml
@@ -327,7 +327,7 @@ foobar:
```
then
```bash
yq eval '.foobar.c' sample.yml
yq '.foobar.c' sample.yml
```
will output
```yaml
@@ -358,7 +358,7 @@ foobar:
```
then
```bash
yq eval '.foobar.thing' sample.yml
yq '.foobar.thing' sample.yml
```
will output
```yaml
@@ -389,7 +389,7 @@ foobar:
```
then
```bash
yq eval '.foobar[]' sample.yml
yq '.foobar[]' sample.yml
```
will output
```yaml
@@ -424,7 +424,7 @@ foobar:
```
then
```bash
yq eval '.foobarList.thing' sample.yml
yq '.foobarList.thing' sample.yml
```
will output
```yaml
@@ -455,7 +455,7 @@ foobar:
```
then
```bash
yq eval '.foobarList[]' sample.yml
yq '.foobarList[]' sample.yml
```
will output
```yaml
@@ -475,7 +475,7 @@ a:
```
then
```bash
yq eval '.a[0, 2]' sample.yml
yq '.a[0, 2]' sample.yml
```
will output
```yaml
+2 -2
View File
@@ -5,7 +5,7 @@ This operator is used to combine different results together.
## Combine scalars
Running
```bash
yq eval --null-input '1, true, "cat"'
yq --null-input '1, true, "cat"'
```
will output
```yaml
@@ -23,7 +23,7 @@ c: fieldC
```
then
```bash
yq eval '.a, .c' sample.yml
yq '.a, .c' sample.yml
```
will output
```yaml
+4 -4
View File
@@ -12,7 +12,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'unique' sample.yml
yq 'unique' sample.yml
```
will output
```yaml
@@ -33,7 +33,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'unique' sample.yml
yq 'unique' sample.yml
```
will output
```yaml
@@ -53,7 +53,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'unique_by(tag)' sample.yml
yq 'unique_by(tag)' sample.yml
```
will output
```yaml
@@ -72,7 +72,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval 'unique_by(.name)' sample.yml
yq 'unique_by(.name)' sample.yml
```
will output
```yaml
@@ -11,7 +11,7 @@ a: cat
```
then
```bash
yq eval '.a as $foo | $foo' sample.yml
yq '.a as $foo | $foo' sample.yml
```
will output
```yaml
@@ -26,7 +26,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.[] as $foo | $foo' sample.yml
yq '.[] as $foo | $foo' sample.yml
```
will output
```yaml
@@ -50,7 +50,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.realnames as $names | .posts[] | {"title":.title, "author": $names[.author]}' sample.yml
yq '.realnames as $names | .posts[] | {"title":.title, "author": $names[.author]}' sample.yml
```
will output
```yaml
@@ -68,7 +68,7 @@ b: b_value
```
then
```bash
yq eval '.a as $x | .b as $y | .b = $x | .a = $y' sample.yml
yq '.a as $x | .b as $y | .b = $x | .a = $y' sample.yml
```
will output
```yaml
@@ -87,7 +87,7 @@ a:
```
then
```bash
yq eval '.a.b ref $x | $x = "new" | $x style="double"' sample.yml
yq '.a.b ref $x | $x = "new" | $x style="double"' sample.yml
```
will output
```yaml
+3 -3
View File
@@ -11,7 +11,7 @@ a:
```
then
```bash
yq eval 'with(.a.deeply.nested; . = "newValue" | . style="single")' sample.yml
yq 'with(.a.deeply.nested; . = "newValue" | . style="single")' sample.yml
```
will output
```yaml
@@ -30,7 +30,7 @@ a:
```
then
```bash
yq eval 'with(.a.deeply; .nested = "newValue" | .other= "newThing")' sample.yml
yq 'with(.a.deeply; .nested = "newValue" | .other= "newThing")' sample.yml
```
will output
```yaml
@@ -51,7 +51,7 @@ myArray:
```
then
```bash
yq eval 'with(.myArray[]; .b = .a + " yum")' sample.yml
yq 'with(.myArray[]; .b = .a + " yum")' sample.yml
```
will output
```yaml
+126
View File
@@ -0,0 +1,126 @@
# JSON
Encode and decode to and from JSON. Note that YAML is a _superset_ of JSON - so `yq` can read any json file without doing anything special.
This means you don't need to 'convert' a JSON file to YAML - however if you want idiomatic YAML styling, then you can use the `-P/--prettyPrint` flag, see examples below.
## Parse json: simple
JSON is a subset of yaml, so all you need to do is prettify the output
Given a sample.json file of:
```json
{"cat": "meow"}
```
then
```bash
yq -P '.' sample.json
```
will output
```yaml
cat: meow
```
## Parse json: complex
JSON is a subset of yaml, so all you need to do is prettify the output
Given a sample.json file of:
```json
{"a":"Easy! as one two three","b":{"c":2,"d":[3,4]}}
```
then
```bash
yq -P '.' sample.json
```
will output
```yaml
a: Easy! as one two three
b:
c: 2
d:
- 3
- 4
```
## Encode json: simple
Given a sample.yml file of:
```yaml
cat: meow
```
then
```bash
yq -o=json '.' sample.yml
```
will output
```json
{
"cat": "meow"
}
```
## Encode json: simple - in one line
Given a sample.yml file of:
```yaml
cat: meow # this is a comment, and it will be dropped.
```
then
```bash
yq -o=json -I=0 '.' sample.yml
```
will output
```json
{"cat":"meow"}
```
## Encode json: comments
Given a sample.yml file of:
```yaml
cat: meow # this is a comment, and it will be dropped.
```
then
```bash
yq -o=json '.' sample.yml
```
will output
```json
{
"cat": "meow"
}
```
## Encode json: anchors
Anchors are dereferenced
Given a sample.yml file of:
```yaml
cat: &ref meow
anotherCat: *ref
```
then
```bash
yq -o=json '.' sample.yml
```
will output
```json
{
"cat": "meow",
"anotherCat": "meow"
}
```
## Encode json: multiple results
Each matching node is converted into a json doc. This is best used with 0 indent (json document per line)
Given a sample.yml file of:
```yaml
things: [{stuff: cool}, {whatever: cat}]
```
then
```bash
yq -o=json -I=0 '.things[]' sample.yml
```
will output
```json
{"stuff":"cool"}
{"whatever":"cat"}
```
+5
View File
@@ -0,0 +1,5 @@
# JSON
Encode and decode to and from JSON. Note that YAML is a _superset_ of JSON - so `yq` can read any json file without doing anything special.
This means you don't need to 'convert' a JSON file to YAML - however if you want idiomatic YAML styling, then you can use the `-P/--prettyPrint` flag, see examples below.
+1 -17
View File
@@ -2,22 +2,6 @@
Encode and decode to and from XML. Whitespace is not conserved for round trips - but the order of the fields are.
As yaml does not have the concept of attributes, xml attributes are converted to regular fields with a prefix to prevent clobbering. This defaults to "+", use the `--xml-attribute-prefix` to change.
Consecutive xml nodes with the same name are assumed to be arrays.
All values in XML are assumed to be strings - but you can use `from_yaml` to parse them into their correct types:
```
yq e -p=xml '.myNumberField |= from_yaml' my.xml
```
XML nodes that have attributes then plain content, e.g:
```xml
<cat name="tiger">meow</cat>
```
The content of the node will be set as a field in the map with the key "+content". Use the `--xml-content-name` flag to change this.
XML content data and attributes are created as fields. This can be controlled by the `'--xml-attribute-prefix` and `--xml-content-name` flags - see below for examples.
+173 -37
View File
@@ -2,39 +2,56 @@
Encode and decode to and from XML. Whitespace is not conserved for round trips - but the order of the fields are.
As yaml does not have the concept of attributes, xml attributes are converted to regular fields with a prefix to prevent clobbering. This defaults to "+", use the `--xml-attribute-prefix` to change.
Consecutive xml nodes with the same name are assumed to be arrays.
All values in XML are assumed to be strings - but you can use `from_yaml` to parse them into their correct types:
```
yq e -p=xml '.myNumberField |= from_yaml' my.xml
```
XML nodes that have attributes then plain content, e.g:
```xml
<cat name="tiger">meow</cat>
```
The content of the node will be set as a field in the map with the key "+content". Use the `--xml-content-name` flag to change this.
XML content data and attributes are created as fields. This can be controlled by the `'--xml-attribute-prefix` and `--xml-content-name` flags - see below for examples.
## Parse xml: simple
Notice how all the values are strings, see the next example on how you can fix that.
Given a sample.xml file of:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<cat>meow</cat>
<cat>
<says>meow</says>
<legs>4</legs>
<cute>true</cute>
</cat>
```
then
```bash
yq e -p=xml '.' sample.xml
yq -p=xml '.' sample.xml
```
will output
```yaml
cat: meow
cat:
says: meow
legs: "4"
cute: "true"
```
## Parse xml: number
All values are assumed to be strings when parsing XML, but you can use the `from_yaml` operator on all the strings values to autoparse into the correct type.
Given a sample.xml file of:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<cat>
<says>meow</says>
<legs>4</legs>
<cute>true</cute>
</cat>
```
then
```bash
yq -p=xml ' (.. | select(tag == "!!str")) |= from_yaml' sample.xml
```
will output
```yaml
cat:
says: meow
legs: 4
cute: true
```
## Parse xml: array
@@ -43,22 +60,22 @@ Consecutive nodes with identical xml names are assumed to be arrays.
Given a sample.xml file of:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<animal>1</animal>
<animal>2</animal>
<animal>cat</animal>
<animal>goat</animal>
```
then
```bash
yq e -p=xml '.' sample.xml
yq -p=xml '.' sample.xml
```
will output
```yaml
animal:
- "1"
- "2"
- cat
- goat
```
## Parse xml: attributes
Attributes are converted to fields, with the attribute prefix.
Attributes are converted to fields, with the default attribute prefix '+'. Use '--xml-attribute-prefix` to set your own.
Given a sample.xml file of:
```xml
@@ -69,7 +86,7 @@ Given a sample.xml file of:
```
then
```bash
yq e -p=xml '.' sample.xml
yq -p=xml '.' sample.xml
```
will output
```yaml
@@ -79,7 +96,7 @@ cat:
```
## Parse xml: attributes with content
Content is added as a field, using the content name
Content is added as a field, using the default content name of `+content`. Use `--xml-content-name` to set your own.
Given a sample.xml file of:
```xml
@@ -88,7 +105,7 @@ Given a sample.xml file of:
```
then
```bash
yq e -p=xml '.' sample.xml
yq -p=xml '.' sample.xml
```
will output
```yaml
@@ -97,6 +114,53 @@ cat:
+legs: "4"
```
## Parse xml: with comments
A best attempt is made to preserve comments.
Given a sample.xml file of:
```xml
<!-- before cat -->
<cat>
<!-- in cat before -->
<x>3<!-- multi
line comment
for x --></x>
<!-- before y -->
<y>
<!-- in y before -->
<d><!-- in d before -->z<!-- in d after --></d>
<!-- in y after -->
</y>
<!-- in_cat_after -->
</cat>
<!-- after cat -->
```
then
```bash
yq -p=xml '.' sample.xml
```
will output
```yaml
# before cat
cat:
# in cat before
x: "3" # multi
# line comment
# for x
# before y
y:
# in y before
# in d before
d: z # in d after
# in y after
# in_cat_after
# after cat
```
## Encode xml: simple
Given a sample.yml file of:
```yaml
@@ -104,11 +168,12 @@ cat: purrs
```
then
```bash
yq e -o=xml '.' sample.yml
yq -o=xml '.' sample.yml
```
will output
```xml
<cat>purrs</cat>```
<cat>purrs</cat>
```
## Encode xml: array
Given a sample.yml file of:
@@ -120,14 +185,15 @@ pets:
```
then
```bash
yq e -o=xml '.' sample.yml
yq -o=xml '.' sample.yml
```
will output
```xml
<pets>
<cat>purrs</cat>
<cat>meows</cat>
</pets>```
</pets>
```
## Encode xml: attributes
Fields with the matching xml-attribute-prefix are assumed to be attributes.
@@ -141,13 +207,14 @@ cat:
```
then
```bash
yq e -o=xml '.' sample.yml
yq -o=xml '.' sample.yml
```
will output
```xml
<cat name="tiger">
<meows>true</meows>
</cat>```
</cat>
```
## Encode xml: attributes with content
Fields with the matching xml-content-name is assumed to be content.
@@ -161,9 +228,78 @@ cat:
```
then
```bash
yq e -o=xml '.' sample.yml
yq -o=xml '.' sample.yml
```
will output
```xml
<cat name="tiger">cool</cat>```
<cat name="tiger">cool</cat>
```
## Encode xml: comments
A best attempt is made to copy comments to xml.
Given a sample.yml file of:
```yaml
# above_cat
cat: # inline_cat
# above_array
array: # inline_array
- val1 # inline_val1
# above_val2
- val2 # inline_val2
# below_cat
```
then
```bash
yq -o=xml '.' sample.yml
```
will output
```xml
<!-- above_cat inline_cat --><cat><!-- above_array inline_array -->
<array>val1<!-- inline_val1 --></array>
<array><!-- above_val2 -->val2<!-- inline_val2 --></array>
</cat><!-- below_cat -->
```
## Round trip: with comments
A best effort is made, but comment positions and white space are not preserved perfectly.
Given a sample.xml file of:
```xml
<!-- before cat -->
<cat>
<!-- in cat before -->
<x>3<!-- multi
line comment
for x --></x>
<!-- before y -->
<y>
<!-- in y before -->
<d><!-- in d before -->z<!-- in d after --></d>
<!-- in y after -->
</y>
<!-- in_cat_after -->
</cat>
<!-- after cat -->
```
then
```bash
yq -p=xml -o=xml '.' sample.xml
```
will output
```xml
<!-- before cat --><cat><!-- in cat before -->
<x>3<!-- multi
line comment
for x --></x><!-- before y -->
<y><!-- in y before
in d before -->
<d>z<!-- in d after --></d><!-- in y after -->
</y><!-- in_cat_after -->
</cat><!-- after cat -->
```
+4 -86
View File
@@ -11,92 +11,10 @@ import (
)
type Encoder interface {
Encode(node *yaml.Node) error
}
type yamlEncoder struct {
destination io.Writer
indent int
colorise bool
firstDoc bool
}
func NewYamlEncoder(destination io.Writer, indent int, colorise bool) Encoder {
if indent < 0 {
indent = 0
}
return &yamlEncoder{destination, indent, colorise, true}
}
func (ye *yamlEncoder) Encode(node *yaml.Node) error {
destination := ye.destination
tempBuffer := bytes.NewBuffer(nil)
if ye.colorise {
destination = tempBuffer
}
var encoder = yaml.NewEncoder(destination)
encoder.SetIndent(ye.indent)
// TODO: work out if the first doc had a separator or not.
if ye.firstDoc {
ye.firstDoc = false
} else if _, err := destination.Write([]byte("---\n")); err != nil {
return err
}
if err := encoder.Encode(node); err != nil {
return err
}
if ye.colorise {
return colorizeAndPrint(tempBuffer.Bytes(), ye.destination)
}
return nil
}
type jsonEncoder struct {
encoder *json.Encoder
}
func mapKeysToStrings(node *yaml.Node) {
if node.Kind == yaml.MappingNode {
for index, child := range node.Content {
if index%2 == 0 { // its a map key
child.Tag = "!!str"
}
}
}
for _, child := range node.Content {
mapKeysToStrings(child)
}
}
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++ {
indentString = indentString + " "
}
encoder.SetIndent("", indentString)
return &jsonEncoder{encoder}
}
func (je *jsonEncoder) Encode(node *yaml.Node) error {
var dataBucket orderedMap
// firstly, convert all map keys to strings
mapKeysToStrings(node)
errorDecoding := node.Decode(&dataBucket)
if errorDecoding != nil {
return errorDecoding
}
return je.encoder.Encode(dataBucket)
Encode(writer io.Writer, node *yaml.Node) error
PrintDocumentSeparator(writer io.Writer) error
PrintLeadingContent(writer io.Writer, content string) error
CanHandleAliases() bool
}
// orderedMap allows to marshal and unmarshal JSON and YAML values keeping the
+23 -10
View File
@@ -9,16 +9,26 @@ import (
)
type csvEncoder struct {
destination csv.Writer
separator rune
}
func NewCsvEncoder(destination io.Writer, separator rune) Encoder {
csvWriter := *csv.NewWriter(destination)
csvWriter.Comma = separator
return &csvEncoder{csvWriter}
func NewCsvEncoder(separator rune) Encoder {
return &csvEncoder{separator}
}
func (e *csvEncoder) encodeRow(contents []*yaml.Node) error {
func (e *csvEncoder) CanHandleAliases() bool {
return false
}
func (e *csvEncoder) PrintDocumentSeparator(writer io.Writer) error {
return nil
}
func (e *csvEncoder) PrintLeadingContent(writer io.Writer, content string) error {
return nil
}
func (e *csvEncoder) encodeRow(csvWriter *csv.Writer, contents []*yaml.Node) error {
stringValues := make([]string, len(contents))
for i, child := range contents {
@@ -28,10 +38,13 @@ func (e *csvEncoder) encodeRow(contents []*yaml.Node) error {
}
stringValues[i] = child.Value
}
return e.destination.Write(stringValues)
return csvWriter.Write(stringValues)
}
func (e *csvEncoder) Encode(originalNode *yaml.Node) error {
func (e *csvEncoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
csvWriter := csv.NewWriter(writer)
csvWriter.Comma = e.separator
// node must be a sequence
node := unwrapDoc(originalNode)
if node.Kind != yaml.SequenceNode {
@@ -40,7 +53,7 @@ func (e *csvEncoder) Encode(originalNode *yaml.Node) error {
return nil
}
if node.Content[0].Kind == yaml.ScalarNode {
return e.encodeRow(node.Content)
return e.encodeRow(csvWriter, node.Content)
}
for i, child := range node.Content {
@@ -48,7 +61,7 @@ func (e *csvEncoder) Encode(originalNode *yaml.Node) error {
if child.Kind != yaml.SequenceNode {
return fmt.Errorf("csv encoding only works for arrays of scalars (string/numbers/booleans), child[%v] is a %v", i, child.Tag)
}
err := e.encodeRow(child.Content)
err := e.encodeRow(csvWriter, child.Content)
if err != nil {
return err
}
+2 -2
View File
@@ -13,13 +13,13 @@ func yamlToCsv(sampleYaml string, separator rune) string {
var output bytes.Buffer
writer := bufio.NewWriter(&output)
var jsonEncoder = NewCsvEncoder(writer, separator)
var jsonEncoder = NewCsvEncoder(separator)
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0, NewYamlDecoder())
if err != nil {
panic(err)
}
node := inputs.Front().Value.(*CandidateNode).Node
err = jsonEncoder.Encode(node)
err = jsonEncoder.Encode(writer, node)
if err != nil {
panic(err)
}
+64
View File
@@ -0,0 +1,64 @@
package yqlib
import (
"encoding/json"
"io"
yaml "gopkg.in/yaml.v3"
)
type jsonEncoder struct {
indentString string
}
func mapKeysToStrings(node *yaml.Node) {
if node.Kind == yaml.MappingNode {
for index, child := range node.Content {
if index%2 == 0 { // its a map key
child.Tag = "!!str"
}
}
}
for _, child := range node.Content {
mapKeysToStrings(child)
}
}
func NewJsonEncoder(indent int) Encoder {
var indentString = ""
for index := 0; index < indent; index++ {
indentString = indentString + " "
}
return &jsonEncoder{indentString}
}
func (je *jsonEncoder) CanHandleAliases() bool {
return false
}
func (je *jsonEncoder) PrintDocumentSeparator(writer io.Writer) error {
return nil
}
func (je *jsonEncoder) PrintLeadingContent(writer io.Writer, content string) error {
return nil
}
func (je *jsonEncoder) Encode(writer io.Writer, node *yaml.Node) error {
var encoder = json.NewEncoder(writer)
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
encoder.SetIndent("", je.indentString)
var dataBucket orderedMap
// firstly, convert all map keys to strings
mapKeysToStrings(node)
errorDecoding := node.Decode(&dataBucket)
if errorDecoding != nil {
return errorDecoding
}
return encoder.Encode(dataBucket)
}
+48 -8
View File
@@ -1,6 +1,8 @@
package yqlib
import (
"bufio"
"errors"
"fmt"
"io"
"strings"
@@ -10,14 +12,54 @@ import (
)
type propertiesEncoder struct {
destination io.Writer
}
func NewPropertiesEncoder(destination io.Writer) Encoder {
return &propertiesEncoder{destination}
func NewPropertiesEncoder() Encoder {
return &propertiesEncoder{}
}
func (pe *propertiesEncoder) Encode(node *yaml.Node) error {
func (pe *propertiesEncoder) CanHandleAliases() bool {
return false
}
func (pe *propertiesEncoder) PrintDocumentSeparator(writer io.Writer) error {
return nil
}
func (pe *propertiesEncoder) PrintLeadingContent(writer io.Writer, content string) error {
reader := bufio.NewReader(strings.NewReader(content))
for {
readline, errReading := reader.ReadString('\n')
if errReading != nil && !errors.Is(errReading, io.EOF) {
return errReading
}
if strings.Contains(readline, "$yqDocSeperator$") {
if err := pe.PrintDocumentSeparator(writer); err != nil {
return err
}
} else {
if err := writeString(writer, readline); err != nil {
return err
}
}
if errors.Is(errReading, io.EOF) {
if readline != "" {
// the last comment we read didn't have a new line, put one in
if err := writeString(writer, "\n"); err != nil {
return err
}
}
break
}
}
return nil
}
func (pe *propertiesEncoder) Encode(writer io.Writer, node *yaml.Node) error {
mapKeysToStrings(node)
p := properties.NewProperties()
err := pe.doEncode(p, node, "")
@@ -25,14 +67,12 @@ func (pe *propertiesEncoder) Encode(node *yaml.Node) error {
return err
}
_, err = p.WriteComment(pe.destination, "#", properties.UTF8)
_, err = p.WriteComment(writer, "#", 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))
p.SetComment(path, headAndLineComment(node))
switch node.Kind {
case yaml.ScalarNode:
_, _, err := p.Set(path, node.Value)
+2 -2
View File
@@ -13,13 +13,13 @@ func yamlToProps(sampleYaml string) string {
var output bytes.Buffer
writer := bufio.NewWriter(&output)
var propsEncoder = NewPropertiesEncoder(writer)
var propsEncoder = NewPropertiesEncoder()
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0, NewYamlDecoder())
if err != nil {
panic(err)
}
node := inputs.Front().Value.(*CandidateNode).Node
err = propsEncoder.Encode(node)
err = propsEncoder.Encode(writer, node)
if err != nil {
panic(err)
}
+2 -2
View File
@@ -13,13 +13,13 @@ func yamlToJson(sampleYaml string, indent int) string {
var output bytes.Buffer
writer := bufio.NewWriter(&output)
var jsonEncoder = NewJsonEncoder(writer, indent)
var jsonEncoder = NewJsonEncoder(indent)
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0, NewYamlDecoder())
if err != nil {
panic(err)
}
node := inputs.Front().Value.(*CandidateNode).Node
err = jsonEncoder.Encode(node)
err = jsonEncoder.Encode(writer, node)
if err != nil {
panic(err)
}
+148 -49
View File
@@ -9,89 +9,155 @@ import (
yaml "gopkg.in/yaml.v3"
)
var XmlPreferences = xmlPreferences{AttributePrefix: "+", ContentName: "+content"}
type xmlEncoder struct {
xmlEncoder *xml.Encoder
attributePrefix string
contentName string
indentString string
}
func NewXmlEncoder(writer io.Writer, indent int, attributePrefix string, contentName string) Encoder {
encoder := xml.NewEncoder(writer)
func NewXmlEncoder(indent int, attributePrefix string, contentName string) Encoder {
var indentString = ""
for index := 0; index < indent; index++ {
indentString = indentString + " "
}
encoder.Indent("", indentString)
return &xmlEncoder{encoder, attributePrefix, contentName}
}
func (e *xmlEncoder) Encode(node *yaml.Node) error {
switch node.Kind {
case yaml.MappingNode:
err := e.encodeTopLevelMap(node)
if err != nil {
return err
}
var charData xml.CharData = []byte("\n")
err = e.xmlEncoder.EncodeToken(charData)
if err != nil {
return err
}
return e.xmlEncoder.Flush()
case yaml.DocumentNode:
return e.Encode(unwrapDoc(node))
case yaml.ScalarNode:
var charData xml.CharData = []byte(node.Value)
err := e.xmlEncoder.EncodeToken(charData)
if err != nil {
return err
}
return e.xmlEncoder.Flush()
}
return fmt.Errorf("unsupported type %v", node.Tag)
return &xmlEncoder{attributePrefix, contentName, indentString}
}
func (e *xmlEncoder) encodeTopLevelMap(node *yaml.Node) error {
func (e *xmlEncoder) CanHandleAliases() bool {
return false
}
func (e *xmlEncoder) PrintDocumentSeparator(writer io.Writer) error {
return nil
}
func (e *xmlEncoder) PrintLeadingContent(writer io.Writer, content string) error {
return nil
}
func (e *xmlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
encoder := xml.NewEncoder(writer)
encoder.Indent("", e.indentString)
switch node.Kind {
case yaml.MappingNode:
err := e.encodeTopLevelMap(encoder, node)
if err != nil {
return err
}
case yaml.DocumentNode:
err := e.encodeComment(encoder, headAndLineComment(node))
if err != nil {
return err
}
err = e.encodeTopLevelMap(encoder, unwrapDoc(node))
if err != nil {
return err
}
err = e.encodeComment(encoder, footComment(node))
if err != nil {
return err
}
case yaml.ScalarNode:
var charData xml.CharData = []byte(node.Value)
err := encoder.EncodeToken(charData)
if err != nil {
return err
}
return encoder.Flush()
default:
return fmt.Errorf("unsupported type %v", node.Tag)
}
var charData xml.CharData = []byte("\n")
return encoder.EncodeToken(charData)
}
func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *yaml.Node) error {
err := e.encodeComment(encoder, headAndLineComment(node))
if err != nil {
return err
}
for i := 0; i < len(node.Content); i += 2 {
key := node.Content[i]
value := node.Content[i+1]
start := xml.StartElement{Name: xml.Name{Local: key.Value}}
err := e.doEncode(value, start)
log.Debugf("comments of key %v", key.Value)
err := e.encodeComment(encoder, headAndLineComment(key))
if err != nil {
return err
}
log.Debugf("recursing")
err = e.doEncode(encoder, value, start)
if err != nil {
return err
}
err = e.encodeComment(encoder, footComment(key))
if err != nil {
return err
}
}
return nil
return e.encodeComment(encoder, footComment(node))
}
func (e *xmlEncoder) doEncode(node *yaml.Node, start xml.StartElement) error {
func (e *xmlEncoder) encodeStart(encoder *xml.Encoder, node *yaml.Node, start xml.StartElement) error {
err := encoder.EncodeToken(start)
if err != nil {
return err
}
return e.encodeComment(encoder, headComment(node))
}
func (e *xmlEncoder) encodeEnd(encoder *xml.Encoder, node *yaml.Node, start xml.StartElement) error {
err := encoder.EncodeToken(start.End())
if err != nil {
return err
}
return e.encodeComment(encoder, footComment(node))
}
func (e *xmlEncoder) doEncode(encoder *xml.Encoder, node *yaml.Node, start xml.StartElement) error {
switch node.Kind {
case yaml.MappingNode:
return e.encodeMap(node, start)
return e.encodeMap(encoder, node, start)
case yaml.SequenceNode:
return e.encodeArray(node, start)
return e.encodeArray(encoder, node, start)
case yaml.ScalarNode:
err := e.xmlEncoder.EncodeToken(start)
err := e.encodeStart(encoder, node, start)
if err != nil {
return err
}
var charData xml.CharData = []byte(node.Value)
err = e.xmlEncoder.EncodeToken(charData)
err = encoder.EncodeToken(charData)
if err != nil {
return err
}
return e.xmlEncoder.EncodeToken(start.End())
if err = e.encodeComment(encoder, lineComment(node)); err != nil {
return err
}
return e.encodeEnd(encoder, node, start)
}
return fmt.Errorf("unsupported type %v", node.Tag)
}
func (e *xmlEncoder) encodeArray(node *yaml.Node, start xml.StartElement) error {
for i := 0; i < len(node.Content); i++ {
value := node.Content[i]
err := e.doEncode(value, start.Copy())
func (e *xmlEncoder) encodeComment(encoder *xml.Encoder, commentStr string) error {
if commentStr != "" {
log.Debugf("encoding comment %v", commentStr)
if !strings.HasSuffix(commentStr, " ") {
commentStr = commentStr + " "
}
var comment xml.Comment = []byte(commentStr)
err := encoder.EncodeToken(comment)
if err != nil {
return err
}
@@ -99,7 +165,23 @@ func (e *xmlEncoder) encodeArray(node *yaml.Node, start xml.StartElement) error
return nil
}
func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
func (e *xmlEncoder) encodeArray(encoder *xml.Encoder, node *yaml.Node, start xml.StartElement) error {
if err := e.encodeComment(encoder, headAndLineComment(node)); err != nil {
return err
}
for i := 0; i < len(node.Content); i++ {
value := node.Content[i]
if err := e.doEncode(encoder, value, start.Copy()); err != nil {
return err
}
}
return e.encodeComment(encoder, footComment(node))
}
func (e *xmlEncoder) encodeMap(encoder *xml.Encoder, node *yaml.Node, start xml.StartElement) error {
log.Debug("its a map")
//first find all the attributes and put them on the start token
for i := 0; i < len(node.Content); i += 2 {
@@ -116,7 +198,7 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
}
}
err := e.xmlEncoder.EncodeToken(start)
err := e.encodeStart(encoder, node, start)
if err != nil {
return err
}
@@ -126,21 +208,38 @@ func (e *xmlEncoder) encodeMap(node *yaml.Node, start xml.StartElement) error {
key := node.Content[i]
value := node.Content[i+1]
err := e.encodeComment(encoder, headAndLineComment(key))
if err != nil {
return err
}
if !strings.HasPrefix(key.Value, e.attributePrefix) && key.Value != e.contentName {
start := xml.StartElement{Name: xml.Name{Local: key.Value}}
err := e.doEncode(value, start)
err := e.doEncode(encoder, value, start)
if err != nil {
return err
}
} else if key.Value == e.contentName {
// directly encode the contents
err = e.encodeComment(encoder, headAndLineComment(value))
if err != nil {
return err
}
var charData xml.CharData = []byte(value.Value)
err = e.xmlEncoder.EncodeToken(charData)
err = encoder.EncodeToken(charData)
if err != nil {
return err
}
err = e.encodeComment(encoder, footComment(value))
if err != nil {
return err
}
}
err = e.encodeComment(encoder, footComment(key))
if err != nil {
return err
}
}
return e.xmlEncoder.EncodeToken(start.End())
return e.encodeEnd(encoder, node, start)
}
+101
View File
@@ -0,0 +1,101 @@
package yqlib
import (
"bufio"
"bytes"
"errors"
"io"
"strings"
yaml "gopkg.in/yaml.v3"
)
type yamlEncoder struct {
indent int
colorise bool
printDocSeparators bool
unwrapScalar bool
}
func NewYamlEncoder(indent int, colorise bool, printDocSeparators bool, unwrapScalar bool) Encoder {
if indent < 0 {
indent = 0
}
return &yamlEncoder{indent, colorise, printDocSeparators, unwrapScalar}
}
func (ye *yamlEncoder) CanHandleAliases() bool {
return true
}
func (ye *yamlEncoder) PrintDocumentSeparator(writer io.Writer) error {
if ye.printDocSeparators {
log.Debug("-- writing doc sep")
if err := writeString(writer, "---\n"); err != nil {
return err
}
}
return nil
}
func (ye *yamlEncoder) PrintLeadingContent(writer io.Writer, content string) error {
// log.Debug("headcommentwas [%v]", content)
reader := bufio.NewReader(strings.NewReader(content))
for {
readline, errReading := reader.ReadString('\n')
if errReading != nil && !errors.Is(errReading, io.EOF) {
return errReading
}
if strings.Contains(readline, "$yqDocSeperator$") {
if err := ye.PrintDocumentSeparator(writer); err != nil {
return err
}
} else {
if err := writeString(writer, readline); err != nil {
return err
}
}
if errors.Is(errReading, io.EOF) {
if readline != "" {
// the last comment we read didn't have a new line, put one in
if err := writeString(writer, "\n"); err != nil {
return err
}
}
break
}
}
return nil
}
func (ye *yamlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
if node.Kind == yaml.ScalarNode && ye.unwrapScalar {
return writeString(writer, node.Value+"\n")
}
destination := writer
tempBuffer := bytes.NewBuffer(nil)
if ye.colorise {
destination = tempBuffer
}
var encoder = yaml.NewEncoder(destination)
encoder.SetIndent(ye.indent)
if err := encoder.Encode(node); err != nil {
return err
}
if ye.colorise {
return colorizeAndPrint(tempBuffer.Bytes(), writer)
}
return nil
}
+1
View File
@@ -25,6 +25,7 @@ func NewExpressionParser() ExpressionParser {
}
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
log.Debug("Parsing expression: [%v]", expression)
tokens, err := p.pathTokeniser.Tokenise(expression)
if err != nil {
return nil, err
+11 -3
View File
@@ -100,7 +100,7 @@ func assignOpToken(updateAssign bool) lex.Action {
}
}
func multiplyWithPrefs() lex.Action {
func multiplyWithPrefs(op *operationType) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
prefs := multiplyPreferences{}
options := string(m.Bytes)
@@ -110,11 +110,14 @@ func multiplyWithPrefs() lex.Action {
if strings.Contains(options, "?") {
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
}
if strings.Contains(options, "n") {
prefs.AssignPrefs = assignPreferences{OnlyWriteNull: true}
}
if strings.Contains(options, "d") {
prefs.DeepMergeArrays = true
}
prefs.TraversePrefs.DontFollowAlias = true
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
op := &Operation{OperationType: op, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil
}
}
@@ -469,13 +472,18 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`strenv\([^\)]+\)`), envOp(true))
lexer.Add([]byte(`env\([^\)]+\)`), envOp(false))
lexer.Add([]byte(`envsubst`), opToken(envsubstOpType))
lexer.Add([]byte(`\[`), literalToken(openCollect, false))
lexer.Add([]byte(`\]\??`), literalToken(closeCollect, true))
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
lexer.Add([]byte(`\*[\+|\?d]*`), multiplyWithPrefs())
lexer.Add([]byte(`\*=[\+|\?dn]*`), multiplyWithPrefs(multiplyAssignOpType))
lexer.Add([]byte(`\*[\+|\?dn]*`), multiplyWithPrefs(multiplyOpType))
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())
+8 -11
View File
@@ -1,30 +1,27 @@
package yqlib
import (
"fmt"
"io"
"io/ioutil"
"os"
)
func safelyRenameFile(from string, to string) {
func tryRenameFile(from string, to string) error {
if renameError := os.Rename(from, to); renameError != nil {
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
log.Debug(renameError.Error())
log.Debug("going to try copying instead")
// can't do this rename when running in docker to a file targeted in a mounted volume,
// so gracefully degrade to copying the entire contents.
if copyError := copyFileContents(from, to); copyError != nil {
log.Errorf("Failed copying from %v to %v", from, to)
log.Error(copyError.Error())
} else {
removeErr := os.Remove(from)
if removeErr != nil {
log.Errorf("failed removing original file: %s", from)
}
return fmt.Errorf("failed copying from %v to %v: %w", from, to, copyError)
}
tryRemoveTempFile(from)
}
return nil
}
func tryRemoveFile(filename string) {
func tryRemoveTempFile(filename string) {
log.Debug("Removing temp file: %v", filename)
removeErr := os.Remove(filename)
if removeErr != nil {
@@ -79,7 +76,7 @@ func createTempFile() (*os.File, error) {
return nil, err
}
file, err := ioutil.TempFile("", "temp")
file, err := os.CreateTemp("", "temp")
if err != nil {
return nil, err
}
+1 -1
View File
@@ -33,7 +33,7 @@ func (f *frontMatterHandlerImpl) GetContentReader() io.Reader {
}
func (f *frontMatterHandlerImpl) CleanUp() {
tryRemoveFile(f.yamlFrontMatterFilename)
tryRemoveTempFile(f.yamlFrontMatterFilename)
}
// Splits the given file by yaml front matter
+9 -8
View File
@@ -1,7 +1,8 @@
package yqlib
import (
"io/ioutil"
"io"
"os"
"testing"
"github.com/mikefarah/yq/v4/test"
@@ -24,7 +25,7 @@ func createTestFile(content string) string {
}
func readFile(filename string) string {
bytes, err := ioutil.ReadFile(filename)
bytes, err := os.ReadFile(filename)
if err != nil {
panic(err)
}
@@ -60,13 +61,13 @@ yaml: doc
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := ioutil.ReadAll(fmHandler.GetContentReader())
contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveFile(file)
tryRemoveTempFile(file)
fmHandler.CleanUp()
}
@@ -97,13 +98,13 @@ yaml: doc
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := ioutil.ReadAll(fmHandler.GetContentReader())
contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveFile(file)
tryRemoveTempFile(file)
fmHandler.CleanUp()
}
@@ -131,12 +132,12 @@ yaml: doc
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := ioutil.ReadAll(fmHandler.GetContentReader())
contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveFile(file)
tryRemoveTempFile(file)
fmHandler.CleanUp()
}
+229
View File
@@ -0,0 +1,229 @@
package yqlib
import (
"bufio"
"bytes"
"fmt"
"strings"
"testing"
"github.com/mikefarah/yq/v4/test"
)
var complexExpectYaml = `D0, P[], (!!map)::a: Easy! as one two three
b:
c: 2
d:
- 3
- 4
`
var jsonScenarios = []formatScenario{
{
description: "Parse json: simple",
subdescription: "JSON is a subset of yaml, so all you need to do is prettify the output",
input: `{"cat": "meow"}`,
expected: "D0, P[], (!!map)::cat: meow\n",
},
{
description: "Parse json: complex",
subdescription: "JSON is a subset of yaml, so all you need to do is prettify the output",
input: `{"a":"Easy! as one two three","b":{"c":2,"d":[3,4]}}`,
expected: complexExpectYaml,
},
{
description: "Encode json: simple",
input: `cat: meow`,
indent: 2,
expected: "{\n \"cat\": \"meow\"\n}\n",
scenarioType: "encode",
},
{
description: "Encode json: simple - in one line",
input: `cat: meow # this is a comment, and it will be dropped.`,
indent: 0,
expected: "{\"cat\":\"meow\"}\n",
scenarioType: "encode",
},
{
description: "Encode json: comments",
input: `cat: meow # this is a comment, and it will be dropped.`,
indent: 2,
expected: "{\n \"cat\": \"meow\"\n}\n",
scenarioType: "encode",
},
{
description: "Encode json: anchors",
subdescription: "Anchors are dereferenced",
input: "cat: &ref meow\nanotherCat: *ref",
indent: 2,
expected: "{\n \"cat\": \"meow\",\n \"anotherCat\": \"meow\"\n}\n",
scenarioType: "encode",
},
{
description: "Encode json: multiple results",
subdescription: "Each matching node is converted into a json doc. This is best used with 0 indent (json document per line)",
input: `things: [{stuff: cool}, {whatever: cat}]`,
expression: `.things[]`,
indent: 0,
expected: "{\"stuff\":\"cool\"}\n{\"whatever\":\"cat\"}\n",
scenarioType: "encode",
},
}
func decodeJson(t *testing.T, jsonString string) *CandidateNode {
docs, err := readDocumentWithLeadingContent(jsonString, "sample.json", 0)
if err != nil {
t.Error(err)
return nil
}
exp, err := NewExpressionParser().ParseExpression(PrettyPrintExp)
if err != nil {
t.Error(err)
return nil
}
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: docs}, exp)
if err != nil {
t.Error(err)
return nil
}
return context.MatchingNodes.Front().Value.(*CandidateNode)
}
func testJsonScenario(t *testing.T, s formatScenario) {
if s.scenarioType == "encode" || s.scenarioType == "roundtrip" {
test.AssertResultWithContext(t, s.expected, processJsonScenario(s), s.description)
} else {
var actual = resultToString(t, decodeJson(t, s.input))
test.AssertResultWithContext(t, s.expected, actual, s.description)
}
}
func processJsonScenario(s formatScenario) string {
var output bytes.Buffer
writer := bufio.NewWriter(&output)
var encoder = NewJsonEncoder(s.indent)
var decoder = NewYamlDecoder()
inputs, err := readDocuments(strings.NewReader(s.input), "sample.yml", 0, decoder)
if err != nil {
panic(err)
}
expression := s.expression
if expression == "" {
expression = "."
}
exp, err := NewExpressionParser().ParseExpression(expression)
if err != nil {
panic(err)
}
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, exp)
if err != nil {
panic(err)
}
printer := NewPrinter(encoder, NewSinglePrinterWriter(writer))
err = printer.PrintResults(context.MatchingNodes)
if err != nil {
panic(err)
}
writer.Flush()
return output.String()
}
func documentJsonDecodeScenario(t *testing.T, w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.json file of:\n")
writeOrPanic(w, fmt.Sprintf("```json\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
writeOrPanic(w, "```bash\nyq -P '.' sample.json\n```\n")
writeOrPanic(w, "will output\n")
var output bytes.Buffer
printer := NewSimpleYamlPrinter(bufio.NewWriter(&output), YamlOutputFormat, true, false, 2, true)
node := decodeJson(t, s.input)
err := printer.PrintResults(node.AsList())
if err != nil {
t.Error(err)
return
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
}
func documentJsonScenario(t *testing.T, w *bufio.Writer, i interface{}) {
s := i.(formatScenario)
if s.skipDoc {
return
}
if s.scenarioType == "encode" {
documentJsonEncodeScenario(w, s)
} else {
documentJsonDecodeScenario(t, w, s)
}
}
func documentJsonEncodeScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression == "" {
expression = "."
}
if s.indent == 2 {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -o=json '%v' sample.yml\n```\n", expression))
} else {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -o=json -I=%v '%v' sample.yml\n```\n", s.indent, expression))
}
writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```json\n%v```\n\n", processJsonScenario(s)))
}
func TestJsonScenarios(t *testing.T) {
for _, tt := range jsonScenarios {
testJsonScenario(t, tt)
}
genericScenarios := make([]interface{}, len(jsonScenarios))
for i, s := range jsonScenarios {
genericScenarios[i] = s
}
documentScenarios(t, "usage", "convert", genericScenarios, documentJsonScenario)
}
+47 -4
View File
@@ -18,10 +18,10 @@ type xmlPreferences struct {
ContentName string
}
var XmlPreferences = xmlPreferences{AttributePrefix: "+", ContentName: "+content"}
var log = logging.MustGetLogger("yq-lib")
var PrettyPrintExp = `(... | (select(tag != "!!str"), select(tag == "!!str") | select(test("(?i)^(y|yes|n|no|on|off)$") | not)) ) style=""`
// GetLogger returns the yq logger instance.
func GetLogger() *logging.Logger {
return log
@@ -57,6 +57,8 @@ var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Prece
var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
var multiplyAssignOpType = &operationType{Type: "MULTIPLY_ASSIGN", NumArgs: 2, Precedence: 42, Handler: multiplyAssignOperator}
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}
@@ -129,6 +131,8 @@ var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler:
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
var envsubstOpType = &operationType{Type: "ENVSUBST", NumArgs: 0, Precedence: 50, Handler: envsubstOperator}
var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}
var selectOpType = &operationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: selectOperator}
@@ -190,9 +194,32 @@ func recurseNodeObjectEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
}
func recursiveNodeEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
if lhs.Kind != rhs.Kind || lhs.Tag != rhs.Tag {
if lhs.Kind != rhs.Kind {
return false
} else if lhs.Tag == "!!null" {
}
if lhs.Kind == yaml.ScalarNode {
//process custom tags of scalar nodes.
//dont worry about matching tags of maps or arrays.
lhsTag := lhs.Tag
rhsTag := rhs.Tag
if !strings.HasPrefix(lhsTag, "!!") {
// custom tag - we have to have a guess
lhsTag = guessTagFromCustomType(lhs)
}
if !strings.HasPrefix(rhsTag, "!!") {
// custom tag - we have to have a guess
rhsTag = guessTagFromCustomType(rhs)
}
if lhsTag != rhsTag {
return false
}
}
if lhs.Tag == "!!null" {
return true
} else if lhs.Kind == yaml.ScalarNode {
@@ -236,6 +263,22 @@ func createScalarNode(value interface{}, stringValue string) *yaml.Node {
return node
}
func headAndLineComment(node *yaml.Node) string {
return headComment(node) + lineComment(node)
}
func headComment(node *yaml.Node) string {
return strings.Replace(node.HeadComment, "#", "", 1)
}
func lineComment(node *yaml.Node) string {
return strings.Replace(node.LineComment, "#", "", 1)
}
func footComment(node *yaml.Node) string {
return strings.Replace(node.FootComment, "#", "", 1)
}
func createValueOperation(value interface{}, stringValue string) *Operation {
var node *yaml.Node = createScalarNode(value, stringValue)
+94 -21
View File
@@ -3,6 +3,7 @@ package yqlib
import (
"fmt"
"strconv"
"strings"
yaml "gopkg.in/yaml.v3"
)
@@ -51,56 +52,128 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
switch lhsNode.Kind {
case yaml.MappingNode:
return nil, fmt.Errorf("maps not yet supported for addition")
addMaps(target, lhs, rhs)
case yaml.SequenceNode:
target.Node.Kind = yaml.SequenceNode
target.Node.Style = lhsNode.Style
target.Node.Tag = "!!seq"
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
addSequences(target, lhs, rhs)
case yaml.ScalarNode:
if rhs.Node.Kind != yaml.ScalarNode {
return nil, fmt.Errorf("%v (%v) cannot be added to a 2%v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
}
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhsNode.Style
return addScalars(target, lhsNode, rhs.Node)
if err := addScalars(target, lhsNode, rhs.Node); err != nil {
return nil, err
}
}
return target, nil
}
func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) {
func guessTagFromCustomType(node *yaml.Node) string {
if node.Value == "" {
log.Warning("node has no value to guess the type with")
return node.Tag
}
if lhs.Tag == "!!str" {
target.Node.Tag = "!!str"
decoder := NewYamlDecoder()
decoder.Init(strings.NewReader(node.Value))
var dataBucket yaml.Node
errorReading := decoder.Decode(&dataBucket)
if errorReading != nil {
log.Warning("could not guess underlying tag type %v", errorReading)
return node.Tag
}
guessedTag := unwrapDoc(&dataBucket).Tag
log.Info("im guessing the tag %v is a %v", node.Tag, guessedTag)
return guessedTag
}
func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) error {
lhsTag := lhs.Tag
rhsTag := rhs.Tag
lhsIsCustom := false
if !strings.HasPrefix(lhsTag, "!!") {
// custom tag - we have to have a guess
lhsTag = guessTagFromCustomType(lhs)
lhsIsCustom = true
}
if !strings.HasPrefix(rhsTag, "!!") {
// custom tag - we have to have a guess
rhsTag = guessTagFromCustomType(rhs)
}
if lhsTag == "!!str" {
target.Node.Tag = lhs.Tag
target.Node.Value = lhs.Value + rhs.Value
} else if lhs.Tag == "!!int" && rhs.Tag == "!!int" {
} else if lhsTag == "!!int" && rhsTag == "!!int" {
format, lhsNum, err := parseInt(lhs.Value)
if err != nil {
return nil, err
return err
}
_, rhsNum, err := parseInt(rhs.Value)
if err != nil {
return nil, err
return err
}
sum := lhsNum + rhsNum
target.Node.Tag = "!!int"
target.Node.Tag = lhs.Tag
target.Node.Value = fmt.Sprintf(format, sum)
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") {
} else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") {
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
if err != nil {
return nil, err
return err
}
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
if err != nil {
return nil, err
return err
}
sum := lhsNum + rhsNum
target.Node.Tag = "!!float"
if lhsIsCustom {
target.Node.Tag = lhs.Tag
} else {
target.Node.Tag = "!!float"
}
target.Node.Value = fmt.Sprintf("%v", sum)
} else {
return nil, fmt.Errorf("%v cannot be added to %v", lhs.Tag, rhs.Tag)
return fmt.Errorf("%v cannot be added to %v", lhsTag, rhsTag)
}
return nil
}
return target, nil
func addSequences(target *CandidateNode, lhs *CandidateNode, rhs *CandidateNode) {
target.Node.Kind = yaml.SequenceNode
if len(lhs.Node.Content) > 0 {
target.Node.Style = lhs.Node.Style
}
target.Node.Tag = lhs.Node.Tag
target.Node.Content = make([]*yaml.Node, len(lhs.Node.Content))
copy(target.Node.Content, lhs.Node.Content)
target.Node.Content = append(target.Node.Content, toNodes(rhs)...)
}
func addMaps(target *CandidateNode, lhsC *CandidateNode, rhsC *CandidateNode) {
lhs := lhsC.Node
rhs := rhsC.Node
target.Node.Content = make([]*yaml.Node, len(lhs.Content))
copy(target.Node.Content, lhs.Content)
for index := 0; index < len(rhs.Content); index = index + 2 {
key := rhs.Content[index]
value := rhs.Content[index+1]
log.Debug("finding %v", key.Value)
indexInLhs := findInArray(target.Node, key)
log.Debug("indexInLhs %v", indexInLhs)
if indexInLhs < 0 {
// not in there, append it
target.Node.Content = append(target.Node.Content, key, value)
} else {
// it's there, replace it
target.Node.Content[indexInLhs+1] = value
}
}
target.Node.Kind = yaml.MappingNode
if len(lhs.Content) > 0 {
target.Node.Style = lhs.Style
}
target.Node.Tag = lhs.Tag
}
+134
View File
@@ -62,6 +62,78 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[a], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
description: "Concatenate to empty array",
document: `{a: []}`,
expression: `.a + "cat"`,
expected: []string{
"D0, P[a], (!!seq)::- cat\n",
},
},
{
skipDoc: true,
description: "Concatenate to existing array",
document: `{a: [dog]}`,
expression: `.a + "cat"`,
expected: []string{
"D0, P[a], (!!seq)::[dog, cat]\n",
},
},
{
skipDoc: true,
description: "Concatenate to empty array",
document: `a: []`,
expression: `.a += "cat"`,
expected: []string{
"D0, P[], (doc)::a:\n - cat\n",
},
},
{
skipDoc: true,
description: "Concatenate to existing array",
document: `a: [dog]`,
expression: `.a += "cat"`,
expected: []string{
"D0, P[], (doc)::a: [dog, cat]\n",
},
},
{
skipDoc: true,
description: "Concatenate to empty object",
document: `{a: {}}`,
expression: `.a + {"b": "cat"}`,
expected: []string{
"D0, P[a], (!!map)::b: cat\n",
},
},
{
skipDoc: true,
description: "Concatenate to existing object",
document: `{a: {c: dog}}`,
expression: `.a + {"b": "cat"}`,
expected: []string{
"D0, P[a], (!!map)::{c: dog, b: cat}\n",
},
},
{
skipDoc: true,
description: "Concatenate to empty object in place",
document: `a: {}`,
expression: `.a += {"b": "cat"}`,
expected: []string{
"D0, P[], (doc)::a:\n b: cat\n",
},
},
{
skipDoc: true,
description: "Concatenate to existing object in place",
document: `a: {c: dog}`,
expression: `.a += {"b": "cat"}`,
expected: []string{
"D0, P[], (doc)::a: {c: dog, b: cat}\n",
},
},
{
description: "Add new object to array",
document: `a: [{dog: woof}]`,
@@ -144,6 +216,68 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (!!str)::cat\n",
},
},
{
description: "Add maps to shallow merge",
subdescription: "Adding objects together shallow merges them. Use `*` to deeply merge.",
document: "a: {thing: {name: Astuff, value: x}, a1: cool}\nb: {thing: {name: Bstuff, legs: 3}, b1: neat}",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: {thing: {name: Bstuff, legs: 3}, a1: cool, b1: neat}\nb: {thing: {name: Bstuff, legs: 3}, b1: neat}\n",
},
},
{
description: "Custom types: that are really strings",
subdescription: "When custom tags are encountered, yq will try to decode the underlying type.",
document: "a: !horse cat\nb: !goat _meow",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: !horse cat_meow\nb: !goat _meow\n",
},
},
{
description: "Custom types: that are really numbers",
subdescription: "When custom tags are encountered, yq will try to decode the underlying type.",
document: "a: !horse 1.2\nb: !goat 2.3",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: !horse 3.5\nb: !goat 2.3\n",
},
},
{
skipDoc: true,
document: "a: !horse 2\nb: !goat 2.3",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: !horse 4.3\nb: !goat 2.3\n",
},
},
{
skipDoc: true,
document: "a: 2\nb: !goat 2.3",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: 4.3\nb: !goat 2.3\n",
},
},
{
skipDoc: true,
description: "Custom types: that are really ints",
document: "a: !horse 2\nb: !goat 3",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: !horse 5\nb: !goat 3\n",
},
},
{
description: "Custom types: that are really arrays",
skipDoc: true,
subdescription: "when custom tags are encountered, yq will try to decode the underlying type.",
document: "a: !horse [a]\nb: !goat [b]",
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::a: !horse [a, b]\nb: !goat [b]\n",
},
},
}
func TestAddOperatorScenarios(t *testing.T) {
+7 -3
View File
@@ -2,13 +2,15 @@ package yqlib
type assignPreferences struct {
DontOverWriteAnchor bool
OnlyWriteNull bool
}
func assignUpdateFunc(prefs assignPreferences) crossFunctionCalculation {
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
rhs.Node = unwrapDoc(rhs.Node)
lhs.UpdateFrom(rhs, prefs)
if !prefs.OnlyWriteNull || lhs.Node.Tag == "!!null" {
lhs.UpdateFrom(rhs, prefs)
}
return lhs, nil
}
}
@@ -76,7 +78,9 @@ func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionN
if expressionNode.Operation.Preferences != nil {
prefs = expressionNode.Operation.Preferences.(assignPreferences)
}
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode), prefs)
if !prefs.OnlyWriteNull || candidate.Node.Tag == "!!null" {
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode), prefs)
}
}
}
return context, nil
+1 -1
View File
@@ -19,7 +19,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
document: "a: []",
expression: `.a += [{"key": "att2", "value": "val2"}]`,
expected: []string{
"D0, P[], (doc)::a: [{key: att2, value: val2}]\n",
"D0, P[], (doc)::a:\n - key: att2\n value: val2\n",
},
},
{
+2 -1
View File
@@ -86,7 +86,8 @@ func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *
var chompRegexp = regexp.MustCompile(`\n$`)
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
if err := processLeadingContent(candidate, writer, false, YamlOutputFormat); err != nil {
var encoder = NewYamlEncoder(2, false, false, false)
if err := encoder.PrintLeadingContent(writer, candidate.LeadingContent); err != nil {
return Context{}, err
}
if err := writer.Flush(); err != nil {
+23 -3
View File
@@ -10,11 +10,31 @@ import (
"gopkg.in/yaml.v3"
)
func yamlToString(candidate *CandidateNode, prefs encoderPreferences) (string, error) {
func configureEncoder(format PrinterOutputFormat, indent int) Encoder {
switch format {
case JsonOutputFormat:
return NewJsonEncoder(indent)
case PropsOutputFormat:
return NewPropertiesEncoder()
case CsvOutputFormat:
return NewCsvEncoder(',')
case TsvOutputFormat:
return NewCsvEncoder('\t')
case YamlOutputFormat:
return NewYamlEncoder(indent, false, true, true)
case XmlOutputFormat:
return NewXmlEncoder(indent, XmlPreferences.AttributePrefix, XmlPreferences.ContentName)
}
panic("invalid encoder")
}
func encodeToString(candidate *CandidateNode, prefs encoderPreferences) (string, error) {
var output bytes.Buffer
log.Debug("printing with indent: %v", prefs.indent)
printer := NewPrinterWithSingleWriter(bufio.NewWriter(&output), prefs.format, true, false, prefs.indent, true)
encoder := configureEncoder(prefs.format, prefs.indent)
printer := NewPrinter(encoder, NewSinglePrinterWriter(bufio.NewWriter(&output)))
err := printer.PrintResults(candidate.AsList())
return output.String(), err
}
@@ -36,7 +56,7 @@ func encodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
stringValue, err := yamlToString(candidate, preferences)
stringValue, err := encodeToString(candidate, preferences)
if err != nil {
return Context{}, err
+25
View File
@@ -1,10 +1,12 @@
package yqlib
import (
"container/list"
"fmt"
"os"
"strings"
envsubst "github.com/a8m/envsubst"
yaml "gopkg.in/yaml.v3"
)
@@ -47,3 +49,26 @@ func envOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
return context.SingleChildContext(target), nil
}
func envsubstOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := unwrapDoc(candidate.Node)
if node.Tag != "!!str" {
log.Warning("EnvSubstOperator, env name:", node.Tag, node.Value)
return Context{}, fmt.Errorf("cannot substitute with %v, can only substitute strings. Hint: Most often you'll want to use '|=' over '=' for this operation", node.Tag)
}
value, err := envsubst.String(node.Value)
if err != nil {
return Context{}, err
}
targetNode := &yaml.Node{Kind: yaml.ScalarNode, Value: value, Tag: "!!str"}
result := candidate.CreateReplacement(targetNode)
results.PushBack(result)
}
return context.ChildContext(results), nil
}
+33
View File
@@ -62,6 +62,39 @@ var envOperatorScenarios = []expressionScenario{
"D0, P[cat], (!!str)::meow\n",
},
},
{
description: "Replace strings with envsubst",
environmentVariable: "cat",
expression: `"the ${myenv} meows" | envsubst`,
expected: []string{
"D0, P[], (!!str)::the cat meows\n",
},
},
{
description: "Replace strings with envsubst, missing variables",
environmentVariable: "cat",
expression: `"the ${myenvnonexisting} meows" | envsubst`,
expected: []string{
"D0, P[], (!!str)::the meows\n",
},
},
{
description: "Replace strings with envsubst, missing variables with defaults",
environmentVariable: "cat",
expression: `"the ${myenvnonexisting-dog} meows" | envsubst`,
expected: []string{
"D0, P[], (!!str)::the dog meows\n",
},
},
{
description: "Replace string environment variable in document",
environmentVariable: "cat meow",
document: "{v: \"${myenv}\"}",
expression: `.v |= envsubst`,
expected: []string{
"D0, P[], (doc)::{v: \"cat meow\"}\n",
},
},
}
func TestEnvOperatorScenarios(t *testing.T) {

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