mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
1 Commits
v4.5.0
...
4.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12edcafd8c |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
github: mikefarah
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
name: Bug report - V3
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug, v3
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -10,14 +10,11 @@ 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/...
|
||||
version of yq:
|
||||
operating system:
|
||||
|
||||
**Input Yaml**
|
||||
Concise yaml document(s) (as simple as possible to show the bug, please keep it to 10 lines or less)
|
||||
Concise yaml document(s) (as simple as possible to show the bug)
|
||||
data1.yml:
|
||||
```yaml
|
||||
this: should really work
|
||||
51
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
51
.github/ISSUE_TEMPLATE/bug_report_v4.md
vendored
@@ -1,51 +0,0 @@
|
||||
---
|
||||
name: Bug report - V4
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug, v4
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
Note that any how to questions should be posted in the discussion board and not raised as an issue.
|
||||
|
||||
Version of yq: 4.X.X
|
||||
Operating system: mac/linux/windows/....
|
||||
Installed via: docker/binary release/homebrew/snap/...
|
||||
|
||||
**Input Yaml**
|
||||
Concise yaml document(s) (as simple as possible to show the bug, please keep it to 10 lines or less)
|
||||
data1.yml:
|
||||
```yaml
|
||||
this: should really work
|
||||
```
|
||||
|
||||
data2.yml:
|
||||
```yaml
|
||||
but: it strangely didn't
|
||||
```
|
||||
|
||||
**Command**
|
||||
The command you ran:
|
||||
```
|
||||
yq eval-all 'select(fileIndex==0) | .a.b.c' data1.yml data2.yml
|
||||
```
|
||||
|
||||
**Actual behavior**
|
||||
|
||||
```yaml
|
||||
cat: meow
|
||||
```
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
```yaml
|
||||
this: should really work
|
||||
but: it strangely didn't
|
||||
```
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,23 +1,17 @@
|
||||
---
|
||||
name: Feature request - V4
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement, v4
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Please describe your feature request.**
|
||||
A clear and concise description of what the request is and what it would solve.
|
||||
Eg. I wish I could use yq to [...]
|
||||
|
||||
Note:
|
||||
- how to questions should be posted in the discussion board and not raised as an issue.
|
||||
- V3 will no longer have any enhancements.
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
If we have data1.yml like:
|
||||
(please keep to around 10 lines )
|
||||
|
||||
```yaml
|
||||
country: Australia
|
||||
@@ -26,7 +20,7 @@ country: Australia
|
||||
And we run a command:
|
||||
|
||||
```bash
|
||||
yq eval 'predictWeatherOf(.country)'
|
||||
yq predictWeather data1.yml
|
||||
```
|
||||
|
||||
it could output
|
||||
|
||||
3
.github/workflows/go.yml
vendored
3
.github/workflows/go.yml
vendored
@@ -28,4 +28,7 @@ jobs:
|
||||
run: |
|
||||
export PATH=${PATH}:`go env GOPATH`/bin
|
||||
scripts/devtools.sh
|
||||
- name: Build
|
||||
run: |
|
||||
export PATH=${PATH}:`go env GOPATH`/bin
|
||||
make local build
|
||||
70
.github/workflows/release.yml
vendored
70
.github/workflows/release.yml
vendored
@@ -1,70 +0,0 @@
|
||||
name: Release YQ
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
publishGitRelease:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '^1.15'
|
||||
- name: Cross compile
|
||||
run: |
|
||||
sudo apt-get install rhash -y
|
||||
go get github.com/mitchellh/gox
|
||||
./scripts/xcompile.sh
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
- uses: shogo82148/actions-upload-release-asset@v1
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: build/*
|
||||
|
||||
publishDocker:
|
||||
environment: dockerhub
|
||||
env:
|
||||
IMAGE_NAME: mikefarah/yq
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }} && docker version
|
||||
|
||||
- name: Build and push image
|
||||
run: |
|
||||
IMAGE_V_VERSION="$(git describe --tags --abbrev=0)"
|
||||
IMAGE_VERSION=${IMAGE_V_VERSION:1}
|
||||
|
||||
SHORT_SHA1=$(git rev-parse --short HEAD)
|
||||
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64"
|
||||
echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}"
|
||||
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||||
docker buildx build --platform "${PLATFORMS}" -t "${IMAGE_NAME}:${IMAGE_VERSION}" -t "${IMAGE_NAME}:latest" -t "${IMAGE_NAME}:4" \
|
||||
--push .
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN CGO_ENABLED=0 make local build
|
||||
|
||||
# Choose alpine as a base image to make this useful for CI, as many
|
||||
# CI tools expect an interactive shell inside the container
|
||||
FROM alpine:3.12.3 as production
|
||||
FROM alpine:3.12 as production
|
||||
|
||||
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
||||
RUN chmod +x /usr/bin/yq
|
||||
|
||||
@@ -4,7 +4,6 @@ COPY scripts/devtools.sh /opt/devtools.sh
|
||||
|
||||
RUN set -e -x \
|
||||
&& /opt/devtools.sh
|
||||
ENV PATH=/go/bin:$PATH
|
||||
|
||||
# install mkdocs
|
||||
RUN set -ex \
|
||||
|
||||
134
README.md
134
README.md
@@ -3,64 +3,24 @@
|
||||
   
|
||||
|
||||
|
||||
a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
|
||||
a lightweight and portable command-line YAML processor
|
||||
|
||||
yq is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as docker, all listed below.
|
||||
|
||||
## 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.
|
||||
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
|
||||
|
||||
## Install
|
||||
|
||||
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
|
||||
|
||||
### 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/)
|
||||
### MacOS:
|
||||
```
|
||||
brew install yq
|
||||
```
|
||||
|
||||
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:
|
||||
### Ubuntu and other Linux distros supporting `snap` packages:
|
||||
```
|
||||
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:
|
||||
|
||||
@@ -79,6 +39,18 @@ 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:
|
||||
@@ -90,7 +62,7 @@ docker run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression
|
||||
#### Run commands interactively:
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v "${PWD}":/workdir --entrypoint sh mikefarah/yq
|
||||
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
|
||||
```
|
||||
|
||||
It can be useful to have a bash function to avoid typing the whole docker command:
|
||||
@@ -109,14 +81,6 @@ GO111MODULE=on go get github.com/mikefarah/yq/v4
|
||||
## 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:
|
||||
```
|
||||
@@ -124,14 +88,6 @@ choco install yq
|
||||
```
|
||||
Supported by @chillum (https://chocolatey.org/packages/yq)
|
||||
|
||||
### Mac:
|
||||
Using [MacPorts](https://www.macports.org/)
|
||||
```
|
||||
sudo port selfupdate
|
||||
sudo port install yq
|
||||
```
|
||||
Supported by @herbygillot (https://ports.macports.org/maintainer/github/herbygillot)
|
||||
|
||||
### Alpine Linux
|
||||
- Enable edge/community repo by adding ```$MIRROR/alpine/edge/community``` to ```/etc/apk/repositories```
|
||||
- Update database index with ```apk update```
|
||||
@@ -152,18 +108,21 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
|
||||
|
||||
## Features
|
||||
- Written in portable go, so you can download a lovely dependency free binary
|
||||
- Uses similar syntax as `jq` but works with YAML and JSON files
|
||||
- Fully supports multi document yaml files
|
||||
- Colorized yaml output
|
||||
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
|
||||
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/v/v4.x/sort-keys)
|
||||
- Manipulate yaml [comments](https://mikefarah.gitbook.io/yq/comment-operators), [styling](https://mikefarah.gitbook.io/yq/style), [tags](https://mikefarah.gitbook.io/yq/tag) and [anchors and aliases](https://mikefarah.gitbook.io/yq/anchor-and-alias-operators).
|
||||
- [Update yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags)
|
||||
- [Complex expressions to select and update](https://mikefarah.gitbook.io/yq/v/v4.x/select#select-and-update-matching-values-in-map)
|
||||
- Keeps yaml formatting and comments when updating (though there are issues with whitespace)
|
||||
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
|
||||
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate)
|
||||
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
|
||||
- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
|
||||
- [Return the lengths of arrays/object/scalars](https://mikefarah.gitbook.io/yq/commands/read#printing-length-of-the-results)
|
||||
- Update a yaml file given a [path expression](https://mikefarah.gitbook.io/yq/commands/write-update#basic) or [script file](https://mikefarah.gitbook.io/yq/commands/write-update#basic)
|
||||
- Update creates any missing entries in the path on the fly
|
||||
- Deeply [compare](https://mikefarah.gitbook.io/yq/commands/compare) yaml files
|
||||
- Keeps yaml formatting and comments when updating
|
||||
- [Validate a yaml file](https://mikefarah.gitbook.io/yq/commands/validate)
|
||||
- Create a yaml file given a [deep path and value](https://mikefarah.gitbook.io/yq/commands/create#creating-a-simple-yaml-file) or a [script file](https://mikefarah.gitbook.io/yq/commands/create#creating-using-a-create-script)
|
||||
- [Prefix a path to a yaml file](https://mikefarah.gitbook.io/yq/commands/prefix)
|
||||
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/usage/convert)
|
||||
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/commands/read#from-stdin)
|
||||
- [Merge](https://mikefarah.gitbook.io/yq/commands/merge) multiple yaml files with various options for [overriding](https://mikefarah.gitbook.io/yq/commands/merge#overwrite-values) and [appending](https://mikefarah.gitbook.io/yq/commands/merge#append-values-with-arrays)
|
||||
- Supports multiple documents in a single yaml file for [reading](https://mikefarah.gitbook.io/yq/commands/read#multiple-documents), [writing](https://mikefarah.gitbook.io/yq/commands/write-update#multiple-documents) and [merging](https://mikefarah.gitbook.io/yq/commands/merge#multiple-documents)
|
||||
- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/commands/shell-completion)
|
||||
|
||||
## [Usage](https://mikefarah.gitbook.io/yq/)
|
||||
|
||||
@@ -175,33 +134,32 @@ Usage:
|
||||
yq [command]
|
||||
|
||||
Available Commands:
|
||||
eval Apply expression to each document in each yaml file given in sequence
|
||||
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
|
||||
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
|
||||
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
|
||||
help Help about any command
|
||||
shell-completion Generate completion script
|
||||
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
|
||||
new yq n [--script/-s script_file] a.b.c newValue
|
||||
prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c
|
||||
read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'
|
||||
shell-completion Generates shell completion scripts
|
||||
validate yq v sample.yaml
|
||||
write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue
|
||||
|
||||
Flags:
|
||||
-C, --colors force print with colors
|
||||
-e, --exit-status set exit status if there are no matches or null or false is returned
|
||||
-C, --colors print with colors
|
||||
-h, --help help for yq
|
||||
-I, --indent int sets indent level for output (default 2)
|
||||
-i, --inplace update the yaml file inplace of first yaml file given.
|
||||
-M, --no-colors force print with no colors
|
||||
-N, --no-doc Don't print document separators (---)
|
||||
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.
|
||||
-P, --prettyPrint pretty print, shorthand for '... style = ""'
|
||||
-j, --tojson output as json. Set indent to 0 to print json in one line.
|
||||
-P, --prettyPrint pretty print
|
||||
-j, --tojson output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc.
|
||||
-v, --verbose verbose mode
|
||||
-V, --version Print version information and quit
|
||||
|
||||
Use "yq [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
Simple Example:
|
||||
|
||||
```bash
|
||||
yq e '.a.b | length' f1.yml f2.yml
|
||||
```
|
||||
## Upgrade from V2
|
||||
If you've been using v2 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/upgrading-from-v2).
|
||||
|
||||
## Known Issues / Missing Features
|
||||
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
|
||||
- You cannot (yet) select multiple paths/keys from the yaml to be printed out (https://github.com/mikefarah/yq/issues/287)
|
||||
|
||||
@@ -14,6 +14,5 @@ var noDocSeparators = false
|
||||
var nullInput = false
|
||||
var verbose = false
|
||||
var version = false
|
||||
var prettyPrint = false
|
||||
|
||||
var completedSuccessfully = false
|
||||
|
||||
@@ -59,29 +59,25 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
||||
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
|
||||
}
|
||||
|
||||
if nullInput && len(args) > 1 {
|
||||
return errors.New("Cannot pass files in when using null-input flag")
|
||||
}
|
||||
|
||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||
|
||||
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer)
|
||||
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
|
||||
} else {
|
||||
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
}
|
||||
|
||||
completedSuccessfully = err == nil
|
||||
|
||||
@@ -33,16 +33,6 @@ 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
|
||||
@@ -83,23 +73,19 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||
|
||||
streamEvaluator := yqlib.NewStreamEvaluator()
|
||||
|
||||
if nullInput && len(args) > 1 {
|
||||
return errors.New("Cannot pass files in when using null-input flag")
|
||||
}
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
|
||||
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer)
|
||||
err = streamEvaluator.EvaluateNew(args[0], printer)
|
||||
} else {
|
||||
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
||||
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
|
||||
@@ -47,8 +47,6 @@ 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.5.0"
|
||||
Version = "4.0.0-beta2"
|
||||
|
||||
// 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,6 +1,4 @@
|
||||
a:
|
||||
key1: "value1"
|
||||
key2: 2.6
|
||||
ab:
|
||||
key1: 6
|
||||
key2: "h"
|
||||
a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mikefarah/yq:4.5.0
|
||||
FROM mikefarah/yq:3
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
||||
12
go.mod
12
go.mod
@@ -2,15 +2,17 @@ module github.com/mikefarah/yq/v4
|
||||
|
||||
require (
|
||||
github.com/elliotchance/orderedmap v1.3.0
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/goccy/go-yaml v1.8.4
|
||||
github.com/jinzhu/copier v0.1.0
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/goccy/go-yaml v1.8.1
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/mattn/go-colorable v0.1.7 // indirect
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/timtadh/data-structures v0.5.3 // indirect
|
||||
github.com/timtadh/lexmachine v0.2.2
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
)
|
||||
|
||||
go 1.15
|
||||
|
||||
44
go.sum
44
go.sum
@@ -39,21 +39,19 @@ 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.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
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/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.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
|
||||
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -101,8 +99,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
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/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/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=
|
||||
@@ -120,9 +118,15 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.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=
|
||||
@@ -200,7 +204,6 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-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=
|
||||
@@ -248,17 +251,21 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-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-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/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/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=
|
||||
@@ -281,9 +288,10 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-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=
|
||||
@@ -313,6 +321,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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=
|
||||
@@ -324,8 +334,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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=
|
||||
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=
|
||||
|
||||
@@ -1,54 +1,29 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
import "container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// A yaml expression evaluator that runs the expression once against all files/nodes in memory.
|
||||
/**
|
||||
Loads all yaml documents of all files given into memory, then runs the given expression once.
|
||||
**/
|
||||
type Evaluator interface {
|
||||
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
||||
|
||||
// EvaluateNodes takes an expression and one or more yaml nodes, returning a list of matching candidate nodes
|
||||
EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error)
|
||||
|
||||
// EvaluateCandidateNodes takes an expression and list of candidate nodes, returning a list of matching candidate nodes
|
||||
EvaluateCandidateNodes(expression string, inputCandidateNodes *list.List) (*list.List, error)
|
||||
}
|
||||
|
||||
type allAtOnceEvaluator struct {
|
||||
treeNavigator DataTreeNavigator
|
||||
treeCreator ExpressionParser
|
||||
treeCreator PathTreeCreator
|
||||
}
|
||||
|
||||
func NewAllAtOnceEvaluator() Evaluator {
|
||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewExpressionParser()}
|
||||
}
|
||||
|
||||
func (e *allAtOnceEvaluator) EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error) {
|
||||
inputCandidates := list.New()
|
||||
for _, node := range nodes {
|
||||
inputCandidates.PushBack(&CandidateNode{Node: node})
|
||||
}
|
||||
return e.EvaluateCandidateNodes(expression, inputCandidates)
|
||||
}
|
||||
|
||||
func (e *allAtOnceEvaluator) EvaluateCandidateNodes(expression string, inputCandidates *list.List) (*list.List, error) {
|
||||
node, err := e.treeCreator.ParseExpression(expression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context, err := e.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputCandidates}, node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return context.MatchingNodes, nil
|
||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
||||
}
|
||||
|
||||
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||
fileIndex := 0
|
||||
|
||||
node, err := treeCreator.ParsePath(expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var allDocuments *list.List = list.New()
|
||||
for _, filename := range filenames {
|
||||
reader, err := readStream(filename)
|
||||
@@ -62,7 +37,7 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
|
||||
allDocuments.PushBackList(fileDocuments)
|
||||
fileIndex = fileIndex + 1
|
||||
}
|
||||
matches, err := e.EvaluateCandidateNodes(expression, allDocuments)
|
||||
matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
var evaluateNodesScenario = []expressionScenario{
|
||||
{
|
||||
document: `a: hello`,
|
||||
expression: `.a`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!str)::hello\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `a: hello`,
|
||||
expression: `.`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: hello\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `- a: "yes"`,
|
||||
expression: `.[] | has("a")`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAllAtOnceEvaluateNodes(t *testing.T) {
|
||||
var evaluator = NewAllAtOnceEvaluator()
|
||||
for _, tt := range evaluateNodesScenario {
|
||||
node := test.ParseData(tt.document)
|
||||
list, _ := evaluator.EvaluateNodes(tt.expression, &node)
|
||||
test.AssertResultComplex(t, tt.expected, resultsToString(list))
|
||||
}
|
||||
}
|
||||
@@ -15,39 +15,12 @@ type CandidateNode struct {
|
||||
Document uint // the document index of this node
|
||||
Filename string
|
||||
FileIndex int
|
||||
// when performing op against all nodes given, this will treat all the nodes as one
|
||||
// (e.g. top level cross document merge). This property does not propegate to child nodes.
|
||||
EvaluateTogether bool
|
||||
}
|
||||
|
||||
func (n *CandidateNode) GetKey() string {
|
||||
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
||||
}
|
||||
|
||||
func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode {
|
||||
return &CandidateNode{
|
||||
Node: node,
|
||||
Path: n.createChildPath(path),
|
||||
Document: n.Document,
|
||||
Filename: n.Filename,
|
||||
FileIndex: n.FileIndex,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *CandidateNode) createChildPath(path interface{}) []interface{} {
|
||||
if path == nil {
|
||||
newPath := make([]interface{}, len(n.Path))
|
||||
copy(newPath, n.Path)
|
||||
return newPath
|
||||
}
|
||||
|
||||
//don't use append as they may actually modify the path of the orignal node!
|
||||
newPath := make([]interface{}, len(n.Path)+1)
|
||||
copy(newPath, n.Path)
|
||||
newPath[len(n.Path)] = path
|
||||
return newPath
|
||||
}
|
||||
|
||||
func (n *CandidateNode) Copy() (*CandidateNode, error) {
|
||||
clone := &CandidateNode{}
|
||||
err := copier.Copy(clone, n)
|
||||
@@ -59,12 +32,10 @@ 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
|
||||
n.Node.Alias = other.Node.Alias
|
||||
n.Node.Anchor = other.Node.Anchor
|
||||
}
|
||||
|
||||
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
||||
|
||||
@@ -17,8 +17,8 @@ func format(attr color.Attribute) string {
|
||||
return fmt.Sprintf("%s[%dm", escape, attr)
|
||||
}
|
||||
|
||||
func colorizeAndPrint(yamlBytes []byte, writer io.Writer) error {
|
||||
tokens := lexer.Tokenize(string(yamlBytes))
|
||||
func ColorizeAndPrint(bytes []byte, writer io.Writer) error {
|
||||
tokens := lexer.Tokenize(string(bytes))
|
||||
var p printer.Printer
|
||||
p.Bool = func() *printer.Property {
|
||||
return &printer.Property{
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
MatchingNodes *list.List
|
||||
Variables map[string]*list.List
|
||||
DontAutoCreate bool
|
||||
}
|
||||
|
||||
func (n *Context) SingleChildContext(candidate *CandidateNode) Context {
|
||||
list := list.New()
|
||||
list.PushBack(candidate)
|
||||
return n.ChildContext(list)
|
||||
}
|
||||
|
||||
func (n *Context) GetVariable(name string) *list.List {
|
||||
if n.Variables == nil {
|
||||
return nil
|
||||
}
|
||||
return n.Variables[name]
|
||||
}
|
||||
|
||||
func (n *Context) SetVariable(name string, value *list.List) {
|
||||
if n.Variables == nil {
|
||||
n.Variables = make(map[string]*list.List)
|
||||
}
|
||||
n.Variables[name] = value
|
||||
}
|
||||
|
||||
func (n *Context) ChildContext(results *list.List) Context {
|
||||
clone := Context{}
|
||||
err := copier.Copy(&clone, n)
|
||||
if err != nil {
|
||||
log.Error("Error cloning context :(")
|
||||
panic(err)
|
||||
}
|
||||
clone.MatchingNodes = results
|
||||
return clone
|
||||
}
|
||||
@@ -3,14 +3,16 @@ package yqlib
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"container/list"
|
||||
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
type DataTreeNavigator interface {
|
||||
// given the context and a expressionNode,
|
||||
// this will process the against the given expressionNode and return
|
||||
// a new context of matching candidates
|
||||
GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error)
|
||||
// given a list of CandidateEntities and a pathNode,
|
||||
// this will process the list against the given pathNode and return
|
||||
// a new list of matching candidates
|
||||
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
|
||||
}
|
||||
|
||||
type dataTreeNavigator struct {
|
||||
@@ -20,22 +22,22 @@ func NewDataTreeNavigator() DataTreeNavigator {
|
||||
return &dataTreeNavigator{}
|
||||
}
|
||||
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
if expressionNode == nil {
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
if pathNode == nil {
|
||||
log.Debugf("getMatchingNodes - nothing to do")
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
log.Debugf("Processing Op: %v", expressionNode.Operation.toString())
|
||||
log.Debugf("Processing Op: %v", pathNode.Operation.toString())
|
||||
if log.IsEnabledFor(logging.DEBUG) {
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
log.Debug(NodeToString(el.Value.(*CandidateNode)))
|
||||
}
|
||||
}
|
||||
log.Debug(">>")
|
||||
handler := expressionNode.Operation.OperationType.Handler
|
||||
handler := pathNode.Operation.OperationType.Handler
|
||||
if handler != nil {
|
||||
return handler(d, context, expressionNode)
|
||||
return handler(d, matchingNodes, pathNode)
|
||||
}
|
||||
return Context{}, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
|
||||
return nil, fmt.Errorf("Unknown operator %v", pathNode.Operation.OperationType)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Add behaves differently according to the type of the LHS:
|
||||
- arrays: concatenate
|
||||
- number scalars: arithmetic addition
|
||||
- string scalars: concatenate
|
||||
- number scalars: arithmetic addition (soon)
|
||||
- string scalars: concatenate (soon)
|
||||
|
||||
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
|
||||
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
||||
|
||||
## Concatenate and assign arrays
|
||||
Given a sample.yml file of:
|
||||
@@ -67,19 +67,23 @@ will output
|
||||
- 2
|
||||
```
|
||||
|
||||
## Add new object to array
|
||||
## Add object to array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- dog: woof
|
||||
- 1
|
||||
- 2
|
||||
c:
|
||||
cat: meow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a + {"cat": "meow"}' sample.yml
|
||||
yq eval '.a + .c' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- dog: woof
|
||||
- 1
|
||||
- 2
|
||||
- cat: meow
|
||||
```
|
||||
|
||||
@@ -127,97 +131,3 @@ b:
|
||||
- 4
|
||||
```
|
||||
|
||||
## String concatenation
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
b: meow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a = .a + .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: catmeow
|
||||
b: meow
|
||||
```
|
||||
|
||||
## Relative string concatenation
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
b: meow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a += .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: catmeow
|
||||
b: meow
|
||||
```
|
||||
|
||||
## Number addition - float
|
||||
If the lhs or rhs are floats then the expression will be calculated with floats.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 3
|
||||
b: 4.9
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a = .a + .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 7.9
|
||||
b: 4.9
|
||||
```
|
||||
|
||||
## Number addition - int
|
||||
If both the lhs and rhs are ints then the expression will be calculated with ints.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 3
|
||||
b: 4
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a = .a + .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 7
|
||||
b: 4
|
||||
```
|
||||
|
||||
## Increment number
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 3
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a += 1' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 4
|
||||
```
|
||||
|
||||
## Add to null
|
||||
Adding to null simply returns the rhs
|
||||
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'null + "cat"'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
```
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
|
||||
|
||||
## LHS is defined
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: bridge
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
bridge
|
||||
```
|
||||
|
||||
## LHS is not defined
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
{}
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
hello
|
||||
```
|
||||
|
||||
## LHS is null
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: ~
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
hello
|
||||
```
|
||||
|
||||
## LHS is false
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: false
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
hello
|
||||
```
|
||||
|
||||
## RHS is an expression
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: false
|
||||
b: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a // .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
```
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
|
||||
|
||||
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||
|
||||
|
||||
## Get anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &billyBob cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | anchor' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
billyBob
|
||||
```
|
||||
|
||||
## Set anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a anchor = "foobar"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: &foobar cat
|
||||
```
|
||||
|
||||
## Set anchor relatively using assign-update
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
b: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a anchor |= .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: &cat
|
||||
b: cat
|
||||
```
|
||||
|
||||
## Get alias
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
b: &billyBob meow
|
||||
a: *billyBob
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | alias' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
billyBob
|
||||
```
|
||||
|
||||
## Set alias
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
b: &meow purr
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a alias = "meow"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
b: &meow purr
|
||||
a: *meow
|
||||
```
|
||||
|
||||
## Set alias relatively using assign-update
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
b: &meow purr
|
||||
a:
|
||||
f: meow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a alias |= .f' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
b: &meow purr
|
||||
a: *meow
|
||||
```
|
||||
|
||||
## Explode alias and anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
a: &a cat
|
||||
b: *a
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.f)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
f:
|
||||
a: cat
|
||||
b: cat
|
||||
```
|
||||
|
||||
## Explode with no aliases or anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: mike
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.a)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: mike
|
||||
```
|
||||
|
||||
## Explode with alias keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
a: &a cat
|
||||
*a: b
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.f)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
f:
|
||||
a: cat
|
||||
cat: b
|
||||
```
|
||||
|
||||
## Explode with merge anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foo:
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar:
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: bar_b
|
||||
a: foo_a
|
||||
thing: bar_thing
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foo_c
|
||||
a: foo_a
|
||||
thing: foobar_thing
|
||||
```
|
||||
|
||||
@@ -34,27 +34,6 @@ a:
|
||||
g: foof
|
||||
```
|
||||
|
||||
## 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
|
||||
@@ -133,7 +112,7 @@ a:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '(.a[] | select(. == "apple")) = "frog"' sample.yml
|
||||
yq eval '(.a.[] | select(. == "apple")) = "frog"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
@@ -13,22 +13,6 @@ 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
|
||||
@@ -78,22 +62,17 @@ b: dog # leave this
|
||||
```
|
||||
|
||||
## Remove all comments
|
||||
Note the use of `...` to ensure key nodes are included.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat # comment
|
||||
# great
|
||||
b: # key comment
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '... comments=""' sample.yml
|
||||
yq eval '.. comments=""' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat
|
||||
b:
|
||||
```
|
||||
|
||||
## Get line comment
|
||||
|
||||
@@ -14,23 +14,6 @@ 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
|
||||
@@ -48,21 +31,6 @@ 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
|
||||
@@ -95,23 +63,3 @@ 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
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Use the `documentIndex` operator (or the `di` shorthand) to select nodes of a particular document.
|
||||
Use the `documentIndex` operator to select nodes of a particular document.
|
||||
## Retrieve a document index
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -17,24 +17,6 @@ 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
|
||||
@@ -44,23 +26,7 @@ a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
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
|
||||
yq eval 'select(. | documentIndex == 1)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -76,7 +42,7 @@ a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | ({"match": ., "doc": documentIndex})' sample.yml
|
||||
yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.
|
||||
|
||||
|
||||
## Read string environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="cat meow" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat meow
|
||||
```
|
||||
|
||||
## Read boolean environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="true" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: true
|
||||
```
|
||||
|
||||
## Read numeric environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="12" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 12
|
||||
```
|
||||
|
||||
## Read yaml environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="{b: fish}" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: {b: fish}
|
||||
```
|
||||
|
||||
## Read boolean environment variable as a string
|
||||
Running
|
||||
```bash
|
||||
myenv="true" yq eval --null-input '.a = strenv(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: "true"
|
||||
```
|
||||
|
||||
## Read numeric environment variable as a string
|
||||
Running
|
||||
```bash
|
||||
myenv="12" yq eval --null-input '.a = strenv(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: "12"
|
||||
```
|
||||
|
||||
## Dynamic key lookup with environment variable
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cat: meow
|
||||
dog: woof
|
||||
```
|
||||
then
|
||||
```bash
|
||||
myenv="cat" yq eval '.[env(myenv)]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
meow
|
||||
```
|
||||
|
||||
98
pkg/yqlib/doc/Explode.md
Normal file
98
pkg/yqlib/doc/Explode.md
Normal file
@@ -0,0 +1,98 @@
|
||||
Explodes (or dereferences) aliases and anchors.
|
||||
## Explode alias and anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
a: &a cat
|
||||
b: *a
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.f)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
f:
|
||||
a: cat
|
||||
b: cat
|
||||
```
|
||||
|
||||
## Explode with no aliases or anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: mike
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.a)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: mike
|
||||
```
|
||||
|
||||
## Explode with alias keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
a: &a cat
|
||||
*a: b
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.f)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
f:
|
||||
a: cat
|
||||
cat: b
|
||||
```
|
||||
|
||||
## Explode with merge anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'explode(.)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
foo:
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar:
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: bar_b
|
||||
a: foo_a
|
||||
thing: bar_thing
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foo_c
|
||||
a: foo_a
|
||||
thing: foobar_thing
|
||||
```
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
||||
|
||||
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(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
```
|
||||
## Get filename
|
||||
Given a sample.yml file of:
|
||||
@@ -18,7 +16,7 @@ yq eval 'filename' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
sample.yml
|
||||
sample.yaml
|
||||
```
|
||||
|
||||
## Get file index
|
||||
@@ -35,17 +33,3 @@ 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,35 +0,0 @@
|
||||
# Keys
|
||||
|
||||
Use the `keys` operator to return map keys or array indices.
|
||||
## Map keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
dog: woof
|
||||
cat: meow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'keys' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- dog
|
||||
- cat
|
||||
```
|
||||
|
||||
## Array keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- apple
|
||||
- banana
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'keys' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 0
|
||||
- 1
|
||||
```
|
||||
|
||||
@@ -16,20 +16,6 @@ will output
|
||||
3
|
||||
```
|
||||
|
||||
## null length
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | length' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
0
|
||||
```
|
||||
|
||||
## Map length
|
||||
returns number of entries
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
||||
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
||||
|
||||
To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
|
||||
|
||||
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||
|
||||
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
|
||||
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||
|
||||
Multiplication of strings and numbers are not yet supported.
|
||||
|
||||
## Merging files
|
||||
Note the use of `eval-all` to ensure all documents are loaded into memory.
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||
@@ -112,26 +111,6 @@ b:
|
||||
- 5
|
||||
```
|
||||
|
||||
## Merge, only existing fields
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
thing: one
|
||||
cat: frog
|
||||
b:
|
||||
missing: two
|
||||
thing: two
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a *? .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
thing: two
|
||||
cat: frog
|
||||
```
|
||||
|
||||
## Merge, appending arrays
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -164,33 +143,6 @@ array:
|
||||
value: banana
|
||||
```
|
||||
|
||||
## Merge, only existing fields, appending arrays
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
thing:
|
||||
- 1
|
||||
- 2
|
||||
b:
|
||||
thing:
|
||||
- 3
|
||||
- 4
|
||||
another:
|
||||
- 1
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a *?+ .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
thing:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
||||
## Merge to prefix an element
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -228,7 +180,7 @@ g: thongs
|
||||
f: *cat
|
||||
```
|
||||
|
||||
## Merge copies anchor names
|
||||
## Merge does not copy anchor names
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -245,7 +197,7 @@ yq eval '.c * .a' sample.yml
|
||||
will output
|
||||
```yaml
|
||||
g: thongs
|
||||
c: &cat frog
|
||||
c: frog
|
||||
```
|
||||
|
||||
## Merge with merge anchors
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
||||
|
||||
## match values form `..`
|
||||
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||
|
||||
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
|
||||
|
||||
```bash
|
||||
yq eval '.. style= "flow"' file.yaml
|
||||
```
|
||||
|
||||
## match values and map keys form `...`
|
||||
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
|
||||
|
||||
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||
|
||||
```bash
|
||||
yq eval '... style= "flow"' file.yaml
|
||||
```
|
||||
## Recurse map (values only)
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '..' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: frog
|
||||
frog
|
||||
```
|
||||
|
||||
## Recursively find nodes with keys
|
||||
Note that this example has wrapped the expression in `[]` to show that there are two matches returned. You do not have to wrap in `[]` in your path expression.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
name: frog
|
||||
b:
|
||||
name: blog
|
||||
age: 12
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[.. | select(has("name"))]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- name: frog
|
||||
b:
|
||||
name: blog
|
||||
age: 12
|
||||
- name: blog
|
||||
age: 12
|
||||
```
|
||||
|
||||
## Recursively find nodes with values
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
nameA: frog
|
||||
b:
|
||||
nameB: frog
|
||||
age: 12
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.. | select(. == "frog")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
frog
|
||||
frog
|
||||
```
|
||||
|
||||
## Recurse map (values and keys)
|
||||
Note that the map key appears in the results
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '...' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: frog
|
||||
a
|
||||
frog
|
||||
```
|
||||
|
||||
## Aliases are not traversed
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[..]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a: &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
- &cat
|
||||
c: frog
|
||||
- frog
|
||||
- *cat
|
||||
```
|
||||
|
||||
## Merge docs are not traversed
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.foobar | [..]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
- foobar_c
|
||||
- *foo
|
||||
- foobar_thing
|
||||
```
|
||||
|
||||
63
pkg/yqlib/doc/Recursive Descent.md
Normal file
63
pkg/yqlib/doc/Recursive Descent.md
Normal file
@@ -0,0 +1,63 @@
|
||||
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches, for instance to set the `style` of all nodes in a yaml doc:
|
||||
|
||||
```bash
|
||||
yq eval '.. style= "flow"' file.yaml
|
||||
```
|
||||
## Aliases are not traversed
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[..]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a: &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
- &cat
|
||||
c: frog
|
||||
- frog
|
||||
- *cat
|
||||
```
|
||||
|
||||
## Merge docs are not traversed
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
!!merge <<:
|
||||
- *foo
|
||||
- *bar
|
||||
c: foobarList_c
|
||||
foobar:
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.foobar | [..]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
- foobar_c
|
||||
- *foo
|
||||
- foobar_thing
|
||||
```
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# Split into Documents
|
||||
|
||||
This operator splits all matches into separate documents
|
||||
|
||||
## Split empty
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'splitDoc'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
## Split array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- a: cat
|
||||
- b: dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] | splitDoc' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat
|
||||
---
|
||||
b: dog
|
||||
```
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# String Operators
|
||||
|
||||
## Join strings
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- cat
|
||||
- meow
|
||||
- 1
|
||||
- null
|
||||
- true
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'join("; ")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat; meow; 1; ; true
|
||||
```
|
||||
|
||||
## Split strings
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cat; meow; 1; ; true
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'split("; ")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- cat
|
||||
- meow
|
||||
- "1"
|
||||
- ""
|
||||
- "true"
|
||||
```
|
||||
|
||||
## Split strings one match
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
word
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'split("; ")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- word
|
||||
```
|
||||
|
||||
@@ -40,26 +40,6 @@ c: "3.2"
|
||||
e: "true"
|
||||
```
|
||||
|
||||
## 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
|
||||
@@ -145,19 +125,19 @@ will output
|
||||
{a: cat, b: 5, c: 3.2, e: true}
|
||||
```
|
||||
|
||||
## 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.
|
||||
## Pretty print
|
||||
Set empty (default) quote style
|
||||
|
||||
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
|
||||
@@ -167,22 +147,6 @@ 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,21 +22,7 @@ will output
|
||||
!!seq
|
||||
```
|
||||
|
||||
## 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
|
||||
## Convert numbers to strings
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
|
||||
@@ -48,24 +48,6 @@ 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
|
||||
|
||||
@@ -124,7 +106,7 @@ b: *cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.b[]' sample.yml
|
||||
yq eval '.b.[]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -308,7 +290,7 @@ foobar:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.foobar[]' sample.yml
|
||||
yq eval '.foobar.[]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -374,7 +356,7 @@ foobar:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.foobarList[]' sample.yml
|
||||
yq eval '.foobarList.[]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -384,21 +366,3 @@ 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,58 +0,0 @@
|
||||
For more complex scenarios, variables can be used to hold values of expression to be used in other expressions.
|
||||
|
||||
## Single value variable
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a as $foo | $foo' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
```
|
||||
|
||||
## Multi value variable
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- cat
|
||||
- dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] as $foo | $foo' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
dog
|
||||
```
|
||||
|
||||
## Using variables as a lookup
|
||||
Example taken from [jq](https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...)
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
"posts":
|
||||
- "title": Frist psot
|
||||
"author": anon
|
||||
- "title": A well-written article
|
||||
"author": person1
|
||||
"realnames":
|
||||
"anon": Anonymous Coward
|
||||
"person1": Person McPherson
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.realnames as $names | .posts[] | {"title":.title, "author": $names[.author]}' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
title: Frist psot
|
||||
author: Anonymous Coward
|
||||
title: A well-written article
|
||||
author: Person McPherson
|
||||
```
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# Operators
|
||||
|
||||
In `yq` expressions are made up of operators. Operators have 0-2 arguments and run against the current 'matching' nodes in the expression tree.
|
||||
|
||||
Lets look at a couple of examples.
|
||||
|
||||
The `length` operator take no arguments, and will simply return the length of _each_ matching node. So if there were 2 nodes, one string and one array, length will update the 'matching' nodes context to be two new numeric scalar nodes representing the lengths of the orignal 'matching' nodes.
|
||||
|
||||
The `=` operator takes two arguments, a `lhs` expression and `rhs` expression. It runs the 'matching' nodes context against the `lhs` expression to find the nodes to update, lets call it `lhsNodes`, and then runs the matching nodes against the `rhs` to find the new values, lets call that `rhsNodes`. It updates the `lhsNodes` values with the `rhsNodes` values and _returns the original matching nodes_. This is important, where length changed the matching nodes to be new nodes with the length values, `=` returns the original matching nodes, albeit with some of the nodes values updated. So `.a = 3` will still return the parent matching node, but with the matching child updated.
|
||||
|
||||
Please see the individual operator docs for more information and examples.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Add behaves differently according to the type of the LHS:
|
||||
- arrays: concatenate
|
||||
- number scalars: arithmetic addition
|
||||
- string scalars: concatenate
|
||||
- number scalars: arithmetic addition (soon)
|
||||
- string scalars: concatenate (soon)
|
||||
|
||||
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`.
|
||||
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This operator is used to provide alternative (or default) values when a particular expression is either null or false.
|
||||
@@ -1,4 +0,0 @@
|
||||
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
|
||||
|
||||
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
|
||||
|
||||
@@ -1 +1 @@
|
||||
Use the `documentIndex` operator (or the `di` shorthand) to select nodes of a particular document.
|
||||
Use the `documentIndex` operator to select nodes of a particular document.
|
||||
@@ -1,2 +0,0 @@
|
||||
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.
|
||||
|
||||
1
pkg/yqlib/doc/headers/Explode.md
Normal file
1
pkg/yqlib/doc/headers/Explode.md
Normal file
@@ -0,0 +1 @@
|
||||
Explodes (or dereferences) aliases and anchors.
|
||||
@@ -1,9 +1,7 @@
|
||||
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
||||
|
||||
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(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
# Keys
|
||||
|
||||
Use the `keys` operator to return map keys or array indices.
|
||||
@@ -1,14 +1,13 @@
|
||||
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
||||
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
||||
|
||||
To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
|
||||
|
||||
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||
|
||||
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
|
||||
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||
|
||||
Multiplication of strings and numbers are not yet supported.
|
||||
|
||||
## Merging files
|
||||
Note the use of `eval-all` to ensure all documents are loaded into memory.
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
||||
|
||||
## match values form `..`
|
||||
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||
|
||||
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
|
||||
|
||||
```bash
|
||||
yq eval '.. style= "flow"' file.yaml
|
||||
```
|
||||
|
||||
## match values and map keys form `...`
|
||||
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
|
||||
|
||||
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||
|
||||
```bash
|
||||
yq eval '... style= "flow"' file.yaml
|
||||
```
|
||||
5
pkg/yqlib/doc/headers/Recursive Descent.md
Normal file
5
pkg/yqlib/doc/headers/Recursive Descent.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches, for instance to set the `style` of all nodes in a yaml doc:
|
||||
|
||||
```bash
|
||||
yq eval '.. style= "flow"' file.yaml
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
# Split into Documents
|
||||
|
||||
This operator splits all matches into separate documents
|
||||
@@ -1 +0,0 @@
|
||||
# String Operators
|
||||
@@ -1 +0,0 @@
|
||||
For more complex scenarios, variables can be used to hold values of expression to be used in other expressions.
|
||||
@@ -50,7 +50,7 @@ func (ye *yamlEncoder) Encode(node *yaml.Node) error {
|
||||
}
|
||||
|
||||
if ye.colorise {
|
||||
return colorizeAndPrint(tempBuffer.Bytes(), ye.destination)
|
||||
return ColorizeAndPrint(tempBuffer.Bytes(), ye.destination)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -76,8 +76,6 @@ func mapKeysToStrings(node *yaml.Node) {
|
||||
|
||||
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
||||
var encoder = json.NewEncoder(destination)
|
||||
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
||||
|
||||
var indentString = ""
|
||||
|
||||
for index := 0; index < indent; index++ {
|
||||
@@ -155,15 +153,11 @@ func (o *orderedMap) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
||||
if o.kv == nil {
|
||||
return json.Marshal(o.altVal)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
||||
if o.kv == nil {
|
||||
if err := enc.Encode(o.altVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
buf.WriteByte('{')
|
||||
for idx, el := range o.kv {
|
||||
if err := enc.Encode(el.K); err != nil {
|
||||
|
||||
@@ -9,11 +9,29 @@ import (
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
func yamlToJson(sampleYaml string, indent int) string {
|
||||
var sampleYaml = `zabbix: winner
|
||||
apple: great
|
||||
banana:
|
||||
- {cobra: kai, angus: bob}
|
||||
`
|
||||
|
||||
var expectedJson = `{
|
||||
"zabbix": "winner",
|
||||
"apple": "great",
|
||||
"banana": [
|
||||
{
|
||||
"cobra": "kai",
|
||||
"angus": "bob"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
writer := bufio.NewWriter(&output)
|
||||
|
||||
var jsonEncoder = NewJsonEncoder(writer, indent)
|
||||
var jsonEncoder = NewJsonEncoder(writer, 2)
|
||||
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -24,33 +42,6 @@ func yamlToJson(sampleYaml string, indent int) string {
|
||||
panic(err)
|
||||
}
|
||||
writer.Flush()
|
||||
test.AssertResult(t, expectedJson, output.String())
|
||||
|
||||
return strings.TrimSuffix(output.String(), "\n")
|
||||
}
|
||||
|
||||
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
|
||||
var sampleYaml = `zabbix: winner
|
||||
apple: great
|
||||
banana:
|
||||
- {cobra: kai, angus: bob}
|
||||
`
|
||||
var expectedJson = `{
|
||||
"zabbix": "winner",
|
||||
"apple": "great",
|
||||
"banana": [
|
||||
{
|
||||
"cobra": "kai",
|
||||
"angus": "bob"
|
||||
}
|
||||
]
|
||||
}`
|
||||
var actualJson = yamlToJson(sampleYaml, 2)
|
||||
test.AssertResult(t, expectedJson, actualJson)
|
||||
}
|
||||
|
||||
func TestJsonEncoderDoesNotEscapeHTMLChars(t *testing.T) {
|
||||
var sampleYaml = `build: "( ./lint && ./format && ./compile ) < src.code"`
|
||||
var expectedJson = `{"build":"( ./lint && ./format && ./compile ) < src.code"}`
|
||||
var actualJson = yamlToJson(sampleYaml, 0)
|
||||
test.AssertResult(t, expectedJson, actualJson)
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var myPathTokeniser = newExpressionTokeniser()
|
||||
var myPathPostfixer = newExpressionPostFixer()
|
||||
|
||||
type ExpressionNode struct {
|
||||
Operation *Operation
|
||||
Lhs *ExpressionNode
|
||||
Rhs *ExpressionNode
|
||||
}
|
||||
|
||||
type ExpressionParser interface {
|
||||
ParseExpression(expression string) (*ExpressionNode, error)
|
||||
}
|
||||
|
||||
type expressionParserImpl struct {
|
||||
}
|
||||
|
||||
func NewExpressionParser() ExpressionParser {
|
||||
return &expressionParserImpl{}
|
||||
}
|
||||
|
||||
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
|
||||
tokens, err := myPathTokeniser.Tokenise(expression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var Operations []*Operation
|
||||
Operations, err = myPathPostfixer.ConvertToPostfix(tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.createExpressionTree(Operations)
|
||||
}
|
||||
|
||||
func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*ExpressionNode, error) {
|
||||
var stack = make([]*ExpressionNode, 0)
|
||||
|
||||
if len(postFixPath) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, Operation := range postFixPath {
|
||||
var newNode = ExpressionNode{Operation: Operation}
|
||||
log.Debugf("pathTree %v ", Operation.toString())
|
||||
if Operation.OperationType.NumArgs > 0 {
|
||||
numArgs := Operation.OperationType.NumArgs
|
||||
if numArgs == 1 {
|
||||
if len(stack) < 1 {
|
||||
return nil, fmt.Errorf("'%v' expects 1 arg but received none", strings.TrimSpace(Operation.StringValue))
|
||||
}
|
||||
remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1]
|
||||
newNode.Rhs = rhs
|
||||
stack = remaining
|
||||
} else if numArgs == 2 {
|
||||
if len(stack) < 2 {
|
||||
return nil, fmt.Errorf("'%v' expects 2 args but there is %v", strings.TrimSpace(Operation.StringValue), len(stack))
|
||||
}
|
||||
remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1]
|
||||
newNode.Lhs = lhs
|
||||
newNode.Rhs = rhs
|
||||
stack = remaining
|
||||
}
|
||||
}
|
||||
stack = append(stack, &newNode)
|
||||
}
|
||||
if len(stack) != 1 {
|
||||
return nil, fmt.Errorf("Bad expression, please check expression syntax")
|
||||
}
|
||||
return stack[0], nil
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
func TestPathTreeNoArgsForTwoArgOp(t *testing.T) {
|
||||
_, err := NewExpressionParser().ParseExpression("=")
|
||||
test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error())
|
||||
}
|
||||
|
||||
func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) {
|
||||
_, err := NewExpressionParser().ParseExpression(".a =")
|
||||
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
||||
}
|
||||
|
||||
func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) {
|
||||
_, err := NewExpressionParser().ParseExpression("= .a")
|
||||
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
|
||||
}
|
||||
|
||||
func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) {
|
||||
_, err := NewExpressionParser().ParseExpression(".a = .b")
|
||||
test.AssertResultComplex(t, nil, err)
|
||||
}
|
||||
|
||||
func TestPathTreeNoArgsForOneArgOp(t *testing.T) {
|
||||
_, err := NewExpressionParser().ParseExpression("explode")
|
||||
test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error())
|
||||
}
|
||||
|
||||
func TestPathTreeOneArgForOneArgOp(t *testing.T) {
|
||||
_, err := NewExpressionParser().ParseExpression("explode(.)")
|
||||
test.AssertResultComplex(t, nil, err)
|
||||
}
|
||||
|
||||
func TestPathTreeExtraArgs(t *testing.T) {
|
||||
_, err := NewExpressionParser().ParseExpression("sortKeys(.) explode(.)")
|
||||
test.AssertResultComplex(t, "Bad expression, please check expression syntax", err.Error())
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
type expressionPostFixer interface {
|
||||
ConvertToPostfix([]*token) ([]*Operation, error)
|
||||
}
|
||||
|
||||
type expressionPostFixerImpl struct {
|
||||
}
|
||||
|
||||
func newExpressionPostFixer() expressionPostFixer {
|
||||
return &expressionPostFixerImpl{}
|
||||
}
|
||||
|
||||
func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) {
|
||||
var newOp *token
|
||||
opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1]
|
||||
log.Debugf("popped %v from opstack to results", newOp.toString(true))
|
||||
return opStack, append(result, newOp.Operation)
|
||||
}
|
||||
|
||||
func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) {
|
||||
var result []*Operation
|
||||
// surround the whole thing with brackets
|
||||
var opStack = []*token{{TokenType: openBracket}}
|
||||
var tokens = append(infixTokens, &token{TokenType: closeBracket})
|
||||
|
||||
for _, currentToken := range tokens {
|
||||
log.Debugf("postfix processing currentToken %v", currentToken.toString(true))
|
||||
switch currentToken.TokenType {
|
||||
case openBracket, openCollect, openCollectObject:
|
||||
opStack = append(opStack, currentToken)
|
||||
log.Debugf("put %v onto the opstack", currentToken.toString(true))
|
||||
case closeCollect, closeCollectObject:
|
||||
var opener tokenType = openCollect
|
||||
var collectOperator *operationType = collectOpType
|
||||
if currentToken.TokenType == closeCollectObject {
|
||||
opener = openCollectObject
|
||||
collectOperator = collectObjectOpType
|
||||
}
|
||||
|
||||
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener {
|
||||
opStack, result = popOpToResult(opStack, result)
|
||||
}
|
||||
if len(opStack) == 0 {
|
||||
return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket")
|
||||
}
|
||||
// now we should have [ as the last element on the opStack, get rid of it
|
||||
opStack = opStack[0 : len(opStack)-1]
|
||||
log.Debugf("deleteing open bracket from opstack")
|
||||
|
||||
//and append a collect to the opStack
|
||||
result = append(result, &Operation{OperationType: collectOperator})
|
||||
log.Debugf("put collect onto the result")
|
||||
result = append(result, &Operation{OperationType: shortPipeOpType})
|
||||
log.Debugf("put shortpipe onto the result")
|
||||
|
||||
case closeBracket:
|
||||
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket {
|
||||
opStack, result = popOpToResult(opStack, result)
|
||||
}
|
||||
if len(opStack) == 0 {
|
||||
return nil, errors.New("Bad path expression, got close brackets without matching opening bracket")
|
||||
}
|
||||
// now we should have ( as the last element on the opStack, get rid of it
|
||||
opStack = opStack[0 : len(opStack)-1]
|
||||
|
||||
default:
|
||||
var currentPrecedence = currentToken.Operation.OperationType.Precedence
|
||||
// pop off higher precedent operators onto the result
|
||||
for len(opStack) > 0 &&
|
||||
opStack[len(opStack)-1].TokenType == operationToken &&
|
||||
opStack[len(opStack)-1].Operation.OperationType.Precedence > currentPrecedence {
|
||||
opStack, result = popOpToResult(opStack, result)
|
||||
}
|
||||
// add this operator to the opStack
|
||||
opStack = append(opStack, currentToken)
|
||||
log.Debugf("put %v onto the opstack", currentToken.toString(true))
|
||||
}
|
||||
}
|
||||
|
||||
if log.IsEnabledFor(logging.DEBUG) {
|
||||
log.Debugf("PostFix Result:")
|
||||
for _, currentToken := range result {
|
||||
log.Debugf("> %v", currentToken.toString())
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
var pathTests = []struct {
|
||||
path string
|
||||
expectedTokens []interface{}
|
||||
expectedPostFix []interface{}
|
||||
}{
|
||||
{
|
||||
`.a | .b | .c`,
|
||||
append(make([]interface{}, 0), "a", "PIPE", "b", "PIPE", "c"),
|
||||
append(make([]interface{}, 0), "a", "b", "c", "PIPE", "PIPE"),
|
||||
},
|
||||
{
|
||||
`[]`,
|
||||
append(make([]interface{}, 0), "[", "EMPTY", "]"),
|
||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`{}`,
|
||||
append(make([]interface{}, 0), "{", "EMPTY", "}"),
|
||||
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`[{}]`,
|
||||
append(make([]interface{}, 0), "[", "{", "EMPTY", "}", "]"),
|
||||
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE", "COLLECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`.realnames as $names | $names["anon"]`,
|
||||
append(make([]interface{}, 0), "realnames", "ASSIGN_VARIABLE", "GET_VARIABLE", "PIPE", "GET_VARIABLE", "TRAVERSE_ARRAY", "[", "anon (string)", "]"),
|
||||
append(make([]interface{}, 0), "realnames", "GET_VARIABLE", "ASSIGN_VARIABLE", "GET_VARIABLE", "anon (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "PIPE"),
|
||||
},
|
||||
{
|
||||
`.b[.a]`,
|
||||
append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"),
|
||||
append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||
},
|
||||
{
|
||||
`.[]`,
|
||||
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
|
||||
append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||
},
|
||||
{
|
||||
`.a[]`,
|
||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
|
||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||
},
|
||||
{
|
||||
`.a.[]`,
|
||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
|
||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||
},
|
||||
{
|
||||
`.a[0]`,
|
||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||
},
|
||||
{
|
||||
`.a.[0]`,
|
||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
|
||||
},
|
||||
{
|
||||
`.a[].c`,
|
||||
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "c"),
|
||||
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "c", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`[3]`,
|
||||
append(make([]interface{}, 0), "[", "3 (int64)", "]"),
|
||||
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`.key.array + .key.array2`,
|
||||
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ADD", "key", "SHORT_PIPE", "array2"),
|
||||
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ADD"),
|
||||
},
|
||||
{
|
||||
`.key.array * .key.array2`,
|
||||
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "MULTIPLY", "key", "SHORT_PIPE", "array2"),
|
||||
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "MULTIPLY"),
|
||||
},
|
||||
{
|
||||
`.key.array // .key.array2`,
|
||||
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ALTERNATIVE", "key", "SHORT_PIPE", "array2"),
|
||||
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ALTERNATIVE"),
|
||||
},
|
||||
{
|
||||
`.a | .[].b == "apple"`,
|
||||
append(make([]interface{}, 0), "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
|
||||
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
|
||||
},
|
||||
{
|
||||
`(.a | .[].b) == "apple"`,
|
||||
append(make([]interface{}, 0), "(", "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
|
||||
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
|
||||
},
|
||||
{
|
||||
`.[] | select(. == "*at")`,
|
||||
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
|
||||
append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
|
||||
},
|
||||
{
|
||||
`[true]`,
|
||||
append(make([]interface{}, 0), "[", "true (bool)", "]"),
|
||||
append(make([]interface{}, 0), "true (bool)", "COLLECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`[true, false]`,
|
||||
append(make([]interface{}, 0), "[", "true (bool)", "UNION", "false (bool)", "]"),
|
||||
append(make([]interface{}, 0), "true (bool)", "false (bool)", "UNION", "COLLECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`"mike": .a`,
|
||||
append(make([]interface{}, 0), "mike (string)", "CREATE_MAP", "a"),
|
||||
append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP"),
|
||||
},
|
||||
{
|
||||
`.a: "mike"`,
|
||||
append(make([]interface{}, 0), "a", "CREATE_MAP", "mike (string)"),
|
||||
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP"),
|
||||
},
|
||||
{
|
||||
`{"mike": .a}`,
|
||||
append(make([]interface{}, 0), "{", "mike (string)", "CREATE_MAP", "a", "}"),
|
||||
append(make([]interface{}, 0), "mike (string)", "a", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`{.a: "mike"}`,
|
||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "mike (string)", "}"),
|
||||
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`{.a: .c, .b.[]: .f.g[]}`,
|
||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "}"),
|
||||
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "f", "g", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
|
||||
},
|
||||
{
|
||||
`explode(.a.b)`,
|
||||
append(make([]interface{}, 0), "EXPLODE", "(", "a", "SHORT_PIPE", "b", ")"),
|
||||
append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "EXPLODE"),
|
||||
},
|
||||
{
|
||||
`.a.b style="folded"`,
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_STYLE", "folded (string)"),
|
||||
append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "folded (string)", "ASSIGN_STYLE"),
|
||||
},
|
||||
{
|
||||
`tag == "str"`,
|
||||
append(make([]interface{}, 0), "GET_TAG", "EQUALS", "str (string)"),
|
||||
append(make([]interface{}, 0), "GET_TAG", "str (string)", "EQUALS"),
|
||||
},
|
||||
{
|
||||
`. tag= "str"`,
|
||||
append(make([]interface{}, 0), "SELF", "ASSIGN_TAG", "str (string)"),
|
||||
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_TAG"),
|
||||
},
|
||||
{
|
||||
`lineComment == "str"`,
|
||||
append(make([]interface{}, 0), "GET_COMMENT", "EQUALS", "str (string)"),
|
||||
append(make([]interface{}, 0), "GET_COMMENT", "str (string)", "EQUALS"),
|
||||
},
|
||||
{
|
||||
`. lineComment= "str"`,
|
||||
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
|
||||
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
|
||||
},
|
||||
{
|
||||
`. lineComment |= "str"`,
|
||||
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
|
||||
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
|
||||
},
|
||||
{
|
||||
`.a.b tag="!!str"`,
|
||||
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"),
|
||||
append(make([]interface{}, 0), "a", "b", "SHORT_PIPE", "!!str (string)", "ASSIGN_TAG"),
|
||||
},
|
||||
{
|
||||
`""`,
|
||||
append(make([]interface{}, 0), " (string)"),
|
||||
append(make([]interface{}, 0), " (string)"),
|
||||
},
|
||||
{
|
||||
`.foo* | (. style="flow")`,
|
||||
append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"),
|
||||
append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"),
|
||||
},
|
||||
}
|
||||
|
||||
var tokeniser = newExpressionTokeniser()
|
||||
var postFixer = newExpressionPostFixer()
|
||||
|
||||
func TestPathParsing(t *testing.T) {
|
||||
for _, tt := range pathTests {
|
||||
tokens, err := tokeniser.Tokenise(tt.path)
|
||||
if err != nil {
|
||||
t.Error(tt.path, err)
|
||||
}
|
||||
var tokenValues []interface{}
|
||||
for _, token := range tokens {
|
||||
tokenValues = append(tokenValues, token.toString(false))
|
||||
}
|
||||
test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, fmt.Sprintf("tokenise: %v", tt.path))
|
||||
|
||||
results, errorP := postFixer.ConvertToPostfix(tokens)
|
||||
|
||||
var readableResults []interface{}
|
||||
for _, token := range results {
|
||||
readableResults = append(readableResults, token.toString())
|
||||
}
|
||||
|
||||
if errorP != nil {
|
||||
t.Error(tt.path, err)
|
||||
}
|
||||
|
||||
test.AssertResultComplexWithContext(t, tt.expectedPostFix, readableResults, fmt.Sprintf("postfix: %v", tt.path))
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
lex "github.com/timtadh/lexmachine"
|
||||
"github.com/timtadh/lexmachine/machines"
|
||||
)
|
||||
|
||||
func skip(*lex.Scanner, *machines.Match) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type tokenType uint32
|
||||
|
||||
const (
|
||||
operationToken = 1 << iota
|
||||
openBracket
|
||||
closeBracket
|
||||
openCollect
|
||||
closeCollect
|
||||
openCollectObject
|
||||
closeCollectObject
|
||||
traverseArrayCollect
|
||||
)
|
||||
|
||||
type token struct {
|
||||
TokenType tokenType
|
||||
Operation *Operation
|
||||
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
|
||||
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
|
||||
|
||||
}
|
||||
|
||||
func (t *token) toString(detail bool) string {
|
||||
if t.TokenType == operationToken {
|
||||
if detail {
|
||||
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
|
||||
}
|
||||
return t.Operation.toString()
|
||||
} else if t.TokenType == openBracket {
|
||||
return "("
|
||||
} else if t.TokenType == closeBracket {
|
||||
return ")"
|
||||
} else if t.TokenType == openCollect {
|
||||
return "["
|
||||
} else if t.TokenType == closeCollect {
|
||||
return "]"
|
||||
} else if t.TokenType == openCollectObject {
|
||||
return "{"
|
||||
} else if t.TokenType == closeCollectObject {
|
||||
return "}"
|
||||
} else if t.TokenType == traverseArrayCollect {
|
||||
return ".["
|
||||
|
||||
} else {
|
||||
return "NFI"
|
||||
}
|
||||
}
|
||||
|
||||
func pathToken(wrapped bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
value := string(m.Bytes)
|
||||
value = value[1:]
|
||||
if wrapped {
|
||||
value = unwrap(value)
|
||||
}
|
||||
log.Debug("PathToken %v", value)
|
||||
op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value, Preferences: traversePreferences{}}
|
||||
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func opToken(op *operationType) lex.Action {
|
||||
return opTokenWithPrefs(op, nil, nil)
|
||||
}
|
||||
|
||||
func opAssignableToken(opType *operationType, assignOpType *operationType) lex.Action {
|
||||
return opTokenWithPrefs(opType, assignOpType, nil)
|
||||
}
|
||||
|
||||
func assignOpToken(updateAssign bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
log.Debug("assignOpToken %v", string(m.Bytes))
|
||||
value := string(m.Bytes)
|
||||
op := &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, UpdateAssign: updateAssign}
|
||||
return &token{TokenType: operationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func multiplyWithPrefs() lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
prefs := multiplyPreferences{}
|
||||
options := string(m.Bytes)
|
||||
if strings.Contains(options, "+") {
|
||||
prefs.AppendArrays = true
|
||||
}
|
||||
if strings.Contains(options, "?") {
|
||||
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
|
||||
}
|
||||
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
|
||||
return &token{TokenType: operationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
|
||||
value := string(m.Bytes)
|
||||
op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences}
|
||||
var assign *Operation
|
||||
if assignOpType != nil {
|
||||
assign = &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, Preferences: preferences}
|
||||
}
|
||||
return &token{TokenType: operationToken, Operation: op, AssignOperation: assign}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func assignAllCommentsOp(updateAssign bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
log.Debug("assignAllCommentsOp %v", string(m.Bytes))
|
||||
value := string(m.Bytes)
|
||||
op := &Operation{
|
||||
OperationType: assignCommentOpType,
|
||||
Value: assignCommentOpType.Type,
|
||||
StringValue: value,
|
||||
UpdateAssign: updateAssign,
|
||||
Preferences: commentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
|
||||
}
|
||||
return &token{TokenType: operationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func literalToken(pType tokenType, checkForPost bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
return &token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func unwrap(value string) string {
|
||||
return value[1 : len(value)-1]
|
||||
}
|
||||
|
||||
func numberValue() lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
var numberString = string(m.Bytes)
|
||||
var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint
|
||||
if errParsingInt != nil {
|
||||
return nil, errParsingInt
|
||||
}
|
||||
|
||||
return &token{TokenType: operationToken, Operation: createValueOperation(number, numberString)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func floatValue() lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
var numberString = string(m.Bytes)
|
||||
var number, errParsingInt = strconv.ParseFloat(numberString, 64) // nolint
|
||||
if errParsingInt != nil {
|
||||
return nil, errParsingInt
|
||||
}
|
||||
return &token{TokenType: operationToken, Operation: createValueOperation(number, numberString)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func booleanValue(val bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
return &token{TokenType: operationToken, Operation: createValueOperation(val, string(m.Bytes))}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func stringValue(wrapped bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
value := string(m.Bytes)
|
||||
if wrapped {
|
||||
value = unwrap(value)
|
||||
}
|
||||
return &token{TokenType: operationToken, Operation: createValueOperation(value, value)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getVariableOpToken() lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
value := string(m.Bytes)
|
||||
|
||||
value = value[1 : len(value)-1]
|
||||
|
||||
getVarOperation := createValueOperation(value, value)
|
||||
getVarOperation.OperationType = getVariableOpType
|
||||
|
||||
return &token{TokenType: operationToken, Operation: getVarOperation, CheckForPostTraverse: true}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func envOp(strenv bool) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
value := string(m.Bytes)
|
||||
preferences := envOpPreferences{}
|
||||
|
||||
if strenv {
|
||||
// strenv( )
|
||||
value = value[7 : len(value)-1]
|
||||
preferences.StringValue = true
|
||||
} else {
|
||||
//env( )
|
||||
value = value[4 : len(value)-1]
|
||||
}
|
||||
|
||||
envOperation := createValueOperation(value, value)
|
||||
envOperation.OperationType = envOpType
|
||||
envOperation.Preferences = preferences
|
||||
|
||||
return &token{TokenType: operationToken, Operation: envOperation}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func nullValue() lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
return &token{TokenType: operationToken, Operation: createValueOperation(nil, string(m.Bytes))}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func selfToken() lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
op := &Operation{OperationType: selfReferenceOpType}
|
||||
return &token{TokenType: operationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func initLexer() (*lex.Lexer, error) {
|
||||
lexer := lex.NewLexer()
|
||||
lexer.Add([]byte(`\(`), literalToken(openBracket, false))
|
||||
lexer.Add([]byte(`\)`), literalToken(closeBracket, true))
|
||||
|
||||
lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false))
|
||||
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
|
||||
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: false}}))
|
||||
|
||||
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
|
||||
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}))
|
||||
|
||||
lexer.Add([]byte(`,`), opToken(unionOpType))
|
||||
lexer.Add([]byte(`:\s*`), opToken(createMapOpType))
|
||||
lexer.Add([]byte(`length`), opToken(lengthOpType))
|
||||
lexer.Add([]byte(`sortKeys`), opToken(sortKeysOpType))
|
||||
lexer.Add([]byte(`select`), opToken(selectOpType))
|
||||
lexer.Add([]byte(`has`), opToken(hasOpType))
|
||||
lexer.Add([]byte(`explode`), opToken(explodeOpType))
|
||||
lexer.Add([]byte(`or`), opToken(orOpType))
|
||||
lexer.Add([]byte(`and`), opToken(andOpType))
|
||||
lexer.Add([]byte(`not`), opToken(notOpType))
|
||||
lexer.Add([]byte(`\/\/`), opToken(alternativeOpType))
|
||||
|
||||
lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType))
|
||||
lexer.Add([]byte(`di`), opToken(getDocumentIndexOpType))
|
||||
lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType))
|
||||
|
||||
lexer.Add([]byte(`join`), opToken(joinStringOpType))
|
||||
lexer.Add([]byte(`split`), opToken(splitStringOpType))
|
||||
lexer.Add([]byte(`keys`), opToken(keysOpType))
|
||||
|
||||
lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType))
|
||||
|
||||
lexer.Add([]byte(`tag`), opAssignableToken(getTagOpType, assignTagOpType))
|
||||
lexer.Add([]byte(`anchor`), opAssignableToken(getAnchorOpType, assignAnchorOpType))
|
||||
lexer.Add([]byte(`alias`), opAssignableToken(getAliasOptype, assignAliasOpType))
|
||||
lexer.Add([]byte(`filename`), opToken(getFilenameOpType))
|
||||
lexer.Add([]byte(`fileIndex`), opToken(getFileIndexOpType))
|
||||
lexer.Add([]byte(`fi`), opToken(getFileIndexOpType))
|
||||
lexer.Add([]byte(`path`), opToken(getPathOpType))
|
||||
|
||||
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{LineComment: true}))
|
||||
|
||||
lexer.Add([]byte(`headComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{HeadComment: true}))
|
||||
|
||||
lexer.Add([]byte(`footComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{FootComment: true}))
|
||||
|
||||
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
|
||||
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
|
||||
|
||||
lexer.Add([]byte(`collect`), opToken(collectOpType))
|
||||
|
||||
lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType))
|
||||
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
|
||||
|
||||
lexer.Add([]byte(`del`), opToken(deleteChildOpType))
|
||||
|
||||
lexer.Add([]byte(`\s*\|=\s*`), assignOpToken(true))
|
||||
|
||||
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
|
||||
|
||||
lexer.Add([]byte(`\."[^ "]+"`), pathToken(true))
|
||||
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false))
|
||||
lexer.Add([]byte(`\.`), selfToken())
|
||||
|
||||
lexer.Add([]byte(`\|`), opToken(pipeOpType))
|
||||
|
||||
lexer.Add([]byte(`-?\d+(\.\d+)`), floatValue())
|
||||
lexer.Add([]byte(`-?[1-9](\.\d+)?[Ee][-+]?\d+`), floatValue())
|
||||
lexer.Add([]byte(`-?\d+`), numberValue())
|
||||
|
||||
lexer.Add([]byte(`[Tt][Rr][Uu][Ee]`), booleanValue(true))
|
||||
lexer.Add([]byte(`[Ff][Aa][Ll][Ss][Ee]`), booleanValue(false))
|
||||
|
||||
lexer.Add([]byte(`[Nn][Uu][Ll][Ll]`), nullValue())
|
||||
lexer.Add([]byte(`~`), nullValue())
|
||||
|
||||
lexer.Add([]byte(`"[^"]*"`), stringValue(true))
|
||||
lexer.Add([]byte(`strenv\([^\)]+\)`), envOp(true))
|
||||
lexer.Add([]byte(`env\([^\)]+\)`), envOp(false))
|
||||
|
||||
lexer.Add([]byte(`\[`), literalToken(openCollect, false))
|
||||
lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
|
||||
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
|
||||
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
|
||||
lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs())
|
||||
lexer.Add([]byte(`\+`), opToken(addOpType))
|
||||
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
|
||||
lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken())
|
||||
lexer.Add([]byte(`as`), opToken(assignVariableOpType))
|
||||
|
||||
err := lexer.Compile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return lexer, nil
|
||||
}
|
||||
|
||||
type expressionTokeniser interface {
|
||||
Tokenise(expression string) ([]*token, error)
|
||||
}
|
||||
|
||||
type expressionTokeniserImpl struct {
|
||||
lexer *lex.Lexer
|
||||
}
|
||||
|
||||
func newExpressionTokeniser() expressionTokeniser {
|
||||
var lexer, err = initLexer()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &expressionTokeniserImpl{lexer}
|
||||
}
|
||||
|
||||
func (p *expressionTokeniserImpl) Tokenise(expression string) ([]*token, error) {
|
||||
scanner, err := p.lexer.Scanner([]byte(expression))
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parsing expression: %v", err)
|
||||
}
|
||||
var tokens []*token
|
||||
for tok, err, eof := scanner.Next(); !eof; tok, err, eof = scanner.Next() {
|
||||
|
||||
if tok != nil {
|
||||
currentToken := tok.(*token)
|
||||
log.Debugf("Tokenising %v", currentToken.toString(true))
|
||||
tokens = append(tokens, currentToken)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parsing expression: %v", err)
|
||||
}
|
||||
}
|
||||
var postProcessedTokens = make([]*token, 0)
|
||||
|
||||
skipNextToken := false
|
||||
|
||||
for index := range tokens {
|
||||
if skipNextToken {
|
||||
skipNextToken = false
|
||||
} else {
|
||||
postProcessedTokens, skipNextToken = p.handleToken(tokens, index, postProcessedTokens)
|
||||
}
|
||||
}
|
||||
|
||||
return postProcessedTokens, nil
|
||||
}
|
||||
|
||||
func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postProcessedTokens []*token) (tokensAccum []*token, skipNextToken bool) {
|
||||
skipNextToken = false
|
||||
currentToken := tokens[index]
|
||||
|
||||
if currentToken.TokenType == traverseArrayCollect {
|
||||
//need to put a traverse array then a collect currentToken
|
||||
// do this by adding traverse then converting currentToken to collect
|
||||
|
||||
if index == 0 || tokens[index-1].TokenType != operationToken ||
|
||||
tokens[index-1].Operation.OperationType != traversePathOpType {
|
||||
op := &Operation{OperationType: selfReferenceOpType, StringValue: "SELF"}
|
||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||
}
|
||||
|
||||
op := &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
|
||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||
|
||||
currentToken = &token{TokenType: openCollect}
|
||||
|
||||
}
|
||||
|
||||
if index != len(tokens)-1 && currentToken.AssignOperation != nil &&
|
||||
tokens[index+1].TokenType == operationToken &&
|
||||
tokens[index+1].Operation.OperationType == assignOpType {
|
||||
currentToken.Operation = currentToken.AssignOperation
|
||||
currentToken.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
|
||||
skipNextToken = true
|
||||
}
|
||||
|
||||
postProcessedTokens = append(postProcessedTokens, currentToken)
|
||||
|
||||
if index != len(tokens)-1 &&
|
||||
((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) ||
|
||||
(currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) {
|
||||
|
||||
op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"}
|
||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||
}
|
||||
|
||||
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
|
||||
tokens[index+1].TokenType == operationToken &&
|
||||
tokens[index+1].Operation.OperationType == traversePathOpType {
|
||||
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
|
||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||
}
|
||||
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
|
||||
tokens[index+1].TokenType == openCollect {
|
||||
|
||||
op := &Operation{OperationType: traverseArrayOpType}
|
||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
|
||||
}
|
||||
return postProcessedTokens, skipNextToken
|
||||
}
|
||||
110
pkg/yqlib/lib.go
110
pkg/yqlib/lib.go
@@ -1,5 +1,3 @@
|
||||
// Use the top level Evaluator or StreamEvaluator to evaluate expressions and return matches.
|
||||
//
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -13,91 +11,77 @@ import (
|
||||
|
||||
var log = logging.MustGetLogger("yq-lib")
|
||||
|
||||
type operationType struct {
|
||||
type OperationType struct {
|
||||
Type string
|
||||
NumArgs uint // number of arguments to the op
|
||||
Precedence uint
|
||||
Handler operatorHandler
|
||||
Handler OperatorHandler
|
||||
}
|
||||
|
||||
// operators TODO:
|
||||
// - cookbook doc for common things
|
||||
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||
|
||||
var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator}
|
||||
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
|
||||
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
||||
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
||||
|
||||
var unionOpType = &operationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: unionOperator}
|
||||
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
||||
|
||||
var pipeOpType = &operationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: pipeOperator}
|
||||
var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: PipeOperator}
|
||||
|
||||
var assignOpType = &operationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: assignUpdateOperator}
|
||||
var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator}
|
||||
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
|
||||
var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator}
|
||||
|
||||
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
|
||||
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
|
||||
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator}
|
||||
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
|
||||
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
|
||||
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
|
||||
var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
|
||||
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
||||
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
|
||||
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
|
||||
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
|
||||
|
||||
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
|
||||
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
|
||||
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
|
||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
|
||||
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
|
||||
|
||||
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
|
||||
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator}
|
||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||
|
||||
var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: pipeOperator}
|
||||
var ShortPipe = &OperationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
|
||||
|
||||
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
|
||||
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
|
||||
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
|
||||
var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator}
|
||||
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
|
||||
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
|
||||
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
|
||||
var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator}
|
||||
var getAliasOptype = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: getAliasOperator}
|
||||
var getDocumentIndexOpType = &operationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: getDocumentIndexOperator}
|
||||
var getFilenameOpType = &operationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: getFilenameOperator}
|
||||
var getFileIndexOpType = &operationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: getFileIndexOperator}
|
||||
var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: getPathOperator}
|
||||
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
|
||||
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
|
||||
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
|
||||
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
|
||||
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
|
||||
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
|
||||
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
|
||||
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
|
||||
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
|
||||
|
||||
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
||||
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
|
||||
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
||||
var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator}
|
||||
|
||||
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
|
||||
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 collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
|
||||
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 55, Handler: traversePathOperator}
|
||||
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: traverseArrayOperator}
|
||||
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
|
||||
var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator}
|
||||
var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator}
|
||||
var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
|
||||
|
||||
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator}
|
||||
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
|
||||
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
|
||||
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
|
||||
var emptyOpType = &operationType{Type: "EMPTY", NumArgs: 50, Handler: emptyOperator}
|
||||
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
|
||||
|
||||
var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}
|
||||
|
||||
var selectOpType = &operationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: selectOperator}
|
||||
var hasOpType = &operationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: hasOperator}
|
||||
var deleteChildOpType = &operationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: deleteChildOperator}
|
||||
var deleteImmediateChildOpType = &operationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: deleteImmediateChildOperator}
|
||||
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}
|
||||
|
||||
type Operation struct {
|
||||
OperationType *operationType
|
||||
OperationType *OperationType
|
||||
Value interface{}
|
||||
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
|
||||
}
|
||||
|
||||
func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
func CreateValueOperation(value interface{}, stringValue string) *Operation {
|
||||
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
|
||||
node.Value = stringValue
|
||||
|
||||
@@ -115,7 +99,7 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
}
|
||||
|
||||
return &Operation{
|
||||
OperationType: valueOpType,
|
||||
OperationType: ValueOp,
|
||||
Value: value,
|
||||
StringValue: stringValue,
|
||||
CandidateNode: &CandidateNode{Node: &node},
|
||||
@@ -124,11 +108,13 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
|
||||
// debugging purposes only
|
||||
func (p *Operation) toString() string {
|
||||
if p.OperationType == traversePathOpType {
|
||||
if p.OperationType == TraversePath {
|
||||
return fmt.Sprintf("%v", p.Value)
|
||||
} else if p.OperationType == selfReferenceOpType {
|
||||
} else if p.OperationType == DocumentFilter {
|
||||
return fmt.Sprintf("d%v", p.Value)
|
||||
} else if p.OperationType == SelfReference {
|
||||
return "SELF"
|
||||
} else if p.OperationType == valueOpType {
|
||||
} else if p.OperationType == ValueOp {
|
||||
return fmt.Sprintf("%v (%T)", p.Value, p.Value)
|
||||
} else {
|
||||
return fmt.Sprintf("%v", p.OperationType.Type)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package yqlib
|
||||
|
||||
func matchKey(name string, pattern string) (matched bool) {
|
||||
func Match(name string, pattern string) (matched bool) {
|
||||
if pattern == "" {
|
||||
return name == pattern
|
||||
}
|
||||
|
||||
@@ -3,26 +3,32 @@ package yqlib
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"strconv"
|
||||
"container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
|
||||
return &ExpressionNode{Operation: &Operation{OperationType: addOpType},
|
||||
Lhs: lhs,
|
||||
func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: Add},
|
||||
Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}},
|
||||
Rhs: rhs}
|
||||
}
|
||||
|
||||
func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
assignmentOp := &Operation{OperationType: assignOpType}
|
||||
assignmentOp.UpdateAssign = false
|
||||
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
assignmentOp := &Operation{OperationType: Assign}
|
||||
assignmentOp.Preferences = &AssignOpPreferences{true}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)}
|
||||
return d.GetMatchingNodes(context, assignmentOpNode)
|
||||
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
|
||||
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
|
||||
}
|
||||
|
||||
func toNodes(candidate *CandidateNode) []*yaml.Node {
|
||||
func toNodes(candidates *list.List) []*yaml.Node {
|
||||
|
||||
if candidates.Len() == 0 {
|
||||
return []*yaml.Node{}
|
||||
}
|
||||
candidate := candidates.Front().Value.(*CandidateNode)
|
||||
|
||||
if candidate.Node.Tag == "!!null" {
|
||||
return []*yaml.Node{}
|
||||
}
|
||||
@@ -36,76 +42,42 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
|
||||
|
||||
}
|
||||
|
||||
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("Add operator")
|
||||
var results = list.New()
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
return crossFunction(d, context, expressionNode, add)
|
||||
}
|
||||
|
||||
func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
|
||||
lhsNode := lhs.Node
|
||||
|
||||
if lhsNode.Tag == "!!null" {
|
||||
return lhs.CreateChild(nil, rhs.Node), nil
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target := lhs.CreateChild(nil, &yaml.Node{})
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
lhsCandidate := el.Value.(*CandidateNode)
|
||||
lhsNode := UnwrapDoc(lhsCandidate.Node)
|
||||
|
||||
switch lhsNode.Kind {
|
||||
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:
|
||||
if rhs.Node.Kind != yaml.ScalarNode {
|
||||
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
|
||||
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")
|
||||
}
|
||||
target.Node.Kind = yaml.ScalarNode
|
||||
target.Node.Style = lhsNode.Style
|
||||
return addScalars(target, lhsNode, rhs.Node)
|
||||
}
|
||||
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) {
|
||||
|
||||
if lhs.Tag == "!!str" {
|
||||
target.Node.Tag = "!!str"
|
||||
target.Node.Value = lhs.Value + rhs.Value
|
||||
} else if lhs.Tag == "!!int" && rhs.Tag == "!!int" {
|
||||
lhsNum, err := strconv.Atoi(lhs.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhsNum, err := strconv.Atoi(rhs.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sum := lhsNum + rhsNum
|
||||
target.Node.Tag = "!!int"
|
||||
target.Node.Value = fmt.Sprintf("%v", sum)
|
||||
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") {
|
||||
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sum := lhsNum + rhsNum
|
||||
target.Node.Tag = "!!float"
|
||||
target.Node.Value = fmt.Sprintf("%v", sum)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%v cannot be added to %v", lhs.Tag, rhs.Tag)
|
||||
}
|
||||
|
||||
return target, nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -5,15 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var addOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{a: foo, b: bar}, {a: 1, b: 2}]`,
|
||||
expression: ".[] | .a + .b",
|
||||
expected: []string{
|
||||
"D0, P[0 a], (!!str)::foobar\n",
|
||||
"D0, P[1 a], (!!int)::3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Concatenate and assign arrays",
|
||||
document: `{a: {val: thing, b: [cat,dog]}}`,
|
||||
@@ -30,14 +21,6 @@ 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]}`,
|
||||
@@ -47,11 +30,11 @@ var addOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Add new object to array",
|
||||
document: `a: [{dog: woof}]`,
|
||||
expression: `.a + {"cat": "meow"}`,
|
||||
description: "Add object to array",
|
||||
document: `{a: [1,2], c: {cat: meow}}`,
|
||||
expression: `.a + .c`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!seq)::[{dog: woof}, {cat: meow}]\n",
|
||||
"D0, P[a], (!!seq)::[1, 2, {cat: meow}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,56 +53,6 @@ var addOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "String concatenation",
|
||||
document: `{a: cat, b: meow}`,
|
||||
expression: `.a = .a + .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Relative string concatenation",
|
||||
document: `{a: cat, b: meow}`,
|
||||
expression: `.a += .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Number addition - float",
|
||||
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
|
||||
document: `{a: 3, b: 4.9}`,
|
||||
expression: `.a = .a + .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: 7.9, b: 4.9}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Number addition - int",
|
||||
subdescription: "If both the lhs and rhs are ints then the expression will be calculated with ints.",
|
||||
document: `{a: 3, b: 4}`,
|
||||
expression: `.a = .a + .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: 7, b: 4}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Increment number",
|
||||
document: `{a: 3}`,
|
||||
expression: `.a += 1`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: 4}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Add to null",
|
||||
subdescription: "Adding to null simply returns the rhs",
|
||||
expression: `null + "cat"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAddOperatorScenarios(t *testing.T) {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
// corssFunction no matches
|
||||
// can boolean use crossfunction
|
||||
|
||||
func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- alternative")
|
||||
return crossFunction(d, context, expressionNode, alternativeFunc)
|
||||
}
|
||||
|
||||
func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
|
||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||
|
||||
isTrue, err := isTruthy(lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if isTrue {
|
||||
return lhs, nil
|
||||
}
|
||||
return rhs, nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var alternativeOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "LHS is defined",
|
||||
expression: `.a // "hello"`,
|
||||
document: `{a: bridge}`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!str)::bridge\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LHS is not defined",
|
||||
expression: `.a // "hello"`,
|
||||
document: `{}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::hello\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LHS is null",
|
||||
expression: `.a // "hello"`,
|
||||
document: `{a: ~}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::hello\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LHS is false",
|
||||
expression: `.a // "hello"`,
|
||||
document: `{a: false}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::hello\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "RHS is an expression",
|
||||
expression: `.a // .b`,
|
||||
document: `{a: false, b: cat}`,
|
||||
expected: []string{
|
||||
"D0, P[b], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `false // true`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAlternativeOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range alternativeOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Alternative (Default value)", alternativeOperatorScenarios)
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
log.Debugf("AssignAlias operator!")
|
||||
|
||||
aliasName := ""
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting aliasName : %v", candidate.GetKey())
|
||||
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
candidate.Node.Kind = yaml.AliasNode
|
||||
candidate.Node.Value = aliasName
|
||||
}
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func getAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("GetAlias operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func assignAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
log.Debugf("AssignAnchor operator!")
|
||||
|
||||
anchorName := ""
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting anchorName of : %v", candidate.GetKey())
|
||||
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
candidate.Node.Anchor = anchorName
|
||||
}
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func getAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("GetAnchor operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
anchor := candidate.Node.Anchor
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func explodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- ExplodeOperation")
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
for childEl := rhs.MatchingNodes.Front(); childEl != nil; childEl = childEl.Next() {
|
||||
err = explodeNode(childEl.Value.(*CandidateNode).Node, context)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func explodeNode(node *yaml.Node, context Context) error {
|
||||
node.Anchor = ""
|
||||
switch node.Kind {
|
||||
case yaml.SequenceNode, yaml.DocumentNode:
|
||||
for index, contentNode := range node.Content {
|
||||
log.Debugf("exploding index %v", index)
|
||||
errorInContent := explodeNode(contentNode, context)
|
||||
if errorInContent != nil {
|
||||
return errorInContent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case yaml.AliasNode:
|
||||
log.Debugf("its an alias!")
|
||||
if node.Alias != nil {
|
||||
node.Kind = node.Alias.Kind
|
||||
node.Style = node.Alias.Style
|
||||
node.Tag = node.Alias.Tag
|
||||
node.Content = node.Alias.Content
|
||||
node.Value = node.Alias.Value
|
||||
node.Alias = nil
|
||||
}
|
||||
return nil
|
||||
case yaml.MappingNode:
|
||||
var newContent = list.New()
|
||||
for index := 0; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
valueNode := node.Content[index+1]
|
||||
log.Debugf("traversing %v", keyNode.Value)
|
||||
if keyNode.Value != "<<" {
|
||||
err := overrideEntry(node, keyNode, valueNode, index, context.ChildContext(newContent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if valueNode.Kind == yaml.SequenceNode {
|
||||
log.Debugf("an alias merge list!")
|
||||
for index := 0; index < len(valueNode.Content); index = index + 1 {
|
||||
aliasNode := valueNode.Content[index]
|
||||
err := applyAlias(node, aliasNode.Alias, index, context.ChildContext(newContent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debugf("an alias merge!")
|
||||
err := applyAlias(node, valueNode.Alias, index, context.ChildContext(newContent))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node.Content = make([]*yaml.Node, newContent.Len())
|
||||
index := 0
|
||||
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
||||
node.Content[index] = newEl.Value.(*yaml.Node)
|
||||
index++
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent Context) error {
|
||||
if alias == nil {
|
||||
return nil
|
||||
}
|
||||
for index := 0; index < len(alias.Content); index = index + 2 {
|
||||
keyNode := alias.Content[index]
|
||||
log.Debugf("applying alias key %v", keyNode.Value)
|
||||
valueNode := alias.Content[index+1]
|
||||
err := overrideEntry(node, keyNode, valueNode, aliasIndex, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent Context) error {
|
||||
|
||||
err := explodeNode(value, newContent)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for newEl := newContent.MatchingNodes.Front(); newEl != nil; newEl = newEl.Next() {
|
||||
valueEl := newEl.Next() // move forward twice
|
||||
keyNode := newEl.Value.(*yaml.Node)
|
||||
log.Debugf("checking new content %v:%v", keyNode.Value, valueEl.Value.(*yaml.Node).Value)
|
||||
if keyNode.Value == key.Value && keyNode.Alias == nil && key.Alias == nil {
|
||||
log.Debugf("overridign new content")
|
||||
valueEl.Value = value
|
||||
return nil
|
||||
}
|
||||
newEl = valueEl // move forward twice
|
||||
}
|
||||
|
||||
for index := startIndex + 2; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
|
||||
if keyNode.Value == key.Value && keyNode.Alias == nil {
|
||||
log.Debugf("content will be overridden at index %v", index)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err = explodeNode(key, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("adding %v:%v", key.Value, value.Value)
|
||||
newContent.MatchingNodes.PushBack(key)
|
||||
newContent.MatchingNodes.PushBack(value)
|
||||
return nil
|
||||
}
|
||||
@@ -1,60 +1,70 @@
|
||||
package yqlib
|
||||
|
||||
func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
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 Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
var rhs Context
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
|
||||
|
||||
var rhs *list.List
|
||||
if !preferences.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if preferences.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// grab the first value
|
||||
first := rhs.MatchingNodes.Front()
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
rhsCandidate := first.Value.(*CandidateNode)
|
||||
rhsCandidate.Node = unwrapDoc(rhsCandidate.Node)
|
||||
candidate.UpdateFrom(rhsCandidate)
|
||||
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
||||
}
|
||||
}
|
||||
|
||||
return context, nil
|
||||
// // if there was nothing given, perhaps we are creating a new yaml doc
|
||||
// if matchingNodes.Len() == 0 {
|
||||
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
|
||||
// return lhs, nil
|
||||
// }
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
// does not update content or values
|
||||
func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// grab the first value
|
||||
first := rhs.MatchingNodes.Front()
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode))
|
||||
}
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
@@ -20,16 +20,6 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::{a: {g: foof}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
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}`,
|
||||
@@ -90,15 +80,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Update selected results",
|
||||
document: `{a: {b: apple, c: cactus}}`,
|
||||
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"`,
|
||||
expression: `(.a.[] | select(. == "apple")) = "frog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func isTruthy(c *CandidateNode) (bool, error) {
|
||||
node := unwrapDoc(c.Node)
|
||||
node := UnwrapDoc(c.Node)
|
||||
value := true
|
||||
|
||||
if node.Tag == "!!null" {
|
||||
@@ -25,54 +25,89 @@ func isTruthy(c *CandidateNode) (bool, error) {
|
||||
|
||||
type boolOp func(bool, bool) bool
|
||||
|
||||
func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
|
||||
lhsTrue, errDecoding := isTruthy(lhs)
|
||||
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)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
return errDecoding
|
||||
}
|
||||
|
||||
rhsTrue, errDecoding := isTruthy(rhs)
|
||||
if errDecoding != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
return createBooleanCandidate(lhs, op(lhsTrue, rhsTrue)), nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
if matchingNodes.Len() == 0 {
|
||||
lhs, err := d.GetMatchingNodes(list.New(), pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(list.New(), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return results, performBoolOp(results, lhs, rhs, op)
|
||||
}
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = performBoolOp(results, lhs, rhs, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- orOp")
|
||||
return crossFunction(d, context, expressionNode, performBoolOp(
|
||||
func(b1 bool, b2 bool) bool {
|
||||
return b1 || b2
|
||||
}))
|
||||
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
||||
return b1 || b2
|
||||
})
|
||||
}
|
||||
|
||||
func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- AndOp")
|
||||
return crossFunction(d, context, expressionNode, performBoolOp(
|
||||
func(b1 bool, b2 bool) bool {
|
||||
return b1 && b2
|
||||
}))
|
||||
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
|
||||
return b1 && b2
|
||||
})
|
||||
}
|
||||
|
||||
func notOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- notOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debug("notOperation checking %v", candidate)
|
||||
truthy, errDecoding := isTruthy(candidate)
|
||||
if errDecoding != nil {
|
||||
return Context{}, errDecoding
|
||||
return nil, errDecoding
|
||||
}
|
||||
result := createBooleanCandidate(candidate, !truthy)
|
||||
results.PushBack(result)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -6,35 +6,34 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func collectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- collectOperation")
|
||||
|
||||
if context.MatchingNodes.Len() == 0 {
|
||||
if matchMap.Len() == 0 {
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return context.SingleChildContext(candidate), nil
|
||||
return nodeToMap(candidate), nil
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
var collectC *CandidateNode
|
||||
if context.MatchingNodes.Front() != nil {
|
||||
collectC = context.MatchingNodes.Front().Value.(*CandidateNode).CreateChild(nil, node)
|
||||
if len(collectC.Path) > 0 {
|
||||
collectC.Path = collectC.Path[:len(collectC.Path)-1]
|
||||
}
|
||||
} else {
|
||||
collectC = &CandidateNode{Node: node}
|
||||
}
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
var document uint = 0
|
||||
var path []interface{}
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Collecting %v", NodeToString(candidate))
|
||||
node.Content = append(node.Content, unwrapDoc(candidate.Node))
|
||||
if path == nil && candidate.Path != nil && len(candidate.Path) > 1 {
|
||||
path = candidate.Path[:len(candidate.Path)-1]
|
||||
document = candidate.Document
|
||||
}
|
||||
node.Content = append(node.Content, candidate.Node)
|
||||
}
|
||||
|
||||
collectC := &CandidateNode{Node: node, Document: document, Path: path}
|
||||
results.PushBack(collectC)
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -17,83 +17,90 @@ import (
|
||||
...
|
||||
*/
|
||||
|
||||
func collectObjectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- collectObjectOperation")
|
||||
|
||||
if context.MatchingNodes.Len() == 0 {
|
||||
if matchMap.Len() == 0 {
|
||||
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return context.SingleChildContext(candidate), nil
|
||||
return nodeToMap(candidate), nil
|
||||
}
|
||||
first := context.MatchingNodes.Front().Value.(*CandidateNode)
|
||||
first := matchMap.Front().Value.(*CandidateNode)
|
||||
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
||||
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
rotated[i] = list.New()
|
||||
}
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidateNode := el.Value.(*CandidateNode)
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
rotated[i].PushBack(candidateNode.CreateChild(i, candidateNode.Node.Content[i]))
|
||||
rotated[i].PushBack(createChildCandidate(candidateNode, i))
|
||||
}
|
||||
}
|
||||
|
||||
newObject := list.New()
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
additions, err := collect(d, context.ChildContext(list.New()), rotated[i])
|
||||
additions, err := collect(d, list.New(), rotated[i])
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
newObject.PushBackList(additions.MatchingNodes)
|
||||
newObject.PushBackList(additions)
|
||||
}
|
||||
|
||||
return context.ChildContext(newObject), nil
|
||||
return newObject, nil
|
||||
|
||||
}
|
||||
|
||||
func collect(d *dataTreeNavigator, context Context, remainingMatches *list.List) (Context, error) {
|
||||
func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode {
|
||||
return &CandidateNode{
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
Filename: candidate.Filename,
|
||||
Node: candidate.Node.Content[index],
|
||||
}
|
||||
}
|
||||
|
||||
func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) {
|
||||
if remainingMatches.Len() == 0 {
|
||||
return context, nil
|
||||
return aggregate, nil
|
||||
}
|
||||
|
||||
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
|
||||
splatted, err := Splat(d, nodeToMap(candidate))
|
||||
|
||||
splatted, err := splat(d, context.SingleChildContext(candidate),
|
||||
traversePreferences{DontFollowAlias: true, IncludeMapKeys: false})
|
||||
|
||||
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
splatEl.Value.(*CandidateNode).Path = nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if context.MatchingNodes.Len() == 0 {
|
||||
if aggregate.Len() == 0 {
|
||||
return collect(d, splatted, remainingMatches)
|
||||
}
|
||||
|
||||
newAgg := list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := aggregate.Front(); el != nil; el = el.Next() {
|
||||
aggCandidate := el.Value.(*CandidateNode)
|
||||
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
splatCandidate := splatEl.Value.(*CandidateNode)
|
||||
newCandidate, err := aggCandidate.Copy()
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newCandidate.Path = nil
|
||||
|
||||
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, context, newCandidate, splatCandidate)
|
||||
newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
newAgg.PushBack(newCandidate)
|
||||
}
|
||||
}
|
||||
return collect(d, context.ChildContext(newAgg), remainingMatches)
|
||||
return collect(d, newAgg, remainingMatches)
|
||||
|
||||
}
|
||||
|
||||
@@ -110,5 +110,5 @@ func TestCollectObjectOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range collectObjectOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Create, Collect into Object", collectObjectOperatorScenarios)
|
||||
documentScenarios(t, "Collect into Object", collectObjectOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -13,15 +13,6 @@ var collectOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "{a: apple}\n---\n{b: frog}",
|
||||
|
||||
expression: `[.]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- {a: apple}\n- {b: frog}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: ``,
|
||||
|
||||
@@ -4,53 +4,38 @@ import (
|
||||
"container/list"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type commentOpPreferences struct {
|
||||
type CommentOpPreferences struct {
|
||||
LineComment bool
|
||||
HeadComment bool
|
||||
FootComment bool
|
||||
}
|
||||
|
||||
func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignComments operator!")
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comment := ""
|
||||
if rhs.Front() != nil {
|
||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||
|
||||
comment := ""
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Setting comment of : %v", candidate.GetKey())
|
||||
if preferences.LineComment {
|
||||
candidate.Node.LineComment = comment
|
||||
@@ -63,15 +48,15 @@ func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNod
|
||||
}
|
||||
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
||||
func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||
log.Debugf("GetComments operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
comment := ""
|
||||
if preferences.LineComment {
|
||||
@@ -84,8 +69,8 @@ func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *
|
||||
comment = strings.Replace(comment, "# ", "", 1)
|
||||
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -13,39 +13,6 @@ var commentOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::a: cat # single\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
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`,
|
||||
@@ -71,12 +38,11 @@ var commentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Remove all comments",
|
||||
subdescription: "Note the use of `...` to ensure key nodes are included.",
|
||||
document: "# hi\n\na: cat # comment\n\n# great\n\nb: # key comment",
|
||||
expression: `... comments=""`,
|
||||
description: "Remove all comments",
|
||||
document: "# hi\n\na: cat # comment\n\n# great\n",
|
||||
expression: `.. comments=""`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat\nb:\n",
|
||||
"D0, P[], (!!map)::a: cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- createMapOperation")
|
||||
|
||||
//each matchingNodes entry should turn into a sequence of keys to create.
|
||||
@@ -18,29 +18,29 @@ func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
|
||||
|
||||
sequences := list.New()
|
||||
|
||||
if context.MatchingNodes.Len() > 0 {
|
||||
if matchingNodes.Len() > 0 {
|
||||
|
||||
for matchingNodeEl := context.MatchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
|
||||
for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
|
||||
matchingNode := matchingNodeEl.Value.(*CandidateNode)
|
||||
sequenceNode, err := sequenceFor(d, context, matchingNode, expressionNode)
|
||||
sequenceNode, err := sequenceFor(d, matchingNode, pathNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
sequences.PushBack(sequenceNode)
|
||||
}
|
||||
} else {
|
||||
sequenceNode, err := sequenceFor(d, context, nil, expressionNode)
|
||||
sequenceNode, err := sequenceFor(d, nil, pathNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
sequences.PushBack(sequenceNode)
|
||||
}
|
||||
|
||||
return context.SingleChildContext(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
|
||||
return nodeToMap(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
|
||||
|
||||
}
|
||||
|
||||
func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, expressionNode *ExpressionNode) (*CandidateNode, error) {
|
||||
func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathTreeNode) (*CandidateNode, error) {
|
||||
var path []interface{}
|
||||
var document uint = 0
|
||||
var matches = list.New()
|
||||
@@ -48,17 +48,17 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
|
||||
if matchingNode != nil {
|
||||
path = matchingNode.Path
|
||||
document = matchingNode.Document
|
||||
matches.PushBack(matchingNode)
|
||||
matches = nodeToMap(matchingNode)
|
||||
}
|
||||
|
||||
mapPairs, err := crossFunction(d, context.ChildContext(matches), expressionNode,
|
||||
func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
mapPairs, err := crossFunction(d, matches, pathNode,
|
||||
func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||
log.Debugf("LHS:", NodeToString(lhs))
|
||||
log.Debugf("RHS:", NodeToString(rhs))
|
||||
node.Content = []*yaml.Node{
|
||||
unwrapDoc(lhs.Node),
|
||||
unwrapDoc(rhs.Node),
|
||||
UnwrapDoc(lhs.Node),
|
||||
UnwrapDoc(rhs.Node),
|
||||
}
|
||||
|
||||
return &CandidateNode{Node: &node, Document: document, Path: path}, nil
|
||||
@@ -67,7 +67,7 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
innerList := listToNodeSeq(mapPairs.MatchingNodes)
|
||||
innerList := listToNodeSeq(mapPairs)
|
||||
innerList.Style = yaml.FlowStyle
|
||||
return &CandidateNode{Node: innerList, Document: document, Path: path}, nil
|
||||
}
|
||||
|
||||
@@ -1,69 +1,42 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
// for each lhs, splat the node,
|
||||
// the intersect it against the rhs expression
|
||||
// recreate the contents using only the intersection result.
|
||||
|
||||
nodesToDelete, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
//need to iterate backwards to ensure correct indices when deleting multiple
|
||||
for el := nodesToDelete.MatchingNodes.Back(); el != nil; el = el.Prev() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
deleteImmediateChildOp := &Operation{
|
||||
OperationType: deleteImmediateChildOpType,
|
||||
Value: candidate.Path[len(candidate.Path)-1],
|
||||
}
|
||||
|
||||
deleteImmediateChildOpNode := &ExpressionNode{
|
||||
Operation: deleteImmediateChildOp,
|
||||
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}),
|
||||
}
|
||||
|
||||
_, err := d.GetMatchingNodes(context, deleteImmediateChildOpNode)
|
||||
elMap := list.New()
|
||||
elMap.PushBack(candidate)
|
||||
nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs)
|
||||
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func deleteImmediateChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
parents, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
realNode := UnwrapDoc(candidate.Node)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
childPath := expressionNode.Operation.Value
|
||||
|
||||
log.Debug("childPath to remove %v", childPath)
|
||||
|
||||
for el := parents.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
parent := el.Value.(*CandidateNode)
|
||||
parentNode := unwrapDoc(parent.Node)
|
||||
if parentNode.Kind == yaml.MappingNode {
|
||||
deleteFromMap(parent, childPath)
|
||||
} else if parentNode.Kind == yaml.SequenceNode {
|
||||
deleteFromArray(parent, childPath)
|
||||
if realNode.Kind == yaml.SequenceNode {
|
||||
deleteFromArray(candidate, nodesToDelete)
|
||||
} else if realNode.Kind == yaml.MappingNode {
|
||||
deleteFromMap(candidate, nodesToDelete)
|
||||
} else {
|
||||
return Context{}, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
|
||||
log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate))
|
||||
}
|
||||
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
||||
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
log.Debug("deleteFromMap")
|
||||
node := unwrapDoc(candidate.Node)
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
contents := node.Content
|
||||
newContents := make([]*yaml.Node, 0)
|
||||
|
||||
@@ -71,9 +44,18 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
||||
key := contents[index]
|
||||
value := contents[index+1]
|
||||
|
||||
childCandidate := candidate.CreateChild(key.Value, value)
|
||||
childCandidate := &CandidateNode{
|
||||
Node: value,
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, key.Value),
|
||||
}
|
||||
|
||||
shouldDelete := key.Value == childPath
|
||||
shouldDelete := false
|
||||
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
|
||||
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
|
||||
shouldDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
|
||||
|
||||
@@ -84,16 +66,27 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
||||
node.Content = newContents
|
||||
}
|
||||
|
||||
func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
|
||||
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||
log.Debug("deleteFromArray")
|
||||
node := unwrapDoc(candidate.Node)
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
contents := node.Content
|
||||
newContents := make([]*yaml.Node, 0)
|
||||
|
||||
for index := 0; index < len(contents); index = index + 1 {
|
||||
value := contents[index]
|
||||
|
||||
shouldDelete := fmt.Sprintf("%v", index) == fmt.Sprintf("%v", childPath)
|
||||
childCandidate := &CandidateNode{
|
||||
Node: value,
|
||||
Document: candidate.Document,
|
||||
Path: append(candidate.Path, index),
|
||||
}
|
||||
|
||||
shouldDelete := false
|
||||
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
|
||||
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
|
||||
shouldDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
if !shouldDelete {
|
||||
newContents = append(newContents, value)
|
||||
|
||||
@@ -13,22 +13,6 @@ 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]`,
|
||||
@@ -37,30 +21,6 @@ var deleteOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::[1, 3]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: [1,2,3]`,
|
||||
expression: `del(.a[])`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: []\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: [10,x,10, 10, x, 10]`,
|
||||
expression: `del(.a[] | select(. == 10))`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: [x, x]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
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}`,
|
||||
@@ -77,14 +37,6 @@ 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) {
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func getDocumentIndexOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func GetDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"}
|
||||
scalar := candidate.CreateChild(nil, node)
|
||||
scalar := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(scalar)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -14,27 +14,10 @@ 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)`,
|
||||
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)`,
|
||||
expression: `select(. | documentIndex == 1)`,
|
||||
expected: []string{
|
||||
"D1, P[], (doc)::a: frog\n",
|
||||
},
|
||||
@@ -42,7 +25,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",
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type envOpPreferences struct {
|
||||
StringValue bool
|
||||
}
|
||||
|
||||
func envOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
envName := expressionNode.Operation.CandidateNode.Node.Value
|
||||
log.Debug("EnvOperator, env name:", envName)
|
||||
|
||||
rawValue := os.Getenv(envName)
|
||||
|
||||
preferences := expressionNode.Operation.Preferences.(envOpPreferences)
|
||||
|
||||
var node *yaml.Node
|
||||
if preferences.StringValue {
|
||||
node = &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: rawValue,
|
||||
}
|
||||
} else if rawValue == "" {
|
||||
return Context{}, fmt.Errorf("Value for env variable '%v' not provided in env()", envName)
|
||||
} else {
|
||||
var dataBucket yaml.Node
|
||||
decoder := yaml.NewDecoder(strings.NewReader(rawValue))
|
||||
errorReading := decoder.Decode(&dataBucket)
|
||||
if errorReading != nil {
|
||||
return Context{}, errorReading
|
||||
}
|
||||
//first node is a doc
|
||||
node = unwrapDoc(&dataBucket)
|
||||
}
|
||||
log.Debug("ENV tag", node.Tag)
|
||||
log.Debug("ENV value", node.Value)
|
||||
log.Debug("ENV Kind", node.Kind)
|
||||
|
||||
target := &CandidateNode{Node: node}
|
||||
|
||||
return context.SingleChildContext(target), nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var envOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Read string environment variable",
|
||||
environmentVariable: "cat meow",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: cat meow\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read boolean environment variable",
|
||||
environmentVariable: "true",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read numeric environment variable",
|
||||
environmentVariable: "12",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: 12\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read yaml environment variable",
|
||||
environmentVariable: "{b: fish}",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: {b: fish}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read boolean environment variable as a string",
|
||||
environmentVariable: "true",
|
||||
expression: `.a = strenv(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: \"true\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read numeric environment variable as a string",
|
||||
environmentVariable: "12",
|
||||
expression: `.a = strenv(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: \"12\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Dynamic key lookup with environment variable",
|
||||
environmentVariable: "cat",
|
||||
document: `{cat: meow, dog: woof}`,
|
||||
expression: `.[env(myenv)]`,
|
||||
expected: []string{
|
||||
"D0, P[cat], (!!str)::meow\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEnvOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range envOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Env Variable Operators", envOperatorScenarios)
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
package yqlib
|
||||
|
||||
func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
func EqualsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- equalsOperation")
|
||||
return crossFunction(d, context, expressionNode, isEquals)
|
||||
return crossFunction(d, matchingNodes, pathNode, isEquals)
|
||||
}
|
||||
|
||||
func isEquals(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
value := false
|
||||
|
||||
lhsNode := unwrapDoc(lhs.Node)
|
||||
rhsNode := unwrapDoc(rhs.Node)
|
||||
|
||||
if lhsNode.Tag == "!!null" {
|
||||
value = (rhsNode.Tag == "!!null")
|
||||
if lhs.Node.Tag == "!!null" {
|
||||
value = (rhs.Node.Tag == "!!null")
|
||||
} else {
|
||||
value = matchKey(lhsNode.Value, rhsNode.Value)
|
||||
value = Match(lhs.Node.Value, rhs.Node.Value)
|
||||
}
|
||||
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
|
||||
return createBooleanCandidate(lhs, value), nil
|
||||
|
||||
@@ -5,15 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var equalsOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "cat",
|
||||
document2: "dog",
|
||||
expression: "select(fi==0) == select(fi==1)",
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Match string",
|
||||
document: `[cat,goat,dog]`,
|
||||
|
||||
151
pkg/yqlib/operator_explode.go
Normal file
151
pkg/yqlib/operator_explode.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- ExplodeOperation")
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
|
||||
err = explodeNode(childEl.Value.(*CandidateNode).Node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return matchMap, nil
|
||||
}
|
||||
|
||||
func explodeNode(node *yaml.Node) error {
|
||||
node.Anchor = ""
|
||||
switch node.Kind {
|
||||
case yaml.SequenceNode, yaml.DocumentNode:
|
||||
for index, contentNode := range node.Content {
|
||||
log.Debugf("exploding index %v", index)
|
||||
errorInContent := explodeNode(contentNode)
|
||||
if errorInContent != nil {
|
||||
return errorInContent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case yaml.AliasNode:
|
||||
log.Debugf("its an alias!")
|
||||
if node.Alias != nil {
|
||||
node.Kind = node.Alias.Kind
|
||||
node.Style = node.Alias.Style
|
||||
node.Tag = node.Alias.Tag
|
||||
node.Content = node.Alias.Content
|
||||
node.Value = node.Alias.Value
|
||||
node.Alias = nil
|
||||
}
|
||||
return nil
|
||||
case yaml.MappingNode:
|
||||
var newContent = list.New()
|
||||
for index := 0; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
valueNode := node.Content[index+1]
|
||||
log.Debugf("traversing %v", keyNode.Value)
|
||||
if keyNode.Value != "<<" {
|
||||
err := overrideEntry(node, keyNode, valueNode, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if valueNode.Kind == yaml.SequenceNode {
|
||||
log.Debugf("an alias merge list!")
|
||||
for index := 0; index < len(valueNode.Content); index = index + 1 {
|
||||
aliasNode := valueNode.Content[index]
|
||||
err := applyAlias(node, aliasNode.Alias, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debugf("an alias merge!")
|
||||
err := applyAlias(node, valueNode.Alias, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
node.Content = make([]*yaml.Node, newContent.Len())
|
||||
index := 0
|
||||
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
||||
node.Content[index] = newEl.Value.(*yaml.Node)
|
||||
index++
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent *list.List) error {
|
||||
if alias == nil {
|
||||
return nil
|
||||
}
|
||||
for index := 0; index < len(alias.Content); index = index + 2 {
|
||||
keyNode := alias.Content[index]
|
||||
log.Debugf("applying alias key %v", keyNode.Value)
|
||||
valueNode := alias.Content[index+1]
|
||||
err := overrideEntry(node, keyNode, valueNode, aliasIndex, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent *list.List) error {
|
||||
|
||||
err := explodeNode(value)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
||||
valueEl := newEl.Next() // move forward twice
|
||||
keyNode := newEl.Value.(*yaml.Node)
|
||||
log.Debugf("checking new content %v:%v", keyNode.Value, valueEl.Value.(*yaml.Node).Value)
|
||||
if keyNode.Value == key.Value && keyNode.Alias == nil && key.Alias == nil {
|
||||
log.Debugf("overridign new content")
|
||||
valueEl.Value = value
|
||||
return nil
|
||||
}
|
||||
newEl = valueEl // move forward twice
|
||||
}
|
||||
|
||||
for index := startIndex + 2; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
|
||||
if keyNode.Value == key.Value && keyNode.Alias == nil {
|
||||
log.Debugf("content will be overridden at index %v", index)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err = explodeNode(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("adding %v:%v", key.Value, value.Value)
|
||||
newContent.PushBack(key)
|
||||
newContent.PushBack(value)
|
||||
return nil
|
||||
}
|
||||
@@ -4,55 +4,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
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",
|
||||
},
|
||||
},
|
||||
var explodeTest = []expressionScenario{
|
||||
{
|
||||
description: "Explode alias and anchor",
|
||||
document: `{f : {a: &a cat, b: *a}}`,
|
||||
@@ -130,9 +82,9 @@ foobar:
|
||||
},
|
||||
}
|
||||
|
||||
func TestAnchorAliaseOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range anchorOperatorScenarios {
|
||||
func TestExplodeOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range explodeTest {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Anchor and Alias Operators", anchorOperatorScenarios)
|
||||
documentScenarios(t, "Explode", explodeTest)
|
||||
}
|
||||
@@ -7,32 +7,32 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func getFilenameOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetFilename")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func getFileIndexOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetFileIndex")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -21,22 +21,6 @@ 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",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "a: cat\nb: dog",
|
||||
expression: `.. lineComment |= filename`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat # sample.yml\nb: dog # sample.yml\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestFileOperatorsScenarios(t *testing.T) {
|
||||
|
||||
@@ -7,26 +7,25 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func hasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("-- hasOperation")
|
||||
var results = list.New()
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
wanted := rhs.MatchingNodes.Front().Value.(*CandidateNode).Node
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
wanted := rhs.Front().Value.(*CandidateNode).Node
|
||||
wantedKey := wanted.Value
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
// grab the first value
|
||||
candidateNode := unwrapDoc(candidate.Node)
|
||||
var contents = candidateNode.Content
|
||||
switch candidateNode.Kind {
|
||||
var contents = candidate.Node.Content
|
||||
switch candidate.Node.Kind {
|
||||
case yaml.MappingNode:
|
||||
candidateHasKey := false
|
||||
for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 {
|
||||
@@ -41,7 +40,7 @@ func hasOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
|
||||
if wanted.Tag == "!!int" {
|
||||
var number, errParsingInt = strconv.ParseInt(wantedKey, 10, 64) // nolint
|
||||
if errParsingInt != nil {
|
||||
return Context{}, errParsingInt
|
||||
return nil, errParsingInt
|
||||
}
|
||||
candidateHasKey = int64(len(contents)) > number
|
||||
}
|
||||
@@ -50,5 +49,5 @@ func hasOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
|
||||
results.PushBack(createBooleanCandidate(candidate, false))
|
||||
}
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -5,14 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var hasOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: hello`,
|
||||
expression: `has("a")`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Has map key",
|
||||
document: `- a: "yes"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user