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

Compare commits

..

1 Commits

Author SHA1 Message Date
Mike Farah
12edcafd8c Bumping version to beta2 2020-12-09 12:16:16 +11:00
139 changed files with 1651 additions and 4393 deletions

View File

@@ -1,8 +1,8 @@
--- ---
name: Bug report - V3 name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ''
labels: bug, v3 labels: bug
assignees: '' assignees: ''
--- ---
@@ -10,14 +10,11 @@ assignees: ''
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. 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:
operating system:
Version of yq: 3.X.X
Operating system: mac/linux/windows/....
Installed via: docker/binary release/homebrew/snap/...
**Input Yaml** **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: data1.yml:
```yaml ```yaml
this: should really work this: should really work

View File

@@ -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.

View File

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

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ RUN CGO_ENABLED=0 make local build
# Choose alpine as a base image to make this useful for CI, as many # Choose alpine as a base image to make this useful for CI, as many
# CI tools expect an interactive shell inside the container # 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 COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
RUN chmod +x /usr/bin/yq RUN chmod +x /usr/bin/yq

View File

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

136
README.md
View File

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

View File

@@ -14,6 +14,5 @@ var noDocSeparators = false
var nullInput = false var nullInput = false
var verbose = false var verbose = false
var version = false var version = false
var prettyPrint = false
var completedSuccessfully = false var completedSuccessfully = false

View File

@@ -59,29 +59,25 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }() 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) printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator() allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
switch len(args) { switch len(args) {
case 0: case 0:
if pipingStdIn { if pipingStdIn {
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer) err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
} else { } else {
cmd.Println(cmd.UsageString()) cmd.Println(cmd.UsageString())
return nil return nil
} }
case 1: case 1:
if nullInput { if nullInput {
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer) err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
} else { } else {
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer) err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
} }
default: default:
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer) err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
} }
completedSuccessfully = err == nil completedSuccessfully = err == nil

View File

@@ -33,16 +33,6 @@ yq e '.a.b = "cool"' -i file.yaml
} }
return cmdEvalSequence 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 { func evaluateSequence(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true cmd.SilenceUsage = true
// 0 args, read std in // 0 args, read std in
@@ -83,23 +73,19 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
streamEvaluator := yqlib.NewStreamEvaluator() streamEvaluator := yqlib.NewStreamEvaluator()
if nullInput && len(args) > 1 {
return errors.New("Cannot pass files in when using null-input flag")
}
switch len(args) { switch len(args) {
case 0: case 0:
if pipingStdIn { if pipingStdIn {
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer) err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
} else { } else {
cmd.Println(cmd.UsageString()) cmd.Println(cmd.UsageString())
return nil return nil
} }
case 1: case 1:
if nullInput { if nullInput {
err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer) err = streamEvaluator.EvaluateNew(args[0], printer)
} else { } else {
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer) err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
} }
default: default:
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer) err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)

View File

@@ -47,8 +47,6 @@ func New() *cobra.Command {
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") 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.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.") rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the 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(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string GitDescribe string
// Version is main version number that is being run at the moment. // Version is main version number that is being run at the moment.
Version = "4.4.1" Version = "4.0.0-beta2"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // 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 // then it means that it is a final release. Otherwise, this is a pre-release

View File

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

12
go.mod
View File

@@ -2,15 +2,17 @@ module github.com/mikefarah/yq/v4
require ( require (
github.com/elliotchance/orderedmap v1.3.0 github.com/elliotchance/orderedmap v1.3.0
github.com/fatih/color v1.10.0 github.com/fatih/color v1.9.0
github.com/goccy/go-yaml v1.8.4 github.com/goccy/go-yaml v1.8.1
github.com/jinzhu/copier v0.1.0 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/spf13/cobra v1.1.1
github.com/timtadh/data-structures v0.5.3 // indirect github.com/timtadh/data-structures v0.5.3 // indirect
github.com/timtadh/lexmachine v0.2.2 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/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 go 1.15

44
go.sum
View File

@@ -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 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw= 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.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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-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-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.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 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/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/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/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.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= 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.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/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/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 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 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.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= 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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/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/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.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.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.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 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.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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/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-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-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-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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-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-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-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-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-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-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-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-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-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-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-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-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-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/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-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-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-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/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-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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/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/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 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= 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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 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.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-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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-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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -1,50 +1,29 @@
package yqlib package yqlib
import ( import "container/list"
"container/list"
yaml "gopkg.in/yaml.v3" /**
) Loads all yaml documents of all files given into memory, then runs the given expression once.
**/
// A yaml expression evaluator that runs the expression once against all files/nodes in memory.
type Evaluator interface { type Evaluator interface {
EvaluateFiles(expression string, filenames []string, printer Printer) error EvaluateFiles(expression string, filenames []string, printer Printer) error
// EvaluateNodes takes an expression and one or more yaml nodes, returning a list of matching candidate nodes
EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error)
// 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 { type allAtOnceEvaluator struct {
treeNavigator DataTreeNavigator treeNavigator DataTreeNavigator
treeCreator ExpressionParser treeCreator PathTreeCreator
} }
func NewAllAtOnceEvaluator() Evaluator { func NewAllAtOnceEvaluator() Evaluator {
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewExpressionParser()} return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
}
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
}
return e.treeNavigator.GetMatchingNodes(inputCandidates, node)
} }
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error { func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
fileIndex := 0 fileIndex := 0
node, err := treeCreator.ParsePath(expression)
if err != nil {
return err
}
var allDocuments *list.List = list.New() var allDocuments *list.List = list.New()
for _, filename := range filenames { for _, filename := range filenames {
reader, err := readStream(filename) reader, err := readStream(filename)
@@ -58,7 +37,7 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
allDocuments.PushBackList(fileDocuments) allDocuments.PushBackList(fileDocuments)
fileIndex = fileIndex + 1 fileIndex = fileIndex + 1
} }
matches, err := e.EvaluateCandidateNodes(expression, allDocuments) matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -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))
}
}

View File

