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
84 changed files with 851 additions and 2432 deletions

View File

@@ -1,8 +1,8 @@
---
name: Bug report - V3
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug, v3
labels: bug
assignees: ''
---
@@ -10,14 +10,11 @@ assignees: ''
**Describe the bug**
A clear and concise description of what the bug is.
Note that any how to questions should be posted in the discussion board and not raised as an issue.
Version of yq: 3.X.X
Operating system: mac/linux/windows/....
Installed via: docker/binary release/homebrew/snap/...
version of yq:
operating system:
**Input Yaml**
Concise yaml document(s) (as simple as possible to show the bug, please keep it to 10 lines or less)
Concise yaml document(s) (as simple as possible to show the bug)
data1.yml:
```yaml
this: should really work

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

View File

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

View File

@@ -1,80 +0,0 @@
name: Release YQ
on:
push:
tags:
- 'v*'
jobs:
publishGitRelease:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.15'
- name: Cross compile
run: |
sudo apt-get install rhash -y
go get github.com/mitchellh/gox
./scripts/xcompile.sh
- name: Create Release
id: create_release
uses: actions/create-release@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: false
- uses: shogo82148/actions-upload-release-asset@v1
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: build/*
publishDocker:
environment: dockerhub
env:
IMAGE_NAME: mikefarah/yq
runs-on: ubuntu-latest
steps:
- 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
# CI tools expect an interactive shell inside the container
FROM alpine:3.12.3 as production
FROM alpine:3.12 as production
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
RUN chmod +x /usr/bin/yq

View File

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

134
README.md
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)
a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
a lightweight and portable command-line YAML processor
yq is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as docker, all listed below.
## V4 released!
V4 is now officially released, it's quite different from V3 (sorry for the migration), however it is much more similar to ```jq```, using a similar expression syntax and therefore support much more complex functionality!
If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3).
Support for v3 will cease August 2021, until then, critical bug and security fixes will still get applied if required.
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
## Install
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
### wget
Use wget to download the pre-compiled binaries:
#### Compressed via tar.gz
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\
tar xz && mv ${BINARY} /usr/bin/yq
```
#### Plain binary
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
chmod +x /usr/bin/yq
```
For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
### MacOS / Linux via Homebrew:
Using [Homebrew](https://brew.sh/)
### MacOS:
```
brew install yq
```
or, for the (deprecated) v3 version:
```
brew install yq@3
```
Note that for v3, as it is a versioned brew it will not add the `yq` command to your path automatically. Please follow the instructions given by brew upon installation.
### Linux via snap:
### Ubuntu and other Linux distros supporting `snap` packages:
```
snap install yq
```
or, for the (deprecated) v3 version:
```
snap install yq --channel=v3/stable
```
#### Snap notes
`yq` installs with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
@@ -79,6 +39,18 @@ sudo mv /etc/myfile.tmp /etc/myfile
rm /etc/myfile.tmp
```
### wget
Use wget to download the pre-compiled binaries:
```bash
wget https://github.com/mikefarah/yq/releases/download/{VERSION}/{BINARY} -O /usr/bin/yq &&\
chmod +x /usr/bin/yq
```
For instance, VERSION=4.0.0 and BINARY=yq_linux_amd64
### Run with Docker
#### Oneshot use:
@@ -103,20 +75,12 @@ yq() {
### Go Get:
```
GO111MODULE=on go get github.com/mikefarah/yq
GO111MODULE=on go get github.com/mikefarah/yq/v4
```
## Community Supported Installation methods
As these are supported by the community :heart: - however, they may be out of date with the officially supported releases.
# Webi
```
webi yq
```
See [webi](https://webinstall.dev/)
Supported by @adithyasunil26 (https://github.com/webinstall/webi-installers/tree/master/yq)
### Windows:
```
@@ -124,14 +88,6 @@ choco install yq
```
Supported by @chillum (https://chocolatey.org/packages/yq)
### Mac:
Using [MacPorts](https://www.macports.org/)
```
sudo port selfupdate
sudo port install yq
```
Supported by @herbygillot (https://ports.macports.org/maintainer/github/herbygillot)
### Alpine Linux
- Enable edge/community repo by adding ```$MIRROR/alpine/edge/community``` to ```/etc/apk/repositories```
- Update database index with ```apk update```
@@ -152,18 +108,21 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
## Features
- Written in portable go, so you can download a lovely dependency free binary
- Uses similar syntax as `jq` but works with YAML and JSON files
- Fully supports multi document yaml files
- Colorized yaml output
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/v/v4.x/sort-keys)
- Manipulate yaml [comments](https://mikefarah.gitbook.io/yq/comment-operators), [styling](https://mikefarah.gitbook.io/yq/style), [tags](https://mikefarah.gitbook.io/yq/tag) and [anchors and aliases](https://mikefarah.gitbook.io/yq/anchor-and-alias-operators).
- [Update yaml inplace](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate#flags)
- [Complex expressions to select and update](https://mikefarah.gitbook.io/yq/v/v4.x/select#select-and-update-matching-values-in-map)
- Keeps yaml formatting and comments when updating (though there are issues with whitespace)
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/v/v4.x/usage/convert)
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate)
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
- [Deep read a yaml file with a given path expression](https://mikefarah.gitbook.io/yq/v/v4.x/traverse)
- [Return the lengths of arrays/object/scalars](https://mikefarah.gitbook.io/yq/commands/read#printing-length-of-the-results)
- Update a yaml file given a [path expression](https://mikefarah.gitbook.io/yq/commands/write-update#basic) or [script file](https://mikefarah.gitbook.io/yq/commands/write-update#basic)
- Update creates any missing entries in the path on the fly
- Deeply [compare](https://mikefarah.gitbook.io/yq/commands/compare) yaml files
- Keeps yaml formatting and comments when updating
- [Validate a yaml file](https://mikefarah.gitbook.io/yq/commands/validate)
- Create a yaml file given a [deep path and value](https://mikefarah.gitbook.io/yq/commands/create#creating-a-simple-yaml-file) or a [script file](https://mikefarah.gitbook.io/yq/commands/create#creating-using-a-create-script)
- [Prefix a path to a yaml file](https://mikefarah.gitbook.io/yq/commands/prefix)
- [Convert to/from json to yaml](https://mikefarah.gitbook.io/yq/usage/convert)
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/commands/read#from-stdin)
- [Merge](https://mikefarah.gitbook.io/yq/commands/merge) multiple yaml files with various options for [overriding](https://mikefarah.gitbook.io/yq/commands/merge#overwrite-values) and [appending](https://mikefarah.gitbook.io/yq/commands/merge#append-values-with-arrays)
- Supports multiple documents in a single yaml file for [reading](https://mikefarah.gitbook.io/yq/commands/read#multiple-documents), [writing](https://mikefarah.gitbook.io/yq/commands/write-update#multiple-documents) and [merging](https://mikefarah.gitbook.io/yq/commands/merge#multiple-documents)
- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/commands/shell-completion)
## [Usage](https://mikefarah.gitbook.io/yq/)
@@ -175,33 +134,32 @@ Usage:
yq [command]
Available Commands:
eval Apply expression to each document in each yaml file given in sequence
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
help Help about any command
shell-completion Generate completion script
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
new yq n [--script/-s script_file] a.b.c newValue
prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c
read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'
shell-completion Generates shell completion scripts
validate yq v sample.yaml
write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue
Flags:
-C, --colors force print with colors
-e, --exit-status set exit status if there are no matches or null or false is returned
-C, --colors print with colors
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-i, --inplace update the yaml file inplace of first yaml file given.
-M, --no-colors force print with no colors
-N, --no-doc Don't print document separators (---)
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.
-P, --prettyPrint pretty print, shorthand for '... style = ""'
-j, --tojson output as json. Set indent to 0 to print json in one line.
-P, --prettyPrint pretty print
-j, --tojson output as json. By default it prints a json document in one line, use the prettyPrint flag to print a formatted doc.
-v, --verbose verbose mode
-V, --version Print version information and quit
Use "yq [command] --help" for more information about a command.
```
Simple Example:
```bash
yq e '.a.b | length' f1.yml f2.yml
```
## Upgrade from V2
If you've been using v2 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/upgrading-from-v2).
## Known Issues / Missing Features
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
- You cannot (yet) select multiple paths/keys from the yaml to be printed out (https://github.com/mikefarah/yq/issues/287)

View File

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

View File

@@ -59,29 +59,25 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
}
if nullInput && len(args) > 1 {
return errors.New("Cannot pass files in when using null-input flag")
}
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
switch len(args) {
case 0:
if pipingStdIn {
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
} else {
cmd.Println(cmd.UsageString())
return nil
}
case 1:
if nullInput {
err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer)
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
} else {
err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
}
default:
err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
}
completedSuccessfully = err == nil

View File

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

View File

@@ -47,8 +47,6 @@ func New() *cobra.Command {
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
rootCmd.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print, shorthand for '... style = \"\"'")
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")

View File

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

View File

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

12
go.mod
View File

@@ -2,15 +2,17 @@ module github.com/mikefarah/yq/v4
require (
github.com/elliotchance/orderedmap v1.3.0
github.com/fatih/color v1.10.0
github.com/goccy/go-yaml v1.8.4
github.com/jinzhu/copier v0.1.0
github.com/fatih/color v1.9.0
github.com/goccy/go-yaml v1.8.1
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/spf13/cobra v1.1.1
github.com/timtadh/data-structures v0.5.3 // indirect
github.com/timtadh/lexmachine v0.2.2
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
)
go 1.15

43
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/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.8.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -101,8 +99,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88=
github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -120,9 +118,15 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -200,7 +204,6 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -248,17 +251,21 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -283,6 +290,8 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -312,6 +321,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
@@ -323,8 +334,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -21,14 +21,6 @@ func (n *CandidateNode) GetKey() string {
return fmt.Sprintf("%v - %v", n.Document, n.Path)
}
func (n *CandidateNode) CreateChildPath(path interface{}) []interface{} {
//don't use append as they may actually modify the path of the orignal node!
newPath := make([]interface{}, len(n.Path)+1)
copy(newPath, n.Path)
newPath[len(n.Path)] = path
return newPath
}
func (n *CandidateNode) Copy() (*CandidateNode, error) {
clone := &CandidateNode{}
err := copier.Copy(clone, n)
@@ -40,7 +32,6 @@ func (n *CandidateNode) Copy() (*CandidateNode, error) {
// updates this candidate from the given candidate node
func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
n.UpdateAttributesFrom(other)
n.Node.Content = other.Node.Content
n.Node.Value = other.Node.Value

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
```
## Update node from another file
Note this will also work when the second file is a scalar (string/number)
Given a sample.yml file of:
```yaml
a: apples
```
And another sample another.yml file of:
```yaml
b: bob
```
then
```bash
yq eval-all 'select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)' sample.yml another.yml
```
will output
```yaml
a:
b: bob
```
## Update node to be the sibling value
Given a sample.yml file of:
```yaml
@@ -133,7 +112,7 @@ a:
```
then
```bash
yq eval '(.a[] | select(. == "apple")) = "frog"' sample.yml
yq eval '(.a.[] | select(. == "apple")) = "frog"' sample.yml
```
will output
```yaml

View File

@@ -13,22 +13,6 @@ will output
a: cat # single
```
## Use update assign to perform relative updates
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq eval '.. lineComment |= .' sample.yml
```
will output
```yaml
a: cat # cat
b: dog # dog
```
## Set head comment
Given a sample.yml file of:
```yaml

View File

@@ -14,23 +14,6 @@ will output
a: cat
```
## Delete nested entry in map
Given a sample.yml file of:
```yaml
a:
a1: fred
a2: frood
```
then
```bash
yq eval 'del(.a.a1)' sample.yml
```
will output
```yaml
a:
a2: frood
```
## Delete entry in array
Given a sample.yml file of:
```yaml
@@ -48,21 +31,6 @@ will output
- 3
```
## Delete nested entry in array
Given a sample.yml file of:
```yaml
- a: cat
b: dog
```
then
```bash
yq eval 'del(.[0].a)' sample.yml
```
will output
```yaml
- b: dog
```
## Delete no matches
Given a sample.yml file of:
```yaml
@@ -95,23 +63,3 @@ will output
b: dog
```
## Recursively delete matching keys
Given a sample.yml file of:
```yaml
a:
name: frog
b:
name: blog
age: 12
```
then
```bash
yq eval 'del(.. | select(has("name")).name)' sample.yml
```
will output
```yaml
a:
b:
age: 12
```

View File

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

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).
Note that the `fileIndex` operator has a short alias of `fi`.
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
```
## Get filename
Given a sample.yml file of:
@@ -18,7 +16,7 @@ yq eval 'filename' sample.yml
```
will output
```yaml
sample.yml
sample.yaml
```
## Get file index
@@ -35,17 +33,3 @@ will output
0
```
## Get file index alias
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval 'fi' sample.yml
```
will output
```yaml
0
```

View File

@@ -1,97 +1,8 @@
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:
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
```
## match values and map keys form `...`
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
```bash
yq eval '... style= "flow"' file.yaml
```
## Recurse map (values only)
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq eval '..' sample.yml
```
will output
```yaml
a: frog
frog
```
## Recursively find nodes with keys
Given a sample.yml file of:
```yaml
a:
name: frog
b:
name: blog
age: 12
```
then
```bash
yq eval '.. | select(has("name"))' sample.yml
```
will output
```yaml
name: frog
b:
name: blog
age: 12
name: blog
age: 12
```
## Recursively find nodes with values
Given a sample.yml file of:
```yaml
a:
nameA: frog
b:
nameB: frog
age: 12
```
then
```bash
yq eval '.. | select(. == "frog")' sample.yml
```
will output
```yaml
frog
frog
```
## Recurse map (values and keys)
Note that the map key appears in the results
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq eval '...' sample.yml
```
will output
```yaml
a: frog
a
frog
```
## Aliases are not traversed
Given a sample.yml file of:
```yaml

View File

@@ -40,26 +40,6 @@ c: "3.2"
e: "true"
```
## Set double quote style on map keys too
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '... style="double"' sample.yml
```
will output
```yaml
"a": "cat"
"b": "5"
"c": "3.2"
"e": "true"
```
## Set single quote style
Given a sample.yml file of:
```yaml
@@ -145,19 +125,19 @@ will output
{a: cat, b: 5, c: 3.2, e: true}
```
## Reset style - or pretty print
Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.
## Pretty print
Set empty (default) quote style
Given a sample.yml file of:
```yaml
a: cat
"b": 5
'c': 3.2
"e": true
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '... style=""' sample.yml
yq eval '.. style=""' sample.yml
```
will output
```yaml
@@ -167,22 +147,6 @@ c: 3.2
e: true
```
## Set style relatively with assign-update
Given a sample.yml file of:
```yaml
a: single
b: double
```
then
```bash
yq eval '.[] style |= .' sample.yml
```
will output
```yaml
a: 'single'
b: "double"
```
## Read style
Given a sample.yml file of:
```yaml

View File

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

View File

@@ -106,7 +106,7 @@ b: *cat
```
then
```bash
yq eval '.b[]' sample.yml
yq eval '.b.[]' sample.yml
```
will output
```yaml
@@ -290,7 +290,7 @@ foobar:
```
then
```bash
yq eval '.foobar[]' sample.yml
yq eval '.foobar.[]' sample.yml
```
will output
```yaml
@@ -356,7 +356,7 @@ foobar:
```
then
```bash
yq eval '.foobarList[]' sample.yml
yq eval '.foobarList.[]' sample.yml
```
will output
```yaml
@@ -366,21 +366,3 @@ bar_thing
foobarList_c
```
## Select multiple indices
Given a sample.yml file of:
```yaml
a:
- a
- b
- c
```
then
```bash
yq eval '.a[0, 2]' sample.yml
```
will output
```yaml
a
c
```

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

@@ -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).
Note that the `fileIndex` operator has a short alias of `fi`.
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
```

View File

@@ -1,19 +1,5 @@
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:
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
```
## 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

@@ -19,7 +19,7 @@ type OperationType struct {
}
// operators TODO:
// - keys operator for controlling key metadata (particularly anchors/aliases)
// - cookbook doc for common things
// - mergeEmpty (sets only if the document is empty, do I do that now?)
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
@@ -36,12 +36,9 @@ var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Pre
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
var AssignAnchor = &OperationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: AssignAnchorOperator}
var AssignAlias = &OperationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: AssignAliasOperator}
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
var Alternative = &OperationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: AlternativeOperator}
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
@@ -53,8 +50,6 @@ var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handle
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
var GetAnchor = &OperationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: GetAnchorOperator}
var GetAlias = &OperationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: GetAliasOperator}
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
@@ -65,7 +60,6 @@ var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Han
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var TraverseArray = &OperationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: TraverseArrayOperator}
var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
@@ -78,7 +72,6 @@ var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Pre
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
var DeleteImmediateChild = &OperationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: DeleteImmediateChildOperator}
type Operation struct {
OperationType *OperationType
@@ -86,7 +79,6 @@ type Operation struct {
StringValue string
CandidateNode *CandidateNode // used for Value Path elements
Preferences interface{}
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs (instead of matching nodes)
}
func CreateValueOperation(value interface{}, stringValue string) *Operation {

View File

@@ -16,13 +16,19 @@ func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
assignmentOp := &Operation{OperationType: Assign}
assignmentOp.UpdateAssign = true
assignmentOp.Preferences = &AssignOpPreferences{true}
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
}
func toNodes(candidate *CandidateNode) []*yaml.Node {
func toNodes(candidates *list.List) []*yaml.Node {
if candidates.Len() == 0 {
return []*yaml.Node{}
}
candidate := candidates.Front().Value.(*CandidateNode)
if candidate.Node.Tag == "!!null" {
return []*yaml.Node{}
}
@@ -38,33 +44,40 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("Add operator")
return crossFunction(d, matchingNodes, pathNode, add)
}
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
target := &CandidateNode{
Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &yaml.Node{},
var results = list.New()
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
lhsNode := lhs.Node
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
switch lhsNode.Kind {
case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for addition")
case yaml.SequenceNode:
target.Node.Kind = yaml.SequenceNode
target.Node.Style = lhsNode.Style
target.Node.Tag = "!!seq"
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
case yaml.ScalarNode:
return nil, fmt.Errorf("Scalars not yet supported for addition")
if err != nil {
return nil, err
}
return target, nil
for el := lhs.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode)
lhsNode := UnwrapDoc(lhsCandidate.Node)
target := &CandidateNode{
Path: lhsCandidate.Path,
Document: lhsCandidate.Document,
Filename: lhsCandidate.Filename,
Node: &yaml.Node{},
}
switch lhsNode.Kind {
case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for addition")
case yaml.SequenceNode:
target.Node.Kind = yaml.SequenceNode
target.Node.Style = lhsNode.Style
target.Node.Tag = "!!seq"
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
results.PushBack(target)
case yaml.ScalarNode:
return nil, fmt.Errorf("Scalars not yet supported for addition")
}
}
return results, nil
}

View File

@@ -21,14 +21,6 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[a], (!!seq)::[1, 2, 3, 4]\n",
},
},
{
skipDoc: true,
expression: `[1] + ([2], [3])`,
expected: []string{
"D0, P[], (!!seq)::- 1\n- 2\n",
"D0, P[], (!!seq)::- 1\n- 3\n",
},
},
{
description: "Concatenate null to array",
document: `{a: [1,2]}`,

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, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- alternative")
return crossFunction(d, matchingNodes, pathNode, alternativeFunc)
}
func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag)
isTrue, err := isTruthy(lhs)
if err != nil {
return nil, err
} else if isTrue {
return lhs, nil
}
return rhs, nil
}

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,20 +2,26 @@ package yqlib
import "container/list"
type AssignOpPreferences struct {
UpdateAssign bool
}
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
var rhs *list.List
if !pathNode.Operation.UpdateAssign {
if !preferences.UpdateAssign {
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
if pathNode.Operation.UpdateAssign {
if preferences.UpdateAssign {
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
}
@@ -27,9 +33,7 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
first := rhs.Front()
if first != nil {
rhsCandidate := first.Value.(*CandidateNode)
rhsCandidate.Node = UnwrapDoc(rhsCandidate.Node)
candidate.UpdateFrom(rhsCandidate)
candidate.UpdateFrom(first.Value.(*CandidateNode))
}
}
// // if there was nothing given, perhaps we are creating a new yaml doc

View File

@@ -20,16 +20,6 @@ var assignOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: {g: foof}}\n",
},
},
{
description: "Update node from another file",
subdescription: "Note this will also work when the second file is a scalar (string/number)",
document: `{a: apples}`,
document2: "{b: bob}",
expression: `select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)`,
expected: []string{
"D0, P[], (doc)::{a: {b: bob}}\n",
},
},
{
description: "Update node to be the sibling value",
document: `{a: {b: child}, b: sibling}`,
@@ -90,15 +80,7 @@ var assignOperatorScenarios = []expressionScenario{
{
description: "Update selected results",
document: `{a: {b: apple, c: cactus}}`,
expression: `(.a[] | select(. == "apple")) = "frog"`,
expected: []string{
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
},
},
{
skipDoc: true,
document: `{a: {b: apple, c: cactus}}`,
expression: `(.a.[] | select(. == "apple")) = "frog"`,
expression: `(.a.[] | select(. == "apple")) = "frog"`,
expected: []string{
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
},

View File

@@ -25,39 +25,74 @@ func isTruthy(c *CandidateNode) (bool, error) {
type boolOp func(bool, bool) bool
func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
lhsTrue, errDecoding := isTruthy(lhs)
func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
lhsCandidate := lhsChild.Value.(*CandidateNode)
lhsTrue, errDecoding := isTruthy(lhsCandidate)
if errDecoding != nil {
return nil, errDecoding
return errDecoding
}
rhsTrue, errDecoding := isTruthy(rhs)
if errDecoding != nil {
return nil, errDecoding
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
rhsCandidate := rhsChild.Value.(*CandidateNode)
rhsTrue, errDecoding := isTruthy(rhsCandidate)
if errDecoding != nil {
return errDecoding
}
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
results.PushBack(boolResult)
}
return createBooleanCandidate(lhs, op(lhsTrue, rhsTrue)), nil
}
return nil
}
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
var results = list.New()
if matchingNodes.Len() == 0 {
lhs, err := d.GetMatchingNodes(list.New(), pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err := d.GetMatchingNodes(list.New(), pathNode.Rhs)
if err != nil {
return nil, err
}
return results, performBoolOp(results, lhs, rhs, op)
}
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
err = performBoolOp(results, lhs, rhs, op)
if err != nil {
return nil, err
}
}
return results, nil
}
func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- orOp")
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
func(b1 bool, b2 bool) bool {
return b1 || b2
}))
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
return b1 || b2
})
}
func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- AndOp")
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
func(b1 bool, b2 bool) bool {
return b1 && b2
}))
return booleanOp(d, matchingNodes, pathNode, func(b1 bool, b2 bool) bool {
return b1 && b2
})
}
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {

View File

@@ -55,7 +55,7 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode {
return &CandidateNode{
Document: candidate.Document,
Path: candidate.CreateChildPath(index),
Path: append(candidate.Path, index),
Filename: candidate.Filename,
Node: candidate.Node.Content[index],
}
@@ -67,9 +67,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
}
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
splatted, err := Splat(d, nodeToMap(candidate),
&TraversePreferences{FollowAlias: false, IncludeMapKeys: false})
splatted, err := Splat(d, nodeToMap(candidate))
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatEl.Value.(*CandidateNode).Path = nil

View File

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

View File

@@ -4,7 +4,7 @@ import (
"container/list"
"strings"
yaml "gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
)
type CommentOpPreferences struct {
@@ -17,6 +17,15 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
log.Debugf("AssignComments operator!")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
comment := ""
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
@@ -25,32 +34,8 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
comment := ""
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
log.Debugf("Setting comment of : %v", candidate.GetKey())
if preferences.LineComment {
candidate.Node.LineComment = comment

View File

@@ -13,39 +13,6 @@ var commentOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: cat # single\n",
},
},
{
skipDoc: true,
document: "a: cat\nb: dog",
expression: `.a lineComment=.b`,
expected: []string{
"D0, P[], (doc)::a: cat # dog\nb: dog\n",
},
},
{
skipDoc: true,
document: "a: cat\n---\na: dog",
expression: `.a lineComment |= documentIndex`,
expected: []string{
"D0, P[], (doc)::a: cat # 0\n",
"D1, P[], (doc)::a: dog # 1\n",
},
},
{
description: "Use update assign to perform relative updates",
document: "a: cat\nb: dog",
expression: `.. lineComment |= .`,
expected: []string{
"D0, P[], (!!map)::a: cat # cat\nb: dog # dog\n",
},
},
{
skipDoc: true,
document: "a: cat\nb: dog",
expression: `.. comments |= .`,
expected: []string{
"D0, P[], (!!map)::a: cat # cat\n# cat\n\n# cat\nb: dog # dog\n# dog\n\n# dog\n",
},
},
{
description: "Set head comment",
document: `a: cat`,

View File

@@ -2,67 +2,39 @@ package yqlib
import (
"container/list"
"fmt"
yaml "gopkg.in/yaml.v3"
)
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
// for each lhs, splat the node,
// the intersect it against the rhs expression
// recreate the contents using only the intersection result.
nodesToDelete, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
for el := nodesToDelete.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
deleteImmediateChildOp := &Operation{
OperationType: DeleteImmediateChild,
Value: candidate.Path[len(candidate.Path)-1],
}
deleteImmediateChildOpNode := &PathTreeNode{
Operation: deleteImmediateChildOp,
Rhs: createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]),
}
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
elMap := list.New()
elMap.PushBack(candidate)
nodesToDelete, err := d.GetMatchingNodes(elMap, pathNode.Rhs)
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
if err != nil {
return nil, err
}
}
return matchingNodes, nil
}
func DeleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
parents, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
realNode := UnwrapDoc(candidate.Node)
if err != nil {
return nil, err
}
childPath := pathNode.Operation.Value
log.Debug("childPath to remove %v", childPath)
for el := parents.Front(); el != nil; el = el.Next() {
parent := el.Value.(*CandidateNode)
parentNode := UnwrapDoc(parent.Node)
if parentNode.Kind == yaml.MappingNode {
deleteFromMap(parent, childPath)
} else if parentNode.Kind == yaml.SequenceNode {
deleteFromArray(parent, childPath)
if realNode.Kind == yaml.SequenceNode {
deleteFromArray(candidate, nodesToDelete)
} else if realNode.Kind == yaml.MappingNode {
deleteFromMap(candidate, nodesToDelete)
} 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
}
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
log.Debug("deleteFromMap")
node := UnwrapDoc(candidate.Node)
contents := node.Content
@@ -75,10 +47,15 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
childCandidate := &CandidateNode{
Node: value,
Document: candidate.Document,
Path: candidate.CreateChildPath(key.Value),
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)
@@ -89,7 +66,7 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
node.Content = newContents
}
func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
log.Debug("deleteFromArray")
node := UnwrapDoc(candidate.Node)
contents := node.Content
@@ -98,7 +75,18 @@ func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
for index := 0; index < len(contents); index = index + 1 {
value := contents[index]
shouldDelete := fmt.Sprintf("%v", index) == fmt.Sprintf("%v", childPath)
childCandidate := &CandidateNode{
Node: value,
Document: candidate.Document,
Path: append(candidate.Path, index),
}
shouldDelete := false
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
shouldDelete = true
}
}
if !shouldDelete {
newContents = append(newContents, value)

View File

@@ -13,22 +13,6 @@ var deleteOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: cat}\n",
},
},
{
description: "Delete nested entry in map",
document: `{a: {a1: fred, a2: frood}}`,
expression: `del(.a.a1)`,
expected: []string{
"D0, P[], (doc)::{a: {a2: frood}}\n",
},
},
{
skipDoc: true,
document: `{a: {a1: fred, a2: frood}}`,
expression: `del(.. | select(.=="frood"))`,
expected: []string{
"D0, P[], (!!map)::{a: {a1: fred}}\n",
},
},
{
description: "Delete entry in array",
document: `[1,2,3]`,
@@ -37,14 +21,6 @@ var deleteOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::[1, 3]\n",
},
},
{
description: "Delete nested entry in array",
document: `[{a: cat, b: dog}]`,
expression: `del(.[0].a)`,
expected: []string{
"D0, P[], (doc)::[{b: dog}]\n",
},
},
{
description: "Delete no matches",
document: `{a: cat, b: dog}`,
@@ -61,14 +37,6 @@ var deleteOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{b: dog}\n",
},
},
{
description: "Recursively delete matching keys",
document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
expression: `del(.. | select(has("name")).name)`,
expected: []string{
"D0, P[], (!!map)::{a: {b: {age: 12}}}\n",
},
},
}
func TestDeleteOperatorScenarios(t *testing.T) {

View File

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

View File

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

View File

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

View File

@@ -21,14 +21,6 @@ var fileOperatorScenarios = []expressionScenario{
"D0, P[], (!!int)::0\n",
},
},
{
description: "Get file index alias",
document: `{a: cat}`,
expression: `fi`,
expected: []string{
"D0, P[], (!!int)::0\n",
},
},
}
func TestFileOperatorsScenarios(t *testing.T) {

View File

@@ -85,9 +85,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
var results = list.New()
// shouldn't recurse arrays if appending
prefs := &RecursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
TraversePreferences: &TraversePreferences{FollowAlias: false}}
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays)
if err != nil {
return nil, err
}
@@ -106,6 +104,19 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
return lhs, nil
}
func createTraversalTree(path []interface{}) *PathTreeNode {
if len(path) == 0 {
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
} else if len(path) == 1 {
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
}
return &PathTreeNode{
Operation: &Operation{OperationType: ShortPipe},
Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])}
}
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
@@ -115,7 +126,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
assignmentOp := &Operation{OperationType: AssignAttributes}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = Assign
assignmentOp.UpdateAssign = false
assignmentOp.Preferences = &AssignOpPreferences{false}
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = AddAssign
}

View File

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

View File

@@ -6,16 +6,10 @@ import (
yaml "gopkg.in/yaml.v3"
)
type RecursiveDescentPreferences struct {
TraversePreferences *TraversePreferences
RecurseArray bool
}
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
var results = list.New()
preferences := pathNode.Operation.Preferences.(*RecursiveDescentPreferences)
err := recursiveDecent(d, results, matchMap, preferences)
err := recursiveDecent(d, results, matchMap, true)
if err != nil {
return nil, err
}
@@ -23,7 +17,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
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() {
candidate := el.Value.(*CandidateNode)
@@ -33,14 +27,14 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li
results.PushBack(candidate)
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
(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 {
return err
}
err = recursiveDecent(d, results, children, preferences)
err = recursiveDecent(d, results, children, recurseArray)
if err != nil {
return err
}

View File

@@ -13,14 +13,6 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::{}\n",
},
},
{
skipDoc: true,
document: `{}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{}\n",
},
},
{
skipDoc: true,
document: `[]`,
@@ -29,14 +21,6 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `[]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `cat`,
@@ -47,50 +31,13 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
{
skipDoc: true,
document: `cat`,
expression: `...`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
},
{
description: "Recurse map (values only)",
document: `{a: frog}`,
expression: `..`,
document: `{a: frog}`,
expression: `..`,
expected: []string{
"D0, P[], (!!map)::{a: frog}\n",
"D0, P[a], (!!str)::frog\n",
},
},
{
description: "Recursively find nodes with keys",
document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
expression: `.. | select(has("name"))`,
expected: []string{
"D0, P[a], (!!map)::{name: frog, b: {name: blog, age: 12}}\n",
"D0, P[a b], (!!map)::{name: blog, age: 12}\n",
},
},
{
description: "Recursively find nodes with values",
document: `{a: {nameA: frog, b: {nameB: frog, age: 12}}}`,
expression: `.. | select(. == "frog")`,
expected: []string{
"D0, P[a nameA], (!!str)::frog\n",
"D0, P[a b nameB], (!!str)::frog\n",
},
},
{
description: "Recurse map (values and keys)",
subdescription: "Note that the map key appears in the results",
document: `{a: frog}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: frog}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `{a: {b: apple}}`,
@@ -101,18 +48,6 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[a b], (!!str)::apple\n",
},
},
{
skipDoc: true,
document: `{a: {b: apple}}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: {b: apple}}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!map)::{b: apple}\n",
"D0, P[a b], (!!str)::b\n",
"D0, P[a b], (!!str)::apple\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
@@ -124,17 +59,6 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[2], (!!int)::3\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[1, 2, 3]\n",
"D0, P[0], (!!int)::1\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!int)::3\n",
},
},
{
skipDoc: true,
document: `[{a: cat},2,true]`,
@@ -147,19 +71,6 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[2], (!!bool)::true\n",
},
},
{
skipDoc: true,
document: `[{a: cat},2,true]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[{a: cat}, 2, true]\n",
"D0, P[0], (!!map)::{a: cat}\n",
"D0, P[0 a], (!!str)::a\n",
"D0, P[0 a], (!!str)::cat\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!bool)::true\n",
},
},
{
description: "Aliases are not traversed",
document: `{a: &cat {c: frog}, b: *cat}`,
@@ -168,20 +79,6 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
},
},
{
skipDoc: true,
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!map)::&cat {c: frog}\n",
"D0, P[a c], (!!str)::c\n",
"D0, P[a c], (!!str)::frog\n",
"D0, P[b], (!!str)::b\n",
"D0, P[b], (alias)::*cat\n",
},
},
{
description: "Merge docs are not traversed",
document: mergeDocSample,
@@ -190,14 +87,6 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobar | [...]`,
expected: []string{
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- c\n- foobar_c\n- !!merge <<\n- *foo\n- thing\n- foobar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
@@ -211,22 +100,6 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList | ...`,
expected: []string{
"D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n",
"D0, P[foobarList b], (!!str)::b\n",
"D0, P[foobarList b], (!!str)::foobarList_b\n",
"D0, P[foobarList <<], (!!merge)::!!merge <<\n",
"D0, P[foobarList <<], (!!seq)::[*foo, *bar]\n",
"D0, P[foobarList << 0], (alias)::*foo\n",
"D0, P[foobarList << 1], (alias)::*bar\n",
"D0, P[foobarList c], (!!str)::c\n",
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
}
func TestRecursiveDescentOperatorScenarios(t *testing.T) {

View File

@@ -4,46 +4,39 @@ import (
"container/list"
"fmt"
yaml "gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
)
func parseStyle(customStyle string) (yaml.Style, error) {
if customStyle == "tagged" {
return yaml.TaggedStyle, nil
} else if customStyle == "double" {
return yaml.DoubleQuotedStyle, nil
} else if customStyle == "single" {
return yaml.SingleQuotedStyle, nil
} else if customStyle == "literal" {
return yaml.LiteralStyle, nil
} else if customStyle == "folded" {
return yaml.FoldedStyle, nil
} else if customStyle == "flow" {
return yaml.FlowStyle, nil
} else if customStyle != "" {
return 0, fmt.Errorf("Unknown style %v", customStyle)
}
return 0, nil
}
func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignStyleOperator: %v")
var style yaml.Style
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil {
return nil, err
}
}
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
customStyle := ""
if rhs.Front() != nil {
customStyle = rhs.Front().Value.(*CandidateNode).Node.Value
}
var style yaml.Style
if customStyle == "tagged" {
style = yaml.TaggedStyle
} else if customStyle == "double" {
style = yaml.DoubleQuotedStyle
} else if customStyle == "single" {
style = yaml.SingleQuotedStyle
} else if customStyle == "literal" {
style = yaml.LiteralStyle
} else if customStyle == "folded" {
style = yaml.FoldedStyle
} else if customStyle == "flow" {
style = yaml.FlowStyle
} else if customStyle != "" {
return nil, fmt.Errorf("Unknown style %v", customStyle)
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
@@ -53,20 +46,6 @@ func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting style of : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil {
return nil, err
}
}
}
candidate.Node.Style = style
}

View File

@@ -21,22 +21,6 @@ var styleOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::a: \"cat\"\nb: \"5\"\nc: \"3.2\"\ne: \"true\"\n",
},
},
{
description: "Set double quote style on map keys too",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `... style="double"`,
expected: []string{
"D0, P[], (!!map)::\"a\": \"cat\"\n\"b\": \"5\"\n\"c\": \"3.2\"\n\"e\": \"true\"\n",
},
},
{
skipDoc: true,
document: "bing: &foo frog\na:\n c: cat\n <<: [*foo]",
expression: `(... | select(tag=="!!str")) style="single"`,
expected: []string{
"D0, P[], (!!map)::'bing': &foo 'frog'\n'a':\n 'c': 'cat'\n !!merge <<: [*foo]\n",
},
},
{
description: "Set single quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
@@ -86,22 +70,14 @@ e: >-
},
},
{
description: "Reset style - or pretty print",
subdescription: "Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.",
document: `{a: cat, "b": 5, 'c': 3.2, "e": true}`,
expression: `... style=""`,
description: "Pretty print",
subdescription: "Set empty (default) quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style=""`,
expected: []string{
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
},
},
{
description: "Set style relatively with assign-update",
document: `{a: single, b: double}`,
expression: `.[] style |= .`,
expected: []string{
"D0, P[], (doc)::{a: 'single', b: \"double\"}\n",
},
},
{
skipDoc: true,
document: `{a: cat, b: double}`,

View File

@@ -3,23 +3,21 @@ package yqlib
import (
"container/list"
yaml "gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
)
func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignTagOperator: %v")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
tag := ""
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
@@ -31,16 +29,6 @@ func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting tag of : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Tag = tag
}
@@ -54,7 +42,7 @@ func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pa
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: UnwrapDoc(candidate.Node).Tag, Tag: "!!str"}
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Tag, Tag: "!!str"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}

