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

Compare commits

...

52 Commits

Author SHA1 Message Date
Mike Farah
f42355cf9f Force re-release 2021-02-10 17:12:54 +11:00
Mike Farah
44bc9971c1 Fixed write-inplace with no expression 2021-02-10 17:06:16 +11:00
Ben Moss
54cf6cf282 Fix pretty-printing
Fixes #716 #715
2021-02-10 16:56:44 +11:00
Mike Farah
29db5ebf4d Version increment 2021-02-10 16:55:05 +11:00
Mike Farah
abaf299115 Preserve comments on map keys 2021-02-10 16:48:10 +11:00
Mike Farah
e9c5573a89 Added space example to docs 2021-02-05 15:00:46 +11:00
Mike Farah
e0fdbe7f35 update deps 2021-02-05 14:57:45 +11:00
Mike Farah
ebfc7212e2 Fixed merge dropping anchors 2021-02-05 14:40:16 +11:00
Mike Farah
ca10ede102 improving docs 2021-02-05 13:32:07 +11:00
Mike Farah
e76a2f3de6 Fixing special character example 2021-02-05 13:29:35 +11:00
Mike Farah
b702686891 Added not equals operator 2021-02-05 09:54:03 +11:00
Mike Farah
c284d4c6b1 Equals now only compares scalars 2021-02-05 09:49:40 +11:00
Mike Farah
63883d3000 Fixed delete bug 2021-02-04 13:47:59 +11:00
Mike Farah
3014192ce8 fixed instructions 2021-02-04 13:12:16 +11:00
Mike Farah
ebeb1146ba v4.5.0 2021-02-04 13:04:53 +11:00
Mike Farah
0bb53ec770 Fixed delete bug 2021-02-04 12:48:07 +11:00
Mike Farah
a50e154652 Added variable doc 2021-02-04 12:44:03 +11:00
Mike Farah
f91d30e46c Fixed variable precedence 2021-02-04 12:39:04 +11:00
Mike Farah
07b053f040 Fixing op precedences 2021-02-04 12:18:54 +11:00
Mike Farah
35c97e832e Fixing op precedences 2021-02-03 17:11:47 +11:00
Mike Farah
8a5c47906e Added variables 2021-02-03 15:51:26 +11:00
Mike Farah
a366cacad3 Dont create entries when selecting 2021-02-03 11:56:35 +11:00
Mike Farah
7c8d3b9e70 Pass context through operators
Allows more sophisticated functionality
2021-02-03 11:56:35 +11:00
Mike Farah
ca10642e23 Added funding button 2021-02-01 15:50:57 +11:00
zy
73518f3915 change version from master to latest(v4) 2021-01-27 10:09:32 +11:00
zy
348ddb7a59 fix: go install fails
Installation by go will result in an error:
module declares its path as: xxx,
but was required as: xxx.

Go install with master branch will fix it.

fix: #676
2021-01-27 10:09:32 +11:00
Mike Farah
f46fe384bd Fixed length of null to be zero 2021-01-26 10:21:16 +11:00
Mike Farah
071ec3c08c fixing docker pipeline for nextime 2021-01-21 21:15:21 +11:00
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
91 changed files with 1825 additions and 702 deletions

1
.github/FUNDING.yml vendored Normal file
View File

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

View File

@@ -40,17 +40,7 @@ jobs:
IMAGE_NAME: mikefarah/yq IMAGE_NAME: mikefarah/yq
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Get latest release tag - uses: actions/checkout@v2
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 - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1

View File

@@ -90,7 +90,7 @@ docker run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression
#### Run commands interactively: #### Run commands interactively:
```bash ```bash
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh docker run --rm -it -v "${PWD}":/workdir --entrypoint sh mikefarah/yq
``` ```
It can be useful to have a bash function to avoid typing the whole docker command: It can be useful to have a bash function to avoid typing the whole docker command:
@@ -103,7 +103,7 @@ yq() {
### Go Get: ### Go Get:
``` ```
GO111MODULE=on go get github.com/mikefarah/yq GO111MODULE=on go get github.com/mikefarah/yq/v4
``` ```
## Community Supported Installation methods ## Community Supported Installation methods

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
FROM mikefarah/yq:4.3.2 FROM mikefarah/yq:4.5.1
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh

8
go.mod
View File

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

19
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/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/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/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0= github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A=
github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw= github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 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/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-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M= github.com/goccy/go-yaml v1.8.8 h1:MGfRB1GeSn/hWXYWS2Pt67iC2GJNnebdIro01ddyucA=
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/goccy/go-yaml v1.8.8/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -101,8 +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/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88= github.com/jinzhu/copier v0.2.3 h1:Oe09ju+9qft7TffZ7l/04AB2f8u1+V4ZMxmp/nnqeOs=
github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jinzhu/copier v0.2.3/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -182,6 +182,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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/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 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ=
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU= github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
@@ -257,8 +259,8 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -324,6 +326,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

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

View File

@@ -2,8 +2,6 @@ package yqlib
import ( import (
"fmt" "fmt"
"strconv"
"strings"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
@@ -15,10 +13,18 @@ type CandidateNode struct {
Document uint // the document index of this node Document uint // the document index of this node
Filename string Filename string
FileIndex int FileIndex int
// when performing op against all nodes given, this will treat all the nodes as one
// (e.g. top level cross document merge). This property does not propegate to child nodes.
EvaluateTogether bool
IsMapKey bool
} }
func (n *CandidateNode) GetKey() string { func (n *CandidateNode) GetKey() string {
return fmt.Sprintf("%v - %v", n.Document, n.Path) keyPrefix := ""
if n.IsMapKey {
keyPrefix = "key-"
}
return fmt.Sprintf("%v%v - %v", keyPrefix, n.Document, n.Path)
} }
func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode { func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode {
@@ -60,11 +66,10 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
n.UpdateAttributesFrom(other) n.UpdateAttributesFrom(other)
n.Node.Content = other.Node.Content n.Node.Content = other.Node.Content
n.Node.Value = other.Node.Value n.Node.Value = other.Node.Value
n.Node.Alias = other.Node.Alias
n.Node.Anchor = other.Node.Anchor
} }
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) { func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
log.Debug("UpdateAttributesFrom: n: %v other: %v", n.GetKey(), other.GetKey())
if n.Node.Kind != other.Node.Kind { if n.Node.Kind != other.Node.Kind {
// clear out the contents when switching to a different type // clear out the contents when switching to a different type
// e.g. map to array // e.g. map to array
@@ -73,6 +78,8 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
} }
n.Node.Kind = other.Node.Kind n.Node.Kind = other.Node.Kind
n.Node.Tag = other.Node.Tag 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 // merge will pickup the style of the new thing
// when autocreating nodes // when autocreating nodes
@@ -83,46 +90,3 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment
n.Node.LineComment = n.Node.LineComment + other.Node.LineComment 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()
}

54
pkg/yqlib/context.go Normal file
View File

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

View File

@@ -209,3 +209,15 @@ will output
a: 4 a: 4
``` ```
## Add to null
Adding to null simply returns the rhs
Running
```bash
yq eval --null-input 'null + "cat"'
```
will output
```yaml
cat
```

View File

@@ -78,17 +78,22 @@ b: dog # leave this
``` ```
## Remove all comments ## Remove all comments
Note the use of `...` to ensure key nodes are included.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat # comment a: cat # comment
# great
b: # key comment
``` ```
then then
```bash ```bash
yq eval '.. comments=""' sample.yml yq eval '... comments=""' sample.yml
``` ```
will output will output
```yaml ```yaml
a: cat a: cat
b:
``` ```
## Get line comment ## Get line comment

View File

@@ -29,6 +29,24 @@ true
false 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 ## Match number
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -47,6 +65,24 @@ true
false 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 ## Match nulls
Running Running
```bash ```bash

35
pkg/yqlib/doc/Keys.md Normal file
View File

@@ -0,0 +1,35 @@
# Keys
Use the `keys` operator to return map keys or array indices.
## Map keys
Given a sample.yml file of:
```yaml
dog: woof
cat: meow
```
then
```bash
yq eval 'keys' sample.yml
```
will output
```yaml
- dog
- cat
```
## Array keys
Given a sample.yml file of:
```yaml
- apple
- banana
```
then
```bash
yq eval 'keys' sample.yml
```
will output
```yaml
- 0
- 1
```

View File

@@ -16,6 +16,20 @@ will output
3 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 ## Map length
returns number of entries returns number of entries

View File

@@ -1,13 +1,14 @@
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS. Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). To concatenate arrays when merging objects, use the *+ form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
To merge only existing fields, use the *? form. Note that this can be used with the concatenate arrays too *+?.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
Multiplication of strings and numbers are not yet supported.
## Merging files ## Merging files
Note the use of eval-all to ensure all documents are loaded into memory. Note the use of `eval-all` to ensure all documents are loaded into memory.
```bash ```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
@@ -70,7 +71,6 @@ Given a sample.yml file of:
a: {things: great} a: {things: great}
b: b:
also: "me" also: "me"
``` ```
then then
```bash ```bash
@@ -111,6 +111,26 @@ b:
- 5 - 5
``` ```
## Merge, only existing fields
Given a sample.yml file of:
```yaml
a:
thing: one
cat: frog
b:
missing: two
thing: two
```
then
```bash
yq eval '.a *? .b' sample.yml
```
will output
```yaml
thing: two
cat: frog
```
## Merge, appending arrays ## Merge, appending arrays
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -143,6 +163,33 @@ array:
value: banana value: banana
``` ```
## Merge, only existing fields, appending arrays
Given a sample.yml file of:
```yaml
a:
thing:
- 1
- 2
b:
thing:
- 3
- 4
another:
- 1
```
then
```bash
yq eval '.a *?+ .b' sample.yml
```
will output
```yaml
thing:
- 1
- 2
- 3
- 4
```
## Merge to prefix an element ## Merge to prefix an element
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -0,0 +1,31 @@
# Split into Documents
This operator splits all matches into separate documents
## Split empty
Running
```bash
yq eval --null-input 'splitDoc'
```
will output
```yaml
```
## Split array
Given a sample.yml file of:
```yaml
- a: cat
- b: dog
```
then
```bash
yq eval '.[] | splitDoc' sample.yml
```
will output
```yaml
a: cat
---
b: dog
```