@@ -15,39 +15,12 @@ type CandidateNode struct {
Document uint // the document index of this node Document uint // the document index of this node
Filename string Filename string
FileIndex int 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 { func (n *CandidateNode) GetKey() string {
return fmt.Sprintf("%v - %v", n.Document, n.Path) 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) { func (n *CandidateNode) Copy() (*CandidateNode, error) {
clone := &CandidateNode{} clone := &CandidateNode{}
err := copier.Copy(clone, n) err := copier.Copy(clone, n)
@@ -59,12 +32,10 @@ func (n *CandidateNode) Copy() (*CandidateNode, error) {
// updates this candidate from the given candidate node // updates this candidate from the given candidate node
func (n *CandidateNode) UpdateFrom(other *CandidateNode) { func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
n.UpdateAttributesFrom(other) n.UpdateAttributesFrom(other)
n.Node.Content = other.Node.Content n.Node.Content = other.Node.Content
n.Node.Value = other.Node.Value n.Node.Value = other.Node.Value
n.Node.Alias = other.Node.Alias n.Node.Alias = other.Node.Alias
n.Node.Anchor = other.Node.Anchor
} }
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {

View File

@@ -17,8 +17,8 @@ func format(attr color.Attribute) string {
return fmt.Sprintf("%s[%dm", escape, attr) return fmt.Sprintf("%s[%dm", escape, attr)
} }
func colorizeAndPrint(yamlBytes []byte, writer io.Writer) error { func ColorizeAndPrint(bytes []byte, writer io.Writer) error {
tokens := lexer.Tokenize(string(yamlBytes)) tokens := lexer.Tokenize(string(bytes))
var p printer.Printer var p printer.Printer
p.Bool = func() *printer.Property { p.Bool = func() *printer.Property {
return &printer.Property{ return &printer.Property{

View File

@@ -9,10 +9,10 @@ import (
) )
type DataTreeNavigator interface { type DataTreeNavigator interface {
// given a list of CandidateEntities and a expressionNode, // given a list of CandidateEntities and a pathNode,
// this will process the list against the given expressionNode and return // this will process the list against the given pathNode and return
// a new list of matching candidates // a new list of matching candidates
GetMatchingNodes(matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
} }
type dataTreeNavigator struct { type dataTreeNavigator struct {
@@ -22,22 +22,22 @@ func NewDataTreeNavigator() DataTreeNavigator {
return &dataTreeNavigator{} return &dataTreeNavigator{}
} }
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
if expressionNode == nil { if pathNode == nil {
log.Debugf("getMatchingNodes - nothing to do") log.Debugf("getMatchingNodes - nothing to do")
return matchingNodes, 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) { if log.IsEnabledFor(logging.DEBUG) {
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
log.Debug(NodeToString(el.Value.(*CandidateNode))) log.Debug(NodeToString(el.Value.(*CandidateNode)))
} }
} }
log.Debug(">>") log.Debug(">>")
handler := expressionNode.Operation.OperationType.Handler handler := pathNode.Operation.OperationType.Handler
if handler != nil { if handler != nil {
return handler(d, matchingNodes, expressionNode) return handler(d, matchingNodes, pathNode)
} }
return nil, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType) return nil, fmt.Errorf("Unknown operator %v", pathNode.Operation.OperationType)
} }

View File

@@ -1,9 +1,9 @@
Add behaves differently according to the type of the LHS: Add behaves differently according to the type of the LHS:
- arrays: concatenate - arrays: concatenate
- number scalars: arithmetic addition - number scalars: arithmetic addition (soon)
- string scalars: concatenate - 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 ## Concatenate and assign arrays
Given a sample.yml file of: Given a sample.yml file of:
@@ -67,19 +67,23 @@ will output
- 2 - 2
``` ```
## Add new object to array ## Add object to array
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: a:
- dog: woof - 1
- 2
c:
cat: meow
``` ```
then then
```bash ```bash
yq eval '.a + {"cat": "meow"}' sample.yml yq eval '.a + .c' sample.yml
``` ```
will output will output
```yaml ```yaml
- dog: woof - 1
- 2
- cat: meow - cat: meow
``` ```
@@ -127,97 +131,3 @@ b:
- 4 - 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
```

View File

@@ -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
```

View File

@@ -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
```

View File

@@ -34,27 +34,6 @@ a:
g: foof 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 ## Update node to be the sibling value
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -133,7 +112,7 @@ a:
``` ```
then then
```bash ```bash
yq eval '(.a[] | select(. == "apple")) = "frog"' sample.yml yq eval '(.a.[] | select(. == "apple")) = "frog"' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@@ -13,22 +13,6 @@ will output
a: cat # single 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 ## Set head comment
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -78,22 +62,17 @@ b: dog # leave this
``` ```
## Remove all comments ## Remove all comments
Note the use of `...` to ensure key nodes are included.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat # comment a: cat # comment
# great
b: # key comment
``` ```
then then
```bash ```bash
yq eval '... comments=""' sample.yml yq eval '.. comments=""' sample.yml
``` ```
will output will output
```yaml ```yaml
a: cat a: cat
b:
``` ```
## Get line comment ## Get line comment

View File

@@ -14,23 +14,6 @@ will output
a: cat 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 ## Delete entry in array
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -48,21 +31,6 @@ will output
- 3 - 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 ## Delete no matches
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -95,23 +63,3 @@ will output
b: dog b: dog
``` ```
## Recursively delete matching keys
Given a sample.yml file of:
```yaml
a:
name: frog
b:
name: blog
age: 12
```
then
```bash
yq eval 'del(.. | select(has("name")).name)' sample.yml
```
will output
```yaml
a:
b:
age: 12
```

View File

@@ -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 ## Retrieve a document index
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -17,24 +17,6 @@ will output
1 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 ## Filter by document index
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -44,23 +26,7 @@ a: frog
``` ```
then then
```bash ```bash
yq eval 'select(documentIndex == 1)' sample.yml yq eval 'select(. | documentIndex == 1)' sample.yml
```
will output
```yaml
a: frog
```
## Filter by document index shorthand
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval 'select(di == 1)' sample.yml
``` ```
will output will output
```yaml ```yaml
@@ -76,7 +42,7 @@ a: frog
``` ```
then then
```bash ```bash
yq eval '.a | ({"match": ., "doc": documentIndex})' sample.yml yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@@ -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
View 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
```

View File

@@ -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). 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 ## 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 ```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 ## Get filename
Given a sample.yml file of: Given a sample.yml file of:
@@ -18,7 +16,7 @@ yq eval 'filename' sample.yml
``` ```
will output will output
```yaml ```yaml
sample.yml sample.yaml
``` ```
## Get file index ## Get file index
@@ -35,17 +33,3 @@ will output
0 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
```

View File

@@ -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
```

View File

@@ -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. 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 ## 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 ```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
@@ -112,26 +111,6 @@ b:
- 5 - 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 ## Merge, appending arrays
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -164,33 +143,6 @@ array:
value: banana 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 ## Merge to prefix an element
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -228,7 +180,7 @@ g: thongs
f: *cat f: *cat
``` ```
## Merge copies anchor names ## Merge does not copy anchor names
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: a:
@@ -245,7 +197,7 @@ yq eval '.c * .a' sample.yml
will output will output
```yaml ```yaml
g: thongs g: thongs
c: &cat frog c: frog
``` ```
## Merge with merge anchors ## Merge with merge anchors

View File

@@ -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
```

View 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
```

View File

@@ -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
```

View File

@@ -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
```

View File

@@ -40,26 +40,6 @@ c: "3.2"
e: "true" 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 ## Set single quote style
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -145,19 +125,19 @@ will output
{a: cat, b: 5, c: 3.2, e: true} {a: cat, b: 5, c: 3.2, e: true}
``` ```
## Reset style - or pretty print ## 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. Set empty (default) quote style
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat a: cat
"b": 5 b: 5
'c': 3.2 c: 3.2
"e": true e: true
``` ```
then then
```bash ```bash
yq eval '... style=""' sample.yml yq eval '.. style=""' sample.yml
``` ```
will output will output
```yaml ```yaml
@@ -167,22 +147,6 @@ c: 3.2
e: true 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 ## Read style
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

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

View File

@@ -48,24 +48,6 @@ will output
frog 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 ## Children don't exist
Nodes are added dynamically while traversing Nodes are added dynamically while traversing
@@ -124,7 +106,7 @@ b: *cat
``` ```
then then
```bash ```bash
yq eval '.b[]' sample.yml yq eval '.b.[]' sample.yml
``` ```
will output will output
```yaml ```yaml
@@ -308,7 +290,7 @@ foobar:
``` ```
then then
```bash ```bash
yq eval '.foobar[]' sample.yml yq eval '.foobar.[]' sample.yml
``` ```
will output will output
```yaml ```yaml
@@ -374,7 +356,7 @@ foobar:
``` ```
then then
```bash ```bash
yq eval '.foobarList[]' sample.yml yq eval '.foobarList.[]' sample.yml
``` ```
will output will output
```yaml ```yaml
@@ -384,21 +366,3 @@ bar_thing
foobarList_c 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
```

View File

@@ -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.

View File

@@ -1,6 +1,6 @@
Add behaves differently according to the type of the LHS: Add behaves differently according to the type of the LHS:
- arrays: concatenate - arrays: concatenate
- number scalars: arithmetic addition - number scalars: arithmetic addition (soon)
- string scalars: concatenate - 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`.

View File

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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

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

View File

@@ -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). 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 ## 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 ```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
``` ```

View File

@@ -1,3 +0,0 @@
# Keys
Use the `keys` operator to return map keys or array indices.

View File

@@ -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. 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 ## 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 ```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml

View File

@@ -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
```

View 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
```

View File

@@ -1,3 +0,0 @@
# Split into Documents
This operator splits all matches into separate documents

View File

@@ -1 +0,0 @@
# String Operators

View File

@@ -50,7 +50,7 @@ func (ye *yamlEncoder) Encode(node *yaml.Node) error {
} }
if ye.colorise { if ye.colorise {
return colorizeAndPrint(tempBuffer.Bytes(), ye.destination) return ColorizeAndPrint(tempBuffer.Bytes(), ye.destination)
} }
return nil return nil
} }
@@ -76,8 +76,6 @@ func mapKeysToStrings(node *yaml.Node) {
func NewJsonEncoder(destination io.Writer, indent int) Encoder { func NewJsonEncoder(destination io.Writer, indent int) Encoder {
var encoder = json.NewEncoder(destination) var encoder = json.NewEncoder(destination)
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
var indentString = "" var indentString = ""
for index := 0; index < indent; index++ { for index := 0; index < indent; index++ {
@@ -155,15 +153,11 @@ func (o *orderedMap) UnmarshalJSON(data []byte) error {
} }
func (o orderedMap) MarshalJSON() ([]byte, error) { func (o orderedMap) MarshalJSON() ([]byte, error) {
if o.kv == nil {
return json.Marshal(o.altVal)
}
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
enc := json.NewEncoder(buf) 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('{') buf.WriteByte('{')
for idx, el := range o.kv { for idx, el := range o.kv {
if err := enc.Encode(el.K); err != nil { if err := enc.Encode(el.K); err != nil {

View File

@@ -9,11 +9,29 @@ import (
"github.com/mikefarah/yq/v4/test" "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 var output bytes.Buffer
writer := bufio.NewWriter(&output) writer := bufio.NewWriter(&output)
var jsonEncoder = NewJsonEncoder(writer, indent) var jsonEncoder = NewJsonEncoder(writer, 2)
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0) inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -24,33 +42,6 @@ func yamlToJson(sampleYaml string, indent int) string {
panic(err) panic(err)
} }
writer.Flush() 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)
} }

View File

@@ -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("expected end of expression but found '%v', please check expression syntax", strings.TrimSpace(stack[1].Operation.StringValue))
}
return stack[0], nil
}

View File

@@ -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, "expected end of expression but found 'explode', please check expression syntax", err.Error())
}

View File

@@ -1,412 +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() string {
if t.TokenType == operationToken {
log.Debug("toString, its an op")
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 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))
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())
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
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.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: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
op = &Operation{OperationType: traverseArrayOpType}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == traverseArrayCollect {
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
return postProcessedTokens, skipNextToken
}

View File

@@ -1,5 +1,3 @@
// Use the top level Evaluator or StreamEvaluator to evaluate expressions and return matches.
//
package yqlib package yqlib
import ( import (
@@ -13,89 +11,77 @@ import (
var log = logging.MustGetLogger("yq-lib") var log = logging.MustGetLogger("yq-lib")
type operationType struct { type OperationType struct {
Type string Type string
NumArgs uint // number of arguments to the op NumArgs uint // number of arguments to the op
Precedence uint Precedence uint
Handler operatorHandler Handler OperatorHandler
} }
// operators TODO: // operators TODO:
// - cookbook doc for common things
// - mergeEmpty (sets only if the document is empty, do I do that now?) // - 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 Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator} 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 Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator} 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 AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator} var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator} var AssignComment = &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 multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator} var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator} 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 Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator} var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator} var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator} var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator} var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator} var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator} var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
var getAliasOptype = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: getAliasOperator} var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
var getDocumentIndexOpType = &operationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: getDocumentIndexOperator} var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
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 explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator} var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator} var SortKeys = &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 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 DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator} var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator} 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: 50, Handler: selfOperator} var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
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 recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator} var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
var selectOpType = &operationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: selectOperator} var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
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}
type Operation struct { type Operation struct {
OperationType *operationType OperationType *OperationType
Value interface{} Value interface{}
StringValue string StringValue string
CandidateNode *CandidateNode // used for Value Path elements CandidateNode *CandidateNode // used for Value Path elements
Preferences interface{} 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} var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
node.Value = stringValue node.Value = stringValue
@@ -113,7 +99,7 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
} }
return &Operation{ return &Operation{
OperationType: valueOpType, OperationType: ValueOp,
Value: value, Value: value,
StringValue: stringValue, StringValue: stringValue,
CandidateNode: &CandidateNode{Node: &node}, CandidateNode: &CandidateNode{Node: &node},
@@ -122,11 +108,13 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
// debugging purposes only // debugging purposes only
func (p *Operation) toString() string { func (p *Operation) toString() string {
if p.OperationType == traversePathOpType { if p.OperationType == TraversePath {
return fmt.Sprintf("%v", p.Value) 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" return "SELF"
} else if p.OperationType == valueOpType { } else if p.OperationType == ValueOp {
return fmt.Sprintf("%v (%T)", p.Value, p.Value) return fmt.Sprintf("%v (%T)", p.Value, p.Value)
} else { } else {
return fmt.Sprintf("%v", p.OperationType.Type) return fmt.Sprintf("%v", p.OperationType.Type)

View File

@@ -1,6 +1,6 @@
package yqlib package yqlib
func matchKey(name string, pattern string) (matched bool) { func Match(name string, pattern string) (matched bool) {
if pattern == "" { if pattern == "" {
return name == pattern return name == pattern
} }

View File

@@ -4,26 +4,31 @@ import (
"fmt" "fmt"
"container/list" "container/list"
"strconv"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode { func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
return &ExpressionNode{Operation: &Operation{OperationType: addOpType}, return &PathTreeNode{Operation: &Operation{OperationType: Add},
Lhs: lhs, Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}},
Rhs: rhs} Rhs: rhs}
} }
func addAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
assignmentOp := &Operation{OperationType: assignOpType} assignmentOp := &Operation{OperationType: Assign}
assignmentOp.UpdateAssign = false assignmentOp.Preferences = &AssignOpPreferences{true}
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)} assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
return d.GetMatchingNodes(matchingNodes, assignmentOpNode) 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" { if candidate.Node.Tag == "!!null" {
return []*yaml.Node{} return []*yaml.Node{}
} }
@@ -37,23 +42,29 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
} }
func addOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("Add operator") 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, matchingNodes, expressionNode, add) if err != nil {
} return nil, err
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node)
lhsNode := lhs.Node
if lhsNode.Tag == "!!null" {
return lhs.CreateChild(nil, rhs.Node), nil
} }
target := lhs.CreateChild(nil, &yaml.Node{}) for el := lhs.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode)
lhsNode := UnwrapDoc(lhsCandidate.Node)
target := &CandidateNode{
Path: lhsCandidate.Path,
Document: lhsCandidate.Document,
Filename: lhsCandidate.Filename,
Node: &yaml.Node{},
}
switch lhsNode.Kind { switch lhsNode.Kind {
case yaml.MappingNode: case yaml.MappingNode:
@@ -63,50 +74,10 @@ func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Candida
target.Node.Style = lhsNode.Style target.Node.Style = lhsNode.Style
target.Node.Tag = "!!seq" target.Node.Tag = "!!seq"
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...) target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
results.PushBack(target)
case yaml.ScalarNode: case yaml.ScalarNode:
if rhs.Node.Kind != yaml.ScalarNode { return nil, fmt.Errorf("Scalars not yet supported for addition")
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
} }
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhsNode.Style
return addScalars(target, lhsNode, rhs.Node)
} }
return results, nil
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
} }

