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

Compare commits

..

73 Commits

Author SHA1 Message Date
Mike Farah
c2ed3a3e6d attempt to fix dockre 2021-01-21 21:11:04 +11:00
Mike Farah
cc4c69bc89 Fixed bad docker version 2021-01-21 21:07:55 +11:00
Mike Farah
917fd0eb6a Bump version 2021-01-18 14:10:35 +11:00
Mike Farah
b2056a2056 Can add and merge append to null 2021-01-18 13:58:46 +11:00
evnp
ba59201217 Don't escape HTML chars when converting to json
json.Encoder and json.Marshal implicitly use HTMLEscape to convert
>, <, &, with \u003c, \u003e, \u0026. This behavior carries over
to yq, where chars will be escaped when outputting json but not when
outputting yaml.

This changeset disables this behavior via encoder.SetEscapeHTML(false).
Unfortunately there is no equivalent option for json.Marshal, so its
single usage has been replaced with an encoder (with escaping disabled).
2021-01-18 13:33:02 +11:00
Mike Farah
cfddee9101 Fixed cross-function combinatorial bug 2021-01-18 13:28:40 +11:00
Mike Farah
1941bb66a5 wip 2021-01-18 10:15:31 +11:00
Mike Farah
29af9e4c63 thoughts 2021-01-16 14:56:52 +11:00
Mike Farah
af3a9ae846 cross function fix wip 2021-01-16 14:09:49 +11:00
Mike Farah
5fb88627ca Fixed doker instructions 2021-01-15 09:44:02 +11:00
Mike Farah
4fc3793fcb Fixing multiply doc 2021-01-14 20:28:57 +11:00
Mike Farah
d612897e89 Incrementing version 2021-01-14 19:50:37 +11:00
Mike Farah
806f906041 Added keys operator 2021-01-14 15:45:07 +11:00
Mike Farah
e8b2f6e383 Added split string operator 2021-01-14 15:05:50 +11:00
Mike Farah
e419b5a39d Added join strings operator 2021-01-14 14:46:50 +11:00
Mike Farah
a5b6e08282 Split doc operator 2021-01-14 14:25:31 +11:00
Mike Farah
2417c0ee8f Fixing add,multiply,alternative operator precendences 2021-01-14 11:16:04 +11:00
Mike Farah
d3b2e37b66 Fixed remove comments example 2021-01-14 09:12:14 +11:00
Mike Farah
54723c1a36 Dont use pointer for env prefs (avoid nil) 2021-01-13 17:00:53 +11:00
Mike Farah
0318c80d33 Dont use pointer for recursive prefs (avoid nil) 2021-01-13 17:00:03 +11:00
Mike Farah
8980846632 Dont use pointer for multiply prefs (avoid nil) 2021-01-13 16:59:01 +11:00
Mike Farah
0fb62d3ae1 Dont use pointer for commment prefs (avoid nil) 2021-01-13 16:56:24 +11:00
Mike Farah
85398727ad Added merge if empty 2021-01-13 16:54:28 +11:00
Mike Farah
0bf3d781f6 Added operator level doc 2021-01-13 15:23:26 +11:00
Mike Farah
5c15936bf3 Incrementing version 2021-01-13 10:29:06 +11:00
Mike Farah
b0735d8152 Renaming pathtree to expression 2021-01-13 10:25:26 +11:00
Mike Farah
f17cbfd007 Removed global vars 2021-01-13 10:04:52 +11:00
Mike Farah
7b7ab70286 UnwrapDoc now private 2021-01-13 10:00:51 +11:00
Mike Farah
43165fa340 Moved eval function to eval interface 2021-01-13 09:35:57 +11:00
Mikhail Katychev
feda9f044d added lib_test.go 2021-01-13 09:22:40 +11:00
Mikhail Katychev
85629af59c added EvaluateNodes and EvaluateCandidateNodes to yqlib 2021-01-13 09:22:40 +11:00
Mike Farah
5fc26e9453 Merge now copies anchor names 2021-01-13 09:21:16 +11:00
Mike Farah
07a6fa4df5 Fixed creation of candidateNode in operators to include file metadata 2021-01-12 19:36:28 +11:00
Mike Farah
ec29ee7c14 Cleaning up exposed public api 2021-01-12 09:58:50 +11:00
Mike Farah
24538c0cc7 Fixed tag operator for top level node 2021-01-12 09:45:57 +11:00
Mike Farah
61398bfd3a Fixed equals operator for top level node 2021-01-12 09:40:37 +11:00
Mike Farah
f7d95021c1 Fixed has operator for top level node 2021-01-12 09:30:24 +11:00
Mike Farah
6bb8b1fb77 fixing exposed functions and interfaces 2021-01-11 17:13:48 +11:00
Mike Farah
5e2c19cc86 fixing exposed functions and interfaces 2021-01-11 16:46:28 +11:00
Mike Farah
30c269a66c Better add documentation 2021-01-11 15:52:06 +11:00
Mike Farah
70a1c60d7b Added scalar addition 2021-01-11 15:43:50 +11:00
Mike Farah
e4d48bbc0d Fixed collect at document level 2021-01-11 14:44:53 +11:00
Mike Farah
369ff94ad0 Better error handling will empty env 2021-01-11 14:38:53 +11:00
Mike Farah
1c7fb14631 Better recursive decent docs 2021-01-10 11:27:18 +11:00
Mike Farah
f4de5c4300 Better docIndex docs 2021-01-10 11:18:13 +11:00
Mike Farah
a72c14f06b Better env docs 2021-01-10 11:09:59 +11:00
Mike Farah
5ed52aca66 Merged env commands in :eye-roll: 2021-01-10 10:51:23 +11:00
Mike Farah
5a5ac0dfef Merge branch 'env_var' 2021-01-10 10:50:31 +11:00
Mike Farah
5a55869745 Bump version 2021-01-09 12:23:20 +11:00
Mike Farah
15e18bb98b Error when passing files and using null-input flag 2021-01-09 12:23:06 +11:00
Mike Farah
644063646e Env Ops! 2021-01-09 12:06:19 +11:00
Mike Farah
aabed1a237 strenv 2021-01-09 11:38:08 +11:00
Mike Farah
c12764dba8 wip 2021-01-08 21:09:43 +11:00
Mike Farah
1d5ecb244d wip 2021-01-08 21:07:46 +11:00
Mike Farah
7798a141cf Bumped go yaml for comment hanlding fixes 2021-01-08 20:56:54 +11:00
Mike Farah
b7583a538d Added webi 2021-01-08 12:14:36 +11:00
Mike Farah
529e88b630 Added recurse examples 2021-01-08 12:11:29 +11:00
Mike Farah
658006eb93 Added another delete example 2021-01-08 11:59:49 +11:00
Mike Farah
2743a7e058 Can assign-update tag 2021-01-06 20:44:28 +11:00
Mike Farah
f95862fba3 Can assign-update style 2021-01-06 20:37:53 +11:00
Mike Farah
3f58c4bc38 Can assign-update aliases and anchors 2021-01-06 20:30:48 +11:00
Mike Farah
a7975df7cd Can assign-update comments 2021-01-06 20:22:50 +11:00
Mike Farah
3d5a5e0360 updating readme 2021-01-06 15:33:20 +11:00
Mike Farah
155755ae2f brew v3! 2021-01-06 15:22:29 +11:00
Mike Farah
601c13531c Updated collect objcet doc 2021-01-06 15:20:54 +11:00
Mike Farah
69d00c89df Added shorthand document index selection 2021-01-05 13:28:37 +11:00
Mike Farah
2faff7b05f Unwrap node in get tag to return proper tag at root level 2021-01-05 13:23:03 +11:00
Mike Farah
165949041d Added v3 snap instructions 2021-01-05 13:05:03 +11:00
Mike Farah
dbd7ab0f13 Refactored doc generation, add fi fileIndex alias 2021-01-02 10:49:33 +11:00
Mike Farah
6d512ad718 Fixed updating yaml from other files 2021-01-02 10:27:32 +11:00
Mike Farah
4fef4a7ab1 update issue template, instruct questions to be raised in disussion 2020-12-31 13:43:44 +11:00
Mike Farah
dcb17b51a9 fixed heading 2020-12-31 09:33:15 +11:00
Mike Farah
385417556d added tar.gz instructions 2020-12-31 09:30:59 +11:00
106 changed files with 799 additions and 2069 deletions

View File