View File

@@ -0,0 +1,52 @@
# String Operators
## Join strings
Given a sample.yml file of:
```yaml
- cat
- meow
- 1
- null
- true
```
then
```bash
yq eval 'join("; ")' sample.yml
```
will output
```yaml
cat; meow; 1; ; true
```
## Split strings
Given a sample.yml file of:
```yaml
cat; meow; 1; ; true
```
then
```bash
yq eval 'split("; ")' sample.yml
```
will output
```yaml
- cat
- meow
- "1"
- ""
- "true"
```
## Split strings one match
Given a sample.yml file of:
```yaml
word
```
then
```bash
yq eval 'split("; ")' sample.yml
```
will output
```yaml
- word
```

View File

@@ -33,7 +33,7 @@ c: banana
``` ```
## Special characters ## Special characters
Use quotes around path elements with special characters Use quotes with brackets around path elements with special characters
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -41,7 +41,23 @@ Given a sample.yml file of:
``` ```
then then
```bash ```bash
yq eval '."{}"' sample.yml 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
``` ```
will output will output
```yaml ```yaml

View File

@@ -0,0 +1,58 @@
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

@@ -0,0 +1,12 @@
# Operators
In `yq` expressions are made up of operators. Operators have 0-2 arguments and run against the current 'matching' nodes in the expression tree.
Lets look at a couple of examples.
The `length` operator take no arguments, and will simply return the length of _each_ matching node. So if there were 2 nodes, one string and one array, length will update the 'matching' nodes context to be two new numeric scalar nodes representing the lengths of the orignal 'matching' nodes.
The `=` operator takes two arguments, a `lhs` expression and `rhs` expression. It runs the 'matching' nodes context against the `lhs` expression to find the nodes to update, lets call it `lhsNodes`, and then runs the matching nodes against the `rhs` to find the new values, lets call that `rhsNodes`. It updates the `lhsNodes` values with the `rhsNodes` values and _returns the original matching nodes_. This is important, where length changed the matching nodes to be new nodes with the length values, `=` returns the original matching nodes, albeit with some of the nodes values updated. So `.a = 3` will still return the parent matching node, but with the matching child updated.
Please see the individual operator docs for more information and examples.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,29 +9,11 @@ import (
"github.com/mikefarah/yq/v4/test" "github.com/mikefarah/yq/v4/test"
) )
var sampleYaml = `zabbix: winner func yamlToJson(sampleYaml string, indent int) string {
apple: great
banana:
- {cobra: kai, angus: bob}
`
var expectedJson = `{
"zabbix": "winner",
"apple": "great",
"banana": [
{
"cobra": "kai",
"angus": "bob"
}
]
}
`
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
var output bytes.Buffer var output bytes.Buffer
writer := bufio.NewWriter(&output) writer := bufio.NewWriter(&output)
var jsonEncoder = NewJsonEncoder(writer, 2) var jsonEncoder = NewJsonEncoder(writer, indent)
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0) inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -42,6 +24,33 @@ func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
panic(err) panic(err)
} }
writer.Flush() writer.Flush()
test.AssertResult(t, expectedJson, output.String())
return strings.TrimSuffix(output.String(), "\n")
}
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
var sampleYaml = `zabbix: winner
apple: great
banana:
- {cobra: kai, angus: bob}
`
var expectedJson = `{
"zabbix": "winner",
"apple": "great",
"banana": [
{
"cobra": "kai",
"angus": "bob"
}
]
}`
var actualJson = yamlToJson(sampleYaml, 2)
test.AssertResult(t, expectedJson, actualJson)
}
func TestJsonEncoderDoesNotEscapeHTMLChars(t *testing.T) {
var sampleYaml = `build: "( ./lint && ./format && ./compile ) < src.code"`
var expectedJson = `{"build":"( ./lint && ./format && ./compile ) < src.code"}`
var actualJson = yamlToJson(sampleYaml, 0)
test.AssertResult(t, expectedJson, actualJson)
} }

View File

@@ -70,7 +70,7 @@ func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*
stack = append(stack, &newNode) stack = append(stack, &newNode)
} }
if len(stack) != 1 { if len(stack) != 1 {
return nil, fmt.Errorf("expected end of expression but found '%v', please check expression syntax", strings.TrimSpace(stack[1].Operation.StringValue)) return nil, fmt.Errorf("Bad expression, please check expression syntax")
} }
return stack[0], nil return stack[0], nil
} }

View File

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

View File

@@ -20,20 +20,22 @@ func newExpressionPostFixer() expressionPostFixer {
func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) { func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) {
var newOp *token var newOp *token
opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1] 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) return opStack, append(result, newOp.Operation)
} }
func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) { func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) {
var result []*Operation var result []*Operation
// surround the whole thing with quotes // surround the whole thing with brackets
var opStack = []*token{&token{TokenType: openBracket}} var opStack = []*token{{TokenType: openBracket}}
var tokens = append(infixTokens, &token{TokenType: closeBracket}) var tokens = append(infixTokens, &token{TokenType: closeBracket})
for _, currentToken := range tokens { for _, currentToken := range tokens {
log.Debugf("postfix processing currentToken %v, %v", currentToken.toString(), currentToken.Operation) log.Debugf("postfix processing currentToken %v", currentToken.toString(true))
switch currentToken.TokenType { switch currentToken.TokenType {
case openBracket, openCollect, openCollectObject: case openBracket, openCollect, openCollectObject:
opStack = append(opStack, currentToken) opStack = append(opStack, currentToken)
log.Debugf("put %v onto the opstack", currentToken.toString(true))
case closeCollect, closeCollectObject: case closeCollect, closeCollectObject:
var opener tokenType = openCollect var opener tokenType = openCollect
var collectOperator *operationType = collectOpType var collectOperator *operationType = collectOpType
@@ -41,23 +43,23 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
opener = openCollectObject opener = openCollectObject
collectOperator = collectObjectOpType collectOperator = collectObjectOpType
} }
itemsInMiddle := false
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener { for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener {
opStack, result = popOpToResult(opStack, result) 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 { if len(opStack) == 0 {
return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket") 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] opStack = opStack[0 : len(opStack)-1]
log.Debugf("deleteing open bracket from opstack")
//and append a collect to the opStack //and append a collect to the opStack
opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: shortPipeOpType}}) result = append(result, &Operation{OperationType: collectOperator})
opStack = append(opStack, &token{TokenType: operationToken, Operation: &Operation{OperationType: collectOperator}}) log.Debugf("put collect onto the result")
result = append(result, &Operation{OperationType: shortPipeOpType})
log.Debugf("put shortpipe onto the result")
case closeBracket: case closeBracket:
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket { for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket {
opStack, result = popOpToResult(opStack, result) opStack, result = popOpToResult(opStack, result)
@@ -73,11 +75,12 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
// pop off higher precedent operators onto the result // pop off higher precedent operators onto the result
for len(opStack) > 0 && for len(opStack) > 0 &&
opStack[len(opStack)-1].TokenType == operationToken && 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) opStack, result = popOpToResult(opStack, result)
} }
// add this operator to the opStack // add this operator to the opStack
opStack = append(opStack, currentToken) opStack = append(opStack, currentToken)
log.Debugf("put %v onto the opstack", currentToken.toString(true))
} }
} }