View File

@@ -5,15 +5,6 @@ import (
) )
var addOperatorScenarios = []expressionScenario{ 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", description: "Concatenate and assign arrays",
document: `{a: {val: thing, b: [cat,dog]}}`, document: `{a: {val: thing, b: [cat,dog]}}`,
@@ -30,14 +21,6 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[a], (!!seq)::[1, 2, 3, 4]\n", "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", description: "Concatenate null to array",
document: `{a: [1,2]}`, document: `{a: [1,2]}`,
@@ -47,11 +30,11 @@ var addOperatorScenarios = []expressionScenario{
}, },
}, },
{ {
description: "Add new object to array", description: "Add object to array",
document: `a: [{dog: woof}]`, document: `{a: [1,2], c: {cat: meow}}`,
expression: `.a + {"cat": "meow"}`, expression: `.a + .c`,
expected: []string{ 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", "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) { func TestAddOperatorScenarios(t *testing.T) {

View File

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

View File

@@ -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)
}

View File

@@ -2,21 +2,27 @@ package yqlib
import "container/list" import "container/list"
func assignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { type AssignOpPreferences struct {
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs) UpdateAssign bool
}
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
var rhs *list.List var rhs *list.List
if !expressionNode.Operation.UpdateAssign { if !preferences.UpdateAssign {
rhs, err = d.GetMatchingNodes(matchingNodes, expressionNode.Rhs) rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
} }
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if expressionNode.Operation.UpdateAssign { if preferences.UpdateAssign {
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs) rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
} }
if err != nil { if err != nil {
@@ -27,25 +33,27 @@ func assignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, expres
first := rhs.Front() first := rhs.Front()
if first != nil { if first != nil {
rhsCandidate := first.Value.(*CandidateNode) candidate.UpdateFrom(first.Value.(*CandidateNode))
rhsCandidate.Node = unwrapDoc(rhsCandidate.Node)
candidate.UpdateFrom(rhsCandidate)
} }
} }
// // if there was nothing given, perhaps we are creating a new yaml doc
// if matchingNodes.Len() == 0 {
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
// return lhs, nil
// }
return matchingNodes, nil return matchingNodes, nil
} }
// does not update content or values // does not update content or values
func assignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -20,16 +20,6 @@ var assignOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: {g: foof}}\n", "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", description: "Update node to be the sibling value",
document: `{a: {b: child}, b: sibling}`, document: `{a: {b: child}, b: sibling}`,
@@ -90,14 +80,6 @@ var assignOperatorScenarios = []expressionScenario{
{ {
description: "Update selected results", description: "Update selected results",
document: `{a: {b: apple, c: cactus}}`, 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{ expected: []string{
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n", "D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",

View File

@@ -7,7 +7,7 @@ import (
) )
func isTruthy(c *CandidateNode) (bool, error) { func isTruthy(c *CandidateNode) (bool, error) {
node := unwrapDoc(c.Node) node := UnwrapDoc(c.Node)
value := true value := true
if node.Tag == "!!null" { if node.Tag == "!!null" {
@@ -25,42 +25,77 @@ func isTruthy(c *CandidateNode) (bool, error) {
type boolOp func(bool, bool) bool type boolOp func(bool, bool) bool
func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
lhs.Node = unwrapDoc(lhs.Node) lhsCandidate := lhsChild.Value.(*CandidateNode)
rhs.Node = unwrapDoc(rhs.Node) lhsTrue, errDecoding := isTruthy(lhsCandidate)
lhsTrue, errDecoding := isTruthy(lhs)
if errDecoding != nil { if errDecoding != nil {
return nil, errDecoding return errDecoding
} }
rhsTrue, errDecoding := isTruthy(rhs) for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
rhsCandidate := rhsChild.Value.(*CandidateNode)
rhsTrue, errDecoding := isTruthy(rhsCandidate)
if errDecoding != nil { if errDecoding != nil {
return nil, errDecoding return errDecoding
} }
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
return createBooleanCandidate(lhs, op(lhsTrue, rhsTrue)), nil results.PushBack(boolResult)
} }
}
return nil
} }
func orOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, 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") log.Debugf("-- orOp")
return crossFunction(d, matchingNodes, expressionNode, performBoolOp( return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
func(b1 bool, b2 bool) bool {
return b1 || b2 return b1 || b2
})) })
} }
func andOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- AndOp") log.Debugf("-- AndOp")
return crossFunction(d, matchingNodes, expressionNode, performBoolOp( return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
func(b1 bool, b2 bool) bool {
return b1 && b2 return b1 && b2
})) })
} }
func notOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) { func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- notOperation") log.Debugf("-- notOperation")
var results = list.New() var results = list.New()

View File

@@ -6,7 +6,7 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func collectOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) { func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- collectOperation") log.Debugf("-- collectOperation")
if matchMap.Len() == 0 { if matchMap.Len() == 0 {
@@ -18,22 +18,21 @@ func collectOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *
var results = list.New() var results = list.New()
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
var collectC *CandidateNode
if matchMap.Front() != nil { var document uint = 0
collectC = matchMap.Front().Value.(*CandidateNode).CreateChild(nil, node) var path []interface{}
if len(collectC.Path) > 0 {
collectC.Path = collectC.Path[:len(collectC.Path)-1]
}
} else {
collectC = &CandidateNode{Node: node}
}
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Collecting %v", NodeToString(candidate)) 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) results.PushBack(collectC)
return results, nil return results, nil

View File

@@ -17,7 +17,7 @@ import (
... ...
*/ */
func collectObjectOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) { func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- collectObjectOperation") log.Debugf("-- collectObjectOperation")
if matchMap.Len() == 0 { if matchMap.Len() == 0 {
@@ -35,7 +35,7 @@ func collectObjectOperator(d *dataTreeNavigator, matchMap *list.List, expression
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidateNode := el.Value.(*CandidateNode) candidateNode := el.Value.(*CandidateNode)
for i := 0; i < len(first.Node.Content); i++ { 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))
} }
} }
@@ -52,15 +52,22 @@ func collectObjectOperator(d *dataTreeNavigator, matchMap *list.List, expression
} }
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) { func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) {
if remainingMatches.Len() == 0 { if remainingMatches.Len() == 0 {
return aggregate, nil return aggregate, nil
} }
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode) candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
splatted, err := Splat(d, nodeToMap(candidate))
splatted, err := splat(d, nodeToMap(candidate),
traversePreferences{DontFollowAlias: true, IncludeMapKeys: false})
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() { for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatEl.Value.(*CandidateNode).Path = nil splatEl.Value.(*CandidateNode).Path = nil
@@ -87,7 +94,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
newCandidate.Path = nil newCandidate.Path = nil
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate) newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

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