@@ -1 +1 @@
bin/*
bin

1
.github/FUNDING.yml vendored
View File

@@ -1 +0,0 @@
github: mikefarah

View File

@@ -3,6 +3,7 @@ on:
push:
tags:
- 'v*'
- 'dockerfix'
jobs:
publishGitRelease:
@@ -40,7 +41,17 @@ jobs:
IMAGE_NAME: mikefarah/yq
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- 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

View File

@@ -6,5 +6,26 @@ RUN set -e -x \
&& /opt/devtools.sh
ENV PATH=/go/bin:$PATH
# install mkdocs
RUN set -ex \
&& buildDeps=' \
build-essential \
python3-dev \
' \
&& apt-get update && apt-get install -y --no-install-recommends \
$buildDeps \
python3 \
python3-setuptools \
python3-wheel \
python3-pip \
&& pip3 install --upgrade \
pip \
'Markdown>=2.6.9' \
'mkdocs>=0.16.3' \
'mkdocs-material>=1.10.1' \
'markdown-include>=0.5.1' \
&& apt-get purge -y --auto-remove $buildDeps \
&& rm -rf /var/lib/apt/lists/*
ENV CGO_ENABLED 0
ENV GOPATH /go:/yq

View File

@@ -17,7 +17,6 @@ help:
@echo ' make vendor Install dependencies to vendor directory.'
@echo ' make format Run code formatter.'
@echo ' make check Run static code analysis (lint).'
@echo ' make secure Run gosec.'
@echo ' make test Run tests on project.'
@echo ' make cover Run tests and capture code coverage metrics on project.'
@echo ' make clean Clean the directory tree of produced artifacts.'
@@ -85,10 +84,6 @@ format: vendor
check: format
${DOCKRUN} bash ./scripts/check.sh
.PHONY: secure
secure:
${DOCKRUN} bash ./scripts/secure.sh
.PHONY: test
test: check
${DOCKRUN} bash ./scripts/test.sh
@@ -101,6 +96,11 @@ cover: check
@find cover -type d -exec chmod 755 {} \; || :
@find cover -type f -exec chmod 644 {} \; || :
.PHONY: build-docs
build-docs: prepare mkdocs.yml mkdocs/*
${DOCKRUN} mkdocs build
@find docs -type d -exec chmod 755 {} \; || :
@find docs -type f -exec chmod 644 {} \; || :
.PHONY: release
release: xcompile

View File

@@ -103,7 +103,7 @@ yq() {
### Go Get:
```
GO111MODULE=on go get github.com/mikefarah/yq/v4
GO111MODULE=on go get github.com/mikefarah/yq
```
## Community Supported Installation methods
@@ -119,8 +119,6 @@ See [webi](https://webinstall.dev/)
Supported by @adithyasunil26 (https://github.com/webinstall/webi-installers/tree/master/yq)
### Windows:
[![Chocolatey](https://img.shields.io/chocolatey/v/yq.svg)](https://chocolatey.org/packages/yq)
[![Chocolatey](https://img.shields.io/chocolatey/dt/yq.svg)](https://chocolatey.org/packages/yq)
```
choco install yq
```
@@ -153,21 +151,19 @@ sudo apt install yq -y
Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
## Features
- [Detailed documentation with many examples](https://mikefarah.gitbook.io/yq/)
- Written in portable go, so you can download a lovely dependency free binary
- Uses similar syntax as `jq` but works with YAML and JSON files
- Fully supports multi document yaml files
- Colorized yaml output
- [Deeply traverse yaml](https://mikefarah.gitbook.io/yq/operators/traverse-read)
- [Sort yaml by keys](https://mikefarah.gitbook.io/yq/operators/sort-keys)
- Manipulate yaml [comments](https://mikefarah.gitbook.io/yq/operators/comment-operators), [styling](https://mikefarah.gitbook.io/yq/operators/style), [tags](https://mikefarah.gitbook.io/yq/operators/tag) and [anchors and aliases](https://mikefarah.gitbook.io/yq/operators/anchor-and-alias-operators).
- [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/operators/select#select-and-update-matching-values-in-map)
- [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)
- [Reduce](https://mikefarah.gitbook.io/yq/operators/reduce) to merge multiple files or sum an array or other fancy things.
## [Usage](https://mikefarah.gitbook.io/yq/)

View File

@@ -42,21 +42,14 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
colorsEnabled = true
}
firstFileIndex := -1
if !nullInput && len(args) == 1 {
firstFileIndex = 0
} else if len(args) > 1 {
firstFileIndex = 1
}
if writeInplace && (firstFileIndex == -1) {
if writeInplace && len(args) < 2 {
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
}
if writeInplace {
// only use colors if its forced
colorsEnabled = forceColor
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[firstFileIndex])
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
out, err = writeInPlaceHandler.CreateTempFile()
if err != nil {
return err

View File

@@ -62,21 +62,14 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
colorsEnabled = true
}
firstFileIndex := -1
if !nullInput && len(args) == 1 {
firstFileIndex = 0
} else if len(args) > 1 {
firstFileIndex = 1
}
if writeInplace && (firstFileIndex == -1) {
if writeInplace && len(args) < 2 {
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
}
if writeInplace {
// only use colors if its forced
colorsEnabled = forceColor
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[firstFileIndex])
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
out, err = writeInPlaceHandler.CreateTempFile()
if err != nil {
return err
@@ -109,7 +102,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
}
default:
err = streamEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
}
completedSuccessfully = err == nil

View File

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

View File

@@ -1,6 +1,4 @@
a:
key1: "value1"
key2: 2.6
ab:
key1: 6
key2: "h"
a: simple # just the best
b: [1, 2]
c:
test: 1

View File

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

10
go.mod
View File

@@ -1,14 +1,14 @@
module github.com/mikefarah/yq/v4
require (
github.com/elliotchance/orderedmap v1.4.0
github.com/elliotchance/orderedmap v1.3.0
github.com/fatih/color v1.10.0
github.com/goccy/go-yaml v1.8.9
github.com/jinzhu/copier v0.2.8
github.com/spf13/cobra v1.1.3
github.com/goccy/go-yaml v1.8.4
github.com/jinzhu/copier v0.1.0
github.com/spf13/cobra v1.1.1
github.com/timtadh/data-structures v0.5.3 // indirect
github.com/timtadh/lexmachine v0.2.2
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e // indirect
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

33
go.sum
View File

@@ -36,8 +36,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A=
github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
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=
@@ -52,8 +52,8 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-yaml v1.8.9 h1:4AEXg2qx+/w29jXnXpMY6mTckmYu1TMoHteKuMf0HFg=
github.com/goccy/go-yaml v1.8.9/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -101,8 +101,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/copier v0.2.8 h1:N8MbL5niMwE3P4dOwurJixz5rMkKfujmMRFmAanSzWE=
github.com/jinzhu/copier v0.2.8/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88=
github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -168,9 +168,10 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -179,9 +180,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ=
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
@@ -255,9 +255,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
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-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-20210317225723-c4fcb01b228e h1:XNp2Flc/1eWQGk5BLzqTAN7fQIwIbfyVTuVxXxZh73M=
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -267,6 +268,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
@@ -279,6 +281,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
@@ -305,6 +308,7 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
@@ -315,10 +319,11 @@ gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
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.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -39,11 +39,7 @@ func (e *allAtOnceEvaluator) EvaluateCandidateNodes(expression string, inputCand
if err != nil {
return nil, err
}
context, err := e.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputCandidates}, node)
if err != nil {
return nil, err
}
return context.MatchingNodes, nil
return e.treeNavigator.GetMatchingNodes(inputCandidates, node)
}
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {

View File

@@ -2,6 +2,8 @@ package yqlib
import (
"fmt"
"strconv"
"strings"
"github.com/jinzhu/copier"
yaml "gopkg.in/yaml.v3"
@@ -16,15 +18,10 @@ type CandidateNode struct {
// when performing op against all nodes given, this will treat all the nodes as one
// (e.g. top level cross document merge). This property does not propegate to child nodes.
EvaluateTogether bool
IsMapKey bool
}
func (n *CandidateNode) GetKey() string {
keyPrefix := ""
if n.IsMapKey {
keyPrefix = "key-"
}
return fmt.Sprintf("%v%v - %v", keyPrefix, n.Document, n.Path)
return fmt.Sprintf("%v - %v", n.Document, n.Path)
}
func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode {
@@ -66,10 +63,11 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
n.UpdateAttributesFrom(other)
n.Node.Content = other.Node.Content
n.Node.Value = other.Node.Value
n.Node.Alias = other.Node.Alias
n.Node.Anchor = other.Node.Anchor
}
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
log.Debug("UpdateAttributesFrom: n: %v other: %v", n.GetKey(), other.GetKey())
if n.Node.Kind != other.Node.Kind {
// clear out the contents when switching to a different type
// e.g. map to array
@@ -78,22 +76,56 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
}
n.Node.Kind = other.Node.Kind
n.Node.Tag = other.Node.Tag
n.Node.Alias = other.Node.Alias
n.Node.Anchor = other.Node.Anchor
// merge will pickup the style of the new thing
// when autocreating nodes
if n.Node.Style == 0 {
n.Node.Style = other.Node.Style
}
if other.Node.FootComment != "" {
n.Node.FootComment = other.Node.FootComment
}
if other.Node.HeadComment != "" {
n.Node.HeadComment = other.Node.HeadComment
}
if other.Node.LineComment != "" {
n.Node.LineComment = other.Node.LineComment
}
n.Node.FootComment = n.Node.FootComment + other.Node.FootComment
n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment
n.Node.LineComment = n.Node.LineComment + other.Node.LineComment
}
func (n *CandidateNode) PathStackToString() string {
return mergePathStackToString(n.Path)
}
func mergePathStackToString(pathStack []interface{}) string {
var sb strings.Builder
for index, path := range pathStack {
switch path.(type) {
case int, int64:
// if arrayMergeStrategy == AppendArrayMergeStrategy {
// sb.WriteString("[+]")
// } else {
sb.WriteString(fmt.Sprintf("[%v]", path))
// }
default:
s := fmt.Sprintf("%v", path)
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
hasDoubleQuotes := strings.Contains(s, "\"")
wrappingCharacterStart := "\""
wrappingCharacterEnd := "\""
if hasDoubleQuotes {
wrappingCharacterStart = "("
wrappingCharacterEnd = ")"
}
if hasSpecial || errParsingInt == nil {
sb.WriteString(wrappingCharacterStart)
}
sb.WriteString(s)
if hasSpecial || errParsingInt == nil {
sb.WriteString(wrappingCharacterEnd)
}
}
if index < len(pathStack)-1 {
sb.WriteString(".")
}
}
return sb.String()
}

View File

@@ -1,54 +0,0 @@
package yqlib
import (
"container/list"
"github.com/jinzhu/copier"
)
type Context struct {
MatchingNodes *list.List
Variables map[string]*list.List
DontAutoCreate bool
}
func (n *Context) SingleChildContext(candidate *CandidateNode) Context {
list := list.New()
list.PushBack(candidate)
return n.ChildContext(list)
}
func (n *Context) GetVariable(name string) *list.List {
if n.Variables == nil {
return nil
}
return n.Variables[name]
}
func (n *Context) SetVariable(name string, value *list.List) {
if n.Variables == nil {
n.Variables = make(map[string]*list.List)
}
n.Variables[name] = value
}
func (n *Context) ChildContext(results *list.List) Context {
clone := Context{}
err := copier.Copy(&clone, n)
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
clone.MatchingNodes = results
return clone
}
func (n *Context) Clone() Context {
clone := Context{}
err := copier.Copy(&clone, n)
if err != nil {
log.Error("Error cloning context :(")
panic(err)
}
return clone
}

View File

@@ -3,14 +3,16 @@ package yqlib
import (
"fmt"
"container/list"
logging "gopkg.in/op/go-logging.v1"
)
type DataTreeNavigator interface {
// given the context and a expressionNode,
// this will process the against the given expressionNode and return
// a new context of matching candidates
GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error)
// given a list of CandidateEntities and a expressionNode,
// this will process the list against the given expressionNode and return
// a new list of matching candidates
GetMatchingNodes(matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error)
}
type dataTreeNavigator struct {
@@ -20,22 +22,22 @@ func NewDataTreeNavigator() DataTreeNavigator {
return &dataTreeNavigator{}
}
func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
if expressionNode == nil {
log.Debugf("getMatchingNodes - nothing to do")
return context, nil
return matchingNodes, nil
}
log.Debugf("Processing Op: %v", expressionNode.Operation.toString())
if log.IsEnabledFor(logging.DEBUG) {
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
log.Debug(NodeToString(el.Value.(*CandidateNode)))
}
}
log.Debug(">>")
handler := expressionNode.Operation.OperationType.Handler
if handler != nil {
return handler(d, context, expressionNode)
return handler(d, matchingNodes, expressionNode)
}
return Context{}, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
return nil, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
}

View File

@@ -101,7 +101,7 @@ will output
- hello
```
## Append to array
## Update array (append)
Given a sample.yml file of:
```yaml
a:
@@ -127,36 +127,6 @@ b:
- 4
```
## Relative append
Given a sample.yml file of:
```yaml
a:
a1:
b:
- cat
a2:
b:
- dog
a3: {}
```
then
```bash
yq eval '.a[].b += ["mouse"]' sample.yml
```
will output
```yaml
a:
a1:
b:
- cat
- mouse
a2:
b:
- dog
- mouse
a3: {b: [mouse]}
```
## String concatenation
Given a sample.yml file of:
```yaml
@@ -173,6 +143,22 @@ a: catmeow
b: meow
```
## Relative string concatenation
Given a sample.yml file of:
```yaml
a: cat
b: meow
```
then
```bash
yq eval '.a += .b' sample.yml
```
will output
```yaml
a: catmeow
b: meow
```
## Number addition - float
If the lhs or rhs are floats then the expression will be calculated with floats.
@@ -209,20 +195,18 @@ a: 7
b: 4
```
## Increment numbers
## Increment number
Given a sample.yml file of:
```yaml
a: 3
b: 5
```
then
```bash
yq eval '.[] += 1' sample.yml
yq eval '.a += 1' sample.yml
```
will output
```yaml
a: 4
b: 6
```
## Add to null

View File

@@ -1,4 +1,4 @@
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.

View File

@@ -1,4 +1,10 @@
This operator is used to update node values. It can be used in either the:
### plain form: `=`
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
### relative form: `|=`
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
## Create yaml file
Running
```bash

View File

@@ -1,15 +1,4 @@
Use these comment operators to set or retrieve comments.
Like the `=` and `|=` assign operators, the same syntax applies when updating comments:
### plain form: `=`
This will assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
### relative form: `|=`
Similar to the plain form, however the RHS evaluates against each matching LHS node! This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
## Set line comment
Given a sample.yml file of:
```yaml

View File

@@ -29,24 +29,6 @@ true
false
```
## Don't match string
Given a sample.yml file of:
```yaml
- cat
- goat
- dog
```
then
```bash
yq eval '.[] | (. != "*at")' sample.yml
```
will output
```yaml
false
false
true
```
## Match number
Given a sample.yml file of:
```yaml
@@ -65,24 +47,6 @@ true
false
```
## Dont match number
Given a sample.yml file of:
```yaml
- 3
- 4
- 5
```
then
```bash
yq eval '.[] | (. != 4)' sample.yml
```
will output
```yaml
true
false
true
```
## Match nulls
Running
```bash

View File

@@ -35,26 +35,6 @@ will output
0
```
## Get file indices of multiple documents
Given a sample.yml file of:
```yaml
a: cat
```
And another sample another.yml file of:
```yaml
a: cat
```
then
```bash
yq eval-all 'fileIndex' sample.yml another.yml
```
will output
```yaml
0
---
1
```
## Get file index alias
Given a sample.yml file of:
```yaml

View File

@@ -19,29 +19,6 @@ true
false
```
## Select, checking for existence of deep paths
Simply pipe in parent expressions into `has`
Given a sample.yml file of:
```yaml
- a:
b:
c: cat
- a:
b:
d: dog
```
then
```bash
yq eval '.[] | select(.a.b | has("c"))' sample.yml
```
will output
```yaml
a:
b:
c: cat
```
## Has array index
Given a sample.yml file of:
```yaml

View File

@@ -16,20 +16,6 @@ will output
3
```
## null length
Given a sample.yml file of:
```yaml
a: null
```
then
```bash
yq eval '.a | length' sample.yml
```
will output
```yaml
0
```
## Map length
returns number of entries

View File

@@ -1,34 +1,19 @@
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
## Objects and arrays - merging
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
### Merge Flags
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
Multiplication of strings and numbers are not yet supported.
- `+` to append arrays
- `?` to only merge existing fields
- `d` to deeply merge arrays
### Merging files
## Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
```
## Multiply integers
Running
```bash
yq eval --null-input '3 * 4'
```
will output
```yaml
12
```
## Merge objects together, returning merged result only
Given a sample.yml file of:
```yaml
@@ -86,6 +71,7 @@ Given a sample.yml file of:
a: {things: great}
b:
also: "me"
```
then
```bash
@@ -205,32 +191,6 @@ thing:
- 4
```
## Merge, deeply merging arrays
Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.
Given a sample.yml file of:
```yaml
a:
- name: fred
age: 12
- name: bob
age: 32
b:
- name: fred
age: 34
```
then
```bash
yq eval '.a *d .b' sample.yml
```
will output
```yaml
- name: fred
age: 34
- name: bob
age: 32
```
## Merge to prefix an element
Given a sample.yml file of:
```yaml

View File

@@ -1,76 +0,0 @@
Reduce is a powerful way to process a collection of data into a new form.
```
<exp> as $<name> ireduce (<init>; <block>)
```
e.g.
```
.[] as $item ireduce (0; . + $item)
```
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
## yq vs jq syntax
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.
## Sum numbers
Given a sample.yml file of:
```yaml
- 10
- 2
- 5
- 3
```
then
```bash
yq eval '.[] as $item ireduce (0; . + $item)' sample.yml
```
will output
```yaml
20
```
## Merge all yaml files together
Given a sample.yml file of:
```yaml
a: cat
```
And another sample another.yml file of:
```yaml
b: dog
```
then
```bash
yq eval-all '. as $item ireduce ({}; . * $item )' sample.yml another.yml
```
will output
```yaml
a: cat
b: dog
```
## Convert an array to an object
Given a sample.yml file of:
```yaml
- name: Cathy
has: apples
- name: Bob
has: bananas
```
then
```bash
yq eval '.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )' sample.yml
```
will output
```yaml
Cathy: apples
Bob: bananas
```

View File

@@ -18,40 +18,6 @@ will output
cat; meow; 1; ; true
```
## Substitute / Replace string
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
Given a sample.yml file of:
```yaml
a: dogs are great
```
then
```bash
yq eval '.a |= sub("dogs", "cats")' sample.yml
```
will output
```yaml
a: cats are great
```
## Substitute / Replace string with regex
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
Given a sample.yml file of:
```yaml
a: cat
b: heat
```
then
```bash
yq eval '.[] |= sub("([a])", "${1}r")' sample.yml
```
will output
```yaml
a: cart
b: heart
```
## Split strings
Given a sample.yml file of:
```yaml

View File

@@ -1,71 +0,0 @@
## Number subtraction - float
If the lhs or rhs are floats then the expression will be calculated with floats.
Given a sample.yml file of:
```yaml
a: 3
b: 4.5
```
then
```bash
yq eval '.a = .a - .b' sample.yml
```
will output
```yaml
a: -1.5
b: 4.5
```
## Number subtraction - float
If the lhs or rhs are floats then the expression will be calculated with floats.
Given a sample.yml file of:
```yaml
a: 3
b: 4.5
```
then
```bash
yq eval '.a = .a - .b' sample.yml
```
will output
```yaml
a: -1.5
b: 4.5
```
## Number subtraction - int
If both the lhs and rhs are ints then the expression will be calculated with ints.
Given a sample.yml file of:
```yaml
a: 3
b: 4
```
then
```bash
yq eval '.a = .a - .b' sample.yml
```
will output
```yaml
a: -1
b: 4
```
## Decrement numbers
Given a sample.yml file of:
```yaml
a: 3
b: 5
```
then
```bash
yq eval '.[] -= 1' sample.yml
```
will output
```yaml
a: 2
b: 4
```

View File

@@ -33,7 +33,7 @@ c: banana
```
## Special characters
Use quotes with brackets around path elements with special characters
Use quotes around path elements with special characters
Given a sample.yml file of:
```yaml
@@ -41,23 +41,7 @@ Given a sample.yml file of:
```
then
```bash
yq eval '.["{}"]' sample.yml
```
will output
```yaml
frog
```
## Keys with spaces
Use quotes with brackets around path elements with special characters
Given a sample.yml file of:
```yaml
"red rabbit": frog
```
then
```bash
yq eval '.["red rabbit"]' sample.yml
yq eval '."{}"' sample.yml
```
will output
```yaml

View File

@@ -1,58 +0,0 @@
For more complex scenarios, variables can be used to hold values of expression to be used in other expressions.
## Single value variable
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval '.a as $foo | $foo' sample.yml
```
will output
```yaml
cat
```
## Multi value variable
Given a sample.yml file of:
```yaml
- cat
- dog
```
then
```bash
yq eval '.[] as $foo | $foo' sample.yml
```
will output
```yaml
cat
dog
```
## Using variables as a lookup
Example taken from [jq](https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...)
Given a sample.yml file of:
```yaml
"posts":
- "title": Frist psot
"author": anon
- "title": A well-written article
"author": person1
"realnames":
"anon": Anonymous Coward
"person1": Person McPherson
```
then
```bash
yq eval '.realnames as $names | .posts[] | {"title":.title, "author": $names[.author]}' sample.yml
```
will output
```yaml
title: Frist psot
author: Anonymous Coward
title: A well-written article
author: Person McPherson
```

View File

@@ -1,4 +1,4 @@
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference (or expands) aliases and remove anchor names).
Use the `alias` and `anchor` operators to read and write yaml aliases and anchors. The `explode` operator normalises a yaml file (dereference aliases and remove anchor names).
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.

View File

@@ -1,11 +1 @@
Use these comment operators to set or retrieve comments.
Like the `=` and `|=` assign operators, the same syntax applies when updating comments:
### plain form: `=`
This will assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
### relative form: `|=`
Similar to the plain form, however the RHS evaluates against each matching LHS node! This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
Use these comment operators to set or retrieve comments.

View File

@@ -1,20 +0,0 @@
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
## Objects and arrays - merging
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
### Merge Flags
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
- `+` to append arrays
- `?` to only merge existing fields
- `d` to deeply merge arrays
### Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
```

View File

@@ -0,0 +1,15 @@
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
Multiplication of strings and numbers are not yet supported.
## Merging files
Note the use of `eval-all` to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
```

View File

@@ -1,21 +0,0 @@
Reduce is a powerful way to process a collection of data into a new form.
```
<exp> as $<name> ireduce (<init>; <block>)
```
e.g.
```
.[] as $item ireduce (0; . + $item)
```
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
## yq vs jq syntax
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.

View File

@@ -1 +0,0 @@
For more complex scenarios, variables can be used to hold values of expression to be used in other expressions.

View File

@@ -5,6 +5,9 @@ import (
"strings"
)
var myPathTokeniser = newExpressionTokeniser()
var myPathPostfixer = newExpressionPostFixer()
type ExpressionNode struct {
Operation *Operation
Lhs *ExpressionNode
@@ -16,21 +19,19 @@ type ExpressionParser interface {
}
type expressionParserImpl struct {
pathTokeniser expressionTokeniser
pathPostFixer expressionPostFixer
}
func NewExpressionParser() ExpressionParser {
return &expressionParserImpl{newExpressionTokeniser(), newExpressionPostFixer()}
return &expressionParserImpl{}
}
func (p *expressionParserImpl) ParseExpression(expression string) (*ExpressionNode, error) {
tokens, err := p.pathTokeniser.Tokenise(expression)
tokens, err := myPathTokeniser.Tokenise(expression)
if err != nil {
return nil, err
}
var Operations []*Operation
Operations, err = p.pathPostFixer.ConvertToPostfix(tokens)
Operations, err = myPathPostfixer.ConvertToPostfix(tokens)
if err != nil {
return nil, err
}
@@ -69,7 +70,7 @@ func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*
stack = append(stack, &newNode)
}
if len(stack) != 1 {
return nil, fmt.Errorf("Bad expression, please check expression syntax")
return nil, fmt.Errorf("expected end of expression but found '%v', please check expression syntax", strings.TrimSpace(stack[1].Operation.StringValue))
}
return stack[0], nil
}

View File

@@ -38,5 +38,5 @@ func TestPathTreeOneArgForOneArgOp(t *testing.T) {
func TestPathTreeExtraArgs(t *testing.T) {
_, err := NewExpressionParser().ParseExpression("sortKeys(.) explode(.)")
test.AssertResultComplex(t, "Bad expression, please check expression syntax", err.Error())
test.AssertResultComplex(t, "expected end of expression but found 'explode', please check expression syntax", err.Error())
}

View File

@@ -20,22 +20,20 @@ func newExpressionPostFixer() expressionPostFixer {
func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) {
var newOp *token
opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1]
log.Debugf("popped %v from opstack to results", newOp.toString(true))
return opStack, append(result, newOp.Operation)
}
func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) {
var result []*Operation
// surround the whole thing with brackets
var opStack = []*token{{TokenType: openBracket}}
// surround the whole thing with quotes
var opStack = []*token{&token{TokenType: openBracket}}
var tokens = append(infixTokens, &token{TokenType: closeBracket})
for _, currentToken := range tokens {
log.Debugf("postfix processing currentToken %v", currentToken.toString(true))
log.Debugf("postfix processing currentToken %v, %v", currentToken.toString(), currentToken.Operation)
switch currentToken.TokenType {
case openBracket, openCollect, openCollectObject:
opStack = append(opStack, currentToken)
log.Debugf("put %v onto the opstack", currentToken.toString(true))
case closeCollect, closeCollectObject:
var opener tokenType = openCollect
var collectOperator *operationType = collectOpType
@@ -43,23 +41,23 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
opener = openCollectObject
collectOperator = collectObjectOpType
}
itemsInMiddle := false
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener {
opStack, result = popOpToResult(opStack, result)
itemsInMiddle = true
}
if !itemsInMiddle {
// must be an empty collection, add the empty object as a LHS parameter
result = append(result, &Operation{OperationType: emptyOpType})
}
if len(opStack) == 0 {
return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket")
}
// now we should have [ as the last element on the opStack, get rid of it
// now we should have [] as the last element on the opStack, get rid of it
opStack = opStack[0 : len(opStack)-1]
log.Debugf("deleteing open bracket from opstack")
//and append a collect to the opStack
result = append(result, &Operation{OperationType: collectOperator})
log.Debugf("put collect onto the result")
result = append(result, &Operation{OperationType: shortPipeOpType})
log.Debugf("put shortpipe onto the result")
opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: shortPipeOpType}})
opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: collectOperator}})
case closeBracket:
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket {
opStack, result = popOpToResult(opStack, result)
@@ -75,12 +73,11 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
// pop off higher precedent operators onto the result
for len(opStack) > 0 &&
opStack[len(opStack)-1].TokenType == operationToken &&
opStack[len(opStack)-1].Operation.OperationType.Precedence > currentPrecedence {
opStack[len(opStack)-1].Operation.OperationType.Precedence >= currentPrecedence {
opStack, result = popOpToResult(opStack, result)
}
// add this operator to the opStack
opStack = append(opStack, currentToken)
log.Debugf("put %v onto the opstack", currentToken.toString(true))
}
}

View File

@@ -12,85 +12,40 @@ var pathTests = []struct {
expectedTokens []interface{}
expectedPostFix []interface{}
}{
{
`[]|join(".")`,
append(make([]interface{}, 0), "[", "EMPTY", "]", "PIPE", "JOIN", "(", ". (string)", ")"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", ". (string)", "JOIN", "PIPE"),
},
{
`{"cool": .b or .c}`,
append(make([]interface{}, 0), "{", "cool (string)", "CREATE_MAP", "b", "OR", "c", "}"),
append(make([]interface{}, 0), "cool (string)", "b", "c", "OR", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`{"cool": []|join(".")}`,
append(make([]interface{}, 0), "{", "cool (string)", "CREATE_MAP", "[", "EMPTY", "]", "PIPE", "JOIN", "(", ". (string)", ")", "}"),
append(make([]interface{}, 0), "cool (string)", "EMPTY", "COLLECT", "SHORT_PIPE", ". (string)", "JOIN", "PIPE", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`.a as $item ireduce (0; . + $item)`, // note - add code to shuffle reduce to this position for postfix
append(make([]interface{}, 0), "a", "ASSIGN_VARIABLE", "GET_VARIABLE", "REDUCE", "(", "0 (int64)", "BLOCK", "SELF", "ADD", "GET_VARIABLE", ")"),
append(make([]interface{}, 0), "a", "GET_VARIABLE", "ASSIGN_VARIABLE", "0 (int64)", "SELF", "GET_VARIABLE", "ADD", "BLOCK", "REDUCE"),
},
{
`.a | .b | .c`,
append(make([]interface{}, 0), "a", "PIPE", "b", "PIPE", "c"),
append(make([]interface{}, 0), "a", "b", "c", "PIPE", "PIPE"),
},
{
`[]`,
append(make([]interface{}, 0), "[", "EMPTY", "]"),
append(make([]interface{}, 0), "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"),
},
{
`{}`,
append(make([]interface{}, 0), "{", "EMPTY", "}"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`[{}]`,
append(make([]interface{}, 0), "[", "{", "EMPTY", "}", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE", "COLLECT", "SHORT_PIPE"),
},
{
`.realnames as $names | $names["anon"]`,
append(make([]interface{}, 0), "realnames", "ASSIGN_VARIABLE", "GET_VARIABLE", "PIPE", "GET_VARIABLE", "TRAVERSE_ARRAY", "[", "anon (string)", "]"),
append(make([]interface{}, 0), "realnames", "GET_VARIABLE", "ASSIGN_VARIABLE", "GET_VARIABLE", "anon (string)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "PIPE"),
},
{
`.b[.a]`,
append(make([]interface{}, 0), "b", "TRAVERSE_ARRAY", "[", "a", "]"),
append(make([]interface{}, 0), "b", "a", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.[]`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
},
{
`.a[]`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.a.[]`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
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", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
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", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"),
},
{
`.a[].c`,
append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "c"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "c", "SHORT_PIPE"),
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "c"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "c", "SHORT_PIPE"),
},
{
`[3]`,
@@ -114,18 +69,18 @@ var pathTests = []struct {
},
{
`.a | .[].b == "apple"`,
append(make([]interface{}, 0), "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
append(make([]interface{}, 0), "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
},
{
`(.a | .[].b) == "apple"`,
append(make([]interface{}, 0), "(", "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
append(make([]interface{}, 0), "(", "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
},
{
`.[] | select(. == "*at")`,
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
},
{
`[true]`,
@@ -158,9 +113,9 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
},
{
`{.a: .c, .b.[]: .f.g[]}`,
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "CREATE_MAP", "f", "SHORT_PIPE", "g", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "}"),
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "f", "g", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
`{.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"),
},
{
`explode(.a.b)`,
@@ -212,6 +167,11 @@ var pathTests = []struct {
append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"),
append(make([]interface{}, 0), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"),
},
{
`{}`,
append(make([]interface{}, 0), "{", "}"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"),
},
}
var tokeniser = newExpressionTokeniser()
@@ -225,7 +185,7 @@ func TestPathParsing(t *testing.T) {
}
var tokenValues []interface{}
for _, token := range tokens {
tokenValues = append(tokenValues, token.toString(false))
tokenValues = append(tokenValues, token.toString())
}
test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, fmt.Sprintf("tokenise: %v", tt.path))

View File

@@ -34,11 +34,9 @@ type token struct {
}
func (t *token) toString(detail bool) string {
func (t *token) toString() string {
if t.TokenType == operationToken {
if detail {
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
}
log.Debug("toString, its an op")
return t.Operation.toString()
} else if t.TokenType == openBracket {
return "("
@@ -100,9 +98,6 @@ func multiplyWithPrefs() lex.Action {
if strings.Contains(options, "?") {
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
}
if strings.Contains(options, "d") {
prefs.DeepMergeArrays = true
}
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil
}
@@ -185,19 +180,6 @@ func stringValue(wrapped bool) lex.Action {
}
}
func getVariableOpToken() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
value = value[1:]
getVarOperation := createValueOperation(value, value)
getVarOperation.OperationType = getVariableOpType
return &token{TokenType: operationToken, Operation: getVarOperation, CheckForPostTraverse: true}, nil
}
}
func envOp(strenv bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
@@ -255,8 +237,6 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`or`), opToken(orOpType))
lexer.Add([]byte(`and`), opToken(andOpType))
lexer.Add([]byte(`not`), opToken(notOpType))
lexer.Add([]byte(`ireduce`), opToken(reduceOpType))
lexer.Add([]byte(`;`), opToken(blockOpType))
lexer.Add([]byte(`\/\/`), opToken(alternativeOpType))
lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType))
@@ -264,8 +244,6 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType))
lexer.Add([]byte(`join`), opToken(joinStringOpType))
lexer.Add([]byte(`sub`), opToken(subStringOpType))
lexer.Add([]byte(`split`), opToken(splitStringOpType))
lexer.Add([]byte(`keys`), opToken(keysOpType))
@@ -291,7 +269,6 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`collect`), opToken(collectOpType))
lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType))
lexer.Add([]byte(`\s*!=\s*`), opToken(notEqualsOpType))
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
lexer.Add([]byte(`del`), opToken(deleteChildOpType))
@@ -324,15 +301,11 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
lexer.Add([]byte(`\*[\+|\?d]*`), multiplyWithPrefs())
lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs())
lexer.Add([]byte(`\+`), opToken(addOpType))
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
lexer.Add([]byte(`\-`), opToken(subtractOpType))
lexer.Add([]byte(`\-=`), opToken(subtractAssignOpType))
lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken())
lexer.Add([]byte(`as`), opToken(assignVariableOpType))
err := lexer.CompileNFA()
err := lexer.Compile()
if err != nil {
return nil, err
}
@@ -366,7 +339,7 @@ func (p *expressionTokeniserImpl) Tokenise(expression string) ([]*token, error)
if tok != nil {
currentToken := tok.(*token)
log.Debugf("Tokenising %v", currentToken.toString(true))
log.Debugf("Tokenising %v", currentToken.toString())
tokens = append(tokens, currentToken)
}
if err != nil {
@@ -396,12 +369,6 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
//need to put a traverse array then a collect currentToken
// do this by adding traverse then converting currentToken to collect
if index == 0 || tokens[index-1].TokenType != operationToken ||
tokens[index-1].Operation.OperationType != traversePathOpType {
op := &Operation{OperationType: selfReferenceOpType, StringValue: "SELF"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
op := &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
@@ -419,14 +386,6 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
postProcessedTokens = append(postProcessedTokens, currentToken)
if index != len(tokens)-1 &&
((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) ||
(currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) {
op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == traversePathOpType {
@@ -436,8 +395,18 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == openCollect {
op := &Operation{OperationType: traverseArrayOpType}
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
op = &Operation{OperationType: traverseArrayOpType}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == traverseArrayCollect {
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
return postProcessedTokens, skipNextToken
}

View File

@@ -25,9 +25,6 @@ type operationType struct {
var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator}
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 35, Handler: reduceOperator}
var blockOpType = &operationType{Type: "BLOCK", Precedence: 10, NumArgs: 2, Handler: emptyOperator}
var unionOpType = &operationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: unionOperator}
@@ -35,11 +32,9 @@ var pipeOpType = &operationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handle
var assignOpType = &operationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: assignUpdateOperator}
var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator}
var subtractAssignOpType = &operationType{Type: "SUBTRACT_ASSIGN", NumArgs: 2, Precedence: 40, Handler: subtractAssignOperator}
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator}
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
@@ -47,21 +42,16 @@ var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precede
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
var subtractOpType = &operationType{Type: "SUBTRACT", NumArgs: 2, Precedence: 42, Handler: subtractOperator}
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator}
//createmap needs to be above union, as we use union to build the components of the objects
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 15, Handler: createMapOperator}
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator}
var shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: pipeOperator}
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator}
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
@@ -75,20 +65,19 @@ var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50,
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
var subStringOpType = &operationType{Type: "SUBSTR", NumArgs: 1, Precedence: 50, Handler: substituteStringOperator}
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 55, Handler: traversePathOperator}
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: traverseArrayOperator}
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator}
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator}
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator}
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: selfOperator}
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
var emptyOpType = &operationType{Type: "EMPTY", NumArgs: 50, Handler: emptyOperator}
var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}

View File

@@ -3,6 +3,7 @@ package yqlib
import (
"fmt"
"container/list"
"strconv"
yaml "gopkg.in/yaml.v3"
@@ -14,12 +15,12 @@ func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
Rhs: rhs}
}
func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func addAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
assignmentOp := &Operation{OperationType: assignOpType}
assignmentOp.UpdateAssign = true
selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(selfExpression, expressionNode.Rhs)}
return d.GetMatchingNodes(context, assignmentOpNode)
assignmentOp.UpdateAssign = false
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)}
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
}
func toNodes(candidate *CandidateNode) []*yaml.Node {
@@ -36,13 +37,13 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
}
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func addOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("Add operator")
return crossFunction(d, context, expressionNode, add, false)
return crossFunction(d, matchingNodes, expressionNode, add)
}
func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node)

View File

@@ -63,21 +63,13 @@ var addOperatorScenarios = []expressionScenario{
},
},
{
description: "Append to array",
description: "Update array (append)",
document: `{a: [1,2], b: [3,4]}`,
expression: `.a = .a + .b`,
expected: []string{
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
},
},
{
description: "Relative append",
document: `a: { a1: {b: [cat]}, a2: {b: [dog]}, a3: {} }`,
expression: `.a[].b += ["mouse"]`,
expected: []string{
"D0, P[], (doc)::a: {a1: {b: [cat, mouse]}, a2: {b: [dog, mouse]}, a3: {b: [mouse]}}\n",
},
},
{
description: "String concatenation",
document: `{a: cat, b: meow}`,
@@ -86,6 +78,14 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
},
},
{
description: "Relative string concatenation",
document: `{a: cat, b: meow}`,
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
},
},
{
description: "Number addition - float",
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
@@ -105,11 +105,11 @@ var addOperatorScenarios = []expressionScenario{
},
},
{
description: "Increment numbers",
document: `{a: 3, b: 5}`,
expression: `.[] += 1`,
description: "Increment number",
document: `{a: 3}`,
expression: `.a += 1`,
expected: []string{
"D0, P[], (doc)::{a: 4, b: 6}\n",
"D0, P[], (doc)::{a: 4}\n",
},
},
{

View File

@@ -1,14 +1,18 @@
package yqlib
func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
import (
"container/list"
)
// corssFunction no matches
// can boolean use crossfunction
func alternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- alternative")
return crossFunction(d, context, expressionNode, alternativeFunc, true)
return crossFunction(d, matchingNodes, expressionNode, alternativeFunc)
}
func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
if lhs == nil {
return rhs, nil
}
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)

View File

@@ -13,14 +13,6 @@ var alternativeOperatorScenarios = []expressionScenario{
"D0, P[a], (!!str)::bridge\n",
},
},
{
expression: `select(tag == "seq") // "cat"`,
skipDoc: true,
document: `a: frog`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
},
{
description: "LHS is not defined",
expression: `.a // "hello"`,

View File

@@ -6,146 +6,146 @@ import (
yaml "gopkg.in/yaml.v3"
)
func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func assignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("AssignAlias operator!")
aliasName := ""
if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting aliasName : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Kind = yaml.AliasNode
candidate.Node.Value = aliasName
}
return context, nil
return matchingNodes, nil
}
func getAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("GetAlias operator!")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}
func assignAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func assignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("AssignAnchor operator!")
anchorName := ""
if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting anchorName of : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Anchor = anchorName
}
return context, nil
return matchingNodes, nil
}
func getAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("GetAnchor operator!")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
anchor := candidate.Node.Anchor
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}
func explodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func explodeOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- ExplodeOperation")
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
for childEl := rhs.MatchingNodes.Front(); childEl != nil; childEl = childEl.Next() {
err = explodeNode(childEl.Value.(*CandidateNode).Node, context)
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
err = explodeNode(childEl.Value.(*CandidateNode).Node)
if err != nil {
return Context{}, err
return nil, err
}
}
}
return context, nil
return matchMap, nil
}
func explodeNode(node *yaml.Node, context Context) error {
func explodeNode(node *yaml.Node) error {
node.Anchor = ""
switch node.Kind {
case yaml.SequenceNode, yaml.DocumentNode:
for index, contentNode := range node.Content {
log.Debugf("exploding index %v", index)
errorInContent := explodeNode(contentNode, context)
errorInContent := explodeNode(contentNode)
if errorInContent != nil {
return errorInContent
}
@@ -169,7 +169,7 @@ func explodeNode(node *yaml.Node, context Context) error {
valueNode := node.Content[index+1]
log.Debugf("traversing %v", keyNode.Value)
if keyNode.Value != "<<" {
err := overrideEntry(node, keyNode, valueNode, index, context.ChildContext(newContent))
err := overrideEntry(node, keyNode, valueNode, index, newContent)
if err != nil {
return err
}
@@ -178,14 +178,14 @@ func explodeNode(node *yaml.Node, context Context) error {
log.Debugf("an alias merge list!")
for index := 0; index < len(valueNode.Content); index = index + 1 {
aliasNode := valueNode.Content[index]
err := applyAlias(node, aliasNode.Alias, index, context.ChildContext(newContent))
err := applyAlias(node, aliasNode.Alias, index, newContent)
if err != nil {
return err
}
}
} else {
log.Debugf("an alias merge!")
err := applyAlias(node, valueNode.Alias, index, context.ChildContext(newContent))
err := applyAlias(node, valueNode.Alias, index, newContent)
if err != nil {
return err
}
@@ -205,7 +205,7 @@ func explodeNode(node *yaml.Node, context Context) error {
}
}
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent Context) error {
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent *list.List) error {
if alias == nil {
return nil
}
@@ -221,15 +221,15 @@ func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent Co
return nil
}
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent Context) error {
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent *list.List) error {
err := explodeNode(value, newContent)
err := explodeNode(value)
if err != nil {
return err
}
for newEl := newContent.MatchingNodes.Front(); newEl != nil; newEl = newEl.Next() {
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
valueEl := newEl.Next() // move forward twice
keyNode := newEl.Value.(*yaml.Node)
log.Debugf("checking new content %v:%v", keyNode.Value, valueEl.Value.(*yaml.Node).Value)
@@ -250,12 +250,12 @@ func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex
}
}
err = explodeNode(key, newContent)
err = explodeNode(key)
if err != nil {
return err
}
log.Debugf("adding %v:%v", key.Value, value.Value)
newContent.MatchingNodes.PushBack(key)
newContent.MatchingNodes.PushBack(value)
newContent.PushBack(key)
newContent.PushBack(value)
return nil
}

View File

@@ -1,28 +1,30 @@
package yqlib
func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
import "container/list"
func assignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
var rhs Context
var rhs *list.List
if !expressionNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err = d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
if expressionNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
}
if err != nil {
return Context{}, err
return nil, err
}
// grab the first value
first := rhs.MatchingNodes.Front()
first := rhs.Front()
if first != nil {
rhsCandidate := first.Value.(*CandidateNode)
@@ -31,31 +33,30 @@ func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode
}
}
return context, nil
return matchingNodes, nil
}
// does not update content or values
func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debug("getting lhs matching nodes for update")
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
func assignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
// grab the first value
first := rhs.MatchingNodes.Front()
first := rhs.Front()
if first != nil {
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode))
}
}
return context, nil
return matchingNodes, nil
}

View File

@@ -143,5 +143,5 @@ func TestAssignOperatorScenarios(t *testing.T) {
for _, tt := range assignOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Assign (Update)", assignOperatorScenarios)
documentScenarios(t, "Assign", assignOperatorScenarios)
}

View File

@@ -25,8 +25,8 @@ func isTruthy(c *CandidateNode) (bool, error) {
type boolOp func(bool, bool) bool
func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
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)
@@ -44,35 +44,35 @@ func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *C
}
}
func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func orOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- orOp")
return crossFunction(d, context, expressionNode, performBoolOp(
return crossFunction(d, matchingNodes, expressionNode, performBoolOp(
func(b1 bool, b2 bool) bool {
return b1 || b2
}), false)
}))
}
func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func andOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- AndOp")
return crossFunction(d, context, expressionNode, performBoolOp(
return crossFunction(d, matchingNodes, expressionNode, performBoolOp(
func(b1 bool, b2 bool) bool {
return b1 && b2
}), false)
}))
}
func notOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func notOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- notOperation")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debug("notOperation checking %v", candidate)
truthy, errDecoding := isTruthy(candidate)
if errDecoding != nil {
return Context{}, errDecoding
return nil, errDecoding
}
result := createBooleanCandidate(candidate, !truthy)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -6,21 +6,21 @@ import (
yaml "gopkg.in/yaml.v3"
)
func collectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func collectOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- collectOperation")
if context.MatchingNodes.Len() == 0 {
if matchMap.Len() == 0 {
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
candidate := &CandidateNode{Node: node}
return context.SingleChildContext(candidate), nil
return nodeToMap(candidate), nil
}
var results = list.New()
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
var collectC *CandidateNode
if context.MatchingNodes.Front() != nil {
collectC = context.MatchingNodes.Front().Value.(*CandidateNode).CreateChild(nil, node)
if matchMap.Front() != nil {
collectC = matchMap.Front().Value.(*CandidateNode).CreateChild(nil, node)
if len(collectC.Path) > 0 {
collectC.Path = collectC.Path[:len(collectC.Path)-1]
}
@@ -28,7 +28,7 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
collectC = &CandidateNode{Node: node}
}
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Collecting %v", NodeToString(candidate))
node.Content = append(node.Content, unwrapDoc(candidate.Node))
@@ -36,5 +36,5 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
results.PushBack(collectC)
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -17,22 +17,22 @@ import (
...
*/
func collectObjectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func collectObjectOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- collectObjectOperation")
if context.MatchingNodes.Len() == 0 {
if matchMap.Len() == 0 {
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"}
candidate := &CandidateNode{Node: node}
return context.SingleChildContext(candidate), nil
return nodeToMap(candidate), nil
}
first := context.MatchingNodes.Front().Value.(*CandidateNode)
first := matchMap.Front().Value.(*CandidateNode)
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
for i := 0; i < len(first.Node.Content); i++ {
rotated[i] = list.New()
}
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidateNode := el.Value.(*CandidateNode)
for i := 0; i < len(first.Node.Content); i++ {
rotated[i].PushBack(candidateNode.CreateChild(i, candidateNode.Node.Content[i]))
@@ -41,59 +41,59 @@ func collectObjectOperator(d *dataTreeNavigator, context Context, expressionNode
newObject := list.New()
for i := 0; i < len(first.Node.Content); i++ {
additions, err := collect(d, context.ChildContext(list.New()), rotated[i])
additions, err := collect(d, list.New(), rotated[i])
if err != nil {
return Context{}, err
return nil, err
}
newObject.PushBackList(additions.MatchingNodes)
newObject.PushBackList(additions)
}
return context.ChildContext(newObject), nil
return newObject, nil
}
func collect(d *dataTreeNavigator, context Context, remainingMatches *list.List) (Context, error) {
func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) {
if remainingMatches.Len() == 0 {
return context, nil
return aggregate, nil
}
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
splatted, err := splat(d, context.SingleChildContext(candidate),
splatted, err := splat(d, nodeToMap(candidate),
traversePreferences{DontFollowAlias: true, IncludeMapKeys: false})
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatEl.Value.(*CandidateNode).Path = nil
}
if err != nil {
return Context{}, err
return nil, err
}
if context.MatchingNodes.Len() == 0 {
if aggregate.Len() == 0 {
return collect(d, splatted, remainingMatches)
}
newAgg := list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := aggregate.Front(); el != nil; el = el.Next() {
aggCandidate := el.Value.(*CandidateNode)
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatCandidate := splatEl.Value.(*CandidateNode)
newCandidate, err := aggCandidate.Copy()
if err != nil {
return Context{}, err
return nil, err
}
newCandidate.Path = nil
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, context, newCandidate, splatCandidate)
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
if err != nil {
return Context{}, err
return nil, err
}
newAgg.PushBack(newCandidate)
}
}
return collect(d, context.ChildContext(newAgg), remainingMatches)
return collect(d, newAgg, remainingMatches)
}

View File

@@ -13,41 +13,41 @@ type commentOpPreferences struct {
FootComment bool
}
func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func assignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("AssignComments operator!")
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
comment := ""
if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
@@ -63,15 +63,15 @@ func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNod
}
}
return context, nil
return matchingNodes, nil
}
func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
log.Debugf("GetComments operator!")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
comment := ""
if preferences.LineComment {
@@ -87,5 +87,5 @@ func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *
result := candidate.CreateChild(nil, node)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -6,7 +6,7 @@ import (
"gopkg.in/yaml.v3"
)
func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func createMapOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- createMapOperation")
//each matchingNodes entry should turn into a sequence of keys to create.
@@ -18,29 +18,29 @@ func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
sequences := list.New()
if context.MatchingNodes.Len() > 0 {
if matchingNodes.Len() > 0 {
for matchingNodeEl := context.MatchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
matchingNode := matchingNodeEl.Value.(*CandidateNode)
sequenceNode, err := sequenceFor(d, context, matchingNode, expressionNode)
sequenceNode, err := sequenceFor(d, matchingNode, expressionNode)
if err != nil {
return Context{}, err
return nil, err
}
sequences.PushBack(sequenceNode)
}
} else {
sequenceNode, err := sequenceFor(d, context, nil, expressionNode)
sequenceNode, err := sequenceFor(d, nil, expressionNode)
if err != nil {
return Context{}, err
return nil, err
}
sequences.PushBack(sequenceNode)
}
return context.SingleChildContext(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
return nodeToMap(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
}
func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, expressionNode *ExpressionNode) (*CandidateNode, error) {
func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, expressionNode *ExpressionNode) (*CandidateNode, error) {
var path []interface{}
var document uint = 0
var matches = list.New()
@@ -48,11 +48,11 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
if matchingNode != nil {
path = matchingNode.Path
document = matchingNode.Document
matches.PushBack(matchingNode)
matches = nodeToMap(matchingNode)
}
mapPairs, err := crossFunction(d, context.ChildContext(matches), expressionNode,
func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
mapPairs, err := crossFunction(d, matches, expressionNode,
func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
log.Debugf("LHS:", NodeToString(lhs))
log.Debugf("RHS:", NodeToString(rhs))
@@ -62,12 +62,12 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
}
return &CandidateNode{Node: &node, Document: document, Path: path}, nil
}, false)
})
if err != nil {
return nil, err
}
innerList := listToNodeSeq(mapPairs.MatchingNodes)
innerList := listToNodeSeq(mapPairs)
innerList.Style = yaml.FlowStyle
return &CandidateNode{Node: innerList, Document: document, Path: path}, nil
}