View File

@@ -12,40 +12,65 @@ var pathTests = []struct {
expectedTokens []interface{} expectedTokens []interface{}
expectedPostFix []interface{} expectedPostFix []interface{}
}{ }{
{
`.a | .b | .c`,
append(make([]interface{}, 0), "a", "PIPE", "b", "PIPE", "c"),
append(make([]interface{}, 0), "a", "b", "c", "PIPE", "PIPE"),
},
{ {
`[]`, `[]`,
append(make([]interface{}, 0), "[", "]"), append(make([]interface{}, 0), "[", "EMPTY", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE"), 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), "TRAVERSE_ARRAY", "[", "]"), append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"), append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
}, },
{ {
`.a[]`, `.a[]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"), append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
}, },
{ {
`.a.[]`, `.a.[]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"), append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
}, },
{ {
`.a[0]`, `.a[0]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"), append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
}, },
{ {
`.a.[0]`, `.a.[0]`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE"), append(make([]interface{}, 0), "a", "0 (int64)", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY"),
}, },
{ {
`.a[].c`, `.a[].c`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "c"), append(make([]interface{}, 0), "a", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "c"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "c", "SHORT_PIPE"), append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "c", "SHORT_PIPE"),
}, },
{ {
`[3]`, `[3]`,
@@ -53,24 +78,34 @@ var pathTests = []struct {
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"), append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"),
}, },
{ {
`d0.a`, `.key.array + .key.array2`,
append(make([]interface{}, 0), "d0", "SHORT_PIPE", "a"), append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ADD", "key", "SHORT_PIPE", "array2"),
append(make([]interface{}, 0), "d0", "a", "SHORT_PIPE"), append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ADD"),
},
{
`.key.array * .key.array2`,
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "MULTIPLY", "key", "SHORT_PIPE", "array2"),
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "MULTIPLY"),
},
{
`.key.array // .key.array2`,
append(make([]interface{}, 0), "key", "SHORT_PIPE", "array", "ALTERNATIVE", "key", "SHORT_PIPE", "array2"),
append(make([]interface{}, 0), "key", "array", "SHORT_PIPE", "key", "array2", "SHORT_PIPE", "ALTERNATIVE"),
}, },
{ {
`.a | .[].b == "apple"`, `.a | .[].b == "apple"`,
append(make([]interface{}, 0), "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"), append(make([]interface{}, 0), "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"), append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "apple (string)", "EQUALS", "PIPE"),
}, },
{ {
`(.a | .[].b) == "apple"`, `(.a | .[].b) == "apple"`,
append(make([]interface{}, 0), "(", "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"), append(make([]interface{}, 0), "(", "a", "PIPE", "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "SHORT_PIPE", "b", ")", "EQUALS", "apple (string)"),
append(make([]interface{}, 0), "a", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"), append(make([]interface{}, 0), "a", "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "b", "SHORT_PIPE", "PIPE", "apple (string)", "EQUALS"),
}, },
{ {
`.[] | select(. == "*at")`, `.[] | select(. == "*at")`,
append(make([]interface{}, 0), "TRAVERSE_ARRAY", "[", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"), append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "EMPTY", "]", "PIPE", "SELECT", "(", "SELF", "EQUALS", "*at (string)", ")"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"), append(make([]interface{}, 0), "SELF", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SELF", "*at (string)", "EQUALS", "SELECT", "PIPE"),
}, },
{ {
`[true]`, `[true]`,
@@ -103,9 +138,9 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"), append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "SHORT_PIPE"),
}, },
{ {
`{.a: .c, .b.[]: .f.g.[]}`, `{.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", "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", "SHORT_PIPE", "f", "g", "SHORT_PIPE", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"), append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "f", "g", "EMPTY", "COLLECT", "SHORT_PIPE", "TRAVERSE_ARRAY", "SHORT_PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "SHORT_PIPE"),
}, },
{ {
`explode(.a.b)`, `explode(.a.b)`,
@@ -157,11 +192,6 @@ var pathTests = []struct {
append(make([]interface{}, 0), "foo*", "PIPE", "(", "SELF", "ASSIGN_STYLE", "flow (string)", ")"), 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), "foo*", "SELF", "flow (string)", "ASSIGN_STYLE", "PIPE"),
}, },
{
`{}`,
append(make([]interface{}, 0), "{", "}"),
append(make([]interface{}, 0), "EMPTY", "COLLECT_OBJECT", "SHORT_PIPE"),
},
} }
var tokeniser = newExpressionTokeniser() var tokeniser = newExpressionTokeniser()
@@ -175,7 +205,7 @@ func TestPathParsing(t *testing.T) {
} }
var tokenValues []interface{} var tokenValues []interface{}
for _, token := range tokens { for _, token := range tokens {
tokenValues = append(tokenValues, token.toString()) tokenValues = append(tokenValues, token.toString(false))
} }
test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, fmt.Sprintf("tokenise: %v", tt.path)) test.AssertResultComplexWithContext(t, tt.expectedTokens, tokenValues, fmt.Sprintf("tokenise: %v", tt.path))

View File

@@ -3,6 +3,7 @@ package yqlib
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
lex "github.com/timtadh/lexmachine" lex "github.com/timtadh/lexmachine"
"github.com/timtadh/lexmachine/machines" "github.com/timtadh/lexmachine/machines"
@@ -33,9 +34,11 @@ type token struct {
} }
func (t *token) toString() string { func (t *token) toString(detail bool) string {
if t.TokenType == operationToken { if t.TokenType == operationToken {
log.Debug("toString, its an op") if detail {
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
}
return t.Operation.toString() return t.Operation.toString()
} else if t.TokenType == openBracket { } else if t.TokenType == openBracket {
return "(" return "("
@@ -65,21 +68,7 @@ func pathToken(wrapped bool) lex.Action {
value = unwrap(value) value = unwrap(value)
} }
log.Debug("PathToken %v", value) log.Debug("PathToken %v", value)
op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value} op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value, Preferences: traversePreferences{}}
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
}
}
func documentToken() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var numberString = string(m.Bytes)
numberString = numberString[1:]
var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint
if errParsingInt != nil {
return nil, errParsingInt
}
log.Debug("documentToken %v", string(m.Bytes))
op := &Operation{OperationType: documentFilterOpType, Value: number, StringValue: numberString}
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
} }
} }
@@ -101,6 +90,21 @@ func assignOpToken(updateAssign bool) lex.Action {
} }
} }
func multiplyWithPrefs() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
prefs := multiplyPreferences{}
options := string(m.Bytes)
if strings.Contains(options, "+") {
prefs.AppendArrays = true
}
if strings.Contains(options, "?") {
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
}
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil
}
}
func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action { func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("opTokenWithPrefs %v", string(m.Bytes)) log.Debug("opTokenWithPrefs %v", string(m.Bytes))
@@ -123,7 +127,7 @@ func assignAllCommentsOp(updateAssign bool) lex.Action {
Value: assignCommentOpType.Type, Value: assignCommentOpType.Type,
StringValue: value, StringValue: value,
UpdateAssign: updateAssign, UpdateAssign: updateAssign,
Preferences: &commentOpPreferences{LineComment: true, HeadComment: true, FootComment: true}, Preferences: commentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
} }
return &token{TokenType: operationToken, Operation: op}, nil return &token{TokenType: operationToken, Operation: op}, nil
} }
@@ -178,10 +182,23 @@ 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 : len(value)-1]
getVarOperation := createValueOperation(value, value)
getVarOperation.OperationType = getVariableOpType
return &token{TokenType: operationToken, Operation: getVarOperation, CheckForPostTraverse: true}, nil
}
}
func envOp(strenv bool) lex.Action { func envOp(strenv bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes) value := string(m.Bytes)
preferences := &envOpPreferences{} preferences := envOpPreferences{}
if strenv { if strenv {
// strenv( ) // strenv( )
@@ -219,11 +236,11 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\)`), literalToken(closeBracket, true)) lexer.Add([]byte(`\)`), literalToken(closeBracket, true))
lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false)) lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false))
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, &recursiveDescentPreferences{RecurseArray: true, lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
TraversePreferences: &traversePreferences{FollowAlias: false, IncludeMapKeys: false}})) TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: false}}))
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, &recursiveDescentPreferences{RecurseArray: true, lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
TraversePreferences: &traversePreferences{FollowAlias: false, IncludeMapKeys: true}})) TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}))
lexer.Add([]byte(`,`), opToken(unionOpType)) lexer.Add([]byte(`,`), opToken(unionOpType))
lexer.Add([]byte(`:\s*`), opToken(createMapOpType)) lexer.Add([]byte(`:\s*`), opToken(createMapOpType))
@@ -239,6 +256,11 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType)) lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType))
lexer.Add([]byte(`di`), opToken(getDocumentIndexOpType)) lexer.Add([]byte(`di`), opToken(getDocumentIndexOpType))
lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType))
lexer.Add([]byte(`join`), opToken(joinStringOpType))
lexer.Add([]byte(`split`), opToken(splitStringOpType))
lexer.Add([]byte(`keys`), opToken(keysOpType))
lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType)) lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType))
@@ -250,11 +272,11 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`fi`), opToken(getFileIndexOpType)) lexer.Add([]byte(`fi`), opToken(getFileIndexOpType))
lexer.Add([]byte(`path`), opToken(getPathOpType)) lexer.Add([]byte(`path`), opToken(getPathOpType))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, &commentOpPreferences{LineComment: true})) lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{LineComment: true}))
lexer.Add([]byte(`headComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, &commentOpPreferences{HeadComment: true})) lexer.Add([]byte(`headComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{HeadComment: true}))
lexer.Add([]byte(`footComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, &commentOpPreferences{FootComment: true})) lexer.Add([]byte(`footComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false)) lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true)) lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
@@ -262,6 +284,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`collect`), opToken(collectOpType)) lexer.Add([]byte(`collect`), opToken(collectOpType))
lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType)) lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType))
lexer.Add([]byte(`\s*!=\s*`), opToken(notEqualsOpType))
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false)) lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
lexer.Add([]byte(`del`), opToken(deleteChildOpType)) lexer.Add([]byte(`del`), opToken(deleteChildOpType))
@@ -270,7 +293,6 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte("( |\t|\n|\r)+"), skip) lexer.Add([]byte("( |\t|\n|\r)+"), skip)
lexer.Add([]byte(`d[0-9]+`), documentToken())
lexer.Add([]byte(`\."[^ "]+"`), pathToken(true)) lexer.Add([]byte(`\."[^ "]+"`), pathToken(true))
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false)) lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false))
lexer.Add([]byte(`\.`), selfToken()) lexer.Add([]byte(`\.`), selfToken())
@@ -295,10 +317,11 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\]`), literalToken(closeCollect, true)) lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false)) lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true)) lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
lexer.Add([]byte(`\*`), opTokenWithPrefs(multiplyOpType, nil, &multiplyPreferences{AppendArrays: false})) lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs())
lexer.Add([]byte(`\*\+`), opTokenWithPrefs(multiplyOpType, nil, &multiplyPreferences{AppendArrays: true}))
lexer.Add([]byte(`\+`), opToken(addOpType)) lexer.Add([]byte(`\+`), opToken(addOpType))
lexer.Add([]byte(`\+=`), opToken(addAssignOpType)) lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken())
lexer.Add([]byte(`as`), opToken(assignVariableOpType))
err := lexer.Compile() err := lexer.Compile()
if err != nil { if err != nil {
@@ -334,7 +357,7 @@ func (p *expressionTokeniserImpl) Tokenise(expression string) ([]*token, error)
if tok != nil { if tok != nil {
currentToken := tok.(*token) currentToken := tok.(*token)
log.Debugf("Tokenising %v", currentToken.toString()) log.Debugf("Tokenising %v", currentToken.toString(true))
tokens = append(tokens, currentToken) tokens = append(tokens, currentToken)
} }
if err != nil { if err != nil {
@@ -364,6 +387,12 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
//need to put a traverse array then a collect currentToken //need to put a traverse array then a collect currentToken
// do this by adding traverse then converting currentToken to collect // 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"} op := &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op}) postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
@@ -381,6 +410,14 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
postProcessedTokens = append(postProcessedTokens, currentToken) 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 && if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == operationToken && tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == traversePathOpType { tokens[index+1].Operation.OperationType == traversePathOpType {
@@ -390,18 +427,8 @@ func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postPr
if index != len(tokens)-1 && currentToken.CheckForPostTraverse && if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == openCollect { tokens[index+1].TokenType == openCollect {
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"} op := &Operation{OperationType: traverseArrayOpType}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op}) 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 return postProcessedTokens, skipNextToken
} }

View File

@@ -35,22 +35,26 @@ var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence:
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator} 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 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 assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator} var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator} var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator} var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: multiplyOperator} var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: addOperator} var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: alternativeOperator} var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator} var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator}
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, 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 shortPipeOpType = &operationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: pipeOperator}
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator} var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator}
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator} 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 getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator} var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator} var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
@@ -63,13 +67,16 @@ var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50,
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator} var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator} var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator} var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator}
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator} var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 55, Handler: traversePathOperator}
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator} var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: traverseArrayOperator}
var documentFilterOpType = &operationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: traversePathOperator} 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 valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator} var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator} var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
@@ -120,8 +127,6 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
func (p *Operation) toString() string { func (p *Operation) toString() string {
if p.OperationType == traversePathOpType { if p.OperationType == traversePathOpType {
return fmt.Sprintf("%v", p.Value) return fmt.Sprintf("%v", p.Value)
} else if p.OperationType == documentFilterOpType {
return fmt.Sprintf("d%v", p.Value)
} else if p.OperationType == selfReferenceOpType { } else if p.OperationType == selfReferenceOpType {
return "SELF" return "SELF"
} else if p.OperationType == valueOpType { } else if p.OperationType == valueOpType {

View File

@@ -3,7 +3,6 @@ package yqlib
import ( import (
"fmt" "fmt"
"container/list"
"strconv" "strconv"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
@@ -15,12 +14,12 @@ func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
Rhs: rhs} Rhs: rhs}
} }
func addAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
assignmentOp := &Operation{OperationType: assignOpType} assignmentOp := &Operation{OperationType: assignOpType}
assignmentOp.UpdateAssign = false assignmentOp.UpdateAssign = false
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)} assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)}
return d.GetMatchingNodes(matchingNodes, assignmentOpNode) return d.GetMatchingNodes(context, assignmentOpNode)
} }
func toNodes(candidate *CandidateNode) []*yaml.Node { func toNodes(candidate *CandidateNode) []*yaml.Node {
@@ -37,19 +36,24 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
} }
func addOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("Add operator") log.Debugf("Add operator")
return crossFunction(d, matchingNodes, expressionNode, add) return crossFunction(d, context, expressionNode, add)
} }
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = unwrapDoc(rhs.Node)
target := lhs.CreateChild(nil, &yaml.Node{})
lhsNode := lhs.Node lhsNode := lhs.Node
if lhsNode.Tag == "!!null" {
return lhs.CreateChild(nil, rhs.Node), nil
}
target := lhs.CreateChild(nil, &yaml.Node{})
switch lhsNode.Kind { switch lhsNode.Kind {
case yaml.MappingNode: case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for addition") return nil, fmt.Errorf("Maps not yet supported for addition")

View File

@@ -5,6 +5,15 @@ import (
) )
var addOperatorScenarios = []expressionScenario{ var addOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `[{a: foo, b: bar}, {a: 1, b: 2}]`,
expression: ".[] | .a + .b",
expected: []string{
"D0, P[0 a], (!!str)::foobar\n",
"D0, P[1 a], (!!int)::3\n",
},
},
{ {
description: "Concatenate and assign arrays", description: "Concatenate and assign arrays",
document: `{a: {val: thing, b: [cat,dog]}}`, document: `{a: {val: thing, b: [cat,dog]}}`,
@@ -103,6 +112,14 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: 4}\n", "D0, P[], (doc)::{a: 4}\n",
}, },
}, },
{
description: "Add to null",
subdescription: "Adding to null simply returns the rhs",
expression: `null + "cat"`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
},
} }
func TestAddOperatorScenarios(t *testing.T) { func TestAddOperatorScenarios(t *testing.T) {

View File

@@ -1,18 +1,14 @@
package yqlib package yqlib
import (
"container/list"
)
// corssFunction no matches // corssFunction no matches
// can boolean use crossfunction // can boolean use crossfunction
func alternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func alternativeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- alternative") log.Debugf("-- alternative")
return crossFunction(d, matchingNodes, expressionNode, alternativeFunc) return crossFunction(d, context, expressionNode, alternativeFunc)
} }
func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = unwrapDoc(rhs.Node)
log.Debugf("Alternative LHS: %v", lhs.Node.Tag) log.Debugf("Alternative LHS: %v", lhs.Node.Tag)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -71,11 +71,12 @@ var commentOperatorScenarios = []expressionScenario{
}, },
}, },
{ {
description: "Remove all comments", description: "Remove all comments",
document: "# hi\n\na: cat # comment\n\n# great\n", subdescription: "Note the use of `...` to ensure key nodes are included.",
expression: `.. comments=""`, document: "# hi\n\na: cat # comment\n\n# great\n\nb: # key comment",
expression: `... comments=""`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::a: cat\n", "D0, P[], (!!map)::a: cat\nb:\n",
}, },
}, },
{ {

View File

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

View File

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

View File

@@ -37,6 +37,38 @@ var deleteOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::[1, 3]\n", "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", description: "Delete nested entry in array",
document: `[{a: cat, b: dog}]`, document: `[{a: cat, b: dog}]`,

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,14 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[], (!!bool)::false\n", "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", description: "Match string",
document: `[cat,goat,dog]`, document: `[cat,goat,dog]`,
@@ -23,7 +31,18 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[1], (!!bool)::true\n", "D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\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", description: "Match number",
document: `[3, 4, 5]`, document: `[3, 4, 5]`,
expression: `.[] | (. == 4)`, expression: `.[] | (. == 4)`,
@@ -32,7 +51,18 @@ var equalsOperatorScenarios = []expressionScenario{
"D0, P[1], (!!bool)::true\n", "D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\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, skipDoc: true,
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`, document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
expression: `.a | (.[].b == "apple")`, expression: `.a | (.[].b == "apple")`,

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
package yqlib
import (
"container/list"
"fmt"
"gopkg.in/yaml.v3"
)
func keysOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- keysOperator")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := unwrapDoc(candidate.Node)
var targetNode *yaml.Node
if node.Kind == yaml.MappingNode {
targetNode = getMapKeys(node)
} else if node.Kind == yaml.SequenceNode {
targetNode = getIndicies(node)
} else {
return Context{}, 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
}
func getMapKeys(node *yaml.Node) *yaml.Node {
contents := make([]*yaml.Node, 0)
for index := 0; index < len(node.Content); index = index + 2 {
contents = append(contents, node.Content[index])
}
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
}
func getIndicies(node *yaml.Node) *yaml.Node {
var contents = make([]*yaml.Node, len(node.Content))
for index := range node.Content {
contents[index] = &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!int",
Value: fmt.Sprintf("%v", index),
}
}
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
}