View File

@@ -19,37 +19,13 @@ var tagOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`,
expression: `tag`,
expected: []string{
"D0, P[], (!!str)::'!!map'\n",
},
},
{
description: "Set custom tag",
document: `{a: str}`,
expression: `.a tag = "!!mikefarah"`,
expected: []string{
"D0, P[], (doc)::{a: !!mikefarah str}\n",
},
},
{
description: "Find numbers and convert them to strings",
description: "Convert numbers to strings",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
expected: []string{
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
},
},
{
skipDoc: true,
document: `{a: "!!frog", b: "!!customTag"}`,
expression: `.[] tag |= .`,
expected: []string{
"D0, P[], (doc)::{a: !!frog \"!!frog\", b: !!customTag \"!!customTag\"}\n",
},
},
}
func TestTagOperatorScenarios(t *testing.T) {

View File

@@ -1,39 +1,46 @@
package yqlib
import (
"container/list"
"fmt"
"strconv"
"container/list"
"github.com/elliotchance/orderedmap"
yaml "gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
)
type TraversePreferences struct {
FollowAlias bool
IncludeMapKeys bool
DontFollowAlias bool
}
func Splat(d *dataTreeNavigator, matches *list.List, prefs *TraversePreferences) (*list.List, error) {
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) {
preferences := &TraversePreferences{DontFollowAlias: true}
splatOperation := &Operation{OperationType: TraversePath, Value: "[]", Preferences: preferences}
splatTreeNode := &PathTreeNode{Operation: splatOperation}
return TraversePathOperator(d, matches, splatTreeNode)
}
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- Traversing")
var matchingNodeMap = list.New()
var newNodes []*CandidateNode
var err error
for el := matchMap.Front(); el != nil; el = el.Next() {
newNodes, err := traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
newNodes, err = traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
if err != nil {
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
for _, n := range newNodes {
matchingNodeMap.PushBack(n)
}
}
return matchingNodeMap, nil
}
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
log.Debug("Traversing %v", NodeToString(matchingNode))
value := matchingNode.Node
@@ -54,8 +61,34 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
switch value.Kind {
case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2)
prefs := &TraversePreferences{FollowAlias: true}
return traverseMap(matchingNode, operation.StringValue, prefs, false)
var newMatches = orderedmap.NewOrderedMap()
err := traverseMap(newMatches, matchingNode, operation)
if err != nil {
return nil, err
}
if newMatches.Len() == 0 {
//no matches, create one automagically
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
node := matchingNode.Node
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: operation.StringValue}, valueNode)
candidateNode := &CandidateNode{
Node: valueNode,
Path: append(matchingNode.Path, operation.StringValue),
Document: matchingNode.Document,
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
arrayMatches := make([]*CandidateNode, newMatches.Len())
i := 0
for el := newMatches.Front(); el != nil; el = el.Next() {
arrayMatches[i] = el.Value.(*CandidateNode)
i++
}
return arrayMatches, nil
case yaml.SequenceNode:
log.Debug("its a sequence of %v things!", len(value.Content))
@@ -68,118 +101,105 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
case yaml.DocumentNode:
log.Debug("digging into doc node")
return traverse(d, &CandidateNode{
Node: matchingNode.Node.Content[0],
Filename: matchingNode.Filename,
FileIndex: matchingNode.FileIndex,
Document: matchingNode.Document}, operation)
Node: matchingNode.Node.Content[0],
Document: matchingNode.Document}, operation)
default:
return list.New(), nil
return nil, nil
}
}
func TraverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
// rhs is a collect expression that will yield indexes to retreive of the arrays
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
prefs := &TraversePreferences{FollowAlias: true}
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs)
func keyMatches(key *yaml.Node, pathNode *Operation) bool {
return pathNode.Value == "[]" || Match(key.Value, pathNode.StringValue)
}
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
var matchingNodeMap = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs)
if err != nil {
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
func traverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, operation *Operation) error {
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
//TODO ALIASES, auto creation?
node := candidate.Node
followAlias := true
if operation.Preferences != nil {
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
}
return matchingNodeMap, nil
}
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
key := contents[index]
value := contents[index+1]
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
node := matchingNode.Node
if node.Tag == "!!null" {
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
// auto vivification, make it into an empty array
node.Tag = ""
node.Kind = yaml.SequenceNode
}
if node.Kind == yaml.AliasNode {
matchingNode.Node = node.Alias
return traverseArrayIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.SequenceNode {
return traverseArrayWithIndices(matchingNode, indicesToTraverse)
} else if node.Kind == yaml.MappingNode {
return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.DocumentNode {
return traverseArrayIndices(&CandidateNode{
Node: matchingNode.Node.Content[0],
Filename: matchingNode.Filename,
FileIndex: matchingNode.FileIndex,
Document: matchingNode.Document}, indicesToTraverse, prefs)
}
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
return list.New(), nil
}
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
if len(indices) == 0 {
return traverseMap(candidate, "", prefs, true)
}
var matchingNodeMap = list.New()
for _, indexNode := range indices {
log.Debug("traverseMapWithIndices: %v", indexNode.Value)
newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false)
if err != nil {
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
}
return matchingNodeMap, nil
}
func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) {
log.Debug("traverseArrayWithIndices")
var newMatches = list.New()
node := UnwrapDoc(candidate.Node)
if len(indices) == 0 {
log.Debug("splatting")
var index int64
for index = 0; index < int64(len(node.Content)); index = index + 1 {
newMatches.PushBack(&CandidateNode{
log.Debug("checking %v (%v)", key.Value, key.Tag)
//skip the 'merge' tag, find a direct match first
if key.Tag == "!!merge" && followAlias {
log.Debug("Merge anchor")
err := traverseMergeAnchor(newMatches, candidate, value, operation)
if err != nil {
return err
}
} else if keyMatches(key, operation) {
log.Debug("MATCHED")
candidateNode := &CandidateNode{
Node: value,
Path: append(candidate.Path, key.Value),
Document: candidate.Document,
Path: candidate.CreateChildPath(index),
Node: node.Content[index],
})
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
}
return nil
}
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, operation *Operation) error {
switch value.Kind {
case yaml.AliasNode:
candidateNode := &CandidateNode{
Node: value.Alias,
Path: originalCandidate.Path,
Document: originalCandidate.Document,
}
return traverseMap(newMatches, candidateNode, operation)
case yaml.SequenceNode:
for _, childValue := range value.Content {
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, operation)
if err != nil {
return err
}
}
}
return nil
}
func traverseArray(candidate *CandidateNode, operation *Operation) ([]*CandidateNode, error) {
log.Debug("operation Value %v", operation.Value)
if operation.Value == "[]" {
var contents = candidate.Node.Content
var newMatches = make([]*CandidateNode, len(contents))
var index int64
for index = 0; index < int64(len(contents)); index = index + 1 {
newMatches[index] = &CandidateNode{
Document: candidate.Document,
Path: append(candidate.Path, index),
Node: contents[index],
}
}
return newMatches, nil
}
for _, indexNode := range indices {
log.Debug("traverseArrayWithIndices: '%v'", indexNode.Value)
index, err := strconv.ParseInt(indexNode.Value, 10, 64)
if err != nil {
return nil, fmt.Errorf("Cannot index array with '%v' (%v)", indexNode.Value, err)
}
switch operation.Value.(type) {
case int64:
index := operation.Value.(int64)
indexToUse := index
contentLength := int64(len(node.Content))
contentLength := int64(len(candidate.Node.Content))
for contentLength <= index {
node.Content = append(node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
contentLength = int64(len(node.Content))
candidate.Node.Content = append(candidate.Node.Content, &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"})
contentLength = int64(len(candidate.Node.Content))
}
if indexToUse < 0 {
@@ -190,115 +210,14 @@ func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*
return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
}
newMatches.PushBack(&CandidateNode{
Node: node.Content[indexToUse],
return []*CandidateNode{&CandidateNode{
Node: candidate.Node.Content[indexToUse],
Document: candidate.Document,
Path: candidate.CreateChildPath(index),
})
}
return newMatches, nil
}
func keyMatches(key *yaml.Node, wantedKey string) bool {
return Match(key.Value, wantedKey)
}
func traverseMap(matchingNode *CandidateNode, key string, prefs *TraversePreferences, splat bool) (*list.List, error) {
var newMatches = orderedmap.NewOrderedMap()
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
if err != nil {
return nil, err
Path: append(candidate.Path, index),
}}, nil
default:
log.Debug("argument not an int (%v), no array matches", operation.Value)
return nil, nil
}
if newMatches.Len() == 0 {
//no matches, create one automagically
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
node := matchingNode.Node
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode)
candidateNode := &CandidateNode{
Node: valueNode,
Path: append(matchingNode.Path, key),
Document: matchingNode.Document,
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
results := list.New()
i := 0
for el := newMatches.Front(); el != nil; el = el.Next() {
results.PushBack(el.Value)
i++
}
return results, nil
}
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs *TraversePreferences, splat bool) error {
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
node := candidate.Node
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
key := contents[index]
value := contents[index+1]
log.Debug("checking %v (%v)", key.Value, key.Tag)
//skip the 'merge' tag, find a direct match first
if key.Tag == "!!merge" && prefs.FollowAlias {
log.Debug("Merge anchor")
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
if err != nil {
return err
}
} else if splat || keyMatches(key, wantedKey) {
log.Debug("MATCHED")
if prefs.IncludeMapKeys {
candidateNode := &CandidateNode{
Node: key,
Path: candidate.CreateChildPath(key.Value),
Document: candidate.Document,
}
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
}
candidateNode := &CandidateNode{
Node: value,
Path: candidate.CreateChildPath(key.Value),
Document: candidate.Document,
}
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
}
return nil
}
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *TraversePreferences, splat bool) error {
switch value.Kind {
case yaml.AliasNode:
candidateNode := &CandidateNode{
Node: value.Alias,
Path: originalCandidate.Path,
Document: originalCandidate.Document,
}
return doTraverseMap(newMatches, candidateNode, wantedKey, prefs, splat)
case yaml.SequenceNode:
for _, childValue := range value.Content {
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey, prefs, splat)
if err != nil {
return err
}
}
}
return nil
}
func traverseArray(candidate *CandidateNode, operation *Operation) (*list.List, error) {
log.Debug("operation Value %v", operation.Value)
indices := []*yaml.Node{&yaml.Node{Value: operation.StringValue}}
return traverseArrayWithIndices(candidate, indices)
}

View File

@@ -65,7 +65,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
{
skipDoc: true,
document: ``,
document: `{}`,
expression: `.[1].a`,
expected: []string{
"D0, P[1 a], (!!null)::null\n",
@@ -137,15 +137,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
description: "Traversing aliases with splat",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b[]`,
expected: []string{
"D0, P[b c], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b.[]`,
expression: `.b.[]`,
expected: []string{
"D0, P[b c], (!!str)::frog\n",
},
@@ -158,6 +150,12 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[b c], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
expression: `.b`,
expected: []string{},
},
{
description: "Traversing arrays by index",
document: `[1,2,3]`,
@@ -217,17 +215,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
description: "Splatting merge anchors",
document: mergeDocSample,
expression: `.foobar[]`,
expected: []string{
"D0, P[foobar c], (!!str)::foo_c\n",
"D0, P[foobar a], (!!str)::foo_a\n",
"D0, P[foobar thing], (!!str)::foobar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobar.[]`,
expression: `.foobar.[]`,
expected: []string{
"D0, P[foobar c], (!!str)::foo_c\n",
"D0, P[foobar a], (!!str)::foo_a\n",
@@ -278,7 +266,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
description: "Splatting merge anchor lists",
document: mergeDocSample,
expression: `.foobarList[]`,
expression: `.foobarList.[]`,
expected: []string{
"D0, P[foobarList b], (!!str)::bar_b\n",
"D0, P[foobarList a], (!!str)::foo_a\n",
@@ -286,123 +274,6 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList.[]`,
expected: []string{
"D0, P[foobarList b], (!!str)::bar_b\n",
"D0, P[foobarList a], (!!str)::foo_a\n",
"D0, P[foobarList thing], (!!str)::bar_thing\n",
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
{
skipDoc: true,
document: `[a,b,c]`,
expression: `.[]`,
expected: []string{
"D0, P[0], (!!str)::a\n",
"D0, P[1], (!!str)::b\n",
"D0, P[2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `[a,b,c]`,
expression: `[]`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[0]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
},
},
{
description: "Select multiple indices",
document: `{a: [a,b,c]}`,
expression: `.a[0, 2]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a.[0, 2]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[-1]`,
expected: []string{
"D0, P[a -1], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a.[-1]`,
expected: []string{
"D0, P[a -1], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[-2]`,
expected: []string{
"D0, P[a -2], (!!str)::b\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a.[-2]`,
expected: []string{
"D0, P[a -2], (!!str)::b\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a.[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a | .[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
"D0, P[a 2], (!!str)::c\n",
},
},
}
func TestTraversePathOperatorScenarios(t *testing.T) {

View File

@@ -36,22 +36,13 @@ var valueOperatorScenarios = []expressionScenario{
expected: []string{
"D0, P[], (!!float)::5e-10\n",
},
},
{
}, {
document: ``,
expression: `"cat"`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
},
{
document: ``,
expression: `"frog jumps"`,
expected: []string{
"D0, P[], (!!str)::frog jumps\n",
},
},
{
}, {
document: ``,
expression: `"1.3"`,
expected: []string{

View File

@@ -2,7 +2,6 @@ package yqlib
import (
"container/list"
"fmt"
"gopkg.in/yaml.v3"
)
@@ -34,15 +33,3 @@ func nodeToMap(candidate *CandidateNode) *list.List {
elMap.PushBack(candidate)
return elMap
}
func createTraversalTree(path []interface{}) *PathTreeNode {
if len(path) == 0 {
return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
} else if len(path) == 1 {
return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
}
return &PathTreeNode{
Operation: &Operation{OperationType: ShortPipe},
Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])}
}

View File

@@ -18,7 +18,6 @@ type expressionScenario struct {
description string
subdescription string
document string
document2 string
expression string
expected []string
skipDoc bool
@@ -39,17 +38,9 @@ func testScenario(t *testing.T, s *expressionScenario) {
if s.document != "" {
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
if err != nil {
t.Error(err, s.document, s.expression)
t.Error(err, s.document)
return
}
if s.document2 != "" {
moreInputs, err := readDocuments(strings.NewReader(s.document2), "another.yml", 1)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
inputs.PushBackList(moreInputs)
}
} else {
candidateNode := &CandidateNode{
Document: 0,
@@ -64,7 +55,7 @@ func testScenario(t *testing.T, s *expressionScenario) {
results, err = treeNavigator.GetMatchingNodes(inputs, node)
if err != nil {
t.Error(fmt.Errorf("%v: %v", err, s.expression))
t.Error(err)
return
}
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
@@ -101,7 +92,7 @@ func copyFromHeader(title string, out *os.File) error {
return err
}
func formatYaml(yaml string, filename string) string {
func formatYaml(yaml string) string {
var output bytes.Buffer
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
@@ -110,7 +101,7 @@ func formatYaml(yaml string, filename string) string {
panic(err)
}
streamEvaluator := NewStreamEvaluator()
err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer)
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(yaml), node, printer)
if err != nil {
panic(err)
}
@@ -137,116 +128,63 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
for _, s := range scenarios {
if !s.skipDoc {
documentScenario(t, w, s)
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
formattedDoc := ""
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document + "\n"
} else {
formattedDoc = formatYaml(s.document)
}
//TODO: pretty here
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
writeOrPanic(w, "then\n")
if s.expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval '%v' sample.yml\n```\n", s.expression))
} else {
writeOrPanic(w, "```bash\nyq eval sample.yml\n```\n")
}
} else {
writeOrPanic(w, "Running\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval --null-input '%v'\n```\n", s.expression))
}
writeOrPanic(w, "will output\n")
var output bytes.Buffer
var err error
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
streamEvaluator := NewStreamEvaluator()
if s.document != "" {
node, err := treeCreator.ParsePath(s.expression)
if err != nil {
t.Error(err)
}
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
if err != nil {
t.Error(err)
}
} else {
err = streamEvaluator.EvaluateNew(s.expression, printer)
if err != nil {
t.Error(err)
}
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
}
}
w.Flush()
}
func documentScenario(t *testing.T, w *bufio.Writer, s expressionScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
formattedDoc, formattedDoc2 := documentInput(w, s)
writeOrPanic(w, "will output\n")
documentOutput(t, w, s, formattedDoc, formattedDoc2)
}
func documentInput(w *bufio.Writer, s expressionScenario) (string, string) {
formattedDoc := ""
formattedDoc2 := ""
command := "eval"
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document + "\n"
} else {
formattedDoc = formatYaml(s.document, "sample.yml")
}
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
files := "sample.yml"
if s.document2 != "" {
if s.dontFormatInputForDoc {
formattedDoc2 = s.document2 + "\n"
} else {
formattedDoc2 = formatYaml(s.document2, "another.yml")
}
writeOrPanic(w, "And another sample another.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc2))
files = "sample.yml another.yml"
command = "eval-all"
}
writeOrPanic(w, "then\n")
if s.expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v '%v' %v\n```\n", command, s.expression, files))
} else {
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v %v\n```\n", command, files))
}
} else {
writeOrPanic(w, "Running\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v --null-input '%v'\n```\n", command, s.expression))
}
return formattedDoc, formattedDoc2
}
func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formattedDoc string, formattedDoc2 string) {
var output bytes.Buffer
var err error
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
node, err := treeCreator.ParsePath(s.expression)
if err != nil {
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
return
}
inputs := list.New()
if s.document != "" {
inputs, err = readDocuments(strings.NewReader(formattedDoc), "sample.yml", 0)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
if s.document2 != "" {
moreInputs, err := readDocuments(strings.NewReader(formattedDoc2), "another.yml", 1)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
inputs.PushBackList(moreInputs)
}
} else {
candidateNode := &CandidateNode{
Document: 0,
Filename: "",
Node: &yaml.Node{Tag: "!!null"},
FileIndex: 0,
}
inputs.PushBack(candidateNode)
}
results, err := treeNavigator.GetMatchingNodes(inputs, node)
if err != nil {
t.Error(err, s.expression)
}
err = printer.PrintResults(results)
if err != nil {
t.Error(err, s.expression)
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
}

View File

@@ -17,35 +17,20 @@ var pathTests = []struct {
append(make([]interface{}, 0), "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
},
{
`.[]`,
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.a[]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"),
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"),
},
{
`.a.[]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.a[0]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.a.[0]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]"),
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE"),
},
{
`.a[].c`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "c"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "c", "SHORT_PIPE"),
append(make([]interface{}, 0), "a", "SHORT_PIPE", "[]", "SHORT_PIPE", "c"),
append(make([]interface{}, 0), "a", "[]", "SHORT_PIPE", "c", "SHORT_PIPE"),
},
{
`[3]`,
@@ -59,18 +44,18 @@ var pathTests = []struct {
},
{
`.a | .[].b == "apple"`,
append(make([]interface{}, 0), "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
append(make([]interface{}, 0), "a", "PIPE", "[]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
},
{
`(.a | .[].b) == "apple"`,
append(make([]interface{}, 0), "(", "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
append(make([]interface{}, 0), "(", "a", "PIPE", "[]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "[]", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
},
{
`.[] | select(. == "*at")`,
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
append(make([]interface{}, 0), "[]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
append(make([]interface{}, 0), "[]", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
},
{
`[true]`,
@@ -104,8 +89,8 @@ var pathTests = []struct {
},
{
`{.a: .c, .b.[]: .f.g.[]}`,
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "}"),
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "SHORT_PIPE", "[]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "SHORT_PIPE", "[]", "}"),
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "[]", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`explode(.a.b)`,
@@ -137,11 +122,7 @@ var pathTests = []struct {
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
},
{
`. lineComment |= "str"`,
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
},
{
`.a.b tag="!!str"`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"),

View File

@@ -1,7 +1,6 @@
package yqlib
import (
"fmt"
"strconv"
lex "github.com/timtadh/lexmachine"
@@ -22,7 +21,7 @@ const (
CloseCollect
OpenCollectObject
CloseCollectObject
TraverseArrayCollect
SplatOrEmptyCollect
)
type Token struct {
@@ -49,9 +48,8 @@ func (t *Token) toString() string {
return "{"
} else if t.TokenType == CloseCollectObject {
return "}"
} else if t.TokenType == TraverseArrayCollect {
return ".["
} else if t.TokenType == SplatOrEmptyCollect {
return "[]?"
} else {
return "NFI"
}
@@ -92,15 +90,6 @@ func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.A
return opTokenWithPrefs(opType, assignOpType, nil)
}
func assignOpToken(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignOpToken %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{OperationType: Assign, Value: Assign.Type, StringValue: value, UpdateAssign: updateAssign}
return &Token{TokenType: OperationToken, Operation: op}, nil
}
}
func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
@@ -114,21 +103,6 @@ func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preference
}
}
func assignAllCommentsOp(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignAllCommentsOp %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{
OperationType: AssignComment,
Value: AssignComment.Type,
StringValue: value,
UpdateAssign: updateAssign,
Preferences: &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
}
return &Token{TokenType: OperationToken, Operation: op}, nil
}
}
func literalToken(pType TokenType, checkForPost bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
return &Token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil
@@ -139,6 +113,23 @@ func unwrap(value string) string {
return value[1 : len(value)-1]
}
func arrayIndextoken(precedingDot bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var numberString = string(m.Bytes)
startIndex := 1
if precedingDot {
startIndex = 2
}
numberString = numberString[startIndex : len(numberString)-1]
var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint
if errParsingInt != nil {
return nil, errParsingInt
}
op := &Operation{OperationType: TraversePath, Value: number, StringValue: numberString}
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
}
}
func numberValue() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var numberString = string(m.Bytes)
@@ -196,12 +187,8 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\(`), literalToken(OpenBracket, false))
lexer.Add([]byte(`\)`), literalToken(CloseBracket, true))
lexer.Add([]byte(`\.\[`), literalToken(TraverseArrayCollect, false))
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(RecursiveDescent, nil, &RecursiveDescentPreferences{RecurseArray: true,
TraversePreferences: &TraversePreferences{FollowAlias: false, IncludeMapKeys: false}}))
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(RecursiveDescent, nil, &RecursiveDescentPreferences{RecurseArray: true,
TraversePreferences: &TraversePreferences{FollowAlias: false, IncludeMapKeys: true}}))
lexer.Add([]byte(`\.\[\]`), pathToken(false))
lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent))
lexer.Add([]byte(`,`), opToken(Union))
lexer.Add([]byte(`:\s*`), opToken(CreateMap))
@@ -213,19 +200,14 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`or`), opToken(Or))
lexer.Add([]byte(`and`), opToken(And))
lexer.Add([]byte(`not`), opToken(Not))
lexer.Add([]byte(`\/\/`), opToken(Alternative))
lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))
lexer.Add([]byte(`di`), opToken(GetDocumentIndex))
lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle))
lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag))
lexer.Add([]byte(`anchor`), opAssignableToken(GetAnchor, AssignAnchor))
lexer.Add([]byte(`alias`), opAssignableToken(GetAlias, AssignAlias))
lexer.Add([]byte(`filename`), opToken(GetFilename))
lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex))
lexer.Add([]byte(`fi`), opToken(GetFileIndex))
lexer.Add([]byte(`path`), opToken(GetPath))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true}))
@@ -234,17 +216,18 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, nil, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true}))
lexer.Add([]byte(`collect`), opToken(Collect))
lexer.Add([]byte(`\s*==\s*`), opToken(Equals))
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
lexer.Add([]byte(`\s*=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{false}))
lexer.Add([]byte(`del`), opToken(DeleteChild))
lexer.Add([]byte(`\s*\|=\s*`), assignOpToken(true))
lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true}))
lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true))
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
@@ -265,7 +248,9 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`[Nn][Uu][Ll][Ll]`), nullValue())
lexer.Add([]byte(`~`), nullValue())
lexer.Add([]byte(`"[^"]*"`), stringValue(true))
lexer.Add([]byte(`"[^ "]*"`), stringValue(true))
lexer.Add([]byte(`\[\]`), literalToken(SplatOrEmptyCollect, true))
lexer.Add([]byte(`\[`), literalToken(OpenCollect, false))
lexer.Add([]byte(`\]`), literalToken(CloseCollect, true))
@@ -303,7 +288,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
scanner, err := p.lexer.Scanner([]byte(path))
if err != nil {
return nil, fmt.Errorf("Parsing expression: %v", err)
return nil, err
}
var tokens []*Token
for tok, err, eof := scanner.Next(); !eof; tok, err, eof = scanner.Next() {
@@ -314,7 +299,7 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
tokens = append(tokens, token)
}
if err != nil {
return nil, fmt.Errorf("Parsing expression: %v", err)
return nil, err
}
}
var postProcessedTokens = make([]*Token, 0)
@@ -335,23 +320,30 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTokens []*Token) (tokensAccum []*Token, skipNextToken bool) {
skipNextToken = false
token := tokens[index]
if token.TokenType == SplatOrEmptyCollect {
if index > 0 && tokens[index-1].TokenType == OperationToken &&
tokens[index-1].Operation.OperationType == TraversePath {
// must be a splat without a preceding dot , e.g. .a[]
// lets put a pipe in front of it, and convert it to a traverse "[]" token
pipeOp := &Operation{OperationType: ShortPipe, Value: "PIPE"}
if token.TokenType == TraverseArrayCollect {
//need to put a traverse array then a collect token
// do this by adding traverse then converting token to collect
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: pipeOp})
op := &Operation{OperationType: TraverseArray, StringValue: "TRAVERSE_ARRAY"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
token = &Token{TokenType: OpenCollect}
traverseOp := &Operation{OperationType: TraversePath, Value: "[]", StringValue: "[]"}
token = &Token{TokenType: OperationToken, Operation: traverseOp, CheckForPostTraverse: true}
} else {
// gotta be a collect empty array, we need to split this into two tokens
// one OpenCollect, the other CloseCollect
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OpenCollect})
token = &Token{TokenType: CloseCollect, CheckForPostTraverse: true}
}
}
if index != len(tokens)-1 && token.AssignOperation != nil &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == Assign {
token.Operation = token.AssignOperation
token.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
skipNextToken = true
}
@@ -363,21 +355,5 @@ func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTok
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
}
if index != len(tokens)-1 && token.CheckForPostTraverse &&
tokens[index+1].TokenType == OpenCollect {
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
op = &Operation{OperationType: TraverseArray}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
}
if index != len(tokens)-1 && token.CheckForPostTraverse &&
tokens[index+1].TokenType == TraverseArrayCollect {
op := &Operation{OperationType: ShortPipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
}
return postProcessedTokens, skipNextToken
}