View File

@@ -1,55 +1,53 @@
package yqlib
import (
"container/list"
"fmt"
yaml "gopkg.in/yaml.v3"
)
func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
contextToUse := context.Clone()
contextToUse.DontAutoCreate = true
nodesToDelete, err := d.GetMatchingNodes(contextToUse, expressionNode.Rhs)
func deleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
nodesToDelete, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
//need to iterate backwards to ensure correct indices when deleting multiple
for el := nodesToDelete.MatchingNodes.Back(); el != nil; el = el.Prev() {
for el := nodesToDelete.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
if len(candidate.Path) > 0 {
deleteImmediateChildOp := &Operation{
OperationType: deleteImmediateChildOpType,
Value: candidate.Path[len(candidate.Path)-1],
}
deleteImmediateChildOp := &Operation{
OperationType: deleteImmediateChildOpType,
Value: candidate.Path[len(candidate.Path)-1],
}
deleteImmediateChildOpNode := &ExpressionNode{
Operation: deleteImmediateChildOp,
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}, false),
}
deleteImmediateChildOpNode := &ExpressionNode{
Operation: deleteImmediateChildOp,
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}),
}
_, err := d.GetMatchingNodes(contextToUse, deleteImmediateChildOpNode)
if err != nil {
return Context{}, err
}
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
if err != nil {
return nil, err
}
}
return context, nil
return matchingNodes, nil
}
func deleteImmediateChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
parents, err := d.GetMatchingNodes(context, expressionNode.Rhs)
func deleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
parents, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
childPath := expressionNode.Operation.Value
log.Debug("childPath to remove %v", childPath)
for el := parents.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := parents.Front(); el != nil; el = el.Next() {
parent := el.Value.(*CandidateNode)
parentNode := unwrapDoc(parent.Node)
if parentNode.Kind == yaml.MappingNode {
@@ -57,11 +55,11 @@ func deleteImmediateChildOperator(d *dataTreeNavigator, context Context, express
} else if parentNode.Kind == yaml.SequenceNode {
deleteFromArray(parent, childPath)
} else {
return Context{}, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
return nil, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
}
}
return context, nil
return matchingNodes, nil
}
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {

View File

@@ -37,38 +37,6 @@ var deleteOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::[1, 3]\n",
},
},
{
skipDoc: true,
document: `a: [1,2,3]`,
expression: `del(.a[])`,
expected: []string{
"D0, P[], (doc)::a: []\n",
},
},
{
skipDoc: true,
document: `a: [10,x,10, 10, x, 10]`,
expression: `del(.a[] | select(. == 10))`,
expected: []string{
"D0, P[], (doc)::a: [x, x]\n",
},
},
{
skipDoc: true,
document: `a: {thing1: yep, thing2: cool, thing3: hi, b: {thing1: cool, great: huh}}`,
expression: `del(..)`,
expected: []string{
"D0, P[], (!!map)::{}\n",
},
},
{
skipDoc: true,
document: `a: {thing1: yep, thing2: cool, thing3: hi, b: {thing1: cool, great: huh}}`,
expression: `del(.. | select(tag == "!!map") | (.b.thing1,.thing2))`,
expected: []string{
"D0, P[], (!!map)::a: {thing1: yep, thing3: hi, b: {great: huh}}\n",
},
},
{
description: "Delete nested entry in array",
document: `[{a: cat, b: dog}]`,

View File

@@ -7,14 +7,14 @@ import (
"gopkg.in/yaml.v3"
)
func getDocumentIndexOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"}
scalar := candidate.CreateChild(nil, node)
results.PushBack(scalar)
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -1,6 +1,7 @@
package yqlib
import (
"container/list"
"fmt"
"os"
"strings"
@@ -12,7 +13,7 @@ type envOpPreferences struct {
StringValue bool
}
func envOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func envOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
envName := expressionNode.Operation.CandidateNode.Node.Value
log.Debug("EnvOperator, env name:", envName)
@@ -28,13 +29,13 @@ func envOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
Value: rawValue,
}
} else if rawValue == "" {
return Context{}, fmt.Errorf("Value for env variable '%v' not provided in env()", envName)
return nil, fmt.Errorf("Value for env variable '%v' not provided in env()", envName)
} else {
var dataBucket yaml.Node
decoder := yaml.NewDecoder(strings.NewReader(rawValue))
errorReading := decoder.Decode(&dataBucket)
if errorReading != nil {
return Context{}, errorReading
return nil, errorReading
}
//first node is a doc
node = unwrapDoc(&dataBucket)
@@ -45,5 +46,5 @@ func envOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
target := &CandidateNode{Node: node}
return context.SingleChildContext(target), nil
return nodeToMap(target), nil
}