View File

@@ -13,15 +13,6 @@ var collectOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[]\n", "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, skipDoc: true,
document: ``, document: ``,

View File

@@ -4,53 +4,38 @@ import (
"container/list" "container/list"
"strings" "strings"
yaml "gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type commentOpPreferences struct { type CommentOpPreferences struct {
LineComment bool LineComment bool
HeadComment bool HeadComment bool
FootComment bool FootComment bool
} }
func assignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignComments operator!") log.Debugf("AssignComments operator!")
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
comment := "" comment := ""
if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil { if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value comment = rhs.Front().Value.(*CandidateNode).Node.Value
} }
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
} }
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
log.Debugf("Setting comment of : %v", candidate.GetKey()) log.Debugf("Setting comment of : %v", candidate.GetKey())
if preferences.LineComment { if preferences.LineComment {
candidate.Node.LineComment = comment candidate.Node.LineComment = comment
@@ -66,8 +51,8 @@ func assignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expr
return matchingNodes, nil return matchingNodes, nil
} }
func getCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
preferences := expressionNode.Operation.Preferences.(commentOpPreferences) preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
log.Debugf("GetComments operator!") log.Debugf("GetComments operator!")
var results = list.New() var results = list.New()
@@ -84,8 +69,8 @@ func getCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, express
comment = strings.Replace(comment, "# ", "", 1) comment = strings.Replace(comment, "# ", "", 1)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return results, nil return results, nil
} }