View File

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

View File

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

View File

@@ -14,6 +14,30 @@ var lengthOperatorScenarios = []expressionScenario{
"D0, P[a], (!!int)::3\n", "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", description: "Map length",
subdescription: "returns number of entries", subdescription: "returns number of entries",

View File

@@ -2,87 +2,72 @@ package yqlib
import ( import (
"fmt" "fmt"
"strconv"
"container/list" "container/list"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type crossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs)
if err != nil {
return nil, err
}
log.Debugf("crossFunction LHS len: %v", lhs.Len())
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs)
if err != nil {
return nil, err
}
log.Debugf("crossFunction RHS len: %v", rhs.Len())
var results = list.New()
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
}
type multiplyPreferences struct { type multiplyPreferences struct {
AppendArrays bool AppendArrays bool
TraversePrefs traversePreferences
} }
func multiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func multiplyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- MultiplyOperator") log.Debugf("-- MultiplyOperator")
return crossFunction(d, matchingNodes, expressionNode, multiply(expressionNode.Operation.Preferences.(*multiplyPreferences))) return crossFunction(d, context, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences)))
} }
func multiply(preferences *multiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = unwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = unwrapDoc(rhs.Node)
log.Debugf("Multipling LHS: %v", lhs.Node.Tag) log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag) log.Debugf("- RHS: %v", rhs.Node.Tag)
shouldAppendArrays := preferences.AppendArrays
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
var newBlank = lhs.CreateChild(nil, &yaml.Node{}) var newBlank = lhs.CreateChild(nil, &yaml.Node{})
var newThing, err = mergeObjects(d, newBlank, lhs, false) var newThing, err = mergeObjects(d, context, newBlank, lhs, multiplyPreferences{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return mergeObjects(d, newThing, rhs, shouldAppendArrays) return mergeObjects(d, context, newThing, rhs, preferences)
} else if lhs.Node.Tag == "!!int" && rhs.Node.Tag == "!!int" {
return multiplyIntegers(lhs, rhs)
} }
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
} }
} }
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) { func multiplyIntegers(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 = "!!int"
lhsNum, err := strconv.Atoi(lhs.Node.Value)
if err != nil {
return nil, err
}
rhsNum, err := strconv.Atoi(rhs.Node.Value)
if err != nil {
return nil, err
}
target.Node.Value = fmt.Sprintf("%v", lhsNum*rhsNum)
return target, nil
}
func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) (*CandidateNode, error) {
shouldAppendArrays := preferences.AppendArrays
var results = list.New() var results = list.New()
// shouldn't recurse arrays if appending // shouldn't recurse arrays if appending
prefs := &recursiveDescentPreferences{RecurseArray: !shouldAppendArrays, prefs := recursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
TraversePreferences: &traversePreferences{FollowAlias: false}} TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}
err := recursiveDecent(d, results, nodeToMap(rhs), prefs) err := recursiveDecent(d, results, context.SingleChildContext(rhs), prefs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -93,7 +78,12 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
} }
for el := results.Front(); el != nil; el = el.Next() { for el := results.Front(); el != nil; el = el.Next() {
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays) candidate := el.Value.(*CandidateNode)
if candidate.Node.Tag == "!!merge" {
continue
}
err := applyAssignment(d, context, pathIndexToStartFrom, lhs, candidate, preferences)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -101,9 +91,9 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
return lhs, nil return lhs, nil
} }
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error { func applyAssignment(d *dataTreeNavigator, context Context, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error {
shouldAppendArrays := preferences.AppendArrays
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) log.Debugf("merge - applyAssignment lhs %v, rhs: %v", lhs.GetKey(), rhs.GetKey())
lhsPath := rhs.Path[pathIndexToStartFrom:] lhsPath := rhs.Path[pathIndexToStartFrom:]
@@ -116,9 +106,9 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
} }
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs} rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs}
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &ExpressionNode{Operation: rhsOp}} assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath, preferences.TraversePrefs, rhs.IsMapKey), Rhs: &ExpressionNode{Operation: rhsOp}}
_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode) _, err := d.GetMatchingNodes(context.SingleChildContext(lhs), assignmentOpNode)
return err return err
} }