View File

@@ -1,33 +1,25 @@
package yqlib
import "gopkg.in/yaml.v3"
import (
"container/list"
)
func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func equalsOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- equalsOperation")
return crossFunction(d, context, expressionNode, isEquals(false), false)
return crossFunction(d, matchingNodes, expressionNode, isEquals)
}
func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
value := false
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
value := false
lhsNode := unwrapDoc(lhs.Node)
rhsNode := unwrapDoc(rhs.Node)
lhsNode := unwrapDoc(lhs.Node)
rhsNode := unwrapDoc(rhs.Node)
if lhsNode.Tag == "!!null" {
value = (rhsNode.Tag == "!!null")
} else if lhsNode.Kind == yaml.ScalarNode && rhsNode.Kind == yaml.ScalarNode {
value = matchKey(lhsNode.Value, rhsNode.Value)
}
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
if flip {
value = !value
}
return createBooleanCandidate(lhs, value), nil
if lhsNode.Tag == "!!null" {
value = (rhsNode.Tag == "!!null")
} else {
value = matchKey(lhsNode.Value, rhsNode.Value)
}
}
func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- equalsOperation")
return crossFunction(d, context, expressionNode, isEquals(true), false)
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
return createBooleanCandidate(lhs, value), nil
}

View File

@@ -14,14 +14,6 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[], (!!bool)::false\n",
},
},
{
skipDoc: true,
document: "{a: { b: {things: \"\"}, f: [1], g: [] }}",
expression: ".. | select(. == \"\")",
expected: []string{
"D0, P[a b things], (!!str)::\"\"\n",
},
},
{
description: "Match string",
document: `[cat,goat,dog]`,
@@ -31,18 +23,7 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\n",
},
},
{
description: "Don't match string",
document: `[cat,goat,dog]`,
expression: `.[] | (. != "*at")`,
expected: []string{
"D0, P[0], (!!bool)::false\n",
"D0, P[1], (!!bool)::false\n",
"D0, P[2], (!!bool)::true\n",
},
},
{
}, {
description: "Match number",
document: `[3, 4, 5]`,
expression: `.[] | (. == 4)`,
@@ -51,18 +32,7 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\n",
},
},
{
description: "Dont match number",
document: `[3, 4, 5]`,
expression: `.[] | (. != 4)`,
expected: []string{
"D0, P[0], (!!bool)::true\n",
"D0, P[1], (!!bool)::false\n",
"D0, P[2], (!!bool)::true\n",
},
},
{
}, {
skipDoc: true,
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
expression: `.a | (.[].b == "apple")`,

View File

@@ -7,32 +7,32 @@ import (
yaml "gopkg.in/yaml.v3"
)
func getFilenameOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("GetFilename")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}
func getFileIndexOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("GetFileIndex")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
result := candidate.CreateChild(nil, node)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -21,16 +21,6 @@ var fileOperatorScenarios = []expressionScenario{
"D0, P[], (!!int)::0\n",
},
},
{
description: "Get file indices of multiple documents",
document: `{a: cat}`,
document2: `{a: cat}`,
expression: `fileIndex`,
expected: []string{
"D0, P[], (!!int)::0\n",
"D0, P[], (!!int)::1\n",
},
},
{
description: "Get file index alias",
document: `{a: cat}`,

View File

@@ -7,20 +7,20 @@ import (
yaml "gopkg.in/yaml.v3"
)
func hasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func hasOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- hasOperation")
var results = list.New()
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
wanted := rhs.MatchingNodes.Front().Value.(*CandidateNode).Node
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
wanted := rhs.Front().Value.(*CandidateNode).Node
wantedKey := wanted.Value
if err != nil {
return Context{}, err
return nil, err
}
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
// grab the first value
@@ -41,7 +41,7 @@ func hasOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
if wanted.Tag == "!!int" {
var number, errParsingInt = strconv.ParseInt(wantedKey, 10, 64) // nolint
if errParsingInt != nil {
return Context{}, errParsingInt
return nil, errParsingInt
}
candidateHasKey = int64(len(contents)) > number
}
@@ -50,5 +50,5 @@ func hasOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
results.PushBack(createBooleanCandidate(candidate, false))
}
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -28,15 +28,6 @@ var hasOperatorScenarios = []expressionScenario{
"D0, P[3], (!!bool)::false\n",
},
},
{
description: "Select, checking for existence of deep paths",
subdescription: "Simply pipe in parent expressions into `has`",
document: "- {a: {b: {c: cat}}}\n- {a: {b: {d: dog}}}",
expression: `.[] | select(.a.b | has("c"))`,
expected: []string{
"D0, P[0], (!!map)::{a: {b: {c: cat}}}\n",
},
},
{
dontFormatInputForDoc: true,
description: "Has array index",

View File

@@ -7,12 +7,12 @@ import (
"gopkg.in/yaml.v3"
)
func keysOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func keysOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- keysOperator")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := unwrapDoc(candidate.Node)
var targetNode *yaml.Node
@@ -21,14 +21,14 @@ func keysOperator(d *dataTreeNavigator, context Context, expressionNode *Express
} else if node.Kind == yaml.SequenceNode {
targetNode = getIndicies(node)
} else {
return Context{}, fmt.Errorf("Cannot get keys of %v, keys only works for maps and arrays", node.Tag)
return nil, fmt.Errorf("Cannot get keys of %v, keys only works for maps and arrays", node.Tag)
}
result := candidate.CreateChild(nil, targetNode)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}
func getMapKeys(node *yaml.Node) *yaml.Node {

View File

@@ -7,21 +7,17 @@ import (
yaml "gopkg.in/yaml.v3"
)
func lengthOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func lengthOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- lengthOperation")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
targetNode := unwrapDoc(candidate.Node)
var length int
switch targetNode.Kind {
case yaml.ScalarNode:
if targetNode.Tag == "!!null" {
length = 0
} else {
length = len(targetNode.Value)
}
length = len(targetNode.Value)
case yaml.MappingNode:
length = len(targetNode.Content) / 2
case yaml.SequenceNode:
@@ -35,5 +31,5 @@ func lengthOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -14,30 +14,6 @@ var lengthOperatorScenarios = []expressionScenario{
"D0, P[a], (!!int)::3\n",
},
},
{
description: "null length",
document: `{a: null}`,
expression: `.a | length`,
expected: []string{
"D0, P[a], (!!int)::0\n",
},
},
{
skipDoc: true,
document: `{a: ~}`,
expression: `.a | length`,
expected: []string{
"D0, P[a], (!!int)::0\n",
},
},
{
skipDoc: true,
document: `{a: key no exist}`,
expression: `.b | length`,
expected: []string{
"D0, P[b], (!!int)::0\n",
},
},
{
description: "Map length",
subdescription: "returns number of entries",

View File

@@ -9,19 +9,77 @@ import (
yaml "gopkg.in/yaml.v3"
)
type crossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
func doCrossFunc(d *dataTreeNavigator, contextList *list.List, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (*list.List, error) {
var results = list.New()
lhs, err := d.GetMatchingNodes(contextList, expressionNode.Lhs)
if err != nil {
return nil, err
}
log.Debugf("crossFunction LHS len: %v", lhs.Len())
rhs, err := d.GetMatchingNodes(contextList, expressionNode.Rhs)
if err != nil {
return nil, err
}
for el := lhs.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode)
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
log.Debugf("Applying calc")
rhsCandidate := rightEl.Value.(*CandidateNode)
resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
if err != nil {
return nil, err
}
results.PushBack(resultCandidate)
}
}
return results, nil
}
func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (*list.List, error) {
var results = list.New()
var evaluateAllTogether = true
for matchEl := matchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
evaluateAllTogether = evaluateAllTogether && matchEl.Value.(*CandidateNode).EvaluateTogether
if !evaluateAllTogether {
break
}
}
if evaluateAllTogether {
return doCrossFunc(d, matchingNodes, expressionNode, calculation)
}
for matchEl := matchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
contextList := nodeToMap(matchEl.Value.(*CandidateNode))
innerResults, err := doCrossFunc(d, contextList, expressionNode, calculation)
if err != nil {
return nil, err
}
results.PushBackList(innerResults)
}
return results, nil
}
type multiplyPreferences struct {
AppendArrays bool
DeepMergeArrays bool
TraversePrefs traversePreferences
AppendArrays bool
TraversePrefs traversePreferences
}
func multiplyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func multiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- MultiplyOperator")
return crossFunction(d, context, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences)), false)
return crossFunction(d, matchingNodes, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences)))
}
func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node)
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
@@ -31,39 +89,18 @@ func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, contex
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
var newBlank = lhs.CreateChild(nil, &yaml.Node{})
var newThing, err = mergeObjects(d, context, newBlank, lhs, multiplyPreferences{})
var newThing, err = mergeObjects(d, newBlank, lhs, multiplyPreferences{})
if err != nil {
return nil, err
}
return mergeObjects(d, context, newThing, rhs, preferences)
return mergeObjects(d, newThing, rhs, preferences)
} else if lhs.Node.Tag == "!!int" && rhs.Node.Tag == "!!int" {
return multiplyIntegers(lhs, rhs)
} else if (lhs.Node.Tag == "!!int" || lhs.Node.Tag == "!!float") && (rhs.Node.Tag == "!!int" || rhs.Node.Tag == "!!float") {
return multiplyFloats(lhs, rhs)
}
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
}
}
func multiplyFloats(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
target := lhs.CreateChild(nil, &yaml.Node{})
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhs.Node.Style
target.Node.Tag = "!!float"
lhsNum, err := strconv.ParseFloat(lhs.Node.Value, 64)
if err != nil {
return nil, err
}
rhsNum, err := strconv.ParseFloat(rhs.Node.Value, 64)
if err != nil {
return nil, err
}
target.Node.Value = fmt.Sprintf("%v", lhsNum*rhsNum)
return target, nil
}
func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
target := lhs.CreateChild(nil, &yaml.Node{})
target.Node.Kind = yaml.ScalarNode
@@ -82,14 +119,14 @@ func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, e
return target, nil
}
func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) (*CandidateNode, error) {
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) (*CandidateNode, error) {
shouldAppendArrays := preferences.AppendArrays
var results = list.New()
// shouldn't recurse arrays if appending
prefs := recursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}
err := recursiveDecent(d, results, context.SingleChildContext(rhs), prefs)
TraversePreferences: traversePreferences{DontFollowAlias: true}}
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
if err != nil {
return nil, err
}
@@ -100,12 +137,7 @@ func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs
}
for el := results.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
if candidate.Node.Tag == "!!merge" {
continue
}
err := applyAssignment(d, context, pathIndexToStartFrom, lhs, candidate, preferences)
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), preferences)
if err != nil {
return nil, err
}
@@ -113,25 +145,24 @@ func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs
return lhs, nil
}
func applyAssignment(d *dataTreeNavigator, context Context, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error {
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error {
shouldAppendArrays := preferences.AppendArrays
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", lhs.GetKey(), rhs.GetKey())
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
lhsPath := rhs.Path[pathIndexToStartFrom:]
assignmentOp := &Operation{OperationType: assignAttributesOpType}
if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = addAssignOpType
} else if !preferences.DeepMergeArrays && rhs.Node.Kind == yaml.SequenceNode ||
(rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode) {
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = assignOpType
assignmentOp.UpdateAssign = false
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = addAssignOpType
}
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs}
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath, preferences.TraversePrefs, rhs.IsMapKey), Rhs: &ExpressionNode{Operation: rhsOp}}
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath, preferences.TraversePrefs), Rhs: &ExpressionNode{Operation: rhsOp}}
_, err := d.GetMatchingNodes(context.SingleChildContext(lhs), assignmentOpNode)
_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode)
return err
}