View File

@@ -13,39 +13,6 @@ var commentOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: cat # single\n", "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", description: "Set head comment",
document: `a: cat`, document: `a: cat`,
@@ -72,11 +39,10 @@ var commentOperatorScenarios = []expressionScenario{
}, },
{ {
description: "Remove all comments", 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",
document: "# hi\n\na: cat # comment\n\n# great\n\nb: # key comment", expression: `.. comments=""`,
expression: `... comments=""`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::a: cat\nb:\n", "D0, P[], (!!map)::a: cat\n",
}, },
}, },
{ {

View File

@@ -6,7 +6,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func createMapOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- createMapOperation") log.Debugf("-- createMapOperation")
//each matchingNodes entry should turn into a sequence of keys to create. //each matchingNodes entry should turn into a sequence of keys to create.
@@ -22,14 +22,14 @@ func createMapOperator(d *dataTreeNavigator, matchingNodes *list.List, expressio
for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() { for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
matchingNode := matchingNodeEl.Value.(*CandidateNode) matchingNode := matchingNodeEl.Value.(*CandidateNode)
sequenceNode, err := sequenceFor(d, matchingNode, expressionNode) sequenceNode, err := sequenceFor(d, matchingNode, pathNode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sequences.PushBack(sequenceNode) sequences.PushBack(sequenceNode)
} }
} else { } else {
sequenceNode, err := sequenceFor(d, nil, expressionNode) sequenceNode, err := sequenceFor(d, nil, pathNode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -40,7 +40,7 @@ func createMapOperator(d *dataTreeNavigator, matchingNodes *list.List, expressio
} }
func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, expressionNode *ExpressionNode) (*CandidateNode, error) { func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathTreeNode) (*CandidateNode, error) {
var path []interface{} var path []interface{}
var document uint = 0 var document uint = 0
var matches = list.New() var matches = list.New()
@@ -51,14 +51,14 @@ func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, expressionNo
matches = nodeToMap(matchingNode) matches = nodeToMap(matchingNode)
} }
mapPairs, err := crossFunction(d, matches, expressionNode, mapPairs, err := crossFunction(d, matches, pathNode,
func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
log.Debugf("LHS:", NodeToString(lhs)) log.Debugf("LHS:", NodeToString(lhs))
log.Debugf("RHS:", NodeToString(rhs)) log.Debugf("RHS:", NodeToString(rhs))
node.Content = []*yaml.Node{ node.Content = []*yaml.Node{
unwrapDoc(lhs.Node), UnwrapDoc(lhs.Node),
unwrapDoc(rhs.Node), UnwrapDoc(rhs.Node),
} }
return &CandidateNode{Node: &node, Document: document, Path: path}, nil return &CandidateNode{Node: &node, Document: document, Path: path}, nil

View File

@@ -2,69 +2,41 @@ package yqlib
import ( import (
"container/list" "container/list"
"fmt"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func deleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, 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(matchingNodes, expressionNode.Rhs) for el := matchingNodes.Front(); el != nil; el = el.Next() {
if err != nil {
return nil, err
}
for el := nodesToDelete.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
elMap := list.New()
deleteImmediateChildOp := &Operation{ elMap.PushBack(candidate)
OperationType: deleteImmediateChildOpType, nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs)
Value: candidate.Path[len(candidate.Path)-1], log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
}
deleteImmediateChildOpNode := &ExpressionNode{
Operation: deleteImmediateChildOp,
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}),
}
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
if err != nil {
return nil, err
}
}
return matchingNodes, nil
}
func deleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
parents, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
childPath := expressionNode.Operation.Value realNode := UnwrapDoc(candidate.Node)
log.Debug("childPath to remove %v", childPath) if realNode.Kind == yaml.SequenceNode {
deleteFromArray(candidate, nodesToDelete)
for el := parents.Front(); el != nil; el = el.Next() { } else if realNode.Kind == yaml.MappingNode {
parent := el.Value.(*CandidateNode) deleteFromMap(candidate, nodesToDelete)
parentNode := unwrapDoc(parent.Node)
if parentNode.Kind == yaml.MappingNode {
deleteFromMap(parent, childPath)
} else if parentNode.Kind == yaml.SequenceNode {
deleteFromArray(parent, childPath)
} else { } else {
return nil, 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 matchingNodes, nil return matchingNodes, nil
} }
func deleteFromMap(candidate *CandidateNode, childPath interface{}) { func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
log.Debug("deleteFromMap") log.Debug("deleteFromMap")
node := unwrapDoc(candidate.Node) node := UnwrapDoc(candidate.Node)
contents := node.Content contents := node.Content
newContents := make([]*yaml.Node, 0) newContents := make([]*yaml.Node, 0)
@@ -72,9 +44,18 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
key := contents[index] key := contents[index]
value := contents[index+1] 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) log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
@@ -85,16 +66,27 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
node.Content = newContents node.Content = newContents
} }
func deleteFromArray(candidate *CandidateNode, childPath interface{}) { func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
log.Debug("deleteFromArray") log.Debug("deleteFromArray")
node := unwrapDoc(candidate.Node) node := UnwrapDoc(candidate.Node)
contents := node.Content contents := node.Content
newContents := make([]*yaml.Node, 0) newContents := make([]*yaml.Node, 0)
for index := 0; index < len(contents); index = index + 1 { for index := 0; index < len(contents); index = index + 1 {
value := contents[index] 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 { if !shouldDelete {
newContents = append(newContents, value) newContents = append(newContents, value)

View File

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

View File

@@ -7,13 +7,13 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func getDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func GetDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
var results = list.New() var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"} 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) results.PushBack(scalar)
} }
return results, nil return results, nil

View File

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

View File

@@ -1,50 +0,0 @@
package yqlib
import (
"container/list"
"fmt"
"os"
"strings"
yaml "gopkg.in/yaml.v3"
)
type envOpPreferences struct {
StringValue bool
}
func envOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, 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 nil, 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 nil, errorReading
}
//first node is a doc
node = unwrapDoc(&dataBucket)
}
log.Debug("ENV tag", node.Tag)
log.Debug("ENV value", node.Value)
log.Debug("ENV Kind", node.Kind)
target := &CandidateNode{Node: node}
return nodeToMap(target), nil
}