View File

@@ -1,9 +1,6 @@
package yqlib
import (
"fmt"
"strings"
)
import "fmt"
var myPathTokeniser = NewPathTokeniser()
var myPathPostfixer = NewPathPostFixer()
@@ -52,16 +49,10 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*Operation) (*PathTreeNod
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
@@ -71,7 +62,7 @@ func (p *pathTreeCreator) CreatePathTree(postFixPath []*Operation) (*PathTreeNod
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 nil, fmt.Errorf("expected stack to have 1 thing but its %v", stack)
}
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 := treeCreator.ParsePath("=")
test.AssertResultComplex(t, "'=' expects 2 args but there is 0", err.Error())
}
func TestPathTreeOneLhsArgsForTwoArgOp(t *testing.T) {
_, err := treeCreator.ParsePath(".a =")
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
}
func TestPathTreeOneRhsArgsForTwoArgOp(t *testing.T) {
_, err := treeCreator.ParsePath("= .a")
test.AssertResultComplex(t, "'=' expects 2 args but there is 1", err.Error())
}
func TestPathTreeTwoArgsForTwoArgOp(t *testing.T) {
_, err := treeCreator.ParsePath(".a = .b")
test.AssertResultComplex(t, nil, err)
}
func TestPathTreeNoArgsForOneArgOp(t *testing.T) {
_, err := treeCreator.ParsePath("explode")
test.AssertResultComplex(t, "'explode' expects 1 arg but received none", err.Error())
}
func TestPathTreeOneArgForOneArgOp(t *testing.T) {
_, err := treeCreator.ParsePath("explode(.)")
test.AssertResultComplex(t, nil, err)
}
func TestPathTreeExtraArgs(t *testing.T) {
_, err := treeCreator.ParsePath("sortKeys(.) explode(.)")
test.AssertResultComplex(t, "expected end of expression but found 'explode', please check expression syntax", err.Error())
}