View File

@@ -4,57 +4,7 @@ import (
"testing"
)
var doc1 = `list:
# Hi this is a comment.
# Hello this is another comment.
- "abc"`
var doc2 = `list2:
# This is yet another comment.
# Indeed this is yet another comment.
- "123"`
var docExpected = `D0, P[], (!!map)::list:
# Hi this is a comment.
# Hello this is another comment.
- "abc"
list2:
# This is yet another comment.
# Indeed this is yet another comment.
- "123"
`
var multiplyOperatorScenarios = []expressionScenario{
{
description: "Multiply integers",
expression: `3 * 4`,
expected: []string{
"D0, P[], (!!int)::12\n",
},
},
{
skipDoc: true,
document: doc1,
document2: doc2,
expression: `select(fi == 0) * select(fi == 1)`,
expected: []string{
docExpected,
},
},
{
skipDoc: true,
expression: `3 * 4.5`,
expected: []string{
"D0, P[], (!!float)::13.5\n",
},
},
{
skipDoc: true,
expression: `4.5 * 3`,
expected: []string{
"D0, P[], (!!float)::13.5\n",
},
},
{
skipDoc: true,
document: `{a: {also: [1]}, b: {also: me}}`,
@@ -63,30 +13,6 @@ var multiplyOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
},
},
{
skipDoc: true,
document: "# b\nb:\n # a\n a: cat",
expression: "{} * .",
expected: []string{
"D0, P[], (!!map)::# b\nb:\n # a\n a: cat\n",
},
},
{
skipDoc: true,
document: "# b\nb:\n # a\n a: cat",
expression: ". * {}",
expected: []string{
"D0, P[], (!!map)::# b\nb:\n # a\n a: cat\n",
},
},
{
skipDoc: true,
document: `{a: &a { b: &b { c: &c cat } } }`,
expression: `{} * .`,
expected: []string{
"D0, P[], (!!map)::{a: &a {b: &b {c: &c cat}}}\n",
},
},
{
skipDoc: true,
document: `{a: 2, b: 5}`,
@@ -166,7 +92,7 @@ var multiplyOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{a: {things: great}, b: {also: me}}`,
expression: `. * {"a": .b}`,
expression: `. * {"a":.b}`,
expected: []string{
"D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n",
},
@@ -174,10 +100,16 @@ var multiplyOperatorScenarios = []expressionScenario{
{
description: "Merge keeps style of LHS",
dontFormatInputForDoc: true,
document: "a: {things: great}\nb:\n also: \"me\"",
expression: `. * {"a":.b}`,
document: `a: {things: great}
b:
also: "me"
`,
expression: `. * {"a":.b}`,
expected: []string{
"D0, P[], (!!map)::a: {things: great, also: \"me\"}\nb:\n also: \"me\"\n",
`D0, P[], (!!map)::a: {things: great, also: "me"}
b:
also: "me"
`,
},
},
{
@@ -207,7 +139,7 @@ var multiplyOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{a: [{thing: one}], b: [{missing: two, thing: two}]}`,
expression: `.a *?d .b`,
expression: `.a *? .b`,
expected: []string{
"D0, P[a], (!!seq)::[{thing: two}]\n",
},
@@ -236,15 +168,6 @@ var multiplyOperatorScenarios = []expressionScenario{
"D0, P[a], (!!map)::{thing: [1, 2, 3, 4]}\n",
},
},
{
description: "Merge, deeply merging arrays",
subdescription: "Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.",
document: `{a: [{name: fred, age: 12}, {name: bob, age: 32}], b: [{name: fred, age: 34}]}`,
expression: `.a *d .b`,
expected: []string{
"D0, P[a], (!!seq)::[{name: fred, age: 34}, {name: bob, age: 32}]\n",
},
},
{
description: "Merge to prefix an element",
document: `{a: cat, b: dog}`,
@@ -283,5 +206,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) {
for _, tt := range multiplyOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Multiply (Merge)", multiplyOperatorScenarios)
documentScenarios(t, "Multiply", multiplyOperatorScenarios)
}