View File

@@ -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)
}

View File

@@ -4,21 +4,18 @@ import (
"container/list" "container/list"
) )
func equalsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func EqualsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- equalsOperation") log.Debugf("-- equalsOperation")
return crossFunction(d, matchingNodes, expressionNode, isEquals) return crossFunction(d, matchingNodes, pathNode, isEquals)
} }
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
value := false value := false
lhsNode := unwrapDoc(lhs.Node) if lhs.Node.Tag == "!!null" {
rhsNode := unwrapDoc(rhs.Node) value = (rhs.Node.Tag == "!!null")
if lhsNode.Tag == "!!null" {
value = (rhsNode.Tag == "!!null")
} else { } 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) log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
return createBooleanCandidate(lhs, value), nil return createBooleanCandidate(lhs, value), nil

View File

@@ -5,15 +5,6 @@ import (
) )
var equalsOperatorScenarios = []expressionScenario{ 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", description: "Match string",
document: `[cat,goat,dog]`, document: `[cat,goat,dog]`,

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func getFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetFilename") log.Debugf("GetFilename")
var results = list.New() var results = list.New()
@@ -15,14 +15,14 @@ func getFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, express
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return results, nil return results, nil
} }
func getFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetFileIndex") log.Debugf("GetFileIndex")
var results = list.New() var results = list.New()
@@ -30,8 +30,8 @@ func getFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, expres
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return results, nil return results, nil

View File