View File

@@ -22,7 +22,6 @@ type resultsPrinter struct {
writer io.Writer
firstTimePrinting bool
previousDocIndex uint
previousFileIndex int
printedMatches bool
}
@@ -90,20 +89,19 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
return nil
}
if p.firstTimePrinting {
node := matchingNodes.Front().Value.(*CandidateNode)
p.previousDocIndex = node.Document
p.previousFileIndex = node.FileIndex
p.previousDocIndex = matchingNodes.Front().Value.(*CandidateNode).Document
p.firstTimePrinting = false
}
for el := matchingNodes.Front(); el != nil; el = el.Next() {
mappedDoc := el.Value.(*CandidateNode)
log.Debug("-- print sep logic: p.firstTimePrinting: %v, previousDocIndex: %v, mappedDoc.Document: %v, printDocSeparators: %v", p.firstTimePrinting, p.previousDocIndex, mappedDoc.Document, p.printDocSeparators)
if (p.previousDocIndex != mappedDoc.Document || p.previousFileIndex != mappedDoc.FileIndex) && p.printDocSeparators {
if (p.previousDocIndex != mappedDoc.Document) && p.printDocSeparators {
log.Debug("-- writing doc sep")
if err := p.writeString(bufferedWriter, "---\n"); err != nil {
return err
}
}
if err := p.printNode(mappedDoc.Node, bufferedWriter); err != nil {

View File

@@ -52,53 +52,7 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) {
writer.Flush()
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleFilesInSequence(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}
el := inputs.Front()
elNode := el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 0
sample1 := nodeToMap(elNode)
el = el.Next()
elNode = el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 1
sample2 := nodeToMap(elNode)
el = el.Next()
elNode = el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 2
sample3 := nodeToMap(elNode)
err = printer.PrintResults(sample1)
if err != nil {
panic(err)
}
err = printer.PrintResults(sample2)
if err != nil {
panic(err)
}
err = printer.PrintResults(sample3)
if err != nil {
panic(err)
}
writer.Flush()
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleDocsInSinglePrint(t *testing.T) {

View File

@@ -1,11 +1,15 @@
- increment version in version.go
- increment version in snapcraft.yaml
- increment version in github-action/Dockerfile
- make sure local build passes
- commit
- tag git with same version number
- commit vX tag - this will trigger github actions
- use github actions to publish docker and make github release
- check github updated yq action in marketplace
- make sure local build passes
- push tag to git
- git push --tags
- make local xcompile (builds binaries for all platforms)
- git release
./scripts/release.sh
./scripts/upload.sh
- snapcraft
- will auto create a candidate, test it works then promote
@@ -26,7 +30,7 @@
- docker
- build and push latest and new version tag
- docker build . -t mikefarah/yq:latest -t mikefarah/yq:3 -t mikefarah/yq:3.X
- docker build . -t mikefarah/yq:latest -t mikefarah/yq:VERSION
- debian package
- ensure you get all vendor dependencies before packaging

View File

@@ -3,12 +3,7 @@
set -o errexit
set -o pipefail
if command -v golangci-lint &> /dev/null
then
golangci-lint run --timeout=5m
else
./bin/golangci-lint run --timeout=5m
fi
./bin/golangci-lint run
# ./bin/golangci-lint \
# --tests \

View File

@@ -2,4 +2,3 @@
find . \( -path ./vendor \) -prune -o -name "*.go" -exec goimports -w {} \;
go mod tidy
go mod vendor

9
scripts/publish-docker.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -ex
VERSION="$(git describe --tags --abbrev=0)"
docker build \
--target production \
--build-arg VERSION=${VERSION} \
-t mikefarah/yq:latest \
-t mikefarah/yq:${VERSION} \
.

21
scripts/release.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
set -ex
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
CURRENT="$(git describe --tags --abbrev=0)"
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
OWNER="mikefarah"
REPO="yq"
release() {
github-release release \
--user "$OWNER" \
--draft \
--repo "$REPO" \
--tag "$CURRENT"
}
release

28
scripts/upload.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -ex
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
CURRENT="$(git describe --tags --abbrev=0)"
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
OWNER="mikefarah"
REPO="yq"
upload() {
mkdir -p ./build-done
while IFS= read -r -d $'\0'; do
file=$REPLY
BINARY=$(basename "${file}")
echo "--> ${BINARY}"
github-release upload \
--replace \
--user "$OWNER" \
--repo "$REPO" \
--tag "$CURRENT" \
--name "${BINARY}" \
--file "$file"
mv "$file" "./build-done/${BINARY}"
done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
}
upload

View File

@@ -1,24 +1,14 @@
#!/bin/bash
set -e
# This assumes that gonative and gox is installed as per the 'one time setup' instructions
# at https://github.com/inconshreveable/gonative
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}" --osarch="darwin/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm linux/arm64 linux/mips linux/mips64 linux/mips64le linux/mipsle linux/ppc64 linux/ppc64le linux/s390x netbsd/386 netbsd/amd64 netbsd/arm openbsd/386 openbsd/amd64 windows/386 windows/amd64"
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}"
# include non-default linux builds too
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -os=linux -output="build/yq_{{.OS}}_{{.Arch}}"
cd build
rhash -r -a . -o checksums
rhash -r -a . -P -o checksums
rhash --list-hashes > checksums_hashes_order
find . -executable -type f | xargs -I {} tar czvf {}.tar.gz {}
# just in case find thinks this is executable...
rm -f checksums_hashes_order.tar.gz
rm -f checksums.tar.gz
rm yq_windows_386.exe.tar.gz
rm yq_windows_amd64.exe.tar.gz
zip yq_windows_386.zip yq_windows_386.exe
zip yq_windows_amd64.zip yq_windows_amd64.exe
rhash --list-hashes > checksums_hashes_order

View File

@@ -1,5 +1,5 @@
name: yq
version: '4.3.0'
version: '4.0.0-beta2'
summary: A lightweight and portable command-line YAML processor
description: |
The aim of the project is to be the jq or sed of yaml files.

Binary file not shown.

60
yq_test.go Normal file
View File

@@ -0,0 +1,60 @@
package main
// import (
// "fmt"
// "runtime"
// "testing"
// "github.com/mikefarah/yq/v2/pkg/marshal"
// "github.com/mikefarah/yq/v2/test"
// )
// func TestMultilineString(t *testing.T) {
// testString := `
// abcd
// efg`
// formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
// test.AssertResult(t, testString, formattedResult)
// }
// func TestNewYaml(t *testing.T) {
// result, _ := newYaml([]string{"b.c", "3"})
// formattedResult := fmt.Sprintf("%v", result)
// test.AssertResult(t,
// "[{b [{c 3}]}]",
// formattedResult)
// }
// func TestNewYamlArray(t *testing.T) {
// result, _ := newYaml([]string{"[0].cat", "meow"})
// formattedResult := fmt.Sprintf("%v", result)
// test.AssertResult(t,
// "[[{cat meow}]]",
// formattedResult)
// }
// func TestNewYaml_WithScript(t *testing.T) {
// writeScript = "examples/instruction_sample.yaml"
// expectedResult := `b:
// c: cat
// e:
// - name: Mike Farah`
// result, _ := newYaml([]string{""})
// actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
// test.AssertResult(t, expectedResult, actualResult)
// }
// func TestNewYaml_WithUnknownScript(t *testing.T) {
// writeScript = "fake-unknown"
// _, err := newYaml([]string{""})
// if err == nil {
// t.Error("Expected error due to unknown file")
// }
// var expectedOutput string
// if runtime.GOOS == "windows" {
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
// } else {
// expectedOutput = `open fake-unknown: no such file or directory`
// }
// test.AssertResult(t, expectedOutput, err.Error())
// }

BIN
yqt

Binary file not shown.