mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
644063646e | ||
|
|
aabed1a237 | ||
|
|
c12764dba8 | ||
|
|
1d5ecb244d | ||
|
|
7798a141cf | ||
|
|
b7583a538d | ||
|
|
529e88b630 | ||
|
|
658006eb93 | ||
|
|
2743a7e058 | ||
|
|
f95862fba3 | ||
|
|
3f58c4bc38 | ||
|
|
a7975df7cd | ||
|
|
3d5a5e0360 | ||
|
|
155755ae2f | ||
|
|
601c13531c | ||
|
|
69d00c89df | ||
|
|
2faff7b05f | ||
|
|
165949041d | ||
|
|
dbd7ab0f13 | ||
|
|
6d512ad718 | ||
|
|
4fef4a7ab1 | ||
|
|
dcb17b51a9 | ||
|
|
385417556d | ||
|
|
edb5f213d7 | ||
|
|
5cfd9b05ee | ||
|
|
0e764c59ce | ||
|
|
1b887e23b3 | ||
|
|
a76b72e691 | ||
|
|
9509831cff | ||
|
|
e92180e89d | ||
|
|
a6ae33c3f1 | ||
|
|
94a563dfd8 | ||
|
|
0328cfd619 | ||
|
|
88663a6ce3 | ||
|
|
b10a9ccfc6 | ||
|
|
9e9e15df73 | ||
|
|
6cc6fdf322 | ||
|
|
a88c2dc5d3 | ||
|
|
ea231006ed | ||
|
|
80f187f1a4 | ||
|
|
98e8b3479f | ||
|
|
eb539ff326 | ||
|
|
c09f7aa707 | ||
|
|
7f5c380d16 | ||
|
|
6a05e517f1 | ||
|
|
8ee6f7dc1a | ||
|
|
8bd54cd603 | ||
|
|
f2f7b6db0f | ||
|
|
e082fee5d4 | ||
|
|
412911561f | ||
|
|
52ae633cb7 | ||
|
|
9356ca59bb | ||
|
|
6dec167f74 | ||
|
|
00f6981314 | ||
|
|
4c60a2a967 | ||
|
|
f4529614c4 | ||
|
|
f059e13f94 | ||
|
|
4dbf158505 | ||
|
|
39a93a2836 | ||
|
|
7c158fce26 | ||
|
|
0bf43d2a55 | ||
|
|
1b0bce5da6 | ||
|
|
f112bde5fe | ||
|
|
e5aa4a87a4 | ||
|
|
f305e8fa12 | ||
|
|
d2d0c2c111 | ||
|
|
2aab79431c | ||
|
|
540d4953f5 | ||
|
|
7849232255 | ||
|
|
57cd67f055 | ||
|
|
6b17fd4fc1 | ||
|
|
ca8cd78616 | ||
|
|
9876b0ce8f | ||
|
|
a23272727d | ||
|
|
1fb37785d6 |
2
.github/ISSUE_TEMPLATE/bug_report_v3.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report_v3.md
vendored
@@ -10,6 +10,8 @@ assignees: ''
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
Note that any how to questions should be posted in the discussion board and not raised as an issue.
|
||||
|
||||
Version of yq: 3.X.X
|
||||
Operating system: mac/linux/windows/....
|
||||
Installed via: docker/binary release/homebrew/snap/...
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
@@ -10,6 +10,8 @@ assignees: ''
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
Note that any how to questions should be posted in the discussion board and not raised as an issue.
|
||||
|
||||
Version of yq: 4.X.X
|
||||
Operating system: mac/linux/windows/....
|
||||
Installed via: docker/binary release/homebrew/snap/...
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -9,9 +9,11 @@ assignees: ''
|
||||
|
||||
**Please describe your feature request.**
|
||||
A clear and concise description of what the request is and what it would solve.
|
||||
Ex. I wish I could use yq to [...]
|
||||
Eg. I wish I could use yq to [...]
|
||||
|
||||
Please note that V3 will no longer have any enhancements.
|
||||
Note:
|
||||
- how to questions should be posted in the discussion board and not raised as an issue.
|
||||
- V3 will no longer have any enhancements.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
If we have data1.yml like:
|
||||
|
||||
46
.github/workflows/release.yml
vendored
46
.github/workflows/release.yml
vendored
@@ -1,11 +1,41 @@
|
||||
name: Publish image to Dockerhub
|
||||
|
||||
name: Release YQ
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
publishGitRelease:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.15'
|
||||
- name: Cross compile
|
||||
run: |
|
||||
sudo apt-get install rhash -y
|
||||
go get github.com/mitchellh/gox
|
||||
./scripts/xcompile.sh
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
- uses: shogo82148/actions-upload-release-asset@v1
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/*
|
||||
|
||||
publishDocker:
|
||||
environment: dockerhub
|
||||
env:
|
||||
IMAGE_NAME: mikefarah/yq
|
||||
runs-on: ubuntu-latest
|
||||
@@ -38,11 +68,13 @@ jobs:
|
||||
|
||||
- name: Build and push image
|
||||
run: |
|
||||
IMAGE_VERSION="$(git describe --tags --abbrev=0)"
|
||||
IMAGE_V_VERSION="$(git describe --tags --abbrev=0)"
|
||||
IMAGE_VERSION=${IMAGE_V_VERSION:1}
|
||||
|
||||
SHORT_SHA1=$(git rev-parse --short HEAD)
|
||||
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64"
|
||||
echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}"
|
||||
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
docker buildx build --platform "${PLATFORMS}" -t "${IMAGE_NAME}:${IMAGE_VERSION}" -t "${IMAGE_NAME}:latest" \
|
||||
docker buildx build --platform "${PLATFORMS}" -t "${IMAGE_NAME}:${IMAGE_VERSION}" -t "${IMAGE_NAME}:latest" -t "${IMAGE_NAME}:4" \
|
||||
--push .
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN CGO_ENABLED=0 make local build
|
||||
|
||||
# Choose alpine as a base image to make this useful for CI, as many
|
||||
# CI tools expect an interactive shell inside the container
|
||||
FROM alpine:3.12 as production
|
||||
FROM alpine:3.12.3 as production
|
||||
|
||||
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
||||
RUN chmod +x /usr/bin/yq
|
||||
|
||||
@@ -4,6 +4,7 @@ COPY scripts/devtools.sh /opt/devtools.sh
|
||||
|
||||
RUN set -e -x \
|
||||
&& /opt/devtools.sh
|
||||
ENV PATH=/go/bin:$PATH
|
||||
|
||||
# install mkdocs
|
||||
RUN set -ex \
|
||||
|
||||
83
README.md
83
README.md
@@ -3,25 +3,64 @@
|
||||
   
|
||||
|
||||
|
||||
a lightweight and portable command-line YAML processor
|
||||
a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
|
||||
|
||||
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
|
||||
yq is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as docker, all listed below.
|
||||
|
||||
## V4 released!
|
||||
V4 is now officially released, it's quite different from V3 (sorry for the migration), however it is much more similar to ```jq```, using a similar expression syntax and therefore support much more complex functionality!
|
||||
|
||||
If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3).
|
||||
|
||||
Support for v3 will cease August 2021, until then, critical bug and security fixes will still get applied if required.
|
||||
|
||||
## Install
|
||||
|
||||
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
|
||||
|
||||
### MacOS:
|
||||
### wget
|
||||
Use wget to download the pre-compiled binaries:
|
||||
|
||||
#### Compressed via tar.gz
|
||||
```bash
|
||||
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\
|
||||
tar xz && mv ${BINARY} /usr/bin/yq
|
||||
```
|
||||
|
||||
#### Plain binary
|
||||
|
||||
```bash
|
||||
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
|
||||
chmod +x /usr/bin/yq
|
||||
```
|
||||
|
||||
For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
|
||||
|
||||
### MacOS / Linux via Homebrew:
|
||||
Using [Homebrew](https://brew.sh/)
|
||||
```
|
||||
brew install yq
|
||||
```
|
||||
|
||||
### Ubuntu and other Linux distros supporting `snap` packages:
|
||||
or, for the (deprecated) v3 version:
|
||||
|
||||
```
|
||||
brew install yq@3
|
||||
```
|
||||
|
||||
Note that for v3, as it is a versioned brew it will not add the `yq` command to your path automatically. Please follow the instructions given by brew upon installation.
|
||||
|
||||
### Linux via snap:
|
||||
```
|
||||
snap install yq
|
||||
```
|
||||
|
||||
or, for the (deprecated) v3 version:
|
||||
|
||||
```
|
||||
snap install yq --channel=v3/stable
|
||||
```
|
||||
|
||||
#### Snap notes
|
||||
`yq` installs with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
|
||||
|
||||
@@ -40,17 +79,6 @@ sudo mv /etc/myfile.tmp /etc/myfile
|
||||
rm /etc/myfile.tmp
|
||||
```
|
||||
|
||||
### wget
|
||||
|
||||
Use wget to download the pre-compiled binaries:
|
||||
|
||||
```bash
|
||||
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
|
||||
chmod +x /usr/bin/yq
|
||||
```
|
||||
|
||||
For instance, VERSION=4.0.0 and BINARY=yq_linux_amd64
|
||||
|
||||
### Run with Docker
|
||||
|
||||
#### Oneshot use:
|
||||
@@ -75,12 +103,20 @@ yq() {
|
||||
|
||||
### Go Get:
|
||||
```
|
||||
GO111MODULE=on go get github.com/mikefarah/yq/v4
|
||||
GO111MODULE=on go get github.com/mikefarah/yq
|
||||
```
|
||||
|
||||
## Community Supported Installation methods
|
||||
As these are supported by the community :heart: - however, they may be out of date with the officially supported releases.
|
||||
|
||||
# Webi
|
||||
|
||||
```
|
||||
webi yq
|
||||
```
|
||||
|
||||
See [webi](https://webinstall.dev/)
|
||||
Supported by @adithyasunil26 (https://github.com/webinstall/webi-installers/tree/master/yq)
|
||||
|
||||
### Windows:
|
||||
```
|
||||
@@ -121,13 +157,13 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
|
||||
- Colorized yaml output
|
||||
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
|
||||
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/v/v4.x/sort-keys)
|
||||
- Manipulate yaml [comments](https://app.gitbook.com/@mikefarah/s/yq/v/v4.x/comment-operators), [styling](https://app.gitbook.com/@mikefarah/s/yq/v/v4.x/style), [tags](https://app.gitbook.com/@mikefarah/s/yq/v/v4.x/tag) and [anchors](https://app.gitbook.com/@mikefarah/s/yq/v/v4.x/explode).
|
||||
- Manipulate yaml [comments](https://mikefarah.gitbook.io/yq/comment-operators), [styling](https://mikefarah.gitbook.io/yq/style), [tags](https://mikefarah.gitbook.io/yq/tag) and [anchors and aliases](https://mikefarah.gitbook.io/yq/anchor-and-alias-operators).
|
||||
- [Update yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags)
|
||||
- [Complex expressions to select and update](https://mikefarah.gitbook.io/yq/v/v4.x/select#select-and-update-matching-values-in-map)
|
||||
- Keeps yaml formatting and comments when updating (though there are issues with whitespace)
|
||||
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
|
||||
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate)
|
||||
- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
|
||||
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
|
||||
|
||||
## [Usage](https://mikefarah.gitbook.io/yq/)
|
||||
|
||||
@@ -153,6 +189,7 @@ Flags:
|
||||
-M, --no-colors force print with no colors
|
||||
-N, --no-doc Don't print document separators (---)
|
||||
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.
|
||||
-P, --prettyPrint pretty print, shorthand for '... style = ""'
|
||||
-j, --tojson output as json. Set indent to 0 to print json in one line.
|
||||
-v, --verbose verbose mode
|
||||
-V, --version Print version information and quit
|
||||
@@ -166,15 +203,5 @@ Simple Example:
|
||||
yq e '.a.b | length' f1.yml f2.yml
|
||||
```
|
||||
|
||||
## Upgrade from V3
|
||||
If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3).
|
||||
|
||||
## V4 is in development!
|
||||
If you're keen - check out the alpha release [here](https://github.com/mikefarah/yq/releases/), or in docker `mikefarah/yq:4-beta1` and the docs (also in beta) [here](https://mikefarah.gitbook.io/yq/v/v4.x-alpha/).
|
||||
|
||||
V4 is quite different from V3 (sorry for the migration), however it will be much more similar to ```jq```, use a similar expression syntax and therefore support much more complex functionality!
|
||||
|
||||
For now - new features will be held off from V3 in anticipation of the V4 build. Critical fixes / issues will still be released.
|
||||
|
||||
## Known Issues / Missing Features
|
||||
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
|
||||
|
||||
@@ -14,5 +14,6 @@ var noDocSeparators = false
|
||||
var nullInput = false
|
||||
var verbose = false
|
||||
var version = false
|
||||
var prettyPrint = false
|
||||
|
||||
var completedSuccessfully = false
|
||||
|
||||
@@ -65,19 +65,19 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
|
||||
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer)
|
||||
} else {
|
||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
|
||||
}
|
||||
|
||||
completedSuccessfully = err == nil
|
||||
|
||||
@@ -33,6 +33,16 @@ yq e '.a.b = "cool"' -i file.yaml
|
||||
}
|
||||
return cmdEvalSequence
|
||||
}
|
||||
|
||||
func processExpression(expression string) string {
|
||||
if prettyPrint && expression == "" {
|
||||
return `... style=""`
|
||||
} else if prettyPrint {
|
||||
return fmt.Sprintf("%v | ... style= \"\"", expression)
|
||||
}
|
||||
return expression
|
||||
}
|
||||
|
||||
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||
cmd.SilenceUsage = true
|
||||
// 0 args, read std in
|
||||
@@ -76,16 +86,16 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = streamEvaluator.EvaluateNew(args[0], printer)
|
||||
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer)
|
||||
} else {
|
||||
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
|
||||
@@ -47,6 +47,8 @@ func New() *cobra.Command {
|
||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
|
||||
rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print, shorthand for '... style = \"\"'")
|
||||
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
|
||||
|
||||
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "4.0.0"
|
||||
Version = "4.2.1"
|
||||
|
||||
// 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mikefarah/yq:3
|
||||
FROM mikefarah/yq:4.2.1
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
||||
12
go.mod
12
go.mod
@@ -2,17 +2,15 @@ module github.com/mikefarah/yq/v4
|
||||
|
||||
require (
|
||||
github.com/elliotchance/orderedmap v1.3.0
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/goccy/go-yaml v1.8.1
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/mattn/go-colorable v0.1.7 // indirect
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/goccy/go-yaml v1.8.4
|
||||
github.com/jinzhu/copier v0.1.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/timtadh/data-structures v0.5.3 // indirect
|
||||
github.com/timtadh/lexmachine v0.2.2
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
go 1.15
|
||||
|
||||
42
go.sum
42
go.sum
@@ -39,19 +39,21 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
|
||||
github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
|
||||
github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-yaml v1.8.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
|
||||
github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
|
||||
github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
|
||||
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -99,8 +101,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
||||
github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88=
|
||||
github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@@ -118,15 +120,9 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -204,6 +200,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -251,22 +248,17 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -291,8 +283,6 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
@@ -322,8 +312,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
|
||||
@@ -335,8 +323,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -21,6 +21,14 @@ func (n *CandidateNode) GetKey() string {
|
||||
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
||||
}
|
||||
|
||||
func (n *CandidateNode) CreateChildPath(path interface{}) []interface{} {
|
||||
//don't use append as they may actually modify the path of the orignal node!
|
||||
newPath := make([]interface{}, len(n.Path)+1)
|
||||
copy(newPath, n.Path)
|
||||
newPath[len(n.Path)] = path
|
||||
return newPath
|
||||
}
|
||||
|
||||
func (n *CandidateNode) Copy() (*CandidateNode, error) {
|
||||
clone := &CandidateNode{}
|
||||
err := copier.Copy(clone, n)
|
||||
@@ -32,6 +40,7 @@ func (n *CandidateNode) Copy() (*CandidateNode, error) {
|
||||
|
||||
// updates this candidate from the given candidate node
|
||||
func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
|
||||
|
||||
n.UpdateAttributesFrom(other)
|
||||
n.Node.Content = other.Node.Content
|
||||
n.Node.Value = other.Node.Value
|
||||
|
||||
73
pkg/yqlib/doc/Alternative (Default value).md
Normal file
73
pkg/yqlib/doc/Alternative (Default value).md
Normal file
@@ -0,0 +1,73 @@
|
||||
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
|
||||
|
||||
## LHS is defined
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: bridge
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
bridge
|
||||
```
|
||||
|
||||
## LHS is not defined
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
{}
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
hello
|
||||
```
|
||||
|
||||
## LHS is null
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: ~
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
hello
|
||||
```
|
||||
|
||||
## LHS is false
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: false
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
hello
|
||||
```
|
||||
|
||||
## RHS is an expression
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: false
|
||||
b: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
```
|
||||
|
||||
194
pkg/yqlib/doc/Anchor and Alias Operators.md
Normal file
194
pkg/yqlib/doc/Anchor and Alias Operators.md
Normal file
@@ -0,0 +1,194 @@
|
||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
|
||||
|
||||
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||
|
||||
|
||||
## Get anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &billyBob cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | anchor' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
billyBob
|
||||
```
|
||||
|
||||
## Set anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a anchor = "foobar"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: &foobar cat
|
||||
```
|
||||
|
||||
## Set anchor relatively using assign-update
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
b: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a anchor |= .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: &cat
|
||||
b: cat
|
||||
```
|
||||
|
||||
## Get alias
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
b: &billyBob meow
|
||||
a: *billyBob
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | alias' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
billyBob
|
||||
```
|
||||
|
||||
## Set alias
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
b: &meow purr
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a alias = "meow"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
b: &meow purr
|
||||
a: *meow
|
||||
```
|
||||
|
||||
## Set alias relatively using assign-update
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
b: &meow purr
|
||||
a:
|
||||
f: meow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a alias |= .f' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
b: &meow purr
|
||||
a: *meow
|
||||
```
|
||||
|
||||
## Explode alias and anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
a: &a cat
|
||||
b: *a
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.f)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
f:
|
||||
a: cat
|
||||
b: cat
|
||||
```
|
||||
|
||||
## Explode with no aliases or anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: mike
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.a)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: mike
|
||||
```
|
||||
|
||||
## Explode with alias keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
a: &a cat
|
||||
*a: b
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.f)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
f:
|
||||
a: cat
|
||||
cat: b
|
||||
```
|
||||
|
||||
## Explode with merge anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foo:
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar:
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: bar_b
|
||||
a: foo_a
|
||||
thing: bar_thing
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foo_c
|
||||
a: foo_a
|
||||
thing: foobar_thing
|
||||
```
|
||||
|
||||
@@ -34,6 +34,27 @@ a:
|
||||
g: foof
|
||||
```
|
||||
|
||||
## Update node from another file
|
||||
Note this will also work when the second file is a scalar (string/number)
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: apples
|
||||
```
|
||||
And another sample another.yml file of:
|
||||
```yaml
|
||||
b: bob
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)' sample.yml another.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
b: bob
|
||||
```
|
||||
|
||||
## Update node to be the sibling value
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -112,7 +133,7 @@ a:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '(.a.[] | select(. == "apple")) = "frog"' sample.yml
|
||||
yq eval '(.a[] | select(. == "apple")) = "frog"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
@@ -13,6 +13,22 @@ will output
|
||||
a: cat # single
|
||||
```
|
||||
|
||||
## Use update assign to perform relative updates
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
b: dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.. lineComment |= .' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat # cat
|
||||
b: dog # dog
|
||||
```
|
||||
|
||||
## Set head comment
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -14,6 +14,23 @@ will output
|
||||
a: cat
|
||||
```
|
||||
|
||||
## Delete nested entry in map
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
a1: fred
|
||||
a2: frood
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'del(.a.a1)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
a2: frood
|
||||
```
|
||||
|
||||
## Delete entry in array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -31,6 +48,21 @@ will output
|
||||
- 3
|
||||
```
|
||||
|
||||
## Delete nested entry in array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- a: cat
|
||||
b: dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'del(.[0].a)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- b: dog
|
||||
```
|
||||
|
||||
## Delete no matches
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -63,3 +95,23 @@ will output
|
||||
b: dog
|
||||
```
|
||||
|
||||
## Recursively delete matching keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
name: frog
|
||||
b:
|
||||
name: blog
|
||||
age: 12
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'del(.. | select(has("name")).name)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
b:
|
||||
age: 12
|
||||
```
|
||||
|
||||
|
||||
@@ -17,6 +17,24 @@ will output
|
||||
1
|
||||
```
|
||||
|
||||
## Retrieve a document index, shorthand
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
---
|
||||
a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | di' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
0
|
||||
---
|
||||
1
|
||||
```
|
||||
|
||||
## Filter by document index
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -26,7 +44,23 @@ a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'select(. | documentIndex == 1)' sample.yml
|
||||
yq eval 'select(documentIndex == 1)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: frog
|
||||
```
|
||||
|
||||
## Filter by document index shorthand
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
---
|
||||
a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'select(di == 1)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -42,7 +76,7 @@ a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml
|
||||
yq eval '.a | ({"match": ., "doc": documentIndex})' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
76
pkg/yqlib/doc/Env Variable Operators.md
Normal file
76
pkg/yqlib/doc/Env Variable Operators.md
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
## Read string environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="cat meow" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat meow
|
||||
```
|
||||
|
||||
## Read boolean environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="true" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: true
|
||||
```
|
||||
|
||||
## Read numeric environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="12" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 12
|
||||
```
|
||||
|
||||
## Read yaml environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="{b: fish}" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: {b: fish}
|
||||
```
|
||||
|
||||
## Read boolean environment variable as a string
|
||||
Running
|
||||
```bash
|
||||
myenv="true" yq eval --null-input '.a = strenv(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: "true"
|
||||
```
|
||||
|
||||
## Read numeric environment variable as a string
|
||||
Running
|
||||
```bash
|
||||
myenv="12" yq eval --null-input '.a = strenv(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: "12"
|
||||
```
|
||||
|
||||
## Dynamic key lookup with environment variable
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cat: meow
|
||||
dog: woof
|
||||
```
|
||||
then
|
||||
```bash
|
||||
myenv="cat" yq eval '.[env(myenv)]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
meow
|
||||
```
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
Explodes (or dereferences) aliases and anchors.
|
||||
## Explode alias and anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
a: &a cat
|
||||
b: *a
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.f)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
f:
|
||||
a: cat
|
||||
b: cat
|
||||
```
|
||||
|
||||
## Explode with no aliases or anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: mike
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.a)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: mike
|
||||
```
|
||||
|
||||
## Explode with alias keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
a: &a cat
|
||||
*a: b
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.f)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
f:
|
||||
a: cat
|
||||
cat: b
|
||||
```
|
||||
|
||||
## Explode with merge anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foo:
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar:
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: bar_b
|
||||
a: foo_a
|
||||
thing: bar_thing
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foo_c
|
||||
a: foo_a
|
||||
thing: foobar_thing
|
||||
```
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
||||
|
||||
Note that the `fileIndex` operator has a short alias of `fi`.
|
||||
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
```
|
||||
## Get filename
|
||||
Given a sample.yml file of:
|
||||
@@ -16,7 +18,7 @@ yq eval 'filename' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
sample.yaml
|
||||
sample.yml
|
||||
```
|
||||
|
||||
## Get file index
|
||||
@@ -33,3 +35,17 @@ will output
|
||||
0
|
||||
```
|
||||
|
||||
## Get file index alias
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'fi' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
0
|
||||
```
|
||||
|
||||
|
||||
@@ -1,8 +1,97 @@
|
||||
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches, for instance to set the `style` of all nodes in a yaml doc:
|
||||
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
||||
|
||||
## match values form `..`
|
||||
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||
|
||||
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
|
||||
|
||||
```bash
|
||||
yq eval '.. style= "flow"' file.yaml
|
||||
```
|
||||
|
||||
## match values and map keys form `...`
|
||||
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
|
||||
|
||||
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||
|
||||
```bash
|
||||
yq eval '... style= "flow"' file.yaml
|
||||
```
|
||||
## Recurse map (values only)
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '..' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: frog
|
||||
frog
|
||||
```
|
||||
|
||||
## Recursively find nodes with keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
name: frog
|
||||
b:
|
||||
name: blog
|
||||
age: 12
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.. | select(has("name"))' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
name: frog
|
||||
b:
|
||||
name: blog
|
||||
age: 12
|
||||
name: blog
|
||||
age: 12
|
||||
```
|
||||
|
||||
## Recursively find nodes with values
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
nameA: frog
|
||||
b:
|
||||
nameB: frog
|
||||
age: 12
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.. | select(. == "frog")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
frog
|
||||
frog
|
||||
```
|
||||
|
||||
## Recurse map (values and keys)
|
||||
Note that the map key appears in the results
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '...' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: frog
|
||||
a
|
||||
frog
|
||||
```
|
||||
|
||||
## Aliases are not traversed
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -40,6 +40,26 @@ c: "3.2"
|
||||
e: "true"
|
||||
```
|
||||
|
||||
## Set double quote style on map keys too
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
b: 5
|
||||
c: 3.2
|
||||
e: true
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '... style="double"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
"a": "cat"
|
||||
"b": "5"
|
||||
"c": "3.2"
|
||||
"e": "true"
|
||||
```
|
||||
|
||||
## Set single quote style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -125,19 +145,19 @@ will output
|
||||
{a: cat, b: 5, c: 3.2, e: true}
|
||||
```
|
||||
|
||||
## Pretty print
|
||||
Set empty (default) quote style
|
||||
## Reset style - or pretty print
|
||||
Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
b: 5
|
||||
c: 3.2
|
||||
e: true
|
||||
"b": 5
|
||||
'c': 3.2
|
||||
"e": true
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.. style=""' sample.yml
|
||||
yq eval '... style=""' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -147,6 +167,22 @@ c: 3.2
|
||||
e: true
|
||||
```
|
||||
|
||||
## Set style relatively with assign-update
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: single
|
||||
b: double
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] style |= .' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 'single'
|
||||
b: "double"
|
||||
```
|
||||
|
||||
## Read style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -22,7 +22,21 @@ will output
|
||||
!!seq
|
||||
```
|
||||
|
||||
## Convert numbers to strings
|
||||
## Set custom tag
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: str
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a tag = "!!mikefarah"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: !!mikefarah str
|
||||
```
|
||||
|
||||
## Find numbers and convert them to strings
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
|
||||
@@ -48,6 +48,24 @@ will output
|
||||
frog
|
||||
```
|
||||
|
||||
## Dynamic keys
|
||||
Expressions within [] can be used to dynamically lookup / calculate keys
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
b: apple
|
||||
apple: crispy yum
|
||||
banana: soft yum
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[.b]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
crispy yum
|
||||
```
|
||||
|
||||
## Children don't exist
|
||||
Nodes are added dynamically while traversing
|
||||
|
||||
@@ -106,7 +124,7 @@ b: *cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.b.[]' sample.yml
|
||||
yq eval '.b[]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -290,7 +308,7 @@ foobar:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.foobar.[]' sample.yml
|
||||
yq eval '.foobar[]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -356,7 +374,7 @@ foobar:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.foobarList.[]' sample.yml
|
||||
yq eval '.foobarList[]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -366,3 +384,21 @@ bar_thing
|
||||
foobarList_c
|
||||
```
|
||||
|
||||
## Select multiple indices
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a[0, 2]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a
|
||||
c
|
||||
```
|
||||
|
||||
|
||||
1
pkg/yqlib/doc/headers/Alternative (Default value).md
Normal file
1
pkg/yqlib/doc/headers/Alternative (Default value).md
Normal file
@@ -0,0 +1 @@
|
||||
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
|
||||
4
pkg/yqlib/doc/headers/Anchor and Alias Operators.md
Normal file
4
pkg/yqlib/doc/headers/Anchor and Alias Operators.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
|
||||
|
||||
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||
|
||||
0
pkg/yqlib/doc/headers/Env Variable Operators.md
Normal file
0
pkg/yqlib/doc/headers/Env Variable Operators.md
Normal file
@@ -1 +0,0 @@
|
||||
Explodes (or dereferences) aliases and anchors.
|
||||
@@ -1,7 +1,9 @@
|
||||
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
||||
|
||||
Note that the `fileIndex` operator has a short alias of `fi`.
|
||||
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
```
|
||||
@@ -1,5 +1,19 @@
|
||||
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches, for instance to set the `style` of all nodes in a yaml doc:
|
||||
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
||||
|
||||
## match values form `..`
|
||||
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||
|
||||
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
|
||||
|
||||
```bash
|
||||
yq eval '.. style= "flow"' file.yaml
|
||||
```
|
||||
|
||||
## match values and map keys form `...`
|
||||
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
|
||||
|
||||
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||
|
||||
```bash
|
||||
yq eval '... style= "flow"' file.yaml
|
||||
```
|
||||
@@ -19,7 +19,7 @@ type OperationType struct {
|
||||
}
|
||||
|
||||
// operators TODO:
|
||||
// - cookbook doc for common things
|
||||
// - keys operator for controlling key metadata (particularly anchors/aliases)
|
||||
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||
|
||||
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
||||
@@ -36,9 +36,12 @@ var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Pre
|
||||
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
|
||||
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
|
||||
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
|
||||
var AssignAnchor = &OperationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: AssignAnchorOperator}
|
||||
var AssignAlias = &OperationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: AssignAliasOperator}
|
||||
|
||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
|
||||
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
|
||||
var Alternative = &OperationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: AlternativeOperator}
|
||||
|
||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||
@@ -50,6 +53,8 @@ var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handle
|
||||
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
|
||||
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
|
||||
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
|
||||
var GetAnchor = &OperationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: GetAnchorOperator}
|
||||
var GetAlias = &OperationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: GetAliasOperator}
|
||||
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
|
||||
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
|
||||
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
|
||||
@@ -60,10 +65,12 @@ var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Han
|
||||
|
||||
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
||||
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||
var TraverseArray = &OperationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: TraverseArrayOperator}
|
||||
|
||||
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
|
||||
var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator}
|
||||
var EnvOp = &OperationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: EnvOperator}
|
||||
var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator}
|
||||
var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
|
||||
|
||||
@@ -72,6 +79,7 @@ var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Pre
|
||||
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
|
||||
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
||||
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
||||
var DeleteImmediateChild = &OperationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: DeleteImmediateChildOperator}
|
||||
|
||||
type Operation struct {
|
||||
OperationType *OperationType
|
||||
@@ -79,6 +87,7 @@ type Operation struct {
|
||||
StringValue string
|
||||
CandidateNode *CandidateNode // used for Value Path elements
|
||||
Preferences interface{}
|
||||
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs (instead of matching nodes)
|
||||
}
|
||||
|
||||
func CreateValueOperation(value interface{}, stringValue string) *Operation {
|
||||
|
||||
@@ -16,19 +16,13 @@ func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
|
||||
|
||||
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
assignmentOp := &Operation{OperationType: Assign}
|
||||
assignmentOp.Preferences = &AssignOpPreferences{true}
|
||||
assignmentOp.UpdateAssign = true
|
||||
|
||||
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
|
||||
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
|
||||
}
|
||||
|
||||
func toNodes(candidates *list.List) []*yaml.Node {
|
||||
|
||||
if candidates.Len() == 0 {
|
||||
return []*yaml.Node{}
|
||||
}
|
||||
candidate := candidates.Front().Value.(*CandidateNode)
|
||||
|
||||
func toNodes(candidate *CandidateNode) []*yaml.Node {
|
||||
if candidate.Node.Tag == "!!null" {
|
||||
return []*yaml.Node{}
|
||||
}
|
||||
@@ -44,40 +38,33 @@ func toNodes(candidates *list.List) []*yaml.Node {
|
||||
|
||||
func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("Add operator")
|
||||
var results = list.New()
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
lhsCandidate := el.Value.(*CandidateNode)
|
||||
lhsNode := UnwrapDoc(lhsCandidate.Node)
|
||||
|
||||
target := &CandidateNode{
|
||||
Path: lhsCandidate.Path,
|
||||
Document: lhsCandidate.Document,
|
||||
Filename: lhsCandidate.Filename,
|
||||
Node: &yaml.Node{},
|
||||
}
|
||||
|
||||
switch lhsNode.Kind {
|
||||
case yaml.MappingNode:
|
||||
return nil, fmt.Errorf("Maps not yet supported for addition")
|
||||
case yaml.SequenceNode:
|
||||
target.Node.Kind = yaml.SequenceNode
|
||||
target.Node.Style = lhsNode.Style
|
||||
target.Node.Tag = "!!seq"
|
||||
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||
results.PushBack(target)
|
||||
case yaml.ScalarNode:
|
||||
return nil, fmt.Errorf("Scalars not yet supported for addition")
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
return crossFunction(d, matchingNodes, pathNode, add)
|
||||
}
|
||||
|
||||
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
|
||||
target := &CandidateNode{
|
||||
Path: lhs.Path,
|
||||
Document: lhs.Document,
|
||||
Filename: lhs.Filename,
|
||||
Node: &yaml.Node{},
|
||||
}
|
||||
lhsNode := lhs.Node
|
||||
|
||||
switch lhsNode.Kind {
|
||||
case yaml.MappingNode:
|
||||
return nil, fmt.Errorf("Maps not yet supported for addition")
|
||||
case yaml.SequenceNode:
|
||||
target.Node.Kind = yaml.SequenceNode
|
||||
target.Node.Style = lhsNode.Style
|
||||
target.Node.Tag = "!!seq"
|
||||
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||
case yaml.ScalarNode:
|
||||
return nil, fmt.Errorf("Scalars not yet supported for addition")
|
||||
}
|
||||
|
||||
return target, nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,14 @@ var addOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a], (!!seq)::[1, 2, 3, 4]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `[1] + ([2], [3])`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- 1\n- 2\n",
|
||||
"D0, P[], (!!seq)::- 1\n- 3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Concatenate null to array",
|
||||
document: `{a: [1,2]}`,
|
||||
|
||||
28
pkg/yqlib/operator_alternative.go
Normal file
28
pkg/yqlib/operator_alternative.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
// corssFunction no matches
|
||||
// can boolean use crossfunction
|
||||
|
||||
func AlternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- alternative")
|
||||
return crossFunction(d, matchingNodes, pathNode, alternativeFunc)
|
||||
}
|
||||
|
||||
func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
|
||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||
|
||||
isTrue, err := isTruthy(lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if isTrue {
|
||||
return lhs, nil
|
||||
}
|
||||
return rhs, nil
|
||||
}
|
||||
62
pkg/yqlib/operator_alternative_test.go
Normal file
62
pkg/yqlib/operator_alternative_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var alternativeOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "LHS is defined",
|
||||
expression: `.a // "hello"`,
|
||||
document: `{a: bridge}`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!str)::bridge\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LHS is not defined",
|
||||
expression: `.a // "hello"`,
|
||||
document: `{}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::hello\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LHS is null",
|
||||
expression: `.a // "hello"`,
|
||||
document: `{a: ~}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::hello\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LHS is false",
|
||||
expression: `.a // "hello"`,
|
||||
document: `{a: false}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::hello\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "RHS is an expression",
|
||||
expression: `.a // .b`,
|
||||
document: `{a: false, b: cat}`,
|
||||
expected: []string{
|
||||
"D0, P[b], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `false // true`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAlternativeOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range alternativeOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Alternative (Default value)", alternativeOperatorScenarios)
|
||||
}
|
||||
@@ -3,9 +3,119 @@ package yqlib
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignAlias operator!")
|
||||
|
||||
aliasName := ""
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rhs.Front() != nil {
|
||||
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting aliasName : %v", candidate.GetKey())
|
||||
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rhs.Front() != nil {
|
||||
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
candidate.Node.Kind = yaml.AliasNode
|
||||
candidate.Node.Value = aliasName
|
||||
}
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func GetAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetAlias operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func AssignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignAnchor operator!")
|
||||
|
||||
anchorName := ""
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.Front() != nil {
|
||||
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting anchorName of : %v", candidate.GetKey())
|
||||
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.Front() != nil {
|
||||
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
candidate.Node.Anchor = anchorName
|
||||
}
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func GetAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetAnchor operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
anchor := candidate.Node.Anchor
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- ExplodeOperation")
|
||||
|
||||
@@ -4,7 +4,55 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var explodeTest = []expressionScenario{
|
||||
var anchorOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Get anchor",
|
||||
document: `a: &billyBob cat`,
|
||||
expression: `.a | anchor`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!str)::billyBob\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set anchor",
|
||||
document: `a: cat`,
|
||||
expression: `.a anchor = "foobar"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: &foobar cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set anchor relatively using assign-update",
|
||||
document: `a: {b: cat}`,
|
||||
expression: `.a anchor |= .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: &cat {b: cat}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get alias",
|
||||
document: `{b: &billyBob meow, a: *billyBob}`,
|
||||
expression: `.a | alias`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!str)::billyBob\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set alias",
|
||||
document: `{b: &meow purr, a: cat}`,
|
||||
expression: `.a alias = "meow"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set alias relatively using assign-update",
|
||||
document: `{b: &meow purr, a: {f: meow}}`,
|
||||
expression: `.a alias |= .f`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Explode alias and anchor",
|
||||
document: `{f : {a: &a cat, b: *a}}`,
|
||||
@@ -82,9 +130,9 @@ foobar:
|
||||
},
|
||||
}
|
||||
|
||||
func TestExplodeOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range explodeTest {
|
||||
func TestAnchorAliaseOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range anchorOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Explode", explodeTest)
|
||||
documentScenarios(t, "Anchor and Alias Operators", anchorOperatorScenarios)
|
||||
}
|
||||
@@ -2,26 +2,20 @@ package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
type AssignOpPreferences struct {
|
||||
UpdateAssign bool
|
||||
}
|
||||
|
||||
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
|
||||
|
||||
var rhs *list.List
|
||||
if !preferences.UpdateAssign {
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
}
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
if preferences.UpdateAssign {
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
}
|
||||
|
||||
@@ -33,7 +27,9 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
||||
rhsCandidate := first.Value.(*CandidateNode)
|
||||
rhsCandidate.Node = UnwrapDoc(rhsCandidate.Node)
|
||||
candidate.UpdateFrom(rhsCandidate)
|
||||
}
|
||||
}
|
||||
// // if there was nothing given, perhaps we are creating a new yaml doc
|
||||
|
||||
@@ -20,6 +20,16 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::{a: {g: foof}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update node from another file",
|
||||
subdescription: "Note this will also work when the second file is a scalar (string/number)",
|
||||
document: `{a: apples}`,
|
||||
document2: "{b: bob}",
|
||||
expression: `select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: bob}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update node to be the sibling value",
|
||||
document: `{a: {b: child}, b: sibling}`,
|
||||
@@ -80,7 +90,15 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Update selected results",
|
||||
document: `{a: {b: apple, c: cactus}}`,
|
||||
expression: `(.a.[] | select(. == "apple")) = "frog"`,
|
||||
expression: `(.a[] | select(. == "apple")) = "frog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {b: apple, c: cactus}}`,
|
||||
expression: `(.a.[] | select(. == "apple")) = "frog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
||||
},
|
||||
|
||||
@@ -25,74 +25,39 @@ func isTruthy(c *CandidateNode) (bool, error) {
|
||||
|
||||
type boolOp func(bool, bool) bool
|
||||
|
||||
func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
|
||||
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
||||
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
||||
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
||||
func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
|
||||
lhsTrue, errDecoding := isTruthy(lhs)
|
||||
if errDecoding != nil {
|
||||
return errDecoding
|
||||
return nil, errDecoding
|
||||
}
|
||||
|
||||
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
|
||||
rhsCandidate := rhsChild.Value.(*CandidateNode)
|
||||
rhsTrue, errDecoding := isTruthy(rhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return errDecoding
|
||||
}
|
||||
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
|
||||
results.PushBack(boolResult)
|
||||
rhsTrue, errDecoding := isTruthy(rhs)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
|
||||
return createBooleanCandidate(lhs, op(lhsTrue, rhsTrue)), nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
if matchingNodes.Len() == 0 {
|
||||
lhs, err := d.GetMatchingNodes(list.New(), pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(list.New(), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return results, performBoolOp(results, lhs, rhs, op)
|
||||
}
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = performBoolOp(results, lhs, rhs, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- orOp")
|
||||
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
||||
return b1 || b2
|
||||
})
|
||||
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
|
||||
func(b1 bool, b2 bool) bool {
|
||||
return b1 || b2
|
||||
}))
|
||||
}
|
||||
|
||||
func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- AndOp")
|
||||
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
||||
return b1 && b2
|
||||
})
|
||||
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
|
||||
func(b1 bool, b2 bool) bool {
|
||||
return b1 && b2
|
||||
}))
|
||||
}
|
||||
|
||||
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
@@ -55,7 +55,7 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
|
||||
func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode {
|
||||
return &CandidateNode{
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
Path: candidate.CreateChildPath(index),
|
||||
Filename: candidate.Filename,
|
||||
Node: candidate.Node.Content[index],
|
||||
}
|
||||
@@ -67,7 +67,9 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
|
||||
}
|
||||
|
||||
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
|
||||
splatted, err := Splat(d, nodeToMap(candidate))
|
||||
|
||||
splatted, err := Splat(d, nodeToMap(candidate),
|
||||
&TraversePreferences{FollowAlias: false, IncludeMapKeys: false})
|
||||
|
||||
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
splatEl.Value.(*CandidateNode).Path = nil
|
||||
|
||||
@@ -110,5 +110,5 @@ func TestCollectObjectOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range collectObjectOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Collect into Object", collectObjectOperatorScenarios)
|
||||
documentScenarios(t, "Create, Collect into Object", collectObjectOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"container/list"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CommentOpPreferences struct {
|
||||
@@ -17,15 +17,6 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
|
||||
|
||||
log.Debugf("AssignComments operator!")
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comment := ""
|
||||
if rhs.Front() != nil {
|
||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
@@ -34,8 +25,32 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
|
||||
|
||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||
|
||||
comment := ""
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.Front() != nil {
|
||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.Front() != nil {
|
||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Setting comment of : %v", candidate.GetKey())
|
||||
if preferences.LineComment {
|
||||
candidate.Node.LineComment = comment
|
||||
|
||||
@@ -13,6 +13,39 @@ var commentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::a: cat # single\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "a: cat\nb: dog",
|
||||
expression: `.a lineComment=.b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: cat # dog\nb: dog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "a: cat\n---\na: dog",
|
||||
expression: `.a lineComment |= documentIndex`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: cat # 0\n",
|
||||
"D1, P[], (doc)::a: dog # 1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Use update assign to perform relative updates",
|
||||
document: "a: cat\nb: dog",
|
||||
expression: `.. lineComment |= .`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat # cat\nb: dog # dog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "a: cat\nb: dog",
|
||||
expression: `.. comments |= .`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat # cat\n# cat\n\n# cat\nb: dog # dog\n# dog\n\n# dog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set head comment",
|
||||
document: `a: cat`,
|
||||
|
||||
@@ -2,39 +2,67 @@ package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
// for each lhs, splat the node,
|
||||
// the intersect it against the rhs expression
|
||||
// recreate the contents using only the intersection result.
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
nodesToDelete, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := nodesToDelete.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
elMap := list.New()
|
||||
elMap.PushBack(candidate)
|
||||
nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs)
|
||||
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
deleteImmediateChildOp := &Operation{
|
||||
OperationType: DeleteImmediateChild,
|
||||
Value: candidate.Path[len(candidate.Path)-1],
|
||||
}
|
||||
|
||||
realNode := UnwrapDoc(candidate.Node)
|
||||
deleteImmediateChildOpNode := &PathTreeNode{
|
||||
Operation: deleteImmediateChildOp,
|
||||
Rhs: createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]),
|
||||
}
|
||||
|
||||
if realNode.Kind == yaml.SequenceNode {
|
||||
deleteFromArray(candidate, nodesToDelete)
|
||||
} else if realNode.Kind == yaml.MappingNode {
|
||||
deleteFromMap(candidate, nodesToDelete)
|
||||
} else {
|
||||
log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate))
|
||||
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
func DeleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
parents, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
childPath := pathNode.Operation.Value
|
||||
|
||||
log.Debug("childPath to remove %v", childPath)
|
||||
|
||||
for el := parents.Front(); el != nil; el = el.Next() {
|
||||
parent := el.Value.(*CandidateNode)
|
||||
parentNode := UnwrapDoc(parent.Node)
|
||||
if parentNode.Kind == yaml.MappingNode {
|
||||
deleteFromMap(parent, childPath)
|
||||
} else if parentNode.Kind == yaml.SequenceNode {
|
||||
deleteFromArray(parent, childPath)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
|
||||
}
|
||||
|
||||
}
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
||||
log.Debug("deleteFromMap")
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
contents := node.Content
|
||||
@@ -47,15 +75,10 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
childCandidate := &CandidateNode{
|
||||
Node: value,
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, key.Value),
|
||||
Path: candidate.CreateChildPath(key.Value),
|
||||
}
|
||||
|
||||
shouldDelete := false
|
||||
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
|
||||
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
|
||||
shouldDelete = true
|
||||
}
|
||||
}
|
||||
shouldDelete := key.Value == childPath
|
||||
|
||||
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
|
||||
|
||||
@@ -66,7 +89,7 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
node.Content = newContents
|
||||
}
|
||||
|
||||
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
|
||||
log.Debug("deleteFromArray")
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
contents := node.Content
|
||||
@@ -75,18 +98,7 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
for index := 0; index < len(contents); index = index + 1 {
|
||||
value := contents[index]
|
||||
|
||||
childCandidate := &CandidateNode{
|
||||
Node: value,
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
}
|
||||
|
||||
shouldDelete := false
|
||||
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
|
||||
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
|
||||
shouldDelete = true
|
||||
}
|
||||
}
|
||||
shouldDelete := fmt.Sprintf("%v", index) == fmt.Sprintf("%v", childPath)
|
||||
|
||||
if !shouldDelete {
|
||||
newContents = append(newContents, value)
|
||||
|
||||
@@ -13,6 +13,22 @@ var deleteOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::{a: cat}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Delete nested entry in map",
|
||||
document: `{a: {a1: fred, a2: frood}}`,
|
||||
expression: `del(.a.a1)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {a2: frood}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {a1: fred, a2: frood}}`,
|
||||
expression: `del(.. | select(.=="frood"))`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {a1: fred}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Delete entry in array",
|
||||
document: `[1,2,3]`,
|
||||
@@ -21,6 +37,14 @@ var deleteOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::[1, 3]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Delete nested entry in array",
|
||||
document: `[{a: cat, b: dog}]`,
|
||||
expression: `del(.[0].a)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::[{b: dog}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Delete no matches",
|
||||
document: `{a: cat, b: dog}`,
|
||||
@@ -37,6 +61,14 @@ var deleteOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::{b: dog}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Recursively delete matching keys",
|
||||
document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
|
||||
expression: `del(.. | select(has("name")).name)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {b: {age: 12}}}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDeleteOperatorScenarios(t *testing.T) {
|
||||
|
||||
@@ -14,10 +14,27 @@ var documentIndexScenarios = []expressionScenario{
|
||||
"D1, P[a], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Retrieve a document index, shorthand",
|
||||
document: "a: cat\n---\na: frog\n",
|
||||
expression: `.a | di`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!int)::0\n",
|
||||
"D1, P[a], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Filter by document index",
|
||||
document: "a: cat\n---\na: frog\n",
|
||||
expression: `select(. | documentIndex == 1)`,
|
||||
expression: `select(documentIndex == 1)`,
|
||||
expected: []string{
|
||||
"D1, P[], (doc)::a: frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Filter by document index shorthand",
|
||||
document: "a: cat\n---\na: frog\n",
|
||||
expression: `select(di == 1)`,
|
||||
expected: []string{
|
||||
"D1, P[], (doc)::a: frog\n",
|
||||
},
|
||||
@@ -25,7 +42,7 @@ var documentIndexScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Print Document Index with matches",
|
||||
document: "a: cat\n---\na: frog\n",
|
||||
expression: `.a | ({"match": ., "doc": (. | documentIndex)})`,
|
||||
expression: `.a | ({"match": ., "doc": documentIndex})`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::match: cat\ndoc: 0\n",
|
||||
"D0, P[], (!!map)::match: frog\ndoc: 1\n",
|
||||
|
||||
52
pkg/yqlib/operator_env.go
Normal file
52
pkg/yqlib/operator_env.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type EnvOpPreferences struct {
|
||||
StringValue bool
|
||||
}
|
||||
|
||||
func EnvOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
envName := pathNode.Operation.CandidateNode.Node.Value
|
||||
log.Debug("EnvOperator, env name:", envName)
|
||||
|
||||
rawValue := os.Getenv(envName)
|
||||
|
||||
preferences := pathNode.Operation.Preferences.(*EnvOpPreferences)
|
||||
|
||||
var node *yaml.Node
|
||||
if preferences.StringValue {
|
||||
node = &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: rawValue,
|
||||
}
|
||||
} else {
|
||||
var dataBucket yaml.Node
|
||||
decoder := yaml.NewDecoder(strings.NewReader(rawValue))
|
||||
errorReading := decoder.Decode(&dataBucket)
|
||||
if errorReading != nil {
|
||||
return nil, errorReading
|
||||
}
|
||||
//first node is a doc
|
||||
node = UnwrapDoc(&dataBucket)
|
||||
}
|
||||
log.Debug("ENV tag", node.Tag)
|
||||
log.Debug("ENV value", node.Value)
|
||||
log.Debug("ENV Kind", node.Kind)
|
||||
|
||||
target := &CandidateNode{
|
||||
Path: make([]interface{}, 0),
|
||||
Document: 0,
|
||||
Filename: "",
|
||||
Node: node,
|
||||
}
|
||||
|
||||
return nodeToMap(target), nil
|
||||
}
|
||||
72
pkg/yqlib/operator_env_test.go
Normal file
72
pkg/yqlib/operator_env_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var envOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Read string environment variable",
|
||||
environmentVariable: "cat meow",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: cat meow\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read boolean environment variable",
|
||||
environmentVariable: "true",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read numeric environment variable",
|
||||
environmentVariable: "12",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: 12\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read yaml environment variable",
|
||||
environmentVariable: "{b: fish}",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: {b: fish}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read boolean environment variable as a string",
|
||||
environmentVariable: "true",
|
||||
expression: `.a = strenv(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: \"true\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read numeric environment variable as a string",
|
||||
environmentVariable: "12",
|
||||
expression: `.a = strenv(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: \"12\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Dynamic key lookup with environment variable",
|
||||
environmentVariable: "cat",
|
||||
document: `{cat: meow, dog: woof}`,
|
||||
expression: `.[env(myenv)]`,
|
||||
expected: []string{
|
||||
"D0, P[cat], (!!str)::meow\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEnvOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range envOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Env Variable Operators", envOperatorScenarios)
|
||||
}
|
||||
@@ -21,6 +21,14 @@ var fileOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!int)::0\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get file index alias",
|
||||
document: `{a: cat}`,
|
||||
expression: `fi`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::0\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestFileOperatorsScenarios(t *testing.T) {
|
||||
|
||||
@@ -85,7 +85,9 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
|
||||
var results = list.New()
|
||||
|
||||
// shouldn't recurse arrays if appending
|
||||
err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays)
|
||||
prefs := &RecursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
|
||||
TraversePreferences: &TraversePreferences{FollowAlias: false}}
|
||||
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -104,19 +106,6 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
|
||||
return lhs, nil
|
||||
}
|
||||
|
||||
func createTraversalTree(path []interface{}) *PathTreeNode {
|
||||
if len(path) == 0 {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
|
||||
} else if len(path) == 1 {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
|
||||
}
|
||||
return &PathTreeNode{
|
||||
Operation: &Operation{OperationType: ShortPipe},
|
||||
Lhs: createTraversalTree(path[0:1]),
|
||||
Rhs: createTraversalTree(path[1:])}
|
||||
|
||||
}
|
||||
|
||||
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
|
||||
|
||||
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
||||
@@ -126,7 +115,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
|
||||
assignmentOp := &Operation{OperationType: AssignAttributes}
|
||||
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
||||
assignmentOp.OperationType = Assign
|
||||
assignmentOp.Preferences = &AssignOpPreferences{false}
|
||||
assignmentOp.UpdateAssign = false
|
||||
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
|
||||
assignmentOp.OperationType = AddAssign
|
||||
}
|
||||
|
||||
@@ -13,6 +13,23 @@ var pathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a b], (!!seq)::- a\n- b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a:
|
||||
b:
|
||||
c:
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
expression: `.a.b.c.[]`,
|
||||
expected: []string{
|
||||
"D0, P[a b c 0], (!!int)::0\n",
|
||||
"D0, P[a b c 1], (!!int)::1\n",
|
||||
"D0, P[a b c 2], (!!int)::2\n",
|
||||
"D0, P[a b c 3], (!!int)::3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get map key",
|
||||
document: `{a: {b: cat}}`,
|
||||
|
||||
@@ -6,10 +6,16 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type RecursiveDescentPreferences struct {
|
||||
TraversePreferences *TraversePreferences
|
||||
RecurseArray bool
|
||||
}
|
||||
|
||||
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
err := recursiveDecent(d, results, matchMap, true)
|
||||
preferences := pathNode.Operation.Preferences.(*RecursiveDescentPreferences)
|
||||
err := recursiveDecent(d, results, matchMap, preferences)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -17,7 +23,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, recurseArray bool) error {
|
||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences *RecursiveDescentPreferences) error {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
@@ -27,14 +33,14 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li
|
||||
results.PushBack(candidate)
|
||||
|
||||
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
|
||||
(recurseArray || candidate.Node.Kind != yaml.SequenceNode) {
|
||||
(preferences.RecurseArray || candidate.Node.Kind != yaml.SequenceNode) {
|
||||
|
||||
children, err := Splat(d, nodeToMap(candidate))
|
||||
children, err := Splat(d, nodeToMap(candidate), preferences.TraversePreferences)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = recursiveDecent(d, results, children, recurseArray)
|
||||
err = recursiveDecent(d, results, children, preferences)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -13,6 +13,14 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!map)::{}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: `...`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[]`,
|
||||
@@ -21,6 +29,14 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[]`,
|
||||
expression: `...`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `cat`,
|
||||
@@ -31,13 +47,50 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: frog}`,
|
||||
expression: `..`,
|
||||
document: `cat`,
|
||||
expression: `...`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Recurse map (values only)",
|
||||
document: `{a: frog}`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: frog}\n",
|
||||
"D0, P[a], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Recursively find nodes with keys",
|
||||
document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
|
||||
expression: `.. | select(has("name"))`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!map)::{name: frog, b: {name: blog, age: 12}}\n",
|
||||
"D0, P[a b], (!!map)::{name: blog, age: 12}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Recursively find nodes with values",
|
||||
document: `{a: {nameA: frog, b: {nameB: frog, age: 12}}}`,
|
||||
expression: `.. | select(. == "frog")`,
|
||||
expected: []string{
|
||||
"D0, P[a nameA], (!!str)::frog\n",
|
||||
"D0, P[a b nameB], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Recurse map (values and keys)",
|
||||
subdescription: "Note that the map key appears in the results",
|
||||
document: `{a: frog}`,
|
||||
expression: `...`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: frog}\n",
|
||||
"D0, P[a], (!!str)::a\n",
|
||||
"D0, P[a], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {b: apple}}`,
|
||||
@@ -48,6 +101,18 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a b], (!!str)::apple\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `...`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {b: apple}}\n",
|
||||
"D0, P[a], (!!str)::a\n",
|
||||
"D0, P[a], (!!map)::{b: apple}\n",
|
||||
"D0, P[a b], (!!str)::b\n",
|
||||
"D0, P[a b], (!!str)::apple\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[1,2,3]`,
|
||||
@@ -59,6 +124,17 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[2], (!!int)::3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[1,2,3]`,
|
||||
expression: `...`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[1, 2, 3]\n",
|
||||
"D0, P[0], (!!int)::1\n",
|
||||
"D0, P[1], (!!int)::2\n",
|
||||
"D0, P[2], (!!int)::3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{a: cat},2,true]`,
|
||||
@@ -71,6 +147,19 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{a: cat},2,true]`,
|
||||
expression: `...`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[{a: cat}, 2, true]\n",
|
||||
"D0, P[0], (!!map)::{a: cat}\n",
|
||||
"D0, P[0 a], (!!str)::a\n",
|
||||
"D0, P[0 a], (!!str)::cat\n",
|
||||
"D0, P[1], (!!int)::2\n",
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Aliases are not traversed",
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
@@ -79,6 +168,20 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `...`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
|
||||
"D0, P[a], (!!str)::a\n",
|
||||
"D0, P[a], (!!map)::&cat {c: frog}\n",
|
||||
"D0, P[a c], (!!str)::c\n",
|
||||
"D0, P[a c], (!!str)::frog\n",
|
||||
"D0, P[b], (!!str)::b\n",
|
||||
"D0, P[b], (alias)::*cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge docs are not traversed",
|
||||
document: mergeDocSample,
|
||||
@@ -87,6 +190,14 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar | [...]`,
|
||||
expected: []string{
|
||||
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- c\n- foobar_c\n- !!merge <<\n- *foo\n- thing\n- foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
@@ -100,6 +211,22 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList | ...`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n",
|
||||
"D0, P[foobarList b], (!!str)::b\n",
|
||||
"D0, P[foobarList b], (!!str)::foobarList_b\n",
|
||||
"D0, P[foobarList <<], (!!merge)::!!merge <<\n",
|
||||
"D0, P[foobarList <<], (!!seq)::[*foo, *bar]\n",
|
||||
"D0, P[foobarList << 0], (alias)::*foo\n",
|
||||
"D0, P[foobarList << 1], (alias)::*bar\n",
|
||||
"D0, P[foobarList c], (!!str)::c\n",
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestRecursiveDescentOperatorScenarios(t *testing.T) {
|
||||
|
||||
@@ -4,39 +4,46 @@ import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func parseStyle(customStyle string) (yaml.Style, error) {
|
||||
if customStyle == "tagged" {
|
||||
return yaml.TaggedStyle, nil
|
||||
} else if customStyle == "double" {
|
||||
return yaml.DoubleQuotedStyle, nil
|
||||
} else if customStyle == "single" {
|
||||
return yaml.SingleQuotedStyle, nil
|
||||
} else if customStyle == "literal" {
|
||||
return yaml.LiteralStyle, nil
|
||||
} else if customStyle == "folded" {
|
||||
return yaml.FoldedStyle, nil
|
||||
} else if customStyle == "flow" {
|
||||
return yaml.FlowStyle, nil
|
||||
} else if customStyle != "" {
|
||||
return 0, fmt.Errorf("Unknown style %v", customStyle)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignStyleOperator: %v")
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
customStyle := ""
|
||||
|
||||
if rhs.Front() != nil {
|
||||
customStyle = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
|
||||
var style yaml.Style
|
||||
if customStyle == "tagged" {
|
||||
style = yaml.TaggedStyle
|
||||
} else if customStyle == "double" {
|
||||
style = yaml.DoubleQuotedStyle
|
||||
} else if customStyle == "single" {
|
||||
style = yaml.SingleQuotedStyle
|
||||
} else if customStyle == "literal" {
|
||||
style = yaml.LiteralStyle
|
||||
} else if customStyle == "folded" {
|
||||
style = yaml.FoldedStyle
|
||||
} else if customStyle == "flow" {
|
||||
style = yaml.FlowStyle
|
||||
} else if customStyle != "" {
|
||||
return nil, fmt.Errorf("Unknown style %v", customStyle)
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.Front() != nil {
|
||||
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
@@ -46,6 +53,20 @@ func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting style of : %v", candidate.GetKey())
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.Front() != nil {
|
||||
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
candidate.Node.Style = style
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,22 @@ var styleOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!map)::a: \"cat\"\nb: \"5\"\nc: \"3.2\"\ne: \"true\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set double quote style on map keys too",
|
||||
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
||||
expression: `... style="double"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::\"a\": \"cat\"\n\"b\": \"5\"\n\"c\": \"3.2\"\n\"e\": \"true\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "bing: &foo frog\na:\n c: cat\n <<: [*foo]",
|
||||
expression: `(... | select(tag=="!!str")) style="single"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::'bing': &foo 'frog'\n'a':\n 'c': 'cat'\n !!merge <<: [*foo]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set single quote style",
|
||||
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
||||
@@ -70,14 +86,22 @@ e: >-
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Pretty print",
|
||||
subdescription: "Set empty (default) quote style",
|
||||
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
||||
expression: `.. style=""`,
|
||||
description: "Reset style - or pretty print",
|
||||
subdescription: "Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.",
|
||||
document: `{a: cat, "b": 5, 'c': 3.2, "e": true}`,
|
||||
expression: `... style=""`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set style relatively with assign-update",
|
||||
document: `{a: single, b: double}`,
|
||||
expression: `.[] style |= .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: 'single', b: \"double\"}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: cat, b: double}`,
|
||||
|
||||
@@ -3,21 +3,23 @@ package yqlib
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignTagOperator: %v")
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag := ""
|
||||
|
||||
if rhs.Front() != nil {
|
||||
tag = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.Front() != nil {
|
||||
tag = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
@@ -29,6 +31,16 @@ func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting tag of : %v", candidate.GetKey())
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.Front() != nil {
|
||||
tag = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
candidate.Node.Tag = tag
|
||||
}
|
||||
|
||||
@@ -42,7 +54,7 @@ func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pa
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Tag, Tag: "!!str"}
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: UnwrapDoc(candidate.Node).Tag, Tag: "!!str"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
@@ -19,13 +19,37 @@ var tagOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Convert numbers to strings",
|
||||
skipDoc: true,
|
||||
document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`,
|
||||
expression: `tag`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::'!!map'\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set custom tag",
|
||||
document: `{a: str}`,
|
||||
expression: `.a tag = "!!mikefarah"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: !!mikefarah str}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Find numbers and convert them to strings",
|
||||
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
||||
expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: "!!frog", b: "!!customTag"}`,
|
||||
expression: `.[] tag |= .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: !!frog \"!!frog\", b: !!customTag \"!!customTag\"}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestTagOperatorScenarios(t *testing.T) {
|
||||
|
||||
@@ -1,46 +1,39 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"container/list"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/elliotchance/orderedmap"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type TraversePreferences struct {
|
||||
DontFollowAlias bool
|
||||
FollowAlias bool
|
||||
IncludeMapKeys bool
|
||||
}
|
||||
|
||||
func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) {
|
||||
preferences := &TraversePreferences{DontFollowAlias: true}
|
||||
splatOperation := &Operation{OperationType: TraversePath, Value: "[]", Preferences: preferences}
|
||||
splatTreeNode := &PathTreeNode{Operation: splatOperation}
|
||||
return TraversePathOperator(d, matches, splatTreeNode)
|
||||
func Splat(d *dataTreeNavigator, matches *list.List, prefs *TraversePreferences) (*list.List, error) {
|
||||
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
|
||||
}
|
||||
|
||||
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- Traversing")
|
||||
var matchingNodeMap = list.New()
|
||||
var newNodes []*CandidateNode
|
||||
var err error
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
|
||||
newNodes, err := traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, n := range newNodes {
|
||||
matchingNodeMap.PushBack(n)
|
||||
}
|
||||
matchingNodeMap.PushBackList(newNodes)
|
||||
}
|
||||
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
|
||||
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
|
||||
log.Debug("Traversing %v", NodeToString(matchingNode))
|
||||
value := matchingNode.Node
|
||||
|
||||
@@ -61,34 +54,8 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
|
||||
switch value.Kind {
|
||||
case yaml.MappingNode:
|
||||
log.Debug("its a map with %v entries", len(value.Content)/2)
|
||||
var newMatches = orderedmap.NewOrderedMap()
|
||||
err := traverseMap(newMatches, matchingNode, operation)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if newMatches.Len() == 0 {
|
||||
//no matches, create one automagically
|
||||
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
|
||||
node := matchingNode.Node
|
||||
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: operation.StringValue}, valueNode)
|
||||
candidateNode := &CandidateNode{
|
||||
Node: valueNode,
|
||||
Path: append(matchingNode.Path, operation.StringValue),
|
||||
Document: matchingNode.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
|
||||
}
|
||||
|
||||
arrayMatches := make([]*CandidateNode, newMatches.Len())
|
||||
i := 0
|
||||
for el := newMatches.Front(); el != nil; el = el.Next() {
|
||||
arrayMatches[i] = el.Value.(*CandidateNode)
|
||||
i++
|
||||
}
|
||||
return arrayMatches, nil
|
||||
prefs := &TraversePreferences{FollowAlias: true}
|
||||
return traverseMap(matchingNode, operation.StringValue, prefs, false)
|
||||
|
||||
case yaml.SequenceNode:
|
||||
log.Debug("its a sequence of %v things!", len(value.Content))
|
||||
@@ -101,105 +68,118 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
|
||||
case yaml.DocumentNode:
|
||||
log.Debug("digging into doc node")
|
||||
return traverse(d, &CandidateNode{
|
||||
Node: matchingNode.Node.Content[0],
|
||||
Document: matchingNode.Document}, operation)
|
||||
Node: matchingNode.Node.Content[0],
|
||||
Filename: matchingNode.Filename,
|
||||
FileIndex: matchingNode.FileIndex,
|
||||
Document: matchingNode.Document}, operation)
|
||||
default:
|
||||
return nil, nil
|
||||
return list.New(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func keyMatches(key *yaml.Node, pathNode *Operation) bool {
|
||||
return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue)
|
||||
}
|
||||
func TraverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
// rhs is a collect expression that will yield indexes to retreive of the arrays
|
||||
|
||||
func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, operation *Operation) error {
|
||||
// value.Content is a concatenated array of key, value,
|
||||
// so keys are in the even indexes, values in odd.
|
||||
// merge aliases are defined first, but we only want to traverse them
|
||||
// if we don't find a match directly on this node first.
|
||||
//TODO ALIASES, auto creation?
|
||||
|
||||
node := candidate.Node
|
||||
|
||||
followAlias := true
|
||||
|
||||
if operation.Preferences != nil {
|
||||
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var contents = node.Content
|
||||
for index := 0; index < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
|
||||
prefs := &TraversePreferences{FollowAlias: true}
|
||||
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs)
|
||||
}
|
||||
|
||||
log.Debug("checking %v (%v)", key.Value, key.Tag)
|
||||
//skip the 'merge' tag, find a direct match first
|
||||
if key.Tag == "!!merge" && followAlias {
|
||||
log.Debug("Merge anchor")
|
||||
err := traverseMergeAnchor(newMatches, candidate, value, operation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if keyMatches(key, operation) {
|
||||
log.Debug("MATCHED")
|
||||
candidateNode := &CandidateNode{
|
||||
Node: value,
|
||||
Path: append(candidate.Path, key.Value),
|
||||
Document: candidate.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
|
||||
var matchingNodeMap = list.New()
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchingNodeMap.PushBackList(newNodes)
|
||||
}
|
||||
|
||||
return nil
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) error {
|
||||
switch value.Kind {
|
||||
case yaml.AliasNode:
|
||||
candidateNode := &CandidateNode{
|
||||
Node: value.Alias,
|
||||
Path: originalCandidate.Path,
|
||||
Document: originalCandidate.Document,
|
||||
}
|
||||
return traverseMap(newMatches, candidateNode, operation)
|
||||
case yaml.SequenceNode:
|
||||
for _, childValue := range value.Content {
|
||||
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, operation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
|
||||
node := matchingNode.Node
|
||||
if node.Tag == "!!null" {
|
||||
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
|
||||
// auto vivification, make it into an empty array
|
||||
node.Tag = ""
|
||||
node.Kind = yaml.SequenceNode
|
||||
}
|
||||
return nil
|
||||
|
||||
if node.Kind == yaml.AliasNode {
|
||||
matchingNode.Node = node.Alias
|
||||
return traverseArrayIndices(matchingNode, indicesToTraverse, prefs)
|
||||
} else if node.Kind == yaml.SequenceNode {
|
||||
return traverseArrayWithIndices(matchingNode, indicesToTraverse)
|
||||
} else if node.Kind == yaml.MappingNode {
|
||||
return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs)
|
||||
} else if node.Kind == yaml.DocumentNode {
|
||||
return traverseArrayIndices(&CandidateNode{
|
||||
Node: matchingNode.Node.Content[0],
|
||||
Filename: matchingNode.Filename,
|
||||
FileIndex: matchingNode.FileIndex,
|
||||
Document: matchingNode.Document}, indicesToTraverse, prefs)
|
||||
}
|
||||
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
|
||||
return list.New(), nil
|
||||
}
|
||||
|
||||
func traverseArray(candidate *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
|
||||
log.Debug("operation Value %v", operation.Value)
|
||||
if operation.Value == "[]" {
|
||||
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
|
||||
if len(indices) == 0 {
|
||||
return traverseMap(candidate, "", prefs, true)
|
||||
}
|
||||
|
||||
var contents = candidate.Node.Content
|
||||
var newMatches = make([]*CandidateNode, len(contents))
|
||||
var matchingNodeMap = list.New()
|
||||
|
||||
for _, indexNode := range indices {
|
||||
log.Debug("traverseMapWithIndices: %v", indexNode.Value)
|
||||
newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchingNodeMap.PushBackList(newNodes)
|
||||
}
|
||||
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) {
|
||||
log.Debug("traverseArrayWithIndices")
|
||||
var newMatches = list.New()
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
if len(indices) == 0 {
|
||||
log.Debug("splatting")
|
||||
var index int64
|
||||
for index = 0; index < int64(len(contents)); index = index + 1 {
|
||||
newMatches[index] = &CandidateNode{
|
||||
for index = 0; index < int64(len(node.Content)); index = index + 1 {
|
||||
|
||||
newMatches.PushBack(&CandidateNode{
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
Node: contents[index],
|
||||
}
|
||||
Path: candidate.CreateChildPath(index),
|
||||
Node: node.Content[index],
|
||||
})
|
||||
}
|
||||
return newMatches, nil
|
||||
|
||||
}
|
||||
|
||||
switch operation.Value.(type) {
|
||||
case int64:
|
||||
index := operation.Value.(int64)
|
||||
for _, indexNode := range indices {
|
||||
log.Debug("traverseArrayWithIndices: '%v'", indexNode.Value)
|
||||
index, err := strconv.ParseInt(indexNode.Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot index array with '%v' (%v)", indexNode.Value, err)
|
||||
}
|
||||
indexToUse := index
|
||||
contentLength := int64(len(candidate.Node.Content))
|
||||
contentLength := int64(len(node.Content))
|
||||
for contentLength <= index {
|
||||
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
|
||||
contentLength = int64(len(candidate.Node.Content))
|
||||
node.Content = append(node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
|
||||
contentLength = int64(len(node.Content))
|
||||
}
|
||||
|
||||
if indexToUse < 0 {
|
||||
@@ -210,14 +190,115 @@ func traverseArray(candidate *CandidateNode, operation *Operation) ([]*Candidate
|
||||
return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
|
||||
}
|
||||
|
||||
return []*CandidateNode{&CandidateNode{
|
||||
Node: candidate.Node.Content[indexToUse],
|
||||
newMatches.PushBack(&CandidateNode{
|
||||
Node: node.Content[indexToUse],
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
}}, nil
|
||||
default:
|
||||
log.Debug("argument not an int (%v), no array matches", operation.Value)
|
||||
return nil, nil
|
||||
Path: candidate.CreateChildPath(index),
|
||||
})
|
||||
}
|
||||
return newMatches, nil
|
||||
}
|
||||
|
||||
func keyMatches(key *yaml.Node, wantedKey string) bool {
|
||||
return Match(key.Value, wantedKey)
|
||||
}
|
||||
|
||||
func traverseMap(matchingNode *CandidateNode, key string, prefs *TraversePreferences, splat bool) (*list.List, error) {
|
||||
var newMatches = orderedmap.NewOrderedMap()
|
||||
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if newMatches.Len() == 0 {
|
||||
//no matches, create one automagically
|
||||
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
|
||||
node := matchingNode.Node
|
||||
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode)
|
||||
candidateNode := &CandidateNode{
|
||||
Node: valueNode,
|
||||
Path: append(matchingNode.Path, key),
|
||||
Document: matchingNode.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
|
||||
}
|
||||
|
||||
results := list.New()
|
||||
i := 0
|
||||
for el := newMatches.Front(); el != nil; el = el.Next() {
|
||||
results.PushBack(el.Value)
|
||||
i++
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs *TraversePreferences, splat bool) error {
|
||||
// value.Content is a concatenated array of key, value,
|
||||
// so keys are in the even indexes, values in odd.
|
||||
// merge aliases are defined first, but we only want to traverse them
|
||||
// if we don't find a match directly on this node first.
|
||||
|
||||
node := candidate.Node
|
||||
|
||||
var contents = node.Content
|
||||
for index := 0; index < len(contents); index = index + 2 {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
|
||||
log.Debug("checking %v (%v)", key.Value, key.Tag)
|
||||
//skip the 'merge' tag, find a direct match first
|
||||
if key.Tag == "!!merge" && prefs.FollowAlias {
|
||||
log.Debug("Merge anchor")
|
||||
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if splat || keyMatches(key, wantedKey) {
|
||||
log.Debug("MATCHED")
|
||||
if prefs.IncludeMapKeys {
|
||||
candidateNode := &CandidateNode{
|
||||
Node: key,
|
||||
Path: candidate.CreateChildPath(key.Value),
|
||||
Document: candidate.Document,
|
||||
}
|
||||
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
|
||||
}
|
||||
candidateNode := &CandidateNode{
|
||||
Node: value,
|
||||
Path: candidate.CreateChildPath(key.Value),
|
||||
Document: candidate.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *TraversePreferences, splat bool) error {
|
||||
switch value.Kind {
|
||||
case yaml.AliasNode:
|
||||
candidateNode := &CandidateNode{
|
||||
Node: value.Alias,
|
||||
Path: originalCandidate.Path,
|
||||
Document: originalCandidate.Document,
|
||||
}
|
||||
return doTraverseMap(newMatches, candidateNode, wantedKey, prefs, splat)
|
||||
case yaml.SequenceNode:
|
||||
for _, childValue := range value.Content {
|
||||
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey, prefs, splat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseArray(candidate *CandidateNode, operation *Operation) (*list.List, error) {
|
||||
log.Debug("operation Value %v", operation.Value)
|
||||
indices := []*yaml.Node{&yaml.Node{Value: operation.StringValue}}
|
||||
return traverseArrayWithIndices(candidate, indices)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,15 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[{}], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Dynamic keys",
|
||||
subdescription: `Expressions within [] can be used to dynamically lookup / calculate keys`,
|
||||
document: `{b: apple, apple: crispy yum, banana: soft yum}`,
|
||||
expression: `.[.b]`,
|
||||
expected: []string{
|
||||
"D0, P[apple], (!!str)::crispy yum\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Children don't exist",
|
||||
subdescription: "Nodes are added dynamically while traversing",
|
||||
@@ -65,7 +74,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
document: ``,
|
||||
expression: `.[1].a`,
|
||||
expected: []string{
|
||||
"D0, P[1 a], (!!null)::null\n",
|
||||
@@ -137,7 +146,15 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Traversing aliases with splat",
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `.b.[]`,
|
||||
expression: `.b[]`,
|
||||
expected: []string{
|
||||
"D0, P[b c], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `.b.[]`,
|
||||
expected: []string{
|
||||
"D0, P[b c], (!!str)::frog\n",
|
||||
},
|
||||
@@ -150,12 +167,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[b c], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[1,2,3]`,
|
||||
expression: `.b`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "Traversing arrays by index",
|
||||
document: `[1,2,3]`,
|
||||
@@ -215,7 +226,17 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Splatting merge anchors",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.[]`,
|
||||
expression: `.foobar[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobar c], (!!str)::foo_c\n",
|
||||
"D0, P[foobar a], (!!str)::foo_a\n",
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar.[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobar c], (!!str)::foo_c\n",
|
||||
"D0, P[foobar a], (!!str)::foo_a\n",
|
||||
@@ -266,7 +287,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Splatting merge anchor lists",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.[]`,
|
||||
expression: `.foobarList[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList b], (!!str)::bar_b\n",
|
||||
"D0, P[foobarList a], (!!str)::foo_a\n",
|
||||
@@ -274,6 +295,123 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList b], (!!str)::bar_b\n",
|
||||
"D0, P[foobarList a], (!!str)::foo_a\n",
|
||||
"D0, P[foobarList thing], (!!str)::bar_thing\n",
|
||||
"D0, P[foobarList c], (!!str)::foobarList_c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[a,b,c]`,
|
||||
expression: `.[]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!str)::a\n",
|
||||
"D0, P[1], (!!str)::b\n",
|
||||
"D0, P[2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[a,b,c]`,
|
||||
expression: `[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[0]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Select multiple indices",
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[0, 2]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[0, 2]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[-1]`,
|
||||
expected: []string{
|
||||
"D0, P[a -1], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[-1]`,
|
||||
expected: []string{
|
||||
"D0, P[a -1], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[-2]`,
|
||||
expected: []string{
|
||||
"D0, P[a -2], (!!str)::b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[-2]`,
|
||||
expected: []string{
|
||||
"D0, P[a -2], (!!str)::b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a.[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a | .[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestTraversePathOperatorScenarios(t *testing.T) {
|
||||
|
||||
@@ -36,13 +36,22 @@ var valueOperatorScenarios = []expressionScenario{
|
||||
expected: []string{
|
||||
"D0, P[], (!!float)::5e-10\n",
|
||||
},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
document: ``,
|
||||
expression: `"cat"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::cat\n",
|
||||
},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
document: ``,
|
||||
expression: `"frog jumps"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::frog jumps\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: ``,
|
||||
expression: `"1.3"`,
|
||||
expected: []string{
|
||||
|
||||
@@ -2,6 +2,7 @@ package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -33,3 +34,15 @@ func nodeToMap(candidate *CandidateNode) *list.List {
|
||||
elMap.PushBack(candidate)
|
||||
return elMap
|
||||
}
|
||||
|
||||
func createTraversalTree(path []interface{}) *PathTreeNode {
|
||||
if len(path) == 0 {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
|
||||
} else if len(path) == 1 {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
|
||||
}
|
||||
return &PathTreeNode{
|
||||
Operation: &Operation{OperationType: ShortPipe},
|
||||
Lhs: createTraversalTree(path[0:1]),
|
||||
Rhs: createTraversalTree(path[1:])}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ import (
|
||||
type expressionScenario struct {
|
||||
description string
|
||||
subdescription string
|
||||
environmentVariable string
|
||||
document string
|
||||
document2 string
|
||||
expression string
|
||||
expected []string
|
||||
skipDoc bool
|
||||
@@ -38,9 +40,17 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
||||
if s.document != "" {
|
||||
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
|
||||
if err != nil {
|
||||
t.Error(err, s.document)
|
||||
t.Error(err, s.document, s.expression)
|
||||
return
|
||||
}
|
||||
if s.document2 != "" {
|
||||
moreInputs, err := readDocuments(strings.NewReader(s.document2), "another.yml", 1)
|
||||
if err != nil {
|
||||
t.Error(err, s.document, s.expression)
|
||||
return
|
||||
}
|
||||
inputs.PushBackList(moreInputs)
|
||||
}
|
||||
} else {
|
||||
candidateNode := &CandidateNode{
|
||||
Document: 0,
|
||||
@@ -52,10 +62,14 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
||||
|
||||
}
|
||||
|
||||
if s.environmentVariable != "" {
|
||||
os.Setenv("myenv", s.environmentVariable)
|
||||
}
|
||||
|
||||
results, err = treeNavigator.GetMatchingNodes(inputs, node)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(fmt.Errorf("%v: %v", err, s.expression))
|
||||
return
|
||||
}
|
||||
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
|
||||
@@ -92,7 +106,7 @@ func copyFromHeader(title string, out *os.File) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func formatYaml(yaml string) string {
|
||||
func formatYaml(yaml string, filename string) string {
|
||||
var output bytes.Buffer
|
||||
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
|
||||
|
||||
@@ -101,7 +115,7 @@ func formatYaml(yaml string) string {
|
||||
panic(err)
|
||||
}
|
||||
streamEvaluator := NewStreamEvaluator()
|
||||
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(yaml), node, printer)
|
||||
err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -128,63 +142,125 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
|
||||
|
||||
for _, s := range scenarios {
|
||||
if !s.skipDoc {
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||
|
||||
if s.subdescription != "" {
|
||||
writeOrPanic(w, s.subdescription)
|
||||
writeOrPanic(w, "\n\n")
|
||||
}
|
||||
formattedDoc := ""
|
||||
if s.document != "" {
|
||||
if s.dontFormatInputForDoc {
|
||||
formattedDoc = s.document + "\n"
|
||||
} else {
|
||||
formattedDoc = formatYaml(s.document)
|
||||
}
|
||||
//TODO: pretty here
|
||||
writeOrPanic(w, "Given a sample.yml file of:\n")
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
|
||||
writeOrPanic(w, "then\n")
|
||||
if s.expression != "" {
|
||||
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval '%v' sample.yml\n```\n", s.expression))
|
||||
} else {
|
||||
writeOrPanic(w, "```bash\nyq eval sample.yml\n```\n")
|
||||
}
|
||||
} else {
|
||||
writeOrPanic(w, "Running\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval --null-input '%v'\n```\n", s.expression))
|
||||
}
|
||||
|
||||
writeOrPanic(w, "will output\n")
|
||||
|
||||
var output bytes.Buffer
|
||||
var err error
|
||||
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
|
||||
streamEvaluator := NewStreamEvaluator()
|
||||
|
||||
if s.document != "" {
|
||||
node, err := treeCreator.ParsePath(s.expression)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
} else {
|
||||
err = streamEvaluator.EvaluateNew(s.expression, printer)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
|
||||
|
||||
documentScenario(t, w, s)
|
||||
}
|
||||
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func documentScenario(t *testing.T, w *bufio.Writer, s expressionScenario) {
|
||||
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||
|
||||
if s.subdescription != "" {
|
||||
writeOrPanic(w, s.subdescription)
|
||||
writeOrPanic(w, "\n\n")
|
||||
}
|
||||
|
||||
formattedDoc, formattedDoc2 := documentInput(w, s)
|
||||
|
||||
writeOrPanic(w, "will output\n")
|
||||
|
||||
documentOutput(t, w, s, formattedDoc, formattedDoc2)
|
||||
}
|
||||
|
||||
func documentInput(w *bufio.Writer, s expressionScenario) (string, string) {
|
||||
formattedDoc := ""
|
||||
formattedDoc2 := ""
|
||||
command := "eval"
|
||||
|
||||
envCommand := ""
|
||||
|
||||
if s.environmentVariable != "" {
|
||||
envCommand = fmt.Sprintf("myenv=\"%v\" ", s.environmentVariable)
|
||||
os.Setenv("myenv", s.environmentVariable)
|
||||
}
|
||||
|
||||
if s.document != "" {
|
||||
if s.dontFormatInputForDoc {
|
||||
formattedDoc = s.document + "\n"
|
||||
} else {
|
||||
formattedDoc = formatYaml(s.document, "sample.yml")
|
||||
}
|
||||
|
||||
writeOrPanic(w, "Given a sample.yml file of:\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
|
||||
|
||||
files := "sample.yml"
|
||||
|
||||
if s.document2 != "" {
|
||||
if s.dontFormatInputForDoc {
|
||||
formattedDoc2 = s.document2 + "\n"
|
||||
} else {
|
||||
formattedDoc2 = formatYaml(s.document2, "another.yml")
|
||||
}
|
||||
|
||||
writeOrPanic(w, "And another sample another.yml file of:\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc2))
|
||||
files = "sample.yml another.yml"
|
||||
command = "eval-all"
|
||||
}
|
||||
|
||||
writeOrPanic(w, "then\n")
|
||||
|
||||
if s.expression != "" {
|
||||
writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v '%v' %v\n```\n", envCommand, command, s.expression, files))
|
||||
} else {
|
||||
writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v %v\n```\n", envCommand, command, files))
|
||||
}
|
||||
} else {
|
||||
writeOrPanic(w, "Running\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v --null-input '%v'\n```\n", envCommand, command, s.expression))
|
||||
}
|
||||
return formattedDoc, formattedDoc2
|
||||
}
|
||||
|
||||
func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formattedDoc string, formattedDoc2 string) {
|
||||
var output bytes.Buffer
|
||||
var err error
|
||||
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
|
||||
|
||||
node, err := treeCreator.ParsePath(s.expression)
|
||||
if err != nil {
|
||||
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
|
||||
return
|
||||
}
|
||||
|
||||
inputs := list.New()
|
||||
|
||||
if s.document != "" {
|
||||
inputs, err = readDocuments(strings.NewReader(formattedDoc), "sample.yml", 0)
|
||||
if err != nil {
|
||||
t.Error(err, s.document, s.expression)
|
||||
return
|
||||
}
|
||||
if s.document2 != "" {
|
||||
moreInputs, err := readDocuments(strings.NewReader(formattedDoc2), "another.yml", 1)
|
||||
if err != nil {
|
||||
t.Error(err, s.document, s.expression)
|
||||
return
|
||||
}
|
||||
inputs.PushBackList(moreInputs)
|
||||
}
|
||||
} else {
|
||||
candidateNode := &CandidateNode{
|
||||
Document: 0,
|
||||
Filename: "",
|
||||
Node: &yaml.Node{Tag: "!!null"},
|
||||
FileIndex: 0,
|
||||
}
|
||||
inputs.PushBack(candidateNode)
|
||||
|
||||
}
|
||||
|
||||
results, err := treeNavigator.GetMatchingNodes(inputs, node)
|
||||
if err != nil {
|
||||
t.Error(err, s.expression)
|
||||
}
|
||||
|
||||
err = printer.PrintResults(results)
|
||||
if err != nil {
|
||||
t.Error(err, s.expression)
|
||||
}
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
|
||||
}
|
||||
|
||||
@@ -17,20 +17,35 @@ var pathTests = []struct {
|
||||
append(make([]interface{}, 0), "[", "]"),
|
||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`.[]`,
|
||||
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]"),
|
||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||
},
|
||||
{
|
||||
`.a[]`,
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"),
|
||||
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"),
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
|
||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`.a.[]`,
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"),
|
||||
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"),
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
|
||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`.a[0]`,
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`.a.[0]`,
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`.a[].c`,
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]", "SHORT_PIPE", "c"),
|
||||
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE", "c", "SHORT_PIPE"),
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "c"),
|
||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "c", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`[3]`,
|
||||
@@ -44,18 +59,18 @@ var pathTests = []struct {
|
||||
},
|
||||
{
|
||||
`.a | .[].b == "apple"`,
|
||||
append(make([]interface{}, 0), "a", "PIPE", "[]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
|
||||
append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
|
||||
append(make([]interface{}, 0), "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
|
||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
|
||||
},
|
||||
{
|
||||
`(.a | .[].b) == "apple"`,
|
||||
append(make([]interface{}, 0), "(", "a", "PIPE", "[]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
|
||||
append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
|
||||
append(make([]interface{}, 0), "(", "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
|
||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
|
||||
},
|
||||
{
|
||||
`.[] | select(. == "*at")`,
|
||||
append(make([]interface{}, 0), "[]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
|
||||
append(make([]interface{}, 0), "[]", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
|
||||
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
|
||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
|
||||
},
|
||||
{
|
||||
`[true]`,
|
||||
@@ -89,8 +104,8 @@ var pathTests = []struct {
|
||||
},
|
||||
{
|
||||
`{.a: .c, .b.[]: .f.g.[]}`,
|
||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "SHORT_PIPE", "[]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "SHORT_PIPE", "[]", "}"),
|
||||
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "[]", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
|
||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "}"),
|
||||
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`explode(.a.b)`,
|
||||
@@ -122,7 +137,11 @@ var pathTests = []struct {
|
||||
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
|
||||
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
|
||||
},
|
||||
|
||||
{
|
||||
`. lineComment |= "str"`,
|
||||
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
|
||||
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
|
||||
},
|
||||
{
|
||||
`.a.b tag="!!str"`,
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"),
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
CloseCollect
|
||||
OpenCollectObject
|
||||
CloseCollectObject
|
||||
SplatOrEmptyCollect
|
||||
TraverseArrayCollect
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
@@ -49,8 +49,9 @@ func (t *Token) toString() string {
|
||||
return "{"
|
||||
} else if t.TokenType == CloseCollectObject {
|
||||
return "}"
|
||||
} else if t.TokenType == SplatOrEmptyCollect {
|
||||
return "[]?"
|
||||
} else if t.TokenType == TraverseArrayCollect {
|
||||
return ".["
|
||||
|
||||
} else {
|
||||
return "NFI"
|
||||
}
|
||||
@@ -91,6 +92,15 @@ func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.A
|
||||
return opTokenWithPrefs(opType, assignOpType, nil)
|
||||
}
|
||||
|
||||
func assignOpToken(updateAssign bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
log.Debug("assignOpToken %v", string(m.Bytes))
|
||||
value := string(m.Bytes)
|
||||
op := &Operation{OperationType: Assign, Value: Assign.Type, StringValue: value, UpdateAssign: updateAssign}
|
||||
return &Token{TokenType: OperationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
|
||||
@@ -104,6 +114,21 @@ func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preference
|
||||
}
|
||||
}
|
||||
|
||||
func assignAllCommentsOp(updateAssign bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
log.Debug("assignAllCommentsOp %v", string(m.Bytes))
|
||||
value := string(m.Bytes)
|
||||
op := &Operation{
|
||||
OperationType: AssignComment,
|
||||
Value: AssignComment.Type,
|
||||
StringValue: value,
|
||||
UpdateAssign: updateAssign,
|
||||
Preferences: &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
|
||||
}
|
||||
return &Token{TokenType: OperationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func literalToken(pType TokenType, checkForPost bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
return &Token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil
|
||||
@@ -114,23 +139,6 @@ func unwrap(value string) string {
|
||||
return value[1 : len(value)-1]
|
||||
}
|
||||
|
||||
func arrayIndextoken(precedingDot bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
var numberString = string(m.Bytes)
|
||||
startIndex := 1
|
||||
if precedingDot {
|
||||
startIndex = 2
|
||||
}
|
||||
numberString = numberString[startIndex : len(numberString)-1]
|
||||
var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint
|
||||
if errParsingInt != nil {
|
||||
return nil, errParsingInt
|
||||
}
|
||||
op := &Operation{OperationType: TraversePath, Value: number, StringValue: numberString}
|
||||
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func numberValue() lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
var numberString = string(m.Bytes)
|
||||
@@ -170,6 +178,28 @@ func stringValue(wrapped bool) lex.Action {
|
||||
}
|
||||
}
|
||||
|
||||
func envOp(strenv bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
value := string(m.Bytes)
|
||||
preferences := &EnvOpPreferences{}
|
||||
|
||||
if strenv {
|
||||
// strenv( )
|
||||
value = value[7 : len(value)-1]
|
||||
preferences.StringValue = true
|
||||
} else {
|
||||
//env( )
|
||||
value = value[4 : len(value)-1]
|
||||
}
|
||||
|
||||
envOperation := CreateValueOperation(value, value)
|
||||
envOperation.OperationType = EnvOp
|
||||
envOperation.Preferences = preferences
|
||||
|
||||
return &Token{TokenType: OperationToken, Operation: envOperation}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func nullValue() lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
return &Token{TokenType: OperationToken, Operation: CreateValueOperation(nil, string(m.Bytes))}, nil
|
||||
@@ -188,8 +218,12 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte(`\(`), literalToken(OpenBracket, false))
|
||||
lexer.Add([]byte(`\)`), literalToken(CloseBracket, true))
|
||||
|
||||
lexer.Add([]byte(`\.\[\]`), pathToken(false))
|
||||
lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent))
|
||||
lexer.Add([]byte(`\.\[`), literalToken(TraverseArrayCollect, false))
|
||||
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(RecursiveDescent, nil, &RecursiveDescentPreferences{RecurseArray: true,
|
||||
TraversePreferences: &TraversePreferences{FollowAlias: false, IncludeMapKeys: false}}))
|
||||
|
||||
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(RecursiveDescent, nil, &RecursiveDescentPreferences{RecurseArray: true,
|
||||
TraversePreferences: &TraversePreferences{FollowAlias: false, IncludeMapKeys: true}}))
|
||||
|
||||
lexer.Add([]byte(`,`), opToken(Union))
|
||||
lexer.Add([]byte(`:\s*`), opToken(CreateMap))
|
||||
@@ -201,14 +235,19 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte(`or`), opToken(Or))
|
||||
lexer.Add([]byte(`and`), opToken(And))
|
||||
lexer.Add([]byte(`not`), opToken(Not))
|
||||
lexer.Add([]byte(`\/\/`), opToken(Alternative))
|
||||
|
||||
lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))
|
||||
lexer.Add([]byte(`di`), opToken(GetDocumentIndex))
|
||||
|
||||
lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle))
|
||||
|
||||
lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag))
|
||||
lexer.Add([]byte(`anchor`), opAssignableToken(GetAnchor, AssignAnchor))
|
||||
lexer.Add([]byte(`alias`), opAssignableToken(GetAlias, AssignAlias))
|
||||
lexer.Add([]byte(`filename`), opToken(GetFilename))
|
||||
lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex))
|
||||
lexer.Add([]byte(`fi`), opToken(GetFileIndex))
|
||||
lexer.Add([]byte(`path`), opToken(GetPath))
|
||||
|
||||
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true}))
|
||||
@@ -217,18 +256,17 @@ func initLexer() (*lex.Lexer, error) {
|
||||
|
||||
lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true}))
|
||||
|
||||
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, nil, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true}))
|
||||
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
|
||||
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
|
||||
|
||||
lexer.Add([]byte(`collect`), opToken(Collect))
|
||||
|
||||
lexer.Add([]byte(`\s*==\s*`), opToken(Equals))
|
||||
lexer.Add([]byte(`\s*=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{false}))
|
||||
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
|
||||
|
||||
lexer.Add([]byte(`del`), opToken(DeleteChild))
|
||||
|
||||
lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true}))
|
||||
|
||||
lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true))
|
||||
lexer.Add([]byte(`\s*\|=\s*`), assignOpToken(true))
|
||||
|
||||
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
|
||||
|
||||
@@ -249,9 +287,9 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte(`[Nn][Uu][Ll][Ll]`), nullValue())
|
||||
lexer.Add([]byte(`~`), nullValue())
|
||||
|
||||
lexer.Add([]byte(`"[^ "]*"`), stringValue(true))
|
||||
|
||||
lexer.Add([]byte(`\[\]`), literalToken(SplatOrEmptyCollect, true))
|
||||
lexer.Add([]byte(`"[^"]*"`), stringValue(true))
|
||||
lexer.Add([]byte(`strenv\([^\)]+\)`), envOp(true))
|
||||
lexer.Add([]byte(`env\([^\)]+\)`), envOp(false))
|
||||
|
||||
lexer.Add([]byte(`\[`), literalToken(OpenCollect, false))
|
||||
lexer.Add([]byte(`\]`), literalToken(CloseCollect, true))
|
||||
@@ -321,30 +359,23 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
|
||||
func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTokens []*Token) (tokensAccum []*Token, skipNextToken bool) {
|
||||
skipNextToken = false
|
||||
token := tokens[index]
|
||||
if token.TokenType == SplatOrEmptyCollect {
|
||||
if index > 0 && tokens[index-1].TokenType == OperationToken &&
|
||||
tokens[index-1].Operation.OperationType == TraversePath {
|
||||
// must be a splat without a preceding dot , e.g. .a[]
|
||||
// lets put a pipe in front of it, and convert it to a traverse "[]" token
|
||||
pipeOp := &Operation{OperationType: ShortPipe, Value: "PIPE"}
|
||||
|
||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: pipeOp})
|
||||
if token.TokenType == TraverseArrayCollect {
|
||||
//need to put a traverse array then a collect token
|
||||
// do this by adding traverse then converting token to collect
|
||||
|
||||
traverseOp := &Operation{OperationType: TraversePath, Value: "[]", StringValue: "[]"}
|
||||
token = &Token{TokenType: OperationToken, Operation: traverseOp, CheckForPostTraverse: true}
|
||||
op := &Operation{OperationType: TraverseArray, StringValue: "TRAVERSE_ARRAY"}
|
||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
||||
|
||||
token = &Token{TokenType: OpenCollect}
|
||||
|
||||
} else {
|
||||
// gotta be a collect empty array, we need to split this into two tokens
|
||||
// one OpenCollect, the other CloseCollect
|
||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OpenCollect})
|
||||
token = &Token{TokenType: CloseCollect, CheckForPostTraverse: true}
|
||||
}
|
||||
}
|
||||
|
||||
if index != len(tokens)-1 && token.AssignOperation != nil &&
|
||||
tokens[index+1].TokenType == OperationToken &&
|
||||
tokens[index+1].Operation.OperationType == Assign {
|
||||
token.Operation = token.AssignOperation
|
||||
token.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
|
||||
skipNextToken = true
|
||||
}
|
||||
|
||||
@@ -356,5 +387,21 @@ func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTok
|
||||
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
|
||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
||||
}
|
||||
if index != len(tokens)-1 && token.CheckForPostTraverse &&
|
||||
tokens[index+1].TokenType == OpenCollect {
|
||||
|
||||
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
|
||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
||||
|
||||
op = &Operation{OperationType: TraverseArray}
|
||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
||||
}
|
||||
if index != len(tokens)-1 && token.CheckForPostTraverse &&
|
||||
tokens[index+1].TokenType == TraverseArrayCollect {
|
||||
|
||||
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
|
||||
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
|
||||
|
||||
}
|
||||
return postProcessedTokens, skipNextToken
|
||||
}
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
- increment version in version.go
|
||||
- increment version in snapcraft.yaml
|
||||
- commit
|
||||
- tag git with same version number
|
||||
- increment version in github-action/Dockerfile
|
||||
- make sure local build passes
|
||||
- push tag to git
|
||||
- 3.4.0, v3
|
||||
- git push --tags
|
||||
- make local xcompile (builds binaries for all platforms)
|
||||
|
||||
- git release
|
||||
./scripts/release.sh
|
||||
./scripts/upload.sh
|
||||
- tag git with same version number
|
||||
- commit vX tag - this will trigger github actions
|
||||
- use github actions to publish docker and make github release
|
||||
- check github updated yq action in marketplace
|
||||
|
||||
- snapcraft
|
||||
- will auto create a candidate, test it works then promote
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
./bin/golangci-lint run --timeout=5m
|
||||
if command -v golangci-lint &> /dev/null
|
||||
then
|
||||
golangci-lint run --timeout=5m
|
||||
else
|
||||
./bin/golangci-lint run --timeout=5m
|
||||
fi
|
||||
|
||||
# ./bin/golangci-lint \
|
||||
# --tests \
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
VERSION="$(git describe --tags --abbrev=0)"
|
||||
docker build \
|
||||
--target production \
|
||||
--build-arg VERSION=${VERSION} \
|
||||
-t mikefarah/yq:latest \
|
||||
-t mikefarah/yq:${VERSION} \
|
||||
.
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
|
||||
|
||||
CURRENT="$(git describe --tags --abbrev=0)"
|
||||
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
|
||||
OWNER="mikefarah"
|
||||
REPO="yq"
|
||||
|
||||
release() {
|
||||
github-release release \
|
||||
--user "$OWNER" \
|
||||
--draft \
|
||||
--repo "$REPO" \
|
||||
--tag "$CURRENT"
|
||||
}
|
||||
|
||||
|
||||
|
||||
release
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
|
||||
|
||||
CURRENT="$(git describe --tags --abbrev=0)"
|
||||
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
|
||||
OWNER="mikefarah"
|
||||
REPO="yq"
|
||||
|
||||
upload() {
|
||||
mkdir -p ./build-done
|
||||
while IFS= read -r -d $'\0'; do
|
||||
file=$REPLY
|
||||
BINARY=$(basename "${file}")
|
||||
echo "--> ${BINARY}"
|
||||
github-release upload \
|
||||
--replace \
|
||||
--user "$OWNER" \
|
||||
--repo "$REPO" \
|
||||
--tag "$CURRENT" \
|
||||
--name "${BINARY}" \
|
||||
--file "$file"
|
||||
mv "$file" "./build-done/${BINARY}"
|
||||
done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
|
||||
}
|
||||
|
||||
|
||||
upload
|
||||
@@ -1,14 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
# This assumes that gonative and gox is installed as per the 'one time setup' instructions
|
||||
# at https://github.com/inconshreveable/gonative
|
||||
|
||||
|
||||
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}"
|
||||
# include non-default linux builds too
|
||||
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -os=linux -output="build/yq_{{.OS}}_{{.Arch}}"
|
||||
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}" --osarch="darwin/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm linux/arm64 linux/mips linux/mips64 linux/mips64le linux/mipsle linux/ppc64 linux/ppc64le linux/s390x netbsd/386 netbsd/amd64 netbsd/arm openbsd/386 openbsd/amd64 windows/386 windows/amd64"
|
||||
|
||||
cd build
|
||||
rhash -r -a . -P -o checksums
|
||||
rhash -r -a . -o checksums
|
||||
|
||||
rhash --list-hashes > checksums_hashes_order
|
||||
rhash --list-hashes > checksums_hashes_order
|
||||
|
||||
find . -executable -type f | xargs -I {} tar czvf {}.tar.gz {}
|
||||
|
||||
# just in case find thinks this is executable...
|
||||
rm -f checksums_hashes_order.tar.gz
|
||||
rm -f checksums.tar.gz
|
||||
|
||||
rm yq_windows_386.exe.tar.gz
|
||||
rm yq_windows_amd64.exe.tar.gz
|
||||
|
||||
zip yq_windows_386.zip yq_windows_386.exe
|
||||
zip yq_windows_amd64.zip yq_windows_amd64.exe
|
||||
@@ -1,5 +1,5 @@
|
||||
name: yq
|
||||
version: '4.0.0'
|
||||
version: '4.2.1'
|
||||
summary: A lightweight and portable command-line YAML processor
|
||||
description: |
|
||||
The aim of the project is to be the jq or sed of yaml files.
|
||||
|
||||
BIN
yq_linux_amd64.tar.gz
Normal file
BIN
yq_linux_amd64.tar.gz
Normal file
Binary file not shown.
60
yq_test.go
60
yq_test.go
@@ -1,60 +0,0 @@
|
||||
package main
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "runtime"
|
||||
// "testing"
|
||||
|
||||
// "github.com/mikefarah/yq/v2/pkg/marshal"
|
||||
// "github.com/mikefarah/yq/v2/test"
|
||||
// )
|
||||
|
||||
// func TestMultilineString(t *testing.T) {
|
||||
// testString := `
|
||||
// abcd
|
||||
// efg`
|
||||
// formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
|
||||
// test.AssertResult(t, testString, formattedResult)
|
||||
// }
|
||||
|
||||
// func TestNewYaml(t *testing.T) {
|
||||
// result, _ := newYaml([]string{"b.c", "3"})
|
||||
// formattedResult := fmt.Sprintf("%v", result)
|
||||
// test.AssertResult(t,
|
||||
// "[{b [{c 3}]}]",
|
||||
// formattedResult)
|
||||
// }
|
||||
|
||||
// func TestNewYamlArray(t *testing.T) {
|
||||
// result, _ := newYaml([]string{"[0].cat", "meow"})
|
||||
// formattedResult := fmt.Sprintf("%v", result)
|
||||
// test.AssertResult(t,
|
||||
// "[[{cat meow}]]",
|
||||
// formattedResult)
|
||||
// }
|
||||
|
||||
// func TestNewYaml_WithScript(t *testing.T) {
|
||||
// writeScript = "examples/instruction_sample.yaml"
|
||||
// expectedResult := `b:
|
||||
// c: cat
|
||||
// e:
|
||||
// - name: Mike Farah`
|
||||
// result, _ := newYaml([]string{""})
|
||||
// actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
|
||||
// test.AssertResult(t, expectedResult, actualResult)
|
||||
// }
|
||||
|
||||
// func TestNewYaml_WithUnknownScript(t *testing.T) {
|
||||
// writeScript = "fake-unknown"
|
||||
// _, err := newYaml([]string{""})
|
||||
// if err == nil {
|
||||
// t.Error("Expected error due to unknown file")
|
||||
// }
|
||||
// var expectedOutput string
|
||||
// if runtime.GOOS == "windows" {
|
||||
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
|
||||
// } else {
|
||||
// expectedOutput = `open fake-unknown: no such file or directory`
|
||||
// }
|
||||
// test.AssertResult(t, expectedOutput, err.Error())
|
||||
// }
|
||||
Reference in New Issue
Block a user