@@ -21,22 +21,6 @@ var fileOperatorScenarios = []expressionScenario{
"D0, P[], (!!int)::0\n", "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) { func TestFileOperatorsScenarios(t *testing.T) {

View File

@@ -7,12 +7,12 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func hasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- hasOperation") log.Debugf("-- hasOperation")
var results = list.New() var results = list.New()
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
wanted := rhs.Front().Value.(*CandidateNode).Node wanted := rhs.Front().Value.(*CandidateNode).Node
wantedKey := wanted.Value wantedKey := wanted.Value
@@ -24,9 +24,8 @@ func hasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
// grab the first value // grab the first value
candidateNode := unwrapDoc(candidate.Node) var contents = candidate.Node.Content
var contents = candidateNode.Content switch candidate.Node.Kind {
switch candidateNode.Kind {
case yaml.MappingNode: case yaml.MappingNode:
candidateHasKey := false candidateHasKey := false
for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 { for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 {

View File

@@ -5,14 +5,6 @@ import (
) )
var hasOperatorScenarios = []expressionScenario{ var hasOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `a: hello`,
expression: `has("a")`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{ {
description: "Has map key", description: "Has map key",
document: `- a: "yes" document: `- a: "yes"

View File

@@ -1,54 +0,0 @@
package yqlib
import (
"container/list"
"fmt"
"gopkg.in/yaml.v3"
)
func keysOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- keysOperator")
var results = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := unwrapDoc(candidate.Node)
var targetNode *yaml.Node
if node.Kind == yaml.MappingNode {
targetNode = getMapKeys(node)
} else if node.Kind == yaml.SequenceNode {
targetNode = getIndicies(node)
} else {
return nil, fmt.Errorf("Cannot get keys of %v, keys only works for maps and arrays", node.Tag)
}
result := candidate.CreateChild(nil, targetNode)
results.PushBack(result)
}
return results, nil
}
func getMapKeys(node *yaml.Node) *yaml.Node {
contents := make([]*yaml.Node, 0)
for index := 0; index < len(node.Content); index = index + 2 {
contents = append(contents, node.Content[index])
}
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
}
func getIndicies(node *yaml.Node) *yaml.Node {
var contents = make([]*yaml.Node, len(node.Content))
for index := range node.Content {
contents[index] = &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!int",
Value: fmt.Sprintf("%v", index),
}
}
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
}

View File

@@ -1,47 +0,0 @@
package yqlib
import (
"testing"
)
var keysOperatorScenarios = []expressionScenario{
{
description: "Map keys",
document: `{dog: woof, cat: meow}`,
expression: `keys`,
expected: []string{
"D0, P[], (!!seq)::- dog\n- cat\n",
},
},
{
skipDoc: true,
document: `{}`,
expression: `keys`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
description: "Array keys",
document: `[apple, banana]`,
expression: `keys`,
expected: []string{
"D0, P[], (!!seq)::- 0\n- 1\n",
},
},
{
skipDoc: true,
document: `[]`,
expression: `keys`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
}
func TestKeysOperatorScenarios(t *testing.T) {
for _, tt := range keysOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Keys", keysOperatorScenarios)
}

View File

@@ -7,13 +7,13 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func lengthOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) { func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- lengthOperation") log.Debugf("-- lengthOperation")
var results = list.New() var results = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
targetNode := unwrapDoc(candidate.Node) targetNode := UnwrapDoc(candidate.Node)
var length int var length int
switch targetNode.Kind { switch targetNode.Kind {
case yaml.ScalarNode: case yaml.ScalarNode:
@@ -27,8 +27,8 @@ func lengthOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *E
} }
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return results, nil return results, nil

View File

@@ -2,28 +2,29 @@ package yqlib
import ( import (
"fmt" "fmt"
"strconv"
"container/list" "container/list"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type crossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
func doCrossFunc(d *dataTreeNavigator, contextList *list.List, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (*list.List, error) { func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*list.List, error) {
var results = list.New() lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
lhs, err := d.GetMatchingNodes(contextList, expressionNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("crossFunction LHS len: %v", lhs.Len()) log.Debugf("crossFunction LHS len: %v", lhs.Len())
rhs, err := d.GetMatchingNodes(contextList, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debugf("crossFunction RHS len: %v", rhs.Len())
var results = list.New()
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode) lhsCandidate := el.Value.(*CandidateNode)
@@ -42,91 +43,49 @@ func doCrossFunc(d *dataTreeNavigator, contextList *list.List, expressionNode *E
return results, nil return results, nil
} }
func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (*list.List, error) { type MultiplyPreferences struct {
var results = list.New()
var evaluateAllTogether = true
for matchEl := matchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
evaluateAllTogether = evaluateAllTogether && matchEl.Value.(*CandidateNode).EvaluateTogether
if !evaluateAllTogether {
break
}
}
if evaluateAllTogether {
return doCrossFunc(d, matchingNodes, expressionNode, calculation)
}
for matchEl := matchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
contextList := nodeToMap(matchEl.Value.(*CandidateNode))
innerResults, err := doCrossFunc(d, contextList, expressionNode, calculation)
if err != nil {
return nil, err
}
results.PushBackList(innerResults)
}
return results, nil
}
type multiplyPreferences struct {
AppendArrays bool AppendArrays bool
TraversePrefs traversePreferences
} }
func multiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- MultiplyOperator") log.Debugf("-- MultiplyOperator")
return crossFunction(d, matchingNodes, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences))) return crossFunction(d, matchingNodes, pathNode, multiply(pathNode.Operation.Preferences.(*MultiplyPreferences)))
} }
func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("Multipling LHS: %v", lhs.Node.Tag) log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag) log.Debugf("- RHS: %v", rhs.Node.Tag)
shouldAppendArrays := preferences.AppendArrays
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
var newBlank = lhs.CreateChild(nil, &yaml.Node{}) var newBlank = &CandidateNode{
var newThing, err = mergeObjects(d, newBlank, lhs, multiplyPreferences{}) Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &yaml.Node{},
}
var newThing, err = mergeObjects(d, newBlank, lhs, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return mergeObjects(d, newThing, rhs, preferences) return mergeObjects(d, newThing, rhs, shouldAppendArrays)
} else if lhs.Node.Tag == "!!int" && rhs.Node.Tag == "!!int" {
return multiplyIntegers(lhs, rhs)
} }
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
} }
} }
func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
target := lhs.CreateChild(nil, &yaml.Node{})
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhs.Node.Style
target.Node.Tag = "!!int"
lhsNum, err := strconv.Atoi(lhs.Node.Value)
if err != nil {
return nil, err
}
rhsNum, err := strconv.Atoi(rhs.Node.Value)
if err != nil {
return nil, err
}
target.Node.Value = fmt.Sprintf("%v", lhsNum*rhsNum)
return target, nil
}
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) (*CandidateNode, error) {
shouldAppendArrays := preferences.AppendArrays
var results = list.New() var results = list.New()
// shouldn't recurse arrays if appending // shouldn't recurse arrays if appending
prefs := recursiveDescentPreferences{RecurseArray: !shouldAppendArrays, err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays)
TraversePreferences: traversePreferences{DontFollowAlias: true}}
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -137,7 +96,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
} }
for el := results.Front(); el != nil; el = el.Next() { for el := results.Front(); el != nil; el = el.Next() {
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), preferences) err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -145,22 +104,35 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
return lhs, nil return lhs, nil
} }
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error { func createTraversalTree(path []interface{}) *PathTreeNode {
shouldAppendArrays := preferences.AppendArrays if len(path) == 0 {
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
} else if len(path) == 1 {
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
}
return &PathTreeNode{
Operation: &Operation{OperationType: ShortPipe},
Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])}
}
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
lhsPath := rhs.Path[pathIndexToStartFrom:] lhsPath := rhs.Path[pathIndexToStartFrom:]
assignmentOp := &Operation{OperationType: assignAttributesOpType} assignmentOp := &Operation{OperationType: AssignAttributes}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = assignOpType assignmentOp.OperationType = Assign
assignmentOp.UpdateAssign = false assignmentOp.Preferences = &AssignOpPreferences{false}
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode { } else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = addAssignOpType assignmentOp.OperationType = AddAssign
} }
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs} rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath, preferences.TraversePrefs), Rhs: &ExpressionNode{Operation: rhsOp}} assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{Operation: rhsOp}}
_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode) _, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode)