View File

@@ -13,6 +13,51 @@ var multiplyOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
}, },
}, },
{
skipDoc: true,
document: "# 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}`,
document2: `{a: 3, b: 10}`,
expression: `.a * .b`,
expected: []string{
"D0, P[a], (!!int)::10\n",
"D0, P[a], (!!int)::20\n",
"D0, P[a], (!!int)::15\n",
"D0, P[a], (!!int)::30\n",
},
},
{
skipDoc: true,
document: `{a: 2}`,
document2: `{b: 10}`,
expression: `select(fi ==0) * select(fi==1)`,
expected: []string{
"D0, P[], (!!map)::{a: 2, b: 10}\n",
},
},
{ {
skipDoc: true, skipDoc: true,
expression: `{} * {"cat":"dog"}`, expression: `{} * {"cat":"dog"}`,
@@ -71,7 +116,7 @@ var multiplyOperatorScenarios = []expressionScenario{
{ {
skipDoc: true, skipDoc: true,
document: `{a: {things: great}, b: {also: me}}`, document: `{a: {things: great}, b: {also: me}}`,
expression: `. * {"a":.b}`, expression: `. * {"a": .b}`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n", "D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n",
}, },
@@ -79,16 +124,10 @@ var multiplyOperatorScenarios = []expressionScenario{
{ {
description: "Merge keeps style of LHS", description: "Merge keeps style of LHS",
dontFormatInputForDoc: true, dontFormatInputForDoc: true,
document: `a: {things: great} document: "a: {things: great}\nb:\n also: \"me\"",
b: expression: `. * {"a":.b}`,
also: "me"
`,
expression: `. * {"a":.b}`,
expected: []string{ expected: []string{
`D0, P[], (!!map)::a: {things: great, also: "me"} "D0, P[], (!!map)::a: {things: great, also: \"me\"}\nb:\n also: \"me\"\n",
b:
also: "me"
`,
}, },
}, },
{ {
@@ -107,6 +146,30 @@ b:
"D0, P[a], (!!seq)::[1, 2]\n", "D0, P[a], (!!seq)::[1, 2]\n",
}, },
}, },
{
description: "Merge, only existing fields",
document: `{a: {thing: one, cat: frog}, b: {missing: two, thing: two}}`,
expression: `.a *? .b`,
expected: []string{
"D0, P[a], (!!map)::{thing: two, cat: frog}\n",
},
},
{
skipDoc: true,
document: `{a: [{thing: one}], b: [{missing: two, thing: two}]}`,
expression: `.a *? .b`,
expected: []string{
"D0, P[a], (!!seq)::[{thing: two}]\n",
},
},
{
skipDoc: true,
document: `{a: {array: [1]}, b: {}}`,
expression: `.b *+ .a`,
expected: []string{
"D0, P[b], (!!map)::{array: [1]}\n",
},
},
{ {
description: "Merge, appending arrays", description: "Merge, appending arrays",
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`, document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
@@ -115,6 +178,14 @@ b:
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n", "D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
}, },
}, },
{
description: "Merge, only existing fields, appending arrays",
document: `{a: {thing: [1,2]}, b: {thing: [3,4], another: [1]}}`,
expression: `.a *?+ .b`,
expected: []string{
"D0, P[a], (!!map)::{thing: [1, 2, 3, 4]}\n",
},
},
{ {
description: "Merge to prefix an element", description: "Merge to prefix an element",
document: `{a: cat, b: dog}`, document: `{a: cat, b: dog}`,

View File

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

View File

@@ -1,11 +1,18 @@
package yqlib package yqlib
import "container/list" func pipeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
func pipeOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { //lhs may update the variable context, we should pass that into the RHS
lhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Lhs) // 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 { if err != nil {
return nil, err return Context{}, err
} }
return d.GetMatchingNodes(lhs, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(lhs, expressionNode.Rhs)
if err != nil {
return Context{}, err
}
return context.ChildContext(rhs.MatchingNodes), nil
} }

View File

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

View File

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

View File

@@ -14,6 +14,14 @@ var selectOperatorScenarios = []expressionScenario{
"D0, P[1], (!!str)::goat\n", "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, skipDoc: true,
document: `[hot, fot, dog]`, document: `[hot, fot, dog]`,

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
package yqlib
import (
"testing"
)
var splitDocOperatorScenarios = []expressionScenario{
{
description: "Split empty",
document: ``,
expression: `splitDoc`,
expected: []string{
"D0, P[], (!!null)::\n",
},
},
{
description: "Split array",
document: `[{a: cat}, {b: dog}]`,
expression: `.[] | splitDoc`,
expected: []string{
"D0, P[0], (!!map)::{a: cat}\n",
"D1, P[1], (!!map)::{b: dog}\n",
},
},
}
func TestSplitDocOperatorScenarios(t *testing.T) {
for _, tt := range splitDocOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Split into Documents", splitDocOperatorScenarios)
}

View File

@@ -0,0 +1,96 @@
package yqlib
import (
"container/list"
"fmt"
"strings"
"gopkg.in/yaml.v3"
)
func joinStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- joinStringOperator")
joinStr := ""
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
if err != nil {
return Context{}, err
}
if rhs.MatchingNodes.Front() != nil {
joinStr = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
}
var results = list.New()
for el := context.MatchingNodes.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)
}
targetNode := join(node.Content, joinStr)
result := candidate.CreateChild(nil, targetNode)
results.PushBack(result)
}
return context.ChildContext(results), nil
}
func join(content []*yaml.Node, joinStr string) *yaml.Node {
var stringsToJoin []string
for _, node := range content {
str := node.Value
if node.Tag == "!!null" {
str = ""
}
stringsToJoin = append(stringsToJoin, str)
}
return &yaml.Node{Kind: yaml.ScalarNode, Value: strings.Join(stringsToJoin, joinStr), Tag: "!!str"}
}
func splitStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- splitStringOperator")
splitStr := ""
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
if err != nil {
return Context{}, err
}
if rhs.MatchingNodes.Front() != nil {
splitStr = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
}
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 == "!!null" {
continue
}
if node.Tag != "!!str" {
return Context{}, 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
}
func split(value string, spltStr string) *yaml.Node {
var contents []*yaml.Node
if value != "" {
var newStrings = strings.Split(value, spltStr)
contents = make([]*yaml.Node, len(newStrings))
for index, str := range newStrings {
contents[index] = &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: str}
}
}
return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents}
}

View File

@@ -0,0 +1,52 @@
package yqlib
import (
"testing"
)
var stringsOperatorScenarios = []expressionScenario{
{
description: "Join strings",
document: `[cat, meow, 1, null, true]`,
expression: `join("; ")`,
expected: []string{
"D0, P[], (!!str)::cat; meow; 1; ; true\n",
},
},
{
description: "Split strings",
document: `"cat; meow; 1; ; true"`,
expression: `split("; ")`,
expected: []string{
"D0, P[], (!!seq)::- cat\n- meow\n- \"1\"\n- \"\"\n- \"true\"\n",
},
},
{
description: "Split strings one match",
document: `"word"`,
expression: `split("; ")`,
expected: []string{
"D0, P[], (!!seq)::- word\n",
},
},
{
skipDoc: true,
document: `""`,
expression: `split("; ")`,
expected: []string{
"D0, P[], (!!seq)::[]\n", // dont actually want this, just not to error
},
},
{
skipDoc: true,
expression: `split("; ")`,
expected: []string{},
},
}
func TestStringsOperatorScenarios(t *testing.T) {
for _, tt := range stringsOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "String Operators", stringsOperatorScenarios)
}

View File

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

View File

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

View File

@@ -10,30 +10,32 @@ import (
) )
type traversePreferences struct { type traversePreferences struct {
FollowAlias bool DontFollowAlias bool
IncludeMapKeys bool IncludeMapKeys bool
DontAutoCreate bool // by default, we automatically create entries on the fly.
DontIncludeMapValues bool
} }
func splat(d *dataTreeNavigator, matches *list.List, prefs *traversePreferences) (*list.List, error) { func splat(d *dataTreeNavigator, context Context, prefs traversePreferences) (Context, error) {
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs) return traverseNodesWithArrayIndices(context, make([]*yaml.Node, 0), prefs)
} }
func traversePathOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) { func traversePathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- Traversing") log.Debugf("-- traversePathOperator")
var matchingNodeMap = list.New() var matches = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() { for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
newNodes, err := traverse(d, el.Value.(*CandidateNode), expressionNode.Operation) newNodes, err := traverse(d, context, el.Value.(*CandidateNode), expressionNode.Operation)
if err != nil { if err != nil {
return nil, err return Context{}, err
} }
matchingNodeMap.PushBackList(newNodes) matches.PushBackList(newNodes)
} }
return matchingNodeMap, nil return context.ChildContext(matches), nil
} }
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) { func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
log.Debug("Traversing %v", NodeToString(matchingNode)) log.Debug("Traversing %v", NodeToString(matchingNode))
value := matchingNode.Node value := matchingNode.Node
@@ -54,8 +56,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
switch value.Kind { switch value.Kind {
case yaml.MappingNode: case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2) log.Debug("its a map with %v entries", len(value.Content)/2)
prefs := &traversePreferences{FollowAlias: true} return traverseMap(context, matchingNode, operation.StringValue, operation.Preferences.(traversePreferences), false)
return traverseMap(matchingNode, operation.StringValue, prefs, false)
case yaml.SequenceNode: case yaml.SequenceNode:
log.Debug("its a sequence of %v things!", len(value.Content)) log.Debug("its a sequence of %v things!", len(value.Content))
@@ -64,44 +65,59 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
case yaml.AliasNode: case yaml.AliasNode:
log.Debug("its an alias!") log.Debug("its an alias!")
matchingNode.Node = matchingNode.Node.Alias matchingNode.Node = matchingNode.Node.Alias
return traverse(d, matchingNode, operation) return traverse(d, context, matchingNode, operation)
case yaml.DocumentNode: case yaml.DocumentNode:
log.Debug("digging into doc node") log.Debug("digging into doc node")
return traverse(d, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), operation) return traverse(d, context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), operation)
default: default:
return list.New(), nil return list.New(), nil
} }
} }
func traverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
// rhs is a collect expression that will yield indexes to retreive of the arrays
rhs, err := d.GetMatchingNodes(matchingNodes, expressionNode.Rhs) //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 { if err != nil {
return nil, err return Context{}, err
} }
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content // rhs is a collect expression that will yield indexes to retreive of the arrays
prefs := &traversePreferences{FollowAlias: true}
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs) rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
if err != nil {
return Context{}, 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
} }
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *traversePreferences) (*list.List, error) { func traverseNodesWithArrayIndices(context Context, indicesToTraverse []*yaml.Node, prefs traversePreferences) (Context, error) {
var matchingNodeMap = list.New() var matchingNodeMap = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs) newNodes, err := traverseArrayIndices(context, candidate, indicesToTraverse, prefs)
if err != nil { if err != nil {
return nil, err return Context{}, err
} }
matchingNodeMap.PushBackList(newNodes) matchingNodeMap.PushBackList(newNodes)
} }
return matchingNodeMap, nil return context.ChildContext(matchingNodeMap), nil
} }
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
node := matchingNode.Node node := matchingNode.Node
if node.Tag == "!!null" { if node.Tag == "!!null" {
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array") log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
@@ -112,28 +128,28 @@ func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml
if node.Kind == yaml.AliasNode { if node.Kind == yaml.AliasNode {
matchingNode.Node = node.Alias matchingNode.Node = node.Alias
return traverseArrayIndices(matchingNode, indicesToTraverse, prefs) return traverseArrayIndices(context, matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.SequenceNode { } else if node.Kind == yaml.SequenceNode {
return traverseArrayWithIndices(matchingNode, indicesToTraverse) return traverseArrayWithIndices(matchingNode, indicesToTraverse)
} else if node.Kind == yaml.MappingNode { } else if node.Kind == yaml.MappingNode {
return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs) return traverseMapWithIndices(context, matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.DocumentNode { } else if node.Kind == yaml.DocumentNode {
return traverseArrayIndices(matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs) return traverseArrayIndices(context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs)
} }
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag) log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
return list.New(), nil return list.New(), nil
} }
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *traversePreferences) (*list.List, error) { func traverseMapWithIndices(context Context, candidate *CandidateNode, indices []*yaml.Node, prefs traversePreferences) (*list.List, error) {
if len(indices) == 0 { if len(indices) == 0 {
return traverseMap(candidate, "", prefs, true) return traverseMap(context, candidate, "", prefs, true)
} }
var matchingNodeMap = list.New() var matchingNodeMap = list.New()
for _, indexNode := range indices { for _, indexNode := range indices {
log.Debug("traverseMapWithIndices: %v", indexNode.Value) log.Debug("traverseMapWithIndices: %v", indexNode.Value)
newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false) newNodes, err := traverseMap(context, candidate, indexNode.Value, prefs, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -188,7 +204,7 @@ func keyMatches(key *yaml.Node, wantedKey string) bool {
return matchKey(key.Value, wantedKey) return matchKey(key.Value, wantedKey)
} }
func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePreferences, splat bool) (*list.List, error) { func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs traversePreferences, splat bool) (*list.List, error) {
var newMatches = orderedmap.NewOrderedMap() var newMatches = orderedmap.NewOrderedMap()
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat) err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
@@ -196,13 +212,24 @@ func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePrefere
return nil, err return nil, err
} }
if newMatches.Len() == 0 { if !prefs.DontAutoCreate && !context.DontAutoCreate && newMatches.Len() == 0 {
//no matches, create one automagically //no matches, create one automagically
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"} valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: key}
node := matchingNode.Node node := matchingNode.Node
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode) node.Content = append(node.Content, keyNode, valueNode)
candidateNode := matchingNode.CreateChild(key, valueNode)
newMatches.Set(candidateNode.GetKey(), candidateNode) 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)
}
} }
results := list.New() results := list.New()
@@ -214,7 +241,7 @@ func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePrefere
return results, nil return results, nil
} }
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs *traversePreferences, splat bool) error { func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs traversePreferences, splat bool) error {
// value.Content is a concatenated array of key, value, // value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd. // so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them // merge aliases are defined first, but we only want to traverse them
@@ -229,7 +256,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
log.Debug("checking %v (%v)", key.Value, key.Tag) log.Debug("checking %v (%v)", key.Value, key.Tag)
//skip the 'merge' tag, find a direct match first //skip the 'merge' tag, find a direct match first
if key.Tag == "!!merge" && prefs.FollowAlias { if key.Tag == "!!merge" && !prefs.DontFollowAlias {
log.Debug("Merge anchor") log.Debug("Merge anchor")
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat) err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
if err != nil { if err != nil {
@@ -238,18 +265,23 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
} else if splat || keyMatches(key, wantedKey) { } else if splat || keyMatches(key, wantedKey) {
log.Debug("MATCHED") log.Debug("MATCHED")
if prefs.IncludeMapKeys { if prefs.IncludeMapKeys {
log.Debug("including key")
candidateNode := candidate.CreateChild(key.Value, key) candidateNode := candidate.CreateChild(key.Value, key)
candidateNode.IsMapKey = true
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode) newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
} }
candidateNode := candidate.CreateChild(key.Value, value) if !prefs.DontIncludeMapValues {
newMatches.Set(candidateNode.GetKey(), candidateNode) log.Debug("including value")
candidateNode := candidate.CreateChild(key.Value, value)
newMatches.Set(candidateNode.GetKey(), candidateNode)
}
} }
} }
return nil return nil
} }
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *traversePreferences, splat bool) error { func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs traversePreferences, splat bool) error {
switch value.Kind { switch value.Kind {
case yaml.AliasNode: case yaml.AliasNode:
candidateNode := originalCandidate.CreateChild(nil, value.Alias) candidateNode := originalCandidate.CreateChild(nil, value.Alias)

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,45 @@
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,10 +4,11 @@ import (
"container/list" "container/list"
"fmt" "fmt"
"github.com/jinzhu/copier"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type operatorHandler func(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) type operatorHandler func(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error)
func unwrapDoc(node *yaml.Node) *yaml.Node { func unwrapDoc(node *yaml.Node) *yaml.Node {
if node.Kind == yaml.DocumentNode { if node.Kind == yaml.DocumentNode {
@@ -16,8 +17,67 @@ func unwrapDoc(node *yaml.Node) *yaml.Node {
return node return node
} }
func emptyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { func emptyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
return list.New(), nil context.MatchingNodes = list.New()
return context, nil
}
type crossFunctionCalculation func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
func doCrossFunc(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (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
}
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode)
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 Context{}, err
}
results.PushBack(resultCandidate)
}
}
return context.ChildContext(results), nil
}
func crossFunction(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation crossFunctionCalculation) (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)
}
for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
innerResults, err := doCrossFunc(d, context.SingleChildContext(matchEl.Value.(*CandidateNode)), expressionNode, calculation)
if err != nil {
return Context{}, err
}
results.PushBackList(innerResults.MatchingNodes)
}
return context.ChildContext(results), nil
} }
func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode { func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
@@ -29,20 +89,25 @@ func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
return owner.CreateChild(nil, node) return owner.CreateChild(nil, node)
} }
func nodeToMap(candidate *CandidateNode) *list.List { func createTraversalTree(path []interface{}, traversePrefs traversePreferences, targetKey bool) *ExpressionNode {
elMap := list.New()
elMap.PushBack(candidate)
return elMap
}
func createTraversalTree(path []interface{}) *ExpressionNode {
if len(path) == 0 { if len(path) == 0 {
return &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}} return &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
} else if len(path) == 1 { } else if len(path) == 1 {
return &ExpressionNode{Operation: &Operation{OperationType: traversePathOpType, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} 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{ return &ExpressionNode{
Operation: &Operation{OperationType: shortPipeOpType}, Operation: &Operation{OperationType: shortPipeOpType},
Lhs: createTraversalTree(path[0:1]), Lhs: createTraversalTree(path[0:1], traversePrefs, false),
Rhs: createTraversalTree(path[1:])} Rhs: createTraversalTree(path[1:], traversePrefs, targetKey),
}
} }

View File

@@ -27,7 +27,6 @@ type expressionScenario struct {
} }
func testScenario(t *testing.T, s *expressionScenario) { func testScenario(t *testing.T, s *expressionScenario) {
var results *list.List
var err error var err error
node, err := NewExpressionParser().ParseExpression(s.expression) node, err := NewExpressionParser().ParseExpression(s.expression)
@@ -55,7 +54,7 @@ func testScenario(t *testing.T, s *expressionScenario) {
candidateNode := &CandidateNode{ candidateNode := &CandidateNode{
Document: 0, Document: 0,
Filename: "", Filename: "",
Node: &yaml.Node{Tag: "!!null"}, Node: &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode},
FileIndex: 0, FileIndex: 0,
} }
inputs.PushBack(candidateNode) inputs.PushBack(candidateNode)
@@ -66,13 +65,13 @@ func testScenario(t *testing.T, s *expressionScenario) {
os.Setenv("myenv", s.environmentVariable) os.Setenv("myenv", s.environmentVariable)
} }
results, err = NewDataTreeNavigator().GetMatchingNodes(inputs, node) context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
if err != nil { if err != nil {
t.Error(fmt.Errorf("%v: %v", err, s.expression)) t.Error(fmt.Errorf("%v: %v", err, s.expression))
return return
} }
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document)) test.AssertResultComplexWithContext(t, s.expected, resultsToString(context.MatchingNodes), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
} }
func resultsToString(results *list.List) []string { func resultsToString(results *list.List) []string {
@@ -245,19 +244,19 @@ func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formatt
candidateNode := &CandidateNode{ candidateNode := &CandidateNode{
Document: 0, Document: 0,
Filename: "", Filename: "",
Node: &yaml.Node{Tag: "!!null"}, Node: &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode},
FileIndex: 0, FileIndex: 0,
} }
inputs.PushBack(candidateNode) inputs.PushBack(candidateNode)
} }
results, err := NewDataTreeNavigator().GetMatchingNodes(inputs, node) context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
if err != nil { if err != nil {
t.Error(err, s.expression) t.Error(err, s.expression)
} }
err = printer.PrintResults(results) err = printer.PrintResults(context.MatchingNodes)
if err != nil { if err != nil {
t.Error(err, s.expression) t.Error(err, s.expression)
} }

View File

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

View File

@@ -3,6 +3,7 @@ package yqlib
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"container/list"
"strings" "strings"
"testing" "testing"
@@ -16,6 +17,12 @@ a: apple
a: coconut a: coconut
` `
func nodeToList(candidate *CandidateNode) *list.List {
elMap := list.New()
elMap.PushBack(candidate)
return elMap
}
func TestPrinterMultipleDocsInSequence(t *testing.T) { func TestPrinterMultipleDocsInSequence(t *testing.T) {
var output bytes.Buffer var output bytes.Buffer
var writer = bufio.NewWriter(&output) var writer = bufio.NewWriter(&output)
@@ -27,13 +34,13 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) {
} }
el := inputs.Front() el := inputs.Front()
sample1 := nodeToMap(el.Value.(*CandidateNode)) sample1 := nodeToList(el.Value.(*CandidateNode))
el = el.Next() el = el.Next()
sample2 := nodeToMap(el.Value.(*CandidateNode)) sample2 := nodeToList(el.Value.(*CandidateNode))
el = el.Next() el = el.Next()
sample3 := nodeToMap(el.Value.(*CandidateNode)) sample3 := nodeToList(el.Value.(*CandidateNode))
err = printer.PrintResults(sample1) err = printer.PrintResults(sample1)
if err != nil { if err != nil {
@@ -68,19 +75,19 @@ func TestPrinterMultipleFilesInSequence(t *testing.T) {
elNode := el.Value.(*CandidateNode) elNode := el.Value.(*CandidateNode)
elNode.Document = 0 elNode.Document = 0
elNode.FileIndex = 0 elNode.FileIndex = 0
sample1 := nodeToMap(elNode) sample1 := nodeToList(elNode)
el = el.Next() el = el.Next()
elNode = el.Value.(*CandidateNode) elNode = el.Value.(*CandidateNode)
elNode.Document = 0 elNode.Document = 0
elNode.FileIndex = 1 elNode.FileIndex = 1
sample2 := nodeToMap(elNode) sample2 := nodeToList(elNode)
el = el.Next() el = el.Next()
elNode = el.Value.(*CandidateNode) elNode = el.Value.(*CandidateNode)
elNode.Document = 0 elNode.Document = 0
elNode.FileIndex = 2 elNode.FileIndex = 2
sample3 := nodeToMap(elNode) sample3 := nodeToList(elNode)
err = printer.PrintResults(sample1) err = printer.PrintResults(sample1)
if err != nil { if err != nil {

View File

@@ -35,17 +35,17 @@ func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error
candidateNode := &CandidateNode{ candidateNode := &CandidateNode{
Document: 0, Document: 0,
Filename: "", Filename: "",
Node: &yaml.Node{Tag: "!!null"}, Node: &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode},
FileIndex: 0, FileIndex: 0,
} }
inputList := list.New() inputList := list.New()
inputList.PushBack(candidateNode) inputList.PushBack(candidateNode)
matches, errorParsing := s.treeNavigator.GetMatchingNodes(inputList, node) result, errorParsing := s.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputList}, node)
if errorParsing != nil { if errorParsing != nil {
return errorParsing return errorParsing
} }
return printer.PrintResults(matches) return printer.PrintResults(result.MatchingNodes)
} }
func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error { 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 := list.New()
inputList.PushBack(candidateNode) inputList.PushBack(candidateNode)
matches, errorParsing := s.treeNavigator.GetMatchingNodes(inputList, node) result, errorParsing := s.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputList}, node)
if errorParsing != nil { if errorParsing != nil {
return errorParsing return errorParsing
} }
err := printer.PrintResults(matches) err := printer.PrintResults(result.MatchingNodes)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -36,10 +36,11 @@ func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List
return nil, errorReading return nil, errorReading
} }
candidateNode := &CandidateNode{ candidateNode := &CandidateNode{
Document: currentIndex, Document: currentIndex,
Filename: filename, Filename: filename,
Node: &dataBucket, Node: &dataBucket,
FileIndex: fileIndex, FileIndex: fileIndex,
EvaluateTogether: true,
} }
inputList.PushBack(candidateNode) inputList.PushBack(candidateNode)

View File

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

View File

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