View File

@@ -16,12 +16,12 @@ func createPathNodeFor(pathElement interface{}) *yaml.Node {
}
}
func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getPathOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("GetPath")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
@@ -35,5 +35,5 @@ func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -1,18 +1,11 @@
package yqlib
func pipeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
import "container/list"
//lhs may update the variable context, we should pass that into the RHS
// BUT we still return the original context back (see jq)
// https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
func pipeOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
rhs, err := d.GetMatchingNodes(lhs, expressionNode.Rhs)
if err != nil {
return Context{}, err
}
return context.ChildContext(rhs.MatchingNodes), nil
return d.GetMatchingNodes(lhs, expressionNode.Rhs)
}

View File

@@ -11,20 +11,20 @@ type recursiveDescentPreferences struct {
RecurseArray bool
}
func recursiveDescentOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func recursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
var results = list.New()
preferences := expressionNode.Operation.Preferences.(recursiveDescentPreferences)
err := recursiveDecent(d, results, context, preferences)
err := recursiveDecent(d, results, matchMap, preferences)
if err != nil {
return Context{}, err
return nil, err
}
return context.ChildContext(results), nil
return results, nil
}
func recursiveDecent(d *dataTreeNavigator, results *list.List, context Context, preferences recursiveDescentPreferences) error {
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences recursiveDescentPreferences) error {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
candidate.Node = unwrapDoc(candidate.Node)
@@ -35,7 +35,7 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, context Context,
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
(preferences.RecurseArray || candidate.Node.Kind != yaml.SequenceNode) {
children, err := splat(d, context.SingleChildContext(candidate), preferences.TraversePreferences)
children, err := splat(d, nodeToMap(candidate), preferences.TraversePreferences)
if err != nil {
return err

View File

@@ -1,59 +0,0 @@
package yqlib
import (
"container/list"
"fmt"
)
func reduceOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- reduceOp")
//.a as $var reduce (0; . + $var)
//lhs is the assignment operator
//rhs is the reduce block
// '.' refers to the current accumulator, initialised to 0
// $var references a single element from the .a
//ensure lhs is actually an assignment
//and rhs is a block (empty)
if expressionNode.Lhs.Operation.OperationType != assignVariableOpType {
return Context{}, fmt.Errorf("reduce must be given a variables assignment, got %v instead", expressionNode.Lhs.Operation.OperationType.Type)
} else if expressionNode.Rhs.Operation.OperationType != blockOpType {
return Context{}, fmt.Errorf("reduce must be given a block, got %v instead", expressionNode.Rhs.Operation.OperationType.Type)
}
arrayExpNode := expressionNode.Lhs.Lhs
array, err := d.GetMatchingNodes(context, arrayExpNode)
log.Debugf("array of %v things", array.MatchingNodes.Len())
if err != nil {
return Context{}, err
}
variableName := expressionNode.Lhs.Rhs.Operation.StringValue
initExp := expressionNode.Rhs.Lhs
accum, err := d.GetMatchingNodes(context, initExp)
if err != nil {
return Context{}, err
}
log.Debugf("with variable %v", variableName)
blockExp := expressionNode.Rhs.Rhs
for el := array.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("REDUCING WITH %v", NodeToString(candidate))
l := list.New()
l.PushBack(candidate)
accum.SetVariable(variableName, l)
accum, err = d.GetMatchingNodes(accum, blockExp)
if err != nil {
return Context{}, err
}
}
return accum, nil
}

View File

@@ -1,40 +0,0 @@
package yqlib
import (
"testing"
)
var reduceOperatorScenarios = []expressionScenario{
{
description: "Sum numbers",
document: `[10,2, 5, 3]`,
expression: `.[] as $item ireduce (0; . + $item)`,
expected: []string{
"D0, P[], (!!int)::20\n",
},
},
{
description: "Merge all yaml files together",
document: `a: cat`,
document2: `b: dog`,
expression: `. as $item ireduce ({}; . * $item )`,
expected: []string{
"D0, P[], (!!map)::a: cat\nb: dog\n",
},
},
{
description: "Convert an array to an object",
document: `[{name: Cathy, has: apples},{name: Bob, has: bananas}]`,
expression: `.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )`,
expected: []string{
"D0, P[], (!!map)::Cathy: apples\nBob: bananas\n",
},
},
}
func TestReduceOperatorScenarios(t *testing.T) {
for _, tt := range reduceOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Reduce", reduceOperatorScenarios)
}

View File

@@ -4,29 +4,28 @@ import (
"container/list"
)
func selectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func selectOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- selectOperation")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
childContext := context.SingleChildContext(candidate)
childContext.DontAutoCreate = true
rhs, err := d.GetMatchingNodes(childContext, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
// grab the first value
first := rhs.MatchingNodes.Front()
first := rhs.Front()
if first != nil {
result := first.Value.(*CandidateNode)
includeResult, errDecoding := isTruthy(result)
if errDecoding != nil {
return Context{}, errDecoding
return nil, errDecoding
}
if includeResult {
@@ -34,5 +33,5 @@ func selectOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
}
}
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -14,14 +14,6 @@ var selectOperatorScenarios = []expressionScenario{
"D0, P[1], (!!str)::goat\n",
},
},
{
skipDoc: true,
document: `[{animal: cat, legs: {cool: true}}, {animal: fish}]`,
expression: `(.[] | select(.legs.cool == true).canWalk) = true | (.[] | .alive.things) = "yes"`,
expected: []string{
"D0, P[], (doc)::[{animal: cat, legs: {cool: true}, canWalk: true, alive: {things: yes}}, {animal: fish, alive: {things: yes}}]\n",
},
},
{
skipDoc: true,
document: `[hot, fot, dog]`,

View File

@@ -1,5 +1,7 @@
package yqlib
func selfOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
return context, nil
import "container/list"
func selfOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
return matchMap, nil
}

View File

@@ -1,32 +1,33 @@
package yqlib
import (
"container/list"
"sort"
yaml "gopkg.in/yaml.v3"
)
func sortKeysOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func sortKeysOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
for childEl := rhs.MatchingNodes.Front(); childEl != nil; childEl = childEl.Next() {
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
node := unwrapDoc(childEl.Value.(*CandidateNode).Node)
if node.Kind == yaml.MappingNode {
sortKeys(node)
}
if err != nil {
return Context{}, err
return nil, err
}
}
}
return context, nil
return matchingNodes, nil
}
func sortKeys(node *yaml.Node) {

View File

@@ -1,14 +1,18 @@
package yqlib
func splitDocumentOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
import (
"container/list"
)
func splitDocumentOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- splitDocumentOperator")
var index uint = 0
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
candidate.Document = index
index = index + 1
}
return context, nil
return matchMap, nil
}

View File

@@ -3,103 +3,37 @@ package yqlib
import (
"container/list"
"fmt"
"regexp"
"strings"
"gopkg.in/yaml.v3"
)
func getSubstituteParameters(d *dataTreeNavigator, block *ExpressionNode, context Context) (string, string, error) {
regEx := ""
replacementText := ""
regExNodes, err := d.GetMatchingNodes(context, block.Lhs)
if err != nil {
return "", "", err
}
if regExNodes.MatchingNodes.Front() != nil {
regEx = regExNodes.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
}
log.Debug("regEx %v", regEx)
replacementNodes, err := d.GetMatchingNodes(context, block.Rhs)
if err != nil {
return "", "", err
}
if replacementNodes.MatchingNodes.Front() != nil {
replacementText = replacementNodes.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
}
return regEx, replacementText, nil
}
func substitute(original string, regex *regexp.Regexp, replacement string) *yaml.Node {
replacedString := regex.ReplaceAllString(original, replacement)
return &yaml.Node{Kind: yaml.ScalarNode, Value: replacedString, Tag: "!!str"}
}
func substituteStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
//rhs block operator
//lhs of block = regex
//rhs of block = replacement expression
block := expressionNode.Rhs
regExStr, replacementText, err := getSubstituteParameters(d, block, context)
if err != nil {
return Context{}, err
}
regEx, err := regexp.Compile(regExStr)
if err != nil {
return Context{}, err
}
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := unwrapDoc(candidate.Node)
if node.Tag != "!!str" {
return Context{}, fmt.Errorf("cannot sustitute with %v, can only substitute strings", node.Tag)
}
targetNode := substitute(node.Value, regEx, replacementText)
result := candidate.CreateChild(nil, targetNode)
results.PushBack(result)
}
return context.ChildContext(results), nil
}
func joinStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func joinStringOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- joinStringOperator")
joinStr := ""
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchMap, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
joinStr = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
joinStr = rhs.Front().Value.(*CandidateNode).Node.Value
}
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := unwrapDoc(candidate.Node)
if node.Kind != yaml.SequenceNode {
return Context{}, fmt.Errorf("cannot join with %v, can only join arrays of scalars", node.Tag)
return nil, fmt.Errorf("Cannot join with %v, can only join arrays of scalars", node.Tag)
}
targetNode := join(node.Content, joinStr)
result := candidate.CreateChild(nil, targetNode)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}
func join(content []*yaml.Node, joinStr string) *yaml.Node {
@@ -115,35 +49,35 @@ func join(content []*yaml.Node, joinStr string) *yaml.Node {
return &yaml.Node{Kind: yaml.ScalarNode, Value: strings.Join(stringsToJoin, joinStr), Tag: "!!str"}
}
func splitStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func splitStringOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- splitStringOperator")
splitStr := ""
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchMap, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
splitStr = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
splitStr = rhs.Front().Value.(*CandidateNode).Node.Value
}
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := unwrapDoc(candidate.Node)
if node.Tag == "!!null" {
continue
}
if node.Tag != "!!str" {
return Context{}, fmt.Errorf("Cannot split %v, can only split strings", node.Tag)
return nil, fmt.Errorf("Cannot split %v, can only split strings", node.Tag)
}
targetNode := split(node.Value, splitStr)
result := candidate.CreateChild(nil, targetNode)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}
func split(value string, spltStr string) *yaml.Node {

View File

@@ -13,24 +13,6 @@ var stringsOperatorScenarios = []expressionScenario{
"D0, P[], (!!str)::cat; meow; 1; ; true\n",
},
},
{
description: "Substitute / Replace string",
subdescription: "This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)",
document: `a: dogs are great`,
expression: `.a |= sub("dogs", "cats")`,
expected: []string{
"D0, P[], (doc)::a: cats are great\n",
},
},
{
description: "Substitute / Replace string with regex",
subdescription: "This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)",
document: "a: cat\nb: heat",
expression: `.[] |= sub("([a])", "${1}r")`,
expected: []string{
"D0, P[], (doc)::a: cart\nb: heart\n",
},
},
{
description: "Split strings",
document: `"cat; meow; 1; ; true"`,

View File

@@ -26,43 +26,43 @@ func parseStyle(customStyle string) (yaml.Style, error) {
return 0, nil
}
func assignStyleOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func assignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("AssignStyleOperator: %v")
var style yaml.Style
if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
style, err = parseStyle(rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value)
if rhs.Front() != nil {
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil {
return Context{}, err
return nil, err
}
}
}
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting style of : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
style, err = parseStyle(rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value)
if rhs.Front() != nil {
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil {
return Context{}, err
return nil, err
}
}
}
@@ -70,15 +70,15 @@ func assignStyleOperator(d *dataTreeNavigator, context Context, expressionNode *
candidate.Node.Style = style
}
return context, nil
return matchingNodes, nil
}
func getStyleOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("GetStyleOperator")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
var style string
switch candidate.Node.Style {
@@ -104,5 +104,5 @@ func getStyleOperator(d *dataTreeNavigator, context Context, expressionNode *Exp
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -1,97 +0,0 @@
package yqlib
import (
"fmt"
"strconv"
yaml "gopkg.in/yaml.v3"
)
func createSubtractOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
return &ExpressionNode{Operation: &Operation{OperationType: subtractOpType},
Lhs: lhs,
Rhs: rhs}
}
func subtractAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
assignmentOp := &Operation{OperationType: assignOpType}
assignmentOp.UpdateAssign = true
selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createSubtractOp(selfExpression, expressionNode.Rhs)}
return d.GetMatchingNodes(context, assignmentOpNode)
}
func subtractOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("Subtract operator")
return crossFunction(d, context, expressionNode, subtract, false)
}
func subtract(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node)
lhsNode := lhs.Node
if lhsNode.Tag == "!!null" {
return lhs.CreateChild(nil, rhs.Node), nil
}
target := lhs.CreateChild(nil, &yaml.Node{})
switch lhsNode.Kind {
case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for subtraction")
case yaml.SequenceNode:
return nil, fmt.Errorf("Sequences not yet supported for subtraction")
// target.Node.Kind = yaml.SequenceNode
// target.Node.Style = lhsNode.Style
// target.Node.Tag = "!!seq"
// target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
case yaml.ScalarNode:
if rhs.Node.Kind != yaml.ScalarNode {
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
}
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhsNode.Style
return subtractScalars(target, lhsNode, rhs.Node)
}
return target, nil
}
func subtractScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) {
if lhs.Tag == "!!str" {
return nil, fmt.Errorf("strings cannot be subtracted")
} else if lhs.Tag == "!!int" && rhs.Tag == "!!int" {
lhsNum, err := strconv.Atoi(lhs.Value)
if err != nil {
return nil, err
}
rhsNum, err := strconv.Atoi(rhs.Value)
if err != nil {
return nil, err
}
result := lhsNum - rhsNum
target.Node.Tag = "!!int"
target.Node.Value = fmt.Sprintf("%v", result)
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") {
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
if err != nil {
return nil, err
}
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
if err != nil {
return nil, err
}
result := lhsNum - rhsNum
target.Node.Tag = "!!float"
target.Node.Value = fmt.Sprintf("%v", result)
} else {
return nil, fmt.Errorf("%v cannot be added to %v", lhs.Tag, rhs.Tag)
}
return target, nil
}

View File

@@ -1,50 +0,0 @@
package yqlib
import (
"testing"
)
var subtractOperatorScenarios = []expressionScenario{
{
description: "Number subtraction - float",
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
document: `{a: 3, b: 4.5}`,
expression: `.a = .a - .b`,
expected: []string{
"D0, P[], (doc)::{a: -1.5, b: 4.5}\n",
},
},
{
description: "Number subtraction - float",
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
document: `{a: 3, b: 4.5}`,
expression: `.a = .a - .b`,
expected: []string{
"D0, P[], (doc)::{a: -1.5, b: 4.5}\n",
},
},
{
description: "Number subtraction - int",
subdescription: "If both the lhs and rhs are ints then the expression will be calculated with ints.",
document: `{a: 3, b: 4}`,
expression: `.a = .a - .b`,
expected: []string{
"D0, P[], (doc)::{a: -1, b: 4}\n",
},
},
{
description: "Decrement numbers",
document: `{a: 3, b: 5}`,
expression: `.[] -= 1`,
expected: []string{
"D0, P[], (doc)::{a: 2, b: 4}\n",
},
},
}
func TestSubtractOperatorScenarios(t *testing.T) {
for _, tt := range subtractOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Subtract", subtractOperatorScenarios)
}

View File

@@ -6,58 +6,58 @@ import (
yaml "gopkg.in/yaml.v3"
)
func assignTagOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func assignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("AssignTagOperator: %v")
tag := ""
if !expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
tag = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting tag of : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
if rhs.MatchingNodes.Front() != nil {
tag = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
unwrapDoc(candidate.Node).Tag = tag
}
return context, nil
return matchingNodes, nil
}
func getTagOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func getTagOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("GetTagOperator")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: unwrapDoc(candidate.Node).Tag, Tag: "!!str"}
result := candidate.CreateChild(nil, node)
results.PushBack(result)
}
return context.ChildContext(results), nil
return results, nil
}

View File

@@ -10,32 +10,31 @@ import (
)
type traversePreferences struct {
DontFollowAlias bool
IncludeMapKeys bool
DontAutoCreate bool // by default, we automatically create entries on the fly.
DontIncludeMapValues bool
DontFollowAlias bool
IncludeMapKeys bool
DontAutoCreate bool // by default, we automatically create entries on the fly.
}
func splat(d *dataTreeNavigator, context Context, prefs traversePreferences) (Context, error) {
return traverseNodesWithArrayIndices(context, make([]*yaml.Node, 0), prefs)
func splat(d *dataTreeNavigator, matches *list.List, prefs traversePreferences) (*list.List, error) {
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
}
func traversePathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- traversePathOperator")
var matches = list.New()
func traversePathOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debugf("-- Traversing")
var matchingNodeMap = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
newNodes, err := traverse(d, context, el.Value.(*CandidateNode), expressionNode.Operation)
for el := matchMap.Front(); el != nil; el = el.Next() {
newNodes, err := traverse(d, el.Value.(*CandidateNode), expressionNode.Operation)
if err != nil {
return Context{}, err
return nil, err
}
matches.PushBackList(newNodes)
matchingNodeMap.PushBackList(newNodes)
}
return context.ChildContext(matches), nil
return matchingNodeMap, nil
}
func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
log.Debug("Traversing %v", NodeToString(matchingNode))
value := matchingNode.Node
@@ -56,7 +55,7 @@ func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode
switch value.Kind {
case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2)
return traverseMap(context, matchingNode, operation.StringValue, operation.Preferences.(traversePreferences), false)
return traverseMap(matchingNode, operation.StringValue, operation.Preferences.(traversePreferences), false)
case yaml.SequenceNode:
log.Debug("its a sequence of %v things!", len(value.Content))
@@ -65,59 +64,43 @@ func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode
case yaml.AliasNode:
log.Debug("its an alias!")
matchingNode.Node = matchingNode.Node.Alias
return traverse(d, context, matchingNode, operation)
return traverse(d, matchingNode, operation)
case yaml.DocumentNode:
log.Debug("digging into doc node")
return traverse(d, context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), operation)
return traverse(d, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), operation)
default:
return list.New(), nil
}
}
func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
//lhs may update the variable context, we should pass that into the RHS
// BUT we still return the original context back (see jq)
// https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
if err != nil {
return Context{}, err
}
func traverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
// rhs is a collect expression that will yield indexes to retreive of the arrays
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
var indicesToTraverse = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Content
//now we traverse the result of the lhs against the indices we found
result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, traversePreferences{})
if err != nil {
return Context{}, err
}
return context.ChildContext(result.MatchingNodes), nil
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, traversePreferences{})
}
func traverseNodesWithArrayIndices(context Context, indicesToTraverse []*yaml.Node, prefs traversePreferences) (Context, error) {
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) {
var matchingNodeMap = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
newNodes, err := traverseArrayIndices(context, candidate, indicesToTraverse, prefs)
newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs)
if err != nil {
return Context{}, err
return nil, err
}
matchingNodeMap.PushBackList(newNodes)
}
return context.ChildContext(matchingNodeMap), nil
return matchingNodeMap, nil
}
func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
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")
@@ -128,28 +111,28 @@ func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesT
if node.Kind == yaml.AliasNode {
matchingNode.Node = node.Alias
return traverseArrayIndices(context, matchingNode, indicesToTraverse, prefs)
return traverseArrayIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.SequenceNode {
return traverseArrayWithIndices(matchingNode, indicesToTraverse)
} else if node.Kind == yaml.MappingNode {
return traverseMapWithIndices(context, matchingNode, indicesToTraverse, prefs)
return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.DocumentNode {
return traverseArrayIndices(context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs)
return traverseArrayIndices(matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs)
}
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
return list.New(), nil
}
func traverseMapWithIndices(context Context, candidate *CandidateNode, indices []*yaml.Node, prefs traversePreferences) (*list.List, error) {
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs traversePreferences) (*list.List, error) {
if len(indices) == 0 {
return traverseMap(context, candidate, "", prefs, true)
return traverseMap(candidate, "", prefs, true)
}
var matchingNodeMap = list.New()
for _, indexNode := range indices {
log.Debug("traverseMapWithIndices: %v", indexNode.Value)
newNodes, err := traverseMap(context, candidate, indexNode.Value, prefs, false)
newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false)
if err != nil {
return nil, err
}
@@ -204,7 +187,7 @@ func keyMatches(key *yaml.Node, wantedKey string) bool {
return matchKey(key.Value, wantedKey)
}
func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs traversePreferences, splat bool) (*list.List, error) {
func traverseMap(matchingNode *CandidateNode, key string, prefs traversePreferences, splat bool) (*list.List, error) {
var newMatches = orderedmap.NewOrderedMap()
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
@@ -212,24 +195,13 @@ func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs
return nil, err
}
if !prefs.DontAutoCreate && !context.DontAutoCreate && newMatches.Len() == 0 {
if !prefs.DontAutoCreate && newMatches.Len() == 0 {
//no matches, create one automagically
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: key}
node := matchingNode.Node
node.Content = append(node.Content, keyNode, valueNode)
if prefs.IncludeMapKeys {
log.Debug("including key")
candidateNode := matchingNode.CreateChild(key, keyNode)
candidateNode.IsMapKey = true
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
}
if !prefs.DontIncludeMapValues {
log.Debug("including value")
candidateNode := matchingNode.CreateChild(key, valueNode)
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode)
candidateNode := matchingNode.CreateChild(key, valueNode)
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
results := list.New()
@@ -265,16 +237,11 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
} else if splat || keyMatches(key, wantedKey) {
log.Debug("MATCHED")
if prefs.IncludeMapKeys {
log.Debug("including key")
candidateNode := candidate.CreateChild(key.Value, key)
candidateNode.IsMapKey = true
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
}
if !prefs.DontIncludeMapValues {
log.Debug("including value")
candidateNode := candidate.CreateChild(key.Value, value)
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
candidateNode := candidate.CreateChild(key.Value, value)
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
}

View File

@@ -47,30 +47,13 @@ var traversePathOperatorScenarios = []expressionScenario{
},
{
description: "Special characters",
subdescription: "Use quotes with brackets around path elements with special characters",
subdescription: "Use quotes around path elements with special characters",
document: `{"{}": frog}`,
expression: `.["{}"]`,
expression: `."{}"`,
expected: []string{
"D0, P[{}], (!!str)::frog\n",
},
},
{
description: "Keys with spaces",
subdescription: "Use quotes with brackets around path elements with special characters",
document: `{"red rabbit": frog}`,
expression: `.["red rabbit"]`,
expected: []string{
"D0, P[red rabbit], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `{"flying fox": frog}`,
expression: `.["flying fox"]`,
expected: []string{
"D0, P[flying fox], (!!str)::frog\n",
},
},
{
description: "Dynamic keys",
subdescription: `Expressions within [] can be used to dynamically lookup / calculate keys`,
@@ -80,14 +63,6 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[apple], (!!str)::crispy yum\n",
},
},
{
skipDoc: true,
document: `{b: apple, fruit: {apple: yum, banana: smooth}}`,
expression: `.fruit[.b]`,
expected: []string{
"D0, P[fruit apple], (!!str)::yum\n",
},
},
{
description: "Children don't exist",
subdescription: "Nodes are added dynamically while traversing",
@@ -108,7 +83,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{}`,
expression: `.a[1]`,
expression: `.a.[1]`,
expected: []string{
"D0, P[a 1], (!!null)::null\n",
},
@@ -179,7 +154,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b[]`,
expression: `.b.[]`,
expected: []string{
"D0, P[b c], (!!str)::frog\n",
},
@@ -261,7 +236,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
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",
@@ -323,7 +298,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList[]`,
expression: `.foobarList.[]`,
expected: []string{
"D0, P[foobarList b], (!!str)::bar_b\n",
"D0, P[foobarList a], (!!str)::foo_a\n",
@@ -369,7 +344,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[0, 2]`,
expression: `.a.[0, 2]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 2], (!!str)::c\n",
@@ -386,7 +361,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[-1]`,
expression: `.a.[-1]`,
expected: []string{
"D0, P[a -1], (!!str)::c\n",
},
@@ -402,7 +377,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[-2]`,
expression: `.a.[-2]`,
expected: []string{
"D0, P[a -2], (!!str)::b\n",
},
@@ -420,7 +395,7 @@ var traversePathOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{a: [a,b,c]}`,
expression: `.a[]`,
expression: `.a.[]`,
expected: []string{
"D0, P[a 0], (!!str)::a\n",
"D0, P[a 1], (!!str)::b\n",
@@ -443,5 +418,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) {
for _, tt := range traversePathOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Traverse (Read)", traversePathOperatorScenarios)
documentScenarios(t, "Traverse", traversePathOperatorScenarios)
}

View File

@@ -1,17 +1,19 @@
package yqlib
func unionOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
import "container/list"
func unionOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return Context{}, err
return nil, err
}
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return Context{}, err
return nil, err
}
for el := rhs.MatchingNodes.Front(); el != nil; el = el.Next() {
for el := rhs.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
lhs.MatchingNodes.PushBack(node)
lhs.PushBack(node)
}
return lhs, nil
}

View File

@@ -1,6 +1,8 @@
package yqlib
func valueOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
import "container/list"
func valueOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) {
log.Debug("value = %v", expressionNode.Operation.CandidateNode.Node.Value)
return context.SingleChildContext(expressionNode.Operation.CandidateNode), nil
return nodeToMap(expressionNode.Operation.CandidateNode), nil
}