View File

@@ -13,27 +13,6 @@ var multiplyOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
}, },
}, },
{
skipDoc: true,
document: `{a: 2, b: 5}`,
document2: `{a: 3, b: 10}`,
expression: `.a * .b`,
expected: []string{
"D0, P[a], (!!int)::10\n",
"D0, P[a], (!!int)::20\n",
"D0, P[a], (!!int)::15\n",
"D0, P[a], (!!int)::30\n",
},
},
{
skipDoc: true,
document: `{a: 2}`,
document2: `{b: 10}`,
expression: `select(fi ==0) * select(fi==1)`,
expected: []string{
"D0, P[], (!!map)::{a: 2, b: 10}\n",
},
},
{ {
skipDoc: true, skipDoc: true,
expression: `{} * {"cat":"dog"}`, expression: `{} * {"cat":"dog"}`,
@@ -128,30 +107,6 @@ b:
"D0, P[a], (!!seq)::[1, 2]\n", "D0, P[a], (!!seq)::[1, 2]\n",
}, },
}, },
{
description: "Merge, only existing fields",
document: `{a: {thing: one, cat: frog}, b: {missing: two, thing: two}}`,
expression: `.a *? .b`,
expected: []string{
"D0, P[a], (!!map)::{thing: two, cat: frog}\n",
},
},
{
skipDoc: true,
document: `{a: [{thing: one}], b: [{missing: two, thing: two}]}`,
expression: `.a *? .b`,
expected: []string{
"D0, P[a], (!!seq)::[{thing: two}]\n",
},
},
{
skipDoc: true,
document: `{a: {array: [1]}, b: {}}`,
expression: `.b *+ .a`,
expected: []string{
"D0, P[b], (!!map)::{array: [1]}\n",
},
},
{ {
description: "Merge, appending arrays", description: "Merge, appending arrays",
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`, document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
@@ -160,14 +115,6 @@ b:
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n", "D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
}, },
}, },
{
description: "Merge, only existing fields, appending arrays",
document: `{a: {thing: [1,2]}, b: {thing: [3,4], another: [1]}}`,
expression: `.a *?+ .b`,
expected: []string{
"D0, P[a], (!!map)::{thing: [1, 2, 3, 4]}\n",
},
},
{ {
description: "Merge to prefix an element", description: "Merge to prefix an element",
document: `{a: cat, b: dog}`, document: `{a: cat, b: dog}`,
@@ -185,11 +132,11 @@ b:
}, },
}, },
{ {
description: "Merge copies anchor names", description: "Merge does not copy anchor names",
document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`, document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`,
expression: `.c * .a`, expression: `.c * .a`,
expected: []string{ expected: []string{
"D0, P[c], (!!map)::{g: thongs, c: &cat frog}\n", "D0, P[c], (!!map)::{g: thongs, c: frog}\n",
}, },
}, },
{ {

View File

@@ -16,7 +16,7 @@ func createPathNodeFor(pathElement interface{}) *yaml.Node {
} }
} }
func getPathOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func GetPathOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetPath") log.Debugf("GetPath")
var results = list.New() var results = list.New()
@@ -31,8 +31,8 @@ func getPathOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionN
content[pathIndex] = createPathNodeFor(path) content[pathIndex] = createPathNodeFor(path)
} }
node.Content = content node.Content = content
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return results, nil return results, nil

View File

@@ -13,23 +13,6 @@ var pathOperatorScenarios = []expressionScenario{
"D0, P[a b], (!!seq)::- a\n- b\n", "D0, P[a b], (!!seq)::- a\n- b\n",
}, },
}, },
{
skipDoc: true,
document: `a:
b:
c:
- 0
- 1
- 2
- 3`,
expression: `.a.b.c.[]`,
expected: []string{
"D0, P[a b c 0], (!!int)::0\n",
"D0, P[a b c 1], (!!int)::1\n",
"D0, P[a b c 2], (!!int)::2\n",
"D0, P[a b c 3], (!!int)::3\n",
},
},
{ {
description: "Get map key", description: "Get map key",
document: `{a: {b: cat}}`, document: `{a: {b: cat}}`,

View File

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

View File

@@ -6,16 +6,10 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type recursiveDescentPreferences struct { func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
TraversePreferences traversePreferences
RecurseArray bool
}
func recursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
var results = list.New() var results = list.New()
preferences := expressionNode.Operation.Preferences.(recursiveDescentPreferences) err := recursiveDecent(d, results, matchMap, true)
err := recursiveDecent(d, results, matchMap, preferences)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -23,24 +17,24 @@ func recursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, express
return results, nil return results, nil
} }
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences recursiveDescentPreferences) error { func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, recurseArray bool) error {
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
candidate.Node = unwrapDoc(candidate.Node) candidate.Node = UnwrapDoc(candidate.Node)
log.Debugf("Recursive Decent, added %v", NodeToString(candidate)) log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
results.PushBack(candidate) results.PushBack(candidate)
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 && if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
(preferences.RecurseArray || candidate.Node.Kind != yaml.SequenceNode) { (recurseArray || candidate.Node.Kind != yaml.SequenceNode) {
children, err := splat(d, nodeToMap(candidate), preferences.TraversePreferences) children, err := Splat(d, nodeToMap(candidate))
if err != nil { if err != nil {
return err return err
} }
err = recursiveDecent(d, results, children, preferences) err = recursiveDecent(d, results, children, recurseArray)
if err != nil { if err != nil {
return err return err
} }

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