View File

@@ -1,29 +0,0 @@
package yqlib
import (
"container/list"
"fmt"
)
func getVariableOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
variableName := expressionNode.Operation.StringValue
log.Debug("getVariableOperator %v", variableName)
result := context.GetVariable(variableName)
if result == nil {
result = list.New()
}
return context.ChildContext(result), nil
}
func assignVariableOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
if err != nil {
return Context{}, nil
}
if expressionNode.Rhs.Operation.OperationType.Type != "GET_VARIABLE" {
return Context{}, fmt.Errorf("RHS of 'as' operator must be a variable name e.g. $foo")
}
variableName := expressionNode.Rhs.Operation.StringValue
context.SetVariable(variableName, lhs.MatchingNodes)
return context, nil
}

View File

@@ -1,45 +0,0 @@
package yqlib
import (
"testing"
)
var variableOperatorScenarios = []expressionScenario{
{
description: "Single value variable",
document: `a: cat`,
expression: `.a as $foo | $foo`,
expected: []string{
"D0, P[a], (!!str)::cat\n",
},
},
{
description: "Multi value variable",
document: `[cat, dog]`,
expression: `.[] as $foo | $foo`,
expected: []string{
"D0, P[0], (!!str)::cat\n",
"D0, P[1], (!!str)::dog\n",
},
},
{
description: "Using variables as a lookup",
subdescription: "Example taken from [jq](https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...)",
document: `{"posts": [{"title": "Frist psot", "author": "anon"},
{"title": "A well-written article", "author": "person1"}],
"realnames": {"anon": "Anonymous Coward",
"person1": "Person McPherson"}}`,
expression: `.realnames as $names | .posts[] | {"title":.title, "author": $names[.author]}`,
expected: []string{
"D0, P[], (!!map)::title: \"Frist psot\"\nauthor: \"Anonymous Coward\"\n",
"D0, P[], (!!map)::title: \"A well-written article\"\nauthor: \"Person McPherson\"\n",
},
},
}
func TestVariableOperatorScenarios(t *testing.T) {
for _, tt := range variableOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Variable Operators", variableOperatorScenarios)
}

View File

@@ -4,11 +4,10 @@ import (
"container/list"
"fmt"
"github.com/jinzhu/copier"
"gopkg.in/yaml.v3"
)
type operatorHandler func(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error)
type operatorHandler func(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error)
func unwrapDoc(node *yaml.Node) *yaml.Node {
if node.Kind == yaml.DocumentNode {
@@ -17,89 +16,8 @@ func unwrapDoc(node *yaml.Node) *yaml.Node {
return node
}
func emptyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
context.MatchingNodes = list.New()
return context, nil
}
type crossFunctionCalculation func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
func resultsForRhs(d *dataTreeNavigator, context Context, lhsCandidate *CandidateNode, rhs Context, calculation crossFunctionCalculation, results *list.List) error {
for rightEl := rhs.MatchingNodes.Front(); rightEl != nil; rightEl = rightEl.Next() {
log.Debugf("Applying calc")
rhsCandidate := rightEl.Value.(*CandidateNode)
resultCandidate, err := calculation(d, context, lhsCandidate, rhsCandidate)
if err != nil {
return err
}
results.PushBack(resultCandidate)
}
return nil
}
func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation, calcWhenEmpty bool) (Context, error) {
var results = list.New()
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
if err != nil {
return Context{}, err
}
log.Debugf("crossFunction LHS len: %v", lhs.MatchingNodes.Len())
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
if err != nil {
return Context{}, err
}
if calcWhenEmpty && lhs.MatchingNodes.Len() == 0 {
if rhs.MatchingNodes.Len() == 0 {
resultCandidate, err := calculation(d, context, nil, nil)
if err != nil {
return Context{}, err
}
results.PushBack(resultCandidate)
}
err := resultsForRhs(d, context, nil, rhs, calculation, results)
if err != nil {
return Context{}, err
}
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode)
err := resultsForRhs(d, context, lhsCandidate, rhs, calculation, results)
if err != nil {
return Context{}, err
}
}
return context.ChildContext(results), nil
}
func crossFunction(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation, calcWhenEmpty bool) (Context, error) {
var results = list.New()
var evaluateAllTogether = true
for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
evaluateAllTogether = evaluateAllTogether && matchEl.Value.(*CandidateNode).EvaluateTogether
if !evaluateAllTogether {
break
}
}
if evaluateAllTogether {
return doCrossFunc(d, context, expressionNode, calculation, calcWhenEmpty)
}
for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
innerResults, err := doCrossFunc(d, context.SingleChildContext(matchEl.Value.(*CandidateNode)), expressionNode, calculation, calcWhenEmpty)
if err != nil {
return Context{}, err
}
results.PushBackList(innerResults.MatchingNodes)
}
return context.ChildContext(results), nil
func emptyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) {
return list.New(), nil
}
func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
@@ -111,25 +29,22 @@ func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
return owner.CreateChild(nil, node)
}
func createTraversalTree(path []interface{}, traversePrefs traversePreferences, targetKey bool) *ExpressionNode {
func nodeToMap(candidate *CandidateNode) *list.List {
elMap := list.New()
elMap.PushBack(candidate)
return elMap
}
func createTraversalTree(path []interface{}, traversePrefs traversePreferences) *ExpressionNode {
if len(path) == 0 {
return &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
} else if len(path) == 1 {
lastPrefs := traversePrefs
if targetKey {
err := copier.Copy(&lastPrefs, traversePrefs)
if err != nil {
panic(err)
}
lastPrefs.IncludeMapKeys = true
lastPrefs.DontIncludeMapValues = true
}
return &ExpressionNode{Operation: &Operation{OperationType: traversePathOpType, Preferences: lastPrefs, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
return &ExpressionNode{Operation: &Operation{OperationType: traversePathOpType, Preferences: traversePrefs, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
}
return &ExpressionNode{
Operation: &Operation{OperationType: shortPipeOpType},
Lhs: createTraversalTree(path[0:1], traversePrefs, false),
Rhs: createTraversalTree(path[1:], traversePrefs, targetKey),
Lhs: createTraversalTree(path[0:1], traversePrefs),
Rhs: createTraversalTree(path[1:], traversePrefs),
}
}

View File

@@ -27,6 +27,7 @@ type expressionScenario struct {
}
func testScenario(t *testing.T, s *expressionScenario) {
var results *list.List
var err error
node, err := NewExpressionParser().ParseExpression(s.expression)
@@ -65,13 +66,13 @@ func testScenario(t *testing.T, s *expressionScenario) {
os.Setenv("myenv", s.environmentVariable)
}
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
results, err = NewDataTreeNavigator().GetMatchingNodes(inputs, node)
if err != nil {
t.Error(fmt.Errorf("%v: %v", err, s.expression))
return
}
test.AssertResultComplexWithContext(t, s.expected, resultsToString(context.MatchingNodes), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
}
func resultsToString(results *list.List) []string {
@@ -251,12 +252,12 @@ func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formatt
}
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
results, err := NewDataTreeNavigator().GetMatchingNodes(inputs, node)
if err != nil {
t.Error(err, s.expression)
}
err = printer.PrintResults(context.MatchingNodes)
err = printer.PrintResults(results)
if err != nil {
t.Error(err, s.expression)
}

View File

@@ -34,7 +34,7 @@ func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEn
unwrapScalar: unwrapScalar,
colorsEnabled: colorsEnabled,
indent: indent,
printDocSeparators: !outputToJSON && printDocSeparators,
printDocSeparators: printDocSeparators,
firstTimePrinting: true,
treeNavigator: NewDataTreeNavigator(),
}
@@ -74,14 +74,14 @@ func (p *resultsPrinter) safelyFlush(writer *bufio.Writer) {
func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
log.Debug("PrintResults for %v matches", matchingNodes.Len())
var err error
if p.outputToJSON {
explodeOp := Operation{OperationType: explodeOpType}
explodeNode := ExpressionNode{Operation: &explodeOp}
context, err := p.treeNavigator.GetMatchingNodes(Context{MatchingNodes: matchingNodes}, &explodeNode)
matchingNodes, err = p.treeNavigator.GetMatchingNodes(matchingNodes, &explodeNode)
if err != nil {
return err
}
matchingNodes = context.MatchingNodes
}
bufferedWriter := bufio.NewWriter(p.writer)

View File

@@ -3,7 +3,6 @@ package yqlib
import (
"bufio"
"bytes"
"container/list"
"strings"
"testing"
@@ -17,12 +16,6 @@ a: apple
a: coconut
`
func nodeToList(candidate *CandidateNode) *list.List {
elMap := list.New()
elMap.PushBack(candidate)
return elMap
}
func TestPrinterMultipleDocsInSequence(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
@@ -34,13 +27,13 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) {
}
el := inputs.Front()
sample1 := nodeToList(el.Value.(*CandidateNode))
sample1 := nodeToMap(el.Value.(*CandidateNode))
el = el.Next()
sample2 := nodeToList(el.Value.(*CandidateNode))
sample2 := nodeToMap(el.Value.(*CandidateNode))
el = el.Next()
sample3 := nodeToList(el.Value.(*CandidateNode))
sample3 := nodeToMap(el.Value.(*CandidateNode))
err = printer.PrintResults(sample1)
if err != nil {
@@ -75,19 +68,19 @@ func TestPrinterMultipleFilesInSequence(t *testing.T) {
elNode := el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 0
sample1 := nodeToList(elNode)
sample1 := nodeToMap(elNode)
el = el.Next()
elNode = el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 1
sample2 := nodeToList(elNode)
sample2 := nodeToMap(elNode)
el = el.Next()
elNode = el.Value.(*CandidateNode)
elNode.Document = 0
elNode.FileIndex = 2
sample3 := nodeToList(elNode)
sample3 := nodeToMap(elNode)
err = printer.PrintResults(sample1)
if err != nil {
@@ -130,9 +123,7 @@ func TestPrinterMultipleDocsInSinglePrint(t *testing.T) {
func TestPrinterMultipleDocsJson(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
// note printDocSeparators is true, it should still not print document separators
// when outputing JSON.
printer := NewPrinter(writer, true, true, false, 0, true)
printer := NewPrinter(writer, true, true, false, 0, false)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {

View File

@@ -41,11 +41,11 @@ func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error
inputList := list.New()
inputList.PushBack(candidateNode)
result, errorParsing := s.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputList}, node)
matches, errorParsing := s.treeNavigator.GetMatchingNodes(inputList, node)
if errorParsing != nil {
return errorParsing
}
return printer.PrintResults(result.MatchingNodes)
return printer.PrintResults(matches)
}
func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
@@ -97,11 +97,11 @@ func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *Expr
inputList := list.New()
inputList.PushBack(candidateNode)
result, errorParsing := s.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputList}, node)
matches, errorParsing := s.treeNavigator.GetMatchingNodes(inputList, node)
if errorParsing != nil {
return errorParsing
}
err := printer.PrintResults(result.MatchingNodes)
err := printer.PrintResults(matches)
if err != nil {
return err
}

View File

@@ -2,14 +2,11 @@
- increment version in snapcraft.yaml
- increment version in github-action/Dockerfile
- make sure local build passes
- run gosec (manual because docker platforms)
- commit version update changes
- tag git with same version number
- commit vX tag - this will trigger github actions
- use github actions to publish docker and make github release
- check github updated yq action in marketplace
- snapcraft
- will auto create a candidate, test it works then promote

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