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

Compare commits

..

27 Commits

Author SHA1 Message Date
Mike Farah
5ed52aca66 Merged env commands in :eye-roll: 2021-01-10 10:51:23 +11:00
Mike Farah
5a5ac0dfef Merge branch 'env_var' 2021-01-10 10:50:31 +11:00
Mike Farah
5a55869745 Bump version 2021-01-09 12:23:20 +11:00
Mike Farah
15e18bb98b Error when passing files and using null-input flag 2021-01-09 12:23:06 +11:00
Mike Farah
644063646e Env Ops! 2021-01-09 12:06:19 +11:00
Mike Farah
aabed1a237 strenv 2021-01-09 11:38:08 +11:00
Mike Farah
c12764dba8 wip 2021-01-08 21:09:43 +11:00
Mike Farah
1d5ecb244d wip 2021-01-08 21:07:46 +11:00
Mike Farah
7798a141cf Bumped go yaml for comment hanlding fixes 2021-01-08 20:56:54 +11:00
Mike Farah
b7583a538d Added webi 2021-01-08 12:14:36 +11:00
Mike Farah
529e88b630 Added recurse examples 2021-01-08 12:11:29 +11:00
Mike Farah
658006eb93 Added another delete example 2021-01-08 11:59:49 +11:00
Mike Farah
2743a7e058 Can assign-update tag 2021-01-06 20:44:28 +11:00
Mike Farah
f95862fba3 Can assign-update style 2021-01-06 20:37:53 +11:00
Mike Farah
3f58c4bc38 Can assign-update aliases and anchors 2021-01-06 20:30:48 +11:00
Mike Farah
a7975df7cd Can assign-update comments 2021-01-06 20:22:50 +11:00
Mike Farah
3d5a5e0360 updating readme 2021-01-06 15:33:20 +11:00
Mike Farah
155755ae2f brew v3! 2021-01-06 15:22:29 +11:00
Mike Farah
601c13531c Updated collect objcet doc 2021-01-06 15:20:54 +11:00
Mike Farah
69d00c89df Added shorthand document index selection 2021-01-05 13:28:37 +11:00
Mike Farah
2faff7b05f Unwrap node in get tag to return proper tag at root level 2021-01-05 13:23:03 +11:00
Mike Farah
165949041d Added v3 snap instructions 2021-01-05 13:05:03 +11:00
Mike Farah
dbd7ab0f13 Refactored doc generation, add fi fileIndex alias 2021-01-02 10:49:33 +11:00
Mike Farah
6d512ad718 Fixed updating yaml from other files 2021-01-02 10:27:32 +11:00
Mike Farah
4fef4a7ab1 update issue template, instruct questions to be raised in disussion 2020-12-31 13:43:44 +11:00
Mike Farah
dcb17b51a9 fixed heading 2020-12-31 09:33:15 +11:00
Mike Farah
385417556d added tar.gz instructions 2020-12-31 09:30:59 +11:00
114 changed files with 1452 additions and 2901 deletions

1
.github/FUNDING.yml vendored
View File

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

View File

@@ -40,7 +40,17 @@ jobs:
IMAGE_NAME: mikefarah/yq IMAGE_NAME: mikefarah/yq
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - name: Get latest release tag
uses: oprypin/find-latest-tag@v1
with:
repository: mikefarah/yq # The repository to scan.
releases-only: true # We know that all relevant tags have a GitHub release for them.
id: yq
- name: Clone source code
uses: actions/checkout@v2
with:
ref: ${{ steps.yq.outputs.tag }}
- name: Set up QEMU - 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 --entrypoint sh mikefarah/yq docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
``` ```
It can be useful to have a bash function to avoid typing the whole docker command: It can be useful to have a bash function to avoid typing the whole docker command:
@@ -103,7 +103,7 @@ yq() {
### Go Get: ### Go Get:
``` ```
GO111MODULE=on go get github.com/mikefarah/yq/v4 GO111MODULE=on go get github.com/mikefarah/yq
``` ```
## Community Supported Installation methods ## Community Supported Installation methods

View File

@@ -42,21 +42,14 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
colorsEnabled = true colorsEnabled = true
} }
firstFileIndex := -1 if writeInplace && len(args) < 2 {
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[firstFileIndex]) writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
out, err = writeInPlaceHandler.CreateTempFile() out, err = writeInPlaceHandler.CreateTempFile()
if err != nil { if err != nil {
return err return err

View File

@@ -62,21 +62,14 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
colorsEnabled = true colorsEnabled = true
} }
firstFileIndex := -1 if writeInplace && len(args) < 2 {
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[firstFileIndex]) writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
out, err = writeInPlaceHandler.CreateTempFile() out, err = writeInPlaceHandler.CreateTempFile()
if err != nil { if err != nil {
return err return err
@@ -109,7 +102,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(processExpression(args[0]), args[1:], printer) err = streamEvaluator.EvaluateFiles(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.5.1" Version = "4.3.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,6 +1,4 @@
a: a: simple # just the best
key1: "value1" b: [1, 2]
key2: 2.6 c:
ab: test: 1
key1: 6
key2: "h"

View File

@@ -1,4 +1,4 @@
FROM mikefarah/yq:4.5.1 FROM mikefarah/yq:4.3.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.4.0 github.com/elliotchance/orderedmap v1.3.0
github.com/fatih/color v1.10.0 github.com/fatih/color v1.10.0
github.com/goccy/go-yaml v1.8.8 github.com/goccy/go-yaml v1.8.4
github.com/jinzhu/copier v0.2.3 github.com/jinzhu/copier v0.1.0
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-20210124154548-22da62e12c0c // indirect golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // 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
) )

20
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.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A= github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys= github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.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.8 h1:MGfRB1GeSn/hWXYWS2Pt67iC2GJNnebdIro01ddyucA= github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
github.com/goccy/go-yaml v1.8.8/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.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.2.3 h1:Oe09ju+9qft7TffZ7l/04AB2f8u1+V4ZMxmp/nnqeOs= github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88=
github.com/jinzhu/copier v0.2.3/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/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,8 +182,6 @@ 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=
@@ -259,8 +257,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-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.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=
@@ -283,7 +281,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
@@ -326,7 +323,6 @@ 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

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

View File

@@ -1,40 +0,0 @@
package yqlib
import (
"testing"
"github.com/mikefarah/yq/v4/test"
)
var evaluateNodesScenario = []expressionScenario{
{
document: `a: hello`,
expression: `.a`,
expected: []string{
"D0, P[a], (!!str)::hello\n",
},
},
{
document: `a: hello`,
expression: `.`,
expected: []string{
"D0, P[], (doc)::a: hello\n",
},
},
{
document: `- a: "yes"`,
expression: `.[] | has("a")`,
expected: []string{
"D0, P[0], (!!bool)::true\n",
},
},
}
func TestAllAtOnceEvaluateNodes(t *testing.T) {
var evaluator = NewAllAtOnceEvaluator()
for _, tt := range evaluateNodesScenario {
node := test.ParseData(tt.document)
list, _ := evaluator.EvaluateNodes(tt.expression, &node)
test.AssertResultComplex(t, tt.expected, resultsToString(list))
}
}

View File

@@ -2,6 +2,8 @@ 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"
@@ -13,37 +15,13 @@ 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 {
keyPrefix := "" return fmt.Sprintf("%v - %v", n.Document, n.Path)
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 {
return &CandidateNode{
Node: node,
Path: n.createChildPath(path),
Document: n.Document,
Filename: n.Filename,
FileIndex: n.FileIndex,
}
}
func (n *CandidateNode) createChildPath(path interface{}) []interface{} {
if path == nil {
newPath := make([]interface{}, len(n.Path))
copy(newPath, n.Path)
return newPath
} }
func (n *CandidateNode) CreateChildPath(path interface{}) []interface{} {
//don't use append as they may actually modify the path of the orignal node! //don't use append as they may actually modify the path of the orignal node!
newPath := make([]interface{}, len(n.Path)+1) newPath := make([]interface{}, len(n.Path)+1)
copy(newPath, n.Path) copy(newPath, n.Path)
@@ -66,10 +44,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
} }
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
@@ -78,8 +56,6 @@ 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
@@ -90,3 +66,46 @@ 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()
}

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
Add behaves differently according to the type of the LHS: Add behaves differently according to the type of the LHS:
- arrays: concatenate - arrays: concatenate
- number scalars: arithmetic addition - number scalars: arithmetic addition (soon)
- string scalars: concatenate - string scalars: concatenate (soon)
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`. Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
## Concatenate and assign arrays ## Concatenate and assign arrays
Given a sample.yml file of: Given a sample.yml file of:
@@ -67,19 +67,23 @@ will output
- 2 - 2
``` ```
## Add new object to array ## Add object to array
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: a:
- dog: woof - 1
- 2
c:
cat: meow
``` ```
then then
```bash ```bash
yq eval '.a + {"cat": "meow"}' sample.yml yq eval '.a + .c' sample.yml
``` ```
will output will output
```yaml ```yaml
- dog: woof - 1
- 2
- cat: meow - cat: meow
``` ```
@@ -127,97 +131,3 @@ b:
- 4 - 4
``` ```
## String concatenation
Given a sample.yml file of:
```yaml
a: cat
b: meow
```
then
```bash
yq eval '.a = .a + .b' sample.yml
```
will output
```yaml
a: catmeow
b: meow
```
## Relative string concatenation
Given a sample.yml file of:
```yaml
a: cat
b: meow
```
then
```bash
yq eval '.a += .b' sample.yml
```
will output
```yaml
a: catmeow
b: meow
```
## Number addition - float
If the lhs or rhs are floats then the expression will be calculated with floats.
Given a sample.yml file of:
```yaml
a: 3
b: 4.9
```
then
```bash
yq eval '.a = .a + .b' sample.yml
```
will output
```yaml
a: 7.9
b: 4.9
```
## Number addition - int
If both the lhs and rhs are ints then the expression will be calculated with ints.
Given a sample.yml file of:
```yaml
a: 3
b: 4
```
then
```bash
yq eval '.a = .a + .b' sample.yml
```
will output
```yaml
a: 7
b: 4
```
## Increment number
Given a sample.yml file of:
```yaml
a: 3
```
then
```bash
yq eval '.a += 1' sample.yml
```
will output
```yaml
a: 4
```
## Add to null
Adding to null simply returns the rhs
Running
```bash
yq eval --null-input 'null + "cat"'
```
will output
```yaml
cat
```

View File

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

View File

@@ -1,4 +1,4 @@
Use the `documentIndex` operator (or the `di` shorthand) to select nodes of a particular document. Use the `documentIndex` operator to select nodes of a particular document.
## Retrieve a document index ## Retrieve a document index
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -1,5 +1,3 @@
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.
## Read string environment variable ## Read string environment variable
Running Running

View File

@@ -29,24 +29,6 @@ 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
@@ -65,24 +47,6 @@ 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

View File

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

View File

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

View File

@@ -33,8 +33,6 @@ frog
``` ```
## Recursively find nodes with keys ## Recursively find nodes with keys
Note that this example has wrapped the expression in `[]` to show that there are two matches returned. You do not have to wrap in `[]` in your path expression.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: a:
@@ -45,15 +43,15 @@ a:
``` ```
then then
```bash ```bash
yq eval '[.. | select(has("name"))]' sample.yml yq eval '.. | select(has("name"))' sample.yml
``` ```
will output will output
```yaml ```yaml
- name: frog name: frog
b: b:
name: blog name: blog
age: 12 age: 12
- name: blog name: blog
age: 12 age: 12
``` ```

View File

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

View File

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

View File

@@ -33,7 +33,7 @@ c: banana
``` ```
## Special characters ## Special characters
Use quotes with brackets around path elements with special characters Use quotes around path elements with special characters
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -41,23 +41,7 @@ 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

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

View File

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

View File

@@ -1,6 +1,6 @@
Add behaves differently according to the type of the LHS: Add behaves differently according to the type of the LHS:
- arrays: concatenate - arrays: concatenate
- number scalars: arithmetic addition - number scalars: arithmetic addition (soon)
- string scalars: concatenate - string scalars: concatenate (soon)
Use `+=` as append assign for things like increment. Note that `.a += .x` is equivalent to running `.a = .a + .x`. Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.

View File

@@ -1 +1 @@
Use the `documentIndex` operator (or the `di` shorthand) to select nodes of a particular document. Use the `documentIndex` operator to select nodes of a particular document.

View File

@@ -1,2 +0,0 @@
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,95 +0,0 @@
package yqlib
import (
"errors"
logging "gopkg.in/op/go-logging.v1"
)
type expressionPostFixer interface {
ConvertToPostfix([]*token) ([]*Operation, error)
}
type expressionPostFixerImpl struct {
}
func newExpressionPostFixer() expressionPostFixer {
return &expressionPostFixerImpl{}
}
func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) {
var newOp *token
opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1]
log.Debugf("popped %v from opstack to results", newOp.toString(true))
return opStack, append(result, newOp.Operation)
}
func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) {
var result []*Operation
// surround the whole thing with brackets
var opStack = []*token{{TokenType: openBracket}}
var tokens = append(infixTokens, &token{TokenType: closeBracket})
for _, currentToken := range tokens {
log.Debugf("postfix processing currentToken %v", currentToken.toString(true))
switch currentToken.TokenType {
case openBracket, openCollect, openCollectObject:
opStack = append(opStack, currentToken)
log.Debugf("put %v onto the opstack", currentToken.toString(true))
case closeCollect, closeCollectObject:
var opener tokenType = openCollect
var collectOperator *operationType = collectOpType
if currentToken.TokenType == closeCollectObject {
opener = openCollectObject
collectOperator = collectObjectOpType
}
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != opener {
opStack, result = popOpToResult(opStack, result)
}
if len(opStack) == 0 {
return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket")
}
// now we should have [ as the last element on the opStack, get rid of it
opStack = opStack[0 : len(opStack)-1]
log.Debugf("deleteing open bracket from opstack")
//and append a collect to the opStack
result = append(result, &Operation{OperationType: collectOperator})
log.Debugf("put collect onto the result")
result = append(result, &Operation{OperationType: shortPipeOpType})
log.Debugf("put shortpipe onto the result")
case closeBracket:
for len(opStack) > 0 && opStack[len(opStack)-1].TokenType != openBracket {
opStack, result = popOpToResult(opStack, result)
}
if len(opStack) == 0 {
return nil, errors.New("Bad path expression, got close brackets without matching opening bracket")
}
// now we should have ( as the last element on the opStack, get rid of it
opStack = opStack[0 : len(opStack)-1]
default:
var currentPrecedence = currentToken.Operation.OperationType.Precedence
// pop off higher precedent operators onto the result
for len(opStack) > 0 &&
opStack[len(opStack)-1].TokenType == operationToken &&
opStack[len(opStack)-1].Operation.OperationType.Precedence > currentPrecedence {
opStack, result = popOpToResult(opStack, result)
}
// add this operator to the opStack
opStack = append(opStack, currentToken)
log.Debugf("put %v onto the opstack", currentToken.toString(true))
}
}
if log.IsEnabledFor(logging.DEBUG) {
log.Debugf("PostFix Result:")
for _, currentToken := range result {
log.Debugf("> %v", currentToken.toString())
}
}
return result, nil
}

View File

@@ -1,436 +0,0 @@
package yqlib
import (
"fmt"
"strconv"
"strings"
lex "github.com/timtadh/lexmachine"
"github.com/timtadh/lexmachine/machines"
)
func skip(*lex.Scanner, *machines.Match) (interface{}, error) {
return nil, nil
}
type tokenType uint32
const (
operationToken = 1 << iota
openBracket
closeBracket
openCollect
closeCollect
openCollectObject
closeCollectObject
traverseArrayCollect
)
type token struct {
TokenType tokenType
Operation *Operation
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
}
func (t *token) toString(detail bool) string {
if t.TokenType == operationToken {
if detail {
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
}
return t.Operation.toString()
} else if t.TokenType == openBracket {
return "("
} else if t.TokenType == closeBracket {
return ")"
} else if t.TokenType == openCollect {
return "["
} else if t.TokenType == closeCollect {
return "]"
} else if t.TokenType == openCollectObject {
return "{"
} else if t.TokenType == closeCollectObject {
return "}"
} else if t.TokenType == traverseArrayCollect {
return ".["
} else {
return "NFI"
}
}
func pathToken(wrapped bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
value = value[1:]
if wrapped {
value = unwrap(value)
}
log.Debug("PathToken %v", value)
op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value, Preferences: traversePreferences{}}
return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil
}
}
func opToken(op *operationType) lex.Action {
return opTokenWithPrefs(op, nil, nil)
}
func opAssignableToken(opType *operationType, assignOpType *operationType) lex.Action {
return opTokenWithPrefs(opType, assignOpType, nil)
}
func assignOpToken(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignOpToken %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, UpdateAssign: updateAssign}
return &token{TokenType: operationToken, Operation: op}, nil
}
}
func multiplyWithPrefs() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
prefs := multiplyPreferences{}
options := string(m.Bytes)
if strings.Contains(options, "+") {
prefs.AppendArrays = true
}
if strings.Contains(options, "?") {
prefs.TraversePrefs = traversePreferences{DontAutoCreate: true}
}
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
return &token{TokenType: operationToken, Operation: op}, nil
}
}
func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences}
var assign *Operation
if assignOpType != nil {
assign = &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, Preferences: preferences}
}
return &token{TokenType: operationToken, Operation: op, AssignOperation: assign}, nil
}
}
func assignAllCommentsOp(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignAllCommentsOp %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{
OperationType: assignCommentOpType,
Value: assignCommentOpType.Type,
StringValue: value,
UpdateAssign: updateAssign,
Preferences: commentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
}
return &token{TokenType: operationToken, Operation: op}, nil
}
}
func literalToken(pType tokenType, checkForPost bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
return &token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil
}
}
func unwrap(value string) string {
return value[1 : len(value)-1]
}
func numberValue() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var numberString = string(m.Bytes)
var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint
if errParsingInt != nil {
return nil, errParsingInt
}
return &token{TokenType: operationToken, Operation: createValueOperation(number, numberString)}, nil
}
}
func floatValue() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
var numberString = string(m.Bytes)
var number, errParsingInt = strconv.ParseFloat(numberString, 64) // nolint
if errParsingInt != nil {
return nil, errParsingInt
}
return &token{TokenType: operationToken, Operation: createValueOperation(number, numberString)}, nil
}
}
func booleanValue(val bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
return &token{TokenType: operationToken, Operation: createValueOperation(val, string(m.Bytes))}, nil
}
}
func stringValue(wrapped bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
if wrapped {
value = unwrap(value)
}
return &token{TokenType: operationToken, Operation: createValueOperation(value, value)}, nil
}
}
func getVariableOpToken() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
value = value[1 : len(value)-1]
getVarOperation := createValueOperation(value, value)
getVarOperation.OperationType = getVariableOpType
return &token{TokenType: operationToken, Operation: getVarOperation, CheckForPostTraverse: true}, nil
}
}
func envOp(strenv bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
preferences := envOpPreferences{}
if strenv {
// strenv( )
value = value[7 : len(value)-1]
preferences.StringValue = true
} else {
//env( )
value = value[4 : len(value)-1]
}
envOperation := createValueOperation(value, value)
envOperation.OperationType = envOpType
envOperation.Preferences = preferences
return &token{TokenType: operationToken, Operation: envOperation}, nil
}
}
func nullValue() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
return &token{TokenType: operationToken, Operation: createValueOperation(nil, string(m.Bytes))}, nil
}
}
func selfToken() lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
op := &Operation{OperationType: selfReferenceOpType}
return &token{TokenType: operationToken, Operation: op}, nil
}
}
func initLexer() (*lex.Lexer, error) {
lexer := lex.NewLexer()
lexer.Add([]byte(`\(`), literalToken(openBracket, false))
lexer.Add([]byte(`\)`), literalToken(closeBracket, true))
lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false))
lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: false}}))
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, recursiveDescentPreferences{RecurseArray: true,
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}))
lexer.Add([]byte(`,`), opToken(unionOpType))
lexer.Add([]byte(`:\s*`), opToken(createMapOpType))
lexer.Add([]byte(`length`), opToken(lengthOpType))
lexer.Add([]byte(`sortKeys`), opToken(sortKeysOpType))
lexer.Add([]byte(`select`), opToken(selectOpType))
lexer.Add([]byte(`has`), opToken(hasOpType))
lexer.Add([]byte(`explode`), opToken(explodeOpType))
lexer.Add([]byte(`or`), opToken(orOpType))
lexer.Add([]byte(`and`), opToken(andOpType))
lexer.Add([]byte(`not`), opToken(notOpType))
lexer.Add([]byte(`ireduce`), opToken(reduceOpType))
lexer.Add([]byte(`;`), opToken(blockOpType))
lexer.Add([]byte(`\/\/`), opToken(alternativeOpType))
lexer.Add([]byte(`documentIndex`), opToken(getDocumentIndexOpType))
lexer.Add([]byte(`di`), opToken(getDocumentIndexOpType))
lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType))
lexer.Add([]byte(`join`), opToken(joinStringOpType))
lexer.Add([]byte(`split`), opToken(splitStringOpType))
lexer.Add([]byte(`keys`), opToken(keysOpType))
lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType))
lexer.Add([]byte(`tag`), opAssignableToken(getTagOpType, assignTagOpType))
lexer.Add([]byte(`anchor`), opAssignableToken(getAnchorOpType, assignAnchorOpType))
lexer.Add([]byte(`alias`), opAssignableToken(getAliasOptype, assignAliasOpType))
lexer.Add([]byte(`filename`), opToken(getFilenameOpType))
lexer.Add([]byte(`fileIndex`), opToken(getFileIndexOpType))
lexer.Add([]byte(`fi`), opToken(getFileIndexOpType))
lexer.Add([]byte(`path`), opToken(getPathOpType))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{LineComment: true}))
lexer.Add([]byte(`headComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{HeadComment: true}))
lexer.Add([]byte(`footComment`), opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
lexer.Add([]byte(`collect`), opToken(collectOpType))
lexer.Add([]byte(`\s*==\s*`), opToken(equalsOpType))
lexer.Add([]byte(`\s*!=\s*`), opToken(notEqualsOpType))
lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
lexer.Add([]byte(`del`), opToken(deleteChildOpType))
lexer.Add([]byte(`\s*\|=\s*`), assignOpToken(true))
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
lexer.Add([]byte(`\."[^ "]+"`), pathToken(true))
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false))
lexer.Add([]byte(`\.`), selfToken())
lexer.Add([]byte(`\|`), opToken(pipeOpType))
lexer.Add([]byte(`-?\d+(\.\d+)`), floatValue())
lexer.Add([]byte(`-?[1-9](\.\d+)?[Ee][-+]?\d+`), floatValue())
lexer.Add([]byte(`-?\d+`), numberValue())
lexer.Add([]byte(`[Tt][Rr][Uu][Ee]`), booleanValue(true))
lexer.Add([]byte(`[Ff][Aa][Ll][Ss][Ee]`), booleanValue(false))
lexer.Add([]byte(`[Nn][Uu][Ll][Ll]`), nullValue())
lexer.Add([]byte(`~`), nullValue())
lexer.Add([]byte(`"[^"]*"`), stringValue(true))
lexer.Add([]byte(`strenv\([^\)]+\)`), envOp(true))
lexer.Add([]byte(`env\([^\)]+\)`), envOp(false))
lexer.Add([]byte(`\[`), literalToken(openCollect, false))
lexer.Add([]byte(`\]`), literalToken(closeCollect, true))
lexer.Add([]byte(`\{`), literalToken(openCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true))
lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs())
lexer.Add([]byte(`\+`), opToken(addOpType))
lexer.Add([]byte(`\+=`), opToken(addAssignOpType))
lexer.Add([]byte(`\$[a-zA-Z_-0-9]+`), getVariableOpToken())
lexer.Add([]byte(`as`), opToken(assignVariableOpType))
err := lexer.Compile()
if err != nil {
return nil, err
}
return lexer, nil
}
type expressionTokeniser interface {
Tokenise(expression string) ([]*token, error)
}
type expressionTokeniserImpl struct {
lexer *lex.Lexer
}
func newExpressionTokeniser() expressionTokeniser {
var lexer, err = initLexer()
if err != nil {
panic(err)
}
return &expressionTokeniserImpl{lexer}
}
func (p *expressionTokeniserImpl) Tokenise(expression string) ([]*token, error) {
scanner, err := p.lexer.Scanner([]byte(expression))
if err != nil {
return nil, fmt.Errorf("Parsing expression: %v", err)
}
var tokens []*token
for tok, err, eof := scanner.Next(); !eof; tok, err, eof = scanner.Next() {
if tok != nil {
currentToken := tok.(*token)
log.Debugf("Tokenising %v", currentToken.toString(true))
tokens = append(tokens, currentToken)
}
if err != nil {
return nil, fmt.Errorf("Parsing expression: %v", err)
}
}
var postProcessedTokens = make([]*token, 0)
skipNextToken := false
for index := range tokens {
if skipNextToken {
skipNextToken = false
} else {
postProcessedTokens, skipNextToken = p.handleToken(tokens, index, postProcessedTokens)
}
}
return postProcessedTokens, nil
}
func (p *expressionTokeniserImpl) handleToken(tokens []*token, index int, postProcessedTokens []*token) (tokensAccum []*token, skipNextToken bool) {
skipNextToken = false
currentToken := tokens[index]
if currentToken.TokenType == traverseArrayCollect {
//need to put a traverse array then a collect currentToken
// do this by adding traverse then converting currentToken to collect
if index == 0 || tokens[index-1].TokenType != operationToken ||
tokens[index-1].Operation.OperationType != traversePathOpType {
op := &Operation{OperationType: selfReferenceOpType, StringValue: "SELF"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
op := &Operation{OperationType: traverseArrayOpType, StringValue: "TRAVERSE_ARRAY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
currentToken = &token{TokenType: openCollect}
}
if index != len(tokens)-1 && currentToken.AssignOperation != nil &&
tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == assignOpType {
currentToken.Operation = currentToken.AssignOperation
currentToken.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
skipNextToken = true
}
postProcessedTokens = append(postProcessedTokens, currentToken)
if index != len(tokens)-1 &&
((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) ||
(currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) {
op := &Operation{OperationType: emptyOpType, StringValue: "EMPTY"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == operationToken &&
tokens[index+1].Operation.OperationType == traversePathOpType {
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
tokens[index+1].TokenType == openCollect {
op := &Operation{OperationType: traverseArrayOpType}
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: op})
}
return postProcessedTokens, skipNextToken
}

View File

@@ -1,5 +1,3 @@
// Use the top level Evaluator or StreamEvaluator to evaluate expressions and return matches.
//
package yqlib package yqlib
import ( import (
@@ -13,95 +11,86 @@ import (
var log = logging.MustGetLogger("yq-lib") var log = logging.MustGetLogger("yq-lib")
type operationType struct { type OperationType struct {
Type string Type string
NumArgs uint // number of arguments to the op NumArgs uint // number of arguments to the op
Precedence uint Precedence uint
Handler operatorHandler Handler OperatorHandler
} }
// operators TODO: // operators TODO:
// - keys operator for controlling key metadata (particularly anchors/aliases)
// - mergeEmpty (sets only if the document is empty, do I do that now?) // - mergeEmpty (sets only if the document is empty, do I do that now?)
var orOpType = &operationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: orOperator} var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
var andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator} var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 5, Handler: reduceOperator}
var blockOpType = &operationType{Type: "BLOCK", Precedence: 10, NumArgs: 2, Handler: emptyOperator} var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
var unionOpType = &operationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: unionOperator} var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: PipeOperator}
var pipeOpType = &operationType{Type: "PIPE", NumArgs: 2, Precedence: 30, Handler: pipeOperator} var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator}
var assignOpType = &operationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: assignUpdateOperator} var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
var addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator} var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
var AssignAnchor = &OperationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: AssignAnchorOperator}
var AssignAlias = &OperationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: AssignAliasOperator}
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator} var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator} var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator} var Alternative = &OperationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: AlternativeOperator}
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator} var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator} var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator} var ShortPipe = &OperationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator}
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 Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
var GetAnchor = &OperationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: GetAnchorOperator}
var GetAlias = &OperationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: GetAliasOperator}
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: lengthOperator} var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator} var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator}
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator}
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
var getAnchorOpType = &operationType{Type: "GET_ANCHOR", NumArgs: 0, Precedence: 50, Handler: getAnchorOperator}
var getAliasOptype = &operationType{Type: "GET_ALIAS", NumArgs: 0, Precedence: 50, Handler: getAliasOperator}
var getDocumentIndexOpType = &operationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: getDocumentIndexOperator}
var getFilenameOpType = &operationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: getFilenameOperator}
var getFileIndexOpType = &operationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: getFileIndexOperator}
var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: getPathOperator}
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator} var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator} var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator} var TraverseArray = &OperationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: TraverseArrayOperator}
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator} var DocumentFilter = &OperationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
var SelfReference = &OperationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: SelfOperator}
var ValueOp = &OperationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: ValueOperator}
var EnvOp = &OperationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: EnvOperator}
var Not = &OperationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: NotOperator}
var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator} var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 55, Handler: traversePathOperator}
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: traverseArrayOperator}
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator} var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator} var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator} var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator} var DeleteImmediateChild = &OperationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: DeleteImmediateChildOperator}
var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
var recursiveDescentOpType = &operationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: recursiveDescentOperator}
var selectOpType = &operationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: selectOperator}
var hasOpType = &operationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: hasOperator}
var deleteChildOpType = &operationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: deleteChildOperator}
var deleteImmediateChildOpType = &operationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: deleteImmediateChildOperator}
type Operation struct { type Operation struct {
OperationType *operationType OperationType *OperationType
Value interface{} Value interface{}
StringValue string StringValue string
CandidateNode *CandidateNode // used for Value Path elements CandidateNode *CandidateNode // used for Value Path elements
Preferences interface{} Preferences interface{}
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs (instead of matching nodes)
} }
func createValueOperation(value interface{}, stringValue string) *Operation { func CreateValueOperation(value interface{}, stringValue string) *Operation {
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode} var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
node.Value = stringValue node.Value = stringValue
@@ -119,7 +108,7 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
} }
return &Operation{ return &Operation{
OperationType: valueOpType, OperationType: ValueOp,
Value: value, Value: value,
StringValue: stringValue, StringValue: stringValue,
CandidateNode: &CandidateNode{Node: &node}, CandidateNode: &CandidateNode{Node: &node},
@@ -128,11 +117,13 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
// debugging purposes only // debugging purposes only
func (p *Operation) toString() string { func (p *Operation) toString() string {
if p.OperationType == traversePathOpType { if p.OperationType == TraversePath {
return fmt.Sprintf("%v", p.Value) return fmt.Sprintf("%v", p.Value)
} else if p.OperationType == selfReferenceOpType { } else if p.OperationType == DocumentFilter {
return fmt.Sprintf("d%v", p.Value)
} else if p.OperationType == SelfReference {
return "SELF" return "SELF"
} else if p.OperationType == valueOpType { } else if p.OperationType == ValueOp {
return fmt.Sprintf("%v (%T)", p.Value, p.Value) return fmt.Sprintf("%v (%T)", p.Value, p.Value)
} else { } else {
return fmt.Sprintf("%v", p.OperationType.Type) return fmt.Sprintf("%v", p.OperationType.Type)

View File

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

View File

@@ -3,23 +3,23 @@ package yqlib
import ( import (
"fmt" "fmt"
"strconv" "container/list"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode { func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
return &ExpressionNode{Operation: &Operation{OperationType: addOpType}, return &PathTreeNode{Operation: &Operation{OperationType: Add},
Lhs: lhs, Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}},
Rhs: rhs} Rhs: rhs}
} }
func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
assignmentOp := &Operation{OperationType: assignOpType} assignmentOp := &Operation{OperationType: Assign}
assignmentOp.UpdateAssign = false assignmentOp.UpdateAssign = true
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)} assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
return d.GetMatchingNodes(context, assignmentOpNode) return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
} }
func toNodes(candidate *CandidateNode) []*yaml.Node { func toNodes(candidate *CandidateNode) []*yaml.Node {
@@ -36,24 +36,24 @@ func toNodes(candidate *CandidateNode) []*yaml.Node {
} }
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("Add operator") log.Debugf("Add operator")
return crossFunction(d, context, expressionNode, add) return crossFunction(d, matchingNodes, pathNode, add)
} }
func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = UnwrapDoc(rhs.Node)
target := &CandidateNode{
Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &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")
@@ -63,48 +63,7 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
target.Node.Tag = "!!seq" target.Node.Tag = "!!seq"
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...) target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
case yaml.ScalarNode: case yaml.ScalarNode:
if rhs.Node.Kind != yaml.ScalarNode { return nil, fmt.Errorf("Scalars not yet supported for addition")
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
}
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhsNode.Style
return addScalars(target, lhsNode, rhs.Node)
}
return target, nil
}
func addScalars(target *CandidateNode, lhs *yaml.Node, rhs *yaml.Node) (*CandidateNode, error) {
if lhs.Tag == "!!str" {
target.Node.Tag = "!!str"
target.Node.Value = lhs.Value + rhs.Value
} else if lhs.Tag == "!!int" && rhs.Tag == "!!int" {
lhsNum, err := strconv.Atoi(lhs.Value)
if err != nil {
return nil, err
}
rhsNum, err := strconv.Atoi(rhs.Value)
if err != nil {
return nil, err
}
sum := lhsNum + rhsNum
target.Node.Tag = "!!int"
target.Node.Value = fmt.Sprintf("%v", sum)
} else if (lhs.Tag == "!!int" || lhs.Tag == "!!float") && (rhs.Tag == "!!int" || rhs.Tag == "!!float") {
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
if err != nil {
return nil, err
}
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
if err != nil {
return nil, err
}
sum := lhsNum + rhsNum
target.Node.Tag = "!!float"
target.Node.Value = fmt.Sprintf("%v", sum)
} else {
return nil, fmt.Errorf("%v cannot be added to %v", lhs.Tag, rhs.Tag)
} }
return target, nil return target, nil

View File

@@ -5,15 +5,6 @@ import (
) )
var addOperatorScenarios = []expressionScenario{ var addOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `[{a: foo, b: bar}, {a: 1, b: 2}]`,
expression: ".[] | .a + .b",
expected: []string{
"D0, P[0 a], (!!str)::foobar\n",
"D0, P[1 a], (!!int)::3\n",
},
},
{ {
description: "Concatenate and assign arrays", description: "Concatenate and assign arrays",
document: `{a: {val: thing, b: [cat,dog]}}`, document: `{a: {val: thing, b: [cat,dog]}}`,
@@ -47,11 +38,11 @@ var addOperatorScenarios = []expressionScenario{
}, },
}, },
{ {
description: "Add new object to array", description: "Add object to array",
document: `a: [{dog: woof}]`, document: `{a: [1,2], c: {cat: meow}}`,
expression: `.a + {"cat": "meow"}`, expression: `.a + .c`,
expected: []string{ expected: []string{
"D0, P[a], (!!seq)::[{dog: woof}, {cat: meow}]\n", "D0, P[a], (!!seq)::[1, 2, {cat: meow}]\n",
}, },
}, },
{ {
@@ -70,56 +61,6 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n", "D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
}, },
}, },
{
description: "String concatenation",
document: `{a: cat, b: meow}`,
expression: `.a = .a + .b`,
expected: []string{
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
},
},
{
description: "Relative string concatenation",
document: `{a: cat, b: meow}`,
expression: `.a += .b`,
expected: []string{
"D0, P[], (doc)::{a: catmeow, b: meow}\n",
},
},
{
description: "Number addition - float",
subdescription: "If the lhs or rhs are floats then the expression will be calculated with floats.",
document: `{a: 3, b: 4.9}`,
expression: `.a = .a + .b`,
expected: []string{
"D0, P[], (doc)::{a: 7.9, b: 4.9}\n",
},
},
{
description: "Number addition - int",
subdescription: "If both the lhs and rhs are ints then the expression will be calculated with ints.",
document: `{a: 3, b: 4}`,
expression: `.a = .a + .b`,
expected: []string{
"D0, P[], (doc)::{a: 7, b: 4}\n",
},
},
{
description: "Increment number",
document: `{a: 3}`,
expression: `.a += 1`,
expected: []string{
"D0, P[], (doc)::{a: 4}\n",
},
},
{
description: "Add to null",
subdescription: "Adding to null simply returns the rhs",
expression: `null + "cat"`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
},
} }
func TestAddOperatorScenarios(t *testing.T) { func TestAddOperatorScenarios(t *testing.T) {

View File

@@ -1,16 +1,20 @@
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, context Context, expressionNode *ExpressionNode) (Context, error) { func AlternativeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- alternative") log.Debugf("-- alternative")
return crossFunction(d, context, expressionNode, alternativeFunc) return crossFunction(d, matchingNodes, pathNode, alternativeFunc)
} }
func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("Alternative LHS: %v", lhs.Node.Tag) log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag) log.Debugf("- RHS: %v", rhs.Node.Tag)

View File

@@ -6,146 +6,146 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignAlias operator!") log.Debugf("AssignAlias operator!")
aliasName := "" aliasName := ""
if !expressionNode.Operation.UpdateAssign { if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
} }
} }
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Setting aliasName : %v", candidate.GetKey()) log.Debugf("Setting aliasName : %v", candidate.GetKey())
if expressionNode.Operation.UpdateAssign { if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
} }
} }
candidate.Node.Kind = yaml.AliasNode candidate.Node.Kind = yaml.AliasNode
candidate.Node.Value = aliasName candidate.Node.Value = aliasName
} }
return context, nil return matchingNodes, nil
} }
func getAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func GetAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetAlias operator!") log.Debugf("GetAlias operator!")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) 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) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }
func assignAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func AssignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignAnchor operator!") log.Debugf("AssignAnchor operator!")
anchorName := "" anchorName := ""
if !expressionNode.Operation.UpdateAssign { if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
} }
} }
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) 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 pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
} }
} }
candidate.Node.Anchor = anchorName candidate.Node.Anchor = anchorName
} }
return context, nil return matchingNodes, nil
} }
func getAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func GetAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetAnchor operator!") log.Debugf("GetAnchor operator!")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) 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) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }
func explodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func ExplodeOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- ExplodeOperation") log.Debugf("-- ExplodeOperation")
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
for childEl := rhs.MatchingNodes.Front(); childEl != nil; childEl = childEl.Next() { for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
err = explodeNode(childEl.Value.(*CandidateNode).Node, context) err = explodeNode(childEl.Value.(*CandidateNode).Node)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
} }
} }
return context, nil return matchMap, nil
} }
func explodeNode(node *yaml.Node, context Context) error { func explodeNode(node *yaml.Node) error {
node.Anchor = "" 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, context) errorInContent := explodeNode(contentNode)
if errorInContent != nil { if errorInContent != nil {
return errorInContent return errorInContent
} }
@@ -169,7 +169,7 @@ func explodeNode(node *yaml.Node, context Context) 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, context.ChildContext(newContent)) err := overrideEntry(node, keyNode, valueNode, index, newContent)
if err != nil { if err != nil {
return err return err
} }
@@ -178,14 +178,14 @@ func explodeNode(node *yaml.Node, context Context) 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, context.ChildContext(newContent)) err := applyAlias(node, aliasNode.Alias, index, 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, context.ChildContext(newContent)) err := applyAlias(node, valueNode.Alias, index, newContent)
if err != nil { if err != nil {
return err return err
} }
@@ -205,7 +205,7 @@ func explodeNode(node *yaml.Node, context Context) error {
} }
} }
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent Context) error { func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent *list.List) error {
if alias == nil { if alias == nil {
return nil return nil
} }
@@ -221,15 +221,15 @@ func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent Co
return nil return nil
} }
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent Context) error { func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent *list.List) error {
err := explodeNode(value, newContent) err := explodeNode(value)
if err != nil { if err != nil {
return err return err
} }
for newEl := newContent.MatchingNodes.Front(); newEl != nil; newEl = newEl.Next() { for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
valueEl := newEl.Next() // move forward twice 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, newContent) err = explodeNode(key)
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.MatchingNodes.PushBack(key) newContent.PushBack(key)
newContent.MatchingNodes.PushBack(value) newContent.PushBack(value)
return nil return nil
} }

View File

@@ -1,61 +1,66 @@
package yqlib package yqlib
func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { import "container/list"
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
var rhs Context var rhs *list.List
if !expressionNode.Operation.UpdateAssign { if !pathNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
} }
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if expressionNode.Operation.UpdateAssign { if pathNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
} }
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
// grab the first value // grab the first value
first := rhs.MatchingNodes.Front() first := rhs.Front()
if first != nil { if first != nil {
rhsCandidate := first.Value.(*CandidateNode) rhsCandidate := first.Value.(*CandidateNode)
rhsCandidate.Node = unwrapDoc(rhsCandidate.Node) rhsCandidate.Node = UnwrapDoc(rhsCandidate.Node)
candidate.UpdateFrom(rhsCandidate) candidate.UpdateFrom(rhsCandidate)
} }
} }
// // if there was nothing given, perhaps we are creating a new yaml doc
return context, nil // if matchingNodes.Len() == 0 {
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
// return lhs, nil
// }
return matchingNodes, nil
} }
// does not update content or values // does not update content or values
func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debug("getting lhs matching nodes for update") lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
// grab the first value // grab the first value
first := rhs.MatchingNodes.Front() first := rhs.Front()
if first != nil { if first != nil {
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode)) candidate.UpdateAttributesFrom(first.Value.(*CandidateNode))
} }
} }
return context, nil return matchingNodes, nil
} }

View File

@@ -7,7 +7,7 @@ import (
) )
func isTruthy(c *CandidateNode) (bool, error) { func isTruthy(c *CandidateNode) (bool, error) {
node := unwrapDoc(c.Node) node := UnwrapDoc(c.Node)
value := true value := true
if node.Tag == "!!null" { if node.Tag == "!!null" {
@@ -25,10 +25,10 @@ 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, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = UnwrapDoc(rhs.Node)
lhsTrue, errDecoding := isTruthy(lhs) lhsTrue, errDecoding := isTruthy(lhs)
if errDecoding != nil { if errDecoding != nil {
@@ -44,35 +44,35 @@ func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *C
} }
} }
func orOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func OrOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- orOp") log.Debugf("-- orOp")
return crossFunction(d, context, expressionNode, performBoolOp( return crossFunction(d, matchingNodes, pathNode, performBoolOp(
func(b1 bool, b2 bool) bool { func(b1 bool, b2 bool) bool {
return b1 || b2 return b1 || b2
})) }))
} }
func andOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- AndOp") log.Debugf("-- AndOp")
return crossFunction(d, context, expressionNode, performBoolOp( return crossFunction(d, matchingNodes, pathNode, performBoolOp(
func(b1 bool, b2 bool) bool { func(b1 bool, b2 bool) bool {
return b1 && b2 return b1 && b2
})) }))
} }
func notOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- notOperation") log.Debugf("-- notOperation")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) 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 Context{}, errDecoding return nil, errDecoding
} }
result := createBooleanCandidate(candidate, !truthy) result := createBooleanCandidate(candidate, !truthy)
results.PushBack(result) results.PushBack(result)
} }
return context.ChildContext(results), nil return results, nil
} }

View File

@@ -6,35 +6,34 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func collectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- collectOperation") log.Debugf("-- collectOperation")
if context.MatchingNodes.Len() == 0 { if matchMap.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 context.SingleChildContext(candidate), nil return nodeToMap(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
if context.MatchingNodes.Front() != nil {
collectC = context.MatchingNodes.Front().Value.(*CandidateNode).CreateChild(nil, node)
if len(collectC.Path) > 0 {
collectC.Path = collectC.Path[:len(collectC.Path)-1]
}
} else {
collectC = &CandidateNode{Node: node}
}
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { var document uint = 0
var path []interface{}
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Collecting %v", NodeToString(candidate)) log.Debugf("Collecting %v", NodeToString(candidate))
node.Content = append(node.Content, unwrapDoc(candidate.Node)) if path == nil && candidate.Path != nil && len(candidate.Path) > 1 {
path = candidate.Path[:len(candidate.Path)-1]
document = candidate.Document
}
node.Content = append(node.Content, candidate.Node)
} }
collectC := &CandidateNode{Node: node, Document: document, Path: path}
results.PushBack(collectC) results.PushBack(collectC)
return context.ChildContext(results), nil return results, nil
} }

View File

@@ -17,83 +17,92 @@ import (
... ...
*/ */
func collectObjectOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- collectObjectOperation") log.Debugf("-- collectObjectOperation")
if context.MatchingNodes.Len() == 0 { if matchMap.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 context.SingleChildContext(candidate), nil return nodeToMap(candidate), nil
} }
first := context.MatchingNodes.Front().Value.(*CandidateNode) first := matchMap.Front().Value.(*CandidateNode)
var rotated []*list.List = make([]*list.List, len(first.Node.Content)) 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 := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidateNode := el.Value.(*CandidateNode) candidateNode := el.Value.(*CandidateNode)
for i := 0; i < len(first.Node.Content); i++ { for i := 0; i < len(first.Node.Content); i++ {
rotated[i].PushBack(candidateNode.CreateChild(i, candidateNode.Node.Content[i])) rotated[i].PushBack(createChildCandidate(candidateNode, i))
} }
} }
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, context.ChildContext(list.New()), rotated[i]) additions, err := collect(d, list.New(), rotated[i])
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
newObject.PushBackList(additions.MatchingNodes) newObject.PushBackList(additions)
} }
return context.ChildContext(newObject), nil return newObject, nil
} }
func collect(d *dataTreeNavigator, context Context, remainingMatches *list.List) (Context, error) { func createChildCandidate(candidate *CandidateNode, index int) *CandidateNode {
return &CandidateNode{
Document: candidate.Document,
Path: candidate.CreateChildPath(index),
Filename: candidate.Filename,
Node: candidate.Node.Content[index],
}
}
func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.List) (*list.List, error) {
if remainingMatches.Len() == 0 { if remainingMatches.Len() == 0 {
return context, nil return aggregate, nil
} }
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode) candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
splatted, err := splat(d, context.SingleChildContext(candidate), splatted, err := Splat(d, nodeToMap(candidate),
traversePreferences{DontFollowAlias: true, IncludeMapKeys: false}) &TraversePreferences{FollowAlias: false, IncludeMapKeys: false})
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() { for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatEl.Value.(*CandidateNode).Path = nil splatEl.Value.(*CandidateNode).Path = nil
} }
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if context.MatchingNodes.Len() == 0 { if aggregate.Len() == 0 {
return collect(d, splatted, remainingMatches) return collect(d, splatted, remainingMatches)
} }
newAgg := list.New() newAgg := list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := aggregate.Front(); el != nil; el = el.Next() {
aggCandidate := el.Value.(*CandidateNode) aggCandidate := el.Value.(*CandidateNode)
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() { for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatCandidate := splatEl.Value.(*CandidateNode) splatCandidate := splatEl.Value.(*CandidateNode)
newCandidate, err := aggCandidate.Copy() newCandidate, err := aggCandidate.Copy()
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
newCandidate.Path = nil newCandidate.Path = nil
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, context, newCandidate, splatCandidate) newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
newAgg.PushBack(newCandidate) newAgg.PushBack(newCandidate)
} }
} }
return collect(d, context.ChildContext(newAgg), remainingMatches) return collect(d, newAgg, remainingMatches)
} }

View File

@@ -13,15 +13,6 @@ var collectOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[]\n", "D0, P[], (!!seq)::[]\n",
}, },
}, },
{
skipDoc: true,
document: "{a: apple}\n---\n{b: frog}",
expression: `[.]`,
expected: []string{
"D0, P[], (!!seq)::- {a: apple}\n- {b: frog}\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: ``, document: ``,

View File

@@ -7,47 +7,47 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type commentOpPreferences struct { type CommentOpPreferences struct {
LineComment bool LineComment bool
HeadComment bool HeadComment bool
FootComment bool FootComment bool
} }
func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignComments operator!") log.Debugf("AssignComments operator!")
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
preferences := expressionNode.Operation.Preferences.(commentOpPreferences) preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
comment := "" comment := ""
if !expressionNode.Operation.UpdateAssign { if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value comment = rhs.Front().Value.(*CandidateNode).Node.Value
} }
} }
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if expressionNode.Operation.UpdateAssign { if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value comment = rhs.Front().Value.(*CandidateNode).Node.Value
} }
} }
@@ -63,15 +63,15 @@ func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNod
} }
} }
return context, nil return matchingNodes, nil
} }
func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
preferences := expressionNode.Operation.Preferences.(commentOpPreferences) preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
log.Debugf("GetComments operator!") log.Debugf("GetComments operator!")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
comment := "" comment := ""
if preferences.LineComment { if preferences.LineComment {
@@ -84,8 +84,8 @@ func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *
comment = strings.Replace(comment, "# ", "", 1) comment = strings.Replace(comment, "# ", "", 1)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }

View File

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

View File

@@ -6,7 +6,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- createMapOperation") log.Debugf("-- createMapOperation")
//each matchingNodes entry should turn into a sequence of keys to create. //each matchingNodes entry should turn into a sequence of keys to create.
@@ -18,29 +18,29 @@ func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
sequences := list.New() sequences := list.New()
if context.MatchingNodes.Len() > 0 { if matchingNodes.Len() > 0 {
for matchingNodeEl := context.MatchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() { for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
matchingNode := matchingNodeEl.Value.(*CandidateNode) matchingNode := matchingNodeEl.Value.(*CandidateNode)
sequenceNode, err := sequenceFor(d, context, matchingNode, expressionNode) sequenceNode, err := sequenceFor(d, matchingNode, pathNode)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
sequences.PushBack(sequenceNode) sequences.PushBack(sequenceNode)
} }
} else { } else {
sequenceNode, err := sequenceFor(d, context, nil, expressionNode) sequenceNode, err := sequenceFor(d, nil, pathNode)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
sequences.PushBack(sequenceNode) sequences.PushBack(sequenceNode)
} }
return context.SingleChildContext(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil return nodeToMap(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
} }
func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, expressionNode *ExpressionNode) (*CandidateNode, error) { func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathTreeNode) (*CandidateNode, error) {
var path []interface{} var path []interface{}
var document uint = 0 var document uint = 0
var matches = list.New() var matches = list.New()
@@ -48,17 +48,17 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
if matchingNode != nil { if matchingNode != nil {
path = matchingNode.Path path = matchingNode.Path
document = matchingNode.Document document = matchingNode.Document
matches.PushBack(matchingNode) matches = nodeToMap(matchingNode)
} }
mapPairs, err := crossFunction(d, context.ChildContext(matches), expressionNode, mapPairs, err := crossFunction(d, matches, pathNode,
func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
log.Debugf("LHS:", NodeToString(lhs)) log.Debugf("LHS:", NodeToString(lhs))
log.Debugf("RHS:", NodeToString(rhs)) log.Debugf("RHS:", NodeToString(rhs))
node.Content = []*yaml.Node{ node.Content = []*yaml.Node{
unwrapDoc(lhs.Node), UnwrapDoc(lhs.Node),
unwrapDoc(rhs.Node), UnwrapDoc(rhs.Node),
} }
return &CandidateNode{Node: &node, Document: document, Path: path}, nil return &CandidateNode{Node: &node, Document: document, Path: path}, nil
@@ -67,7 +67,7 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
if err != nil { if err != nil {
return nil, err return nil, err
} }
innerList := listToNodeSeq(mapPairs.MatchingNodes) innerList := listToNodeSeq(mapPairs)
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,72 +1,70 @@
package yqlib package yqlib
import ( import (
"container/list"
"fmt" "fmt"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
contextToUse := context.Clone()
contextToUse.DontAutoCreate = true nodesToDelete, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
nodesToDelete, err := d.GetMatchingNodes(contextToUse, expressionNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
//need to iterate backwards to ensure correct indices when deleting multiple
for el := nodesToDelete.MatchingNodes.Back(); el != nil; el = el.Prev() { for el := nodesToDelete.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if len(candidate.Path) > 0 {
deleteImmediateChildOp := &Operation{ deleteImmediateChildOp := &Operation{
OperationType: deleteImmediateChildOpType, OperationType: DeleteImmediateChild,
Value: candidate.Path[len(candidate.Path)-1], Value: candidate.Path[len(candidate.Path)-1],
} }
deleteImmediateChildOpNode := &ExpressionNode{ deleteImmediateChildOpNode := &PathTreeNode{
Operation: deleteImmediateChildOp, Operation: deleteImmediateChildOp,
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}, false), Rhs: createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]),
} }
_, err := d.GetMatchingNodes(contextToUse, deleteImmediateChildOpNode) _, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
} }
} return matchingNodes, nil
return context, nil
} }
func deleteImmediateChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func DeleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
parents, err := d.GetMatchingNodes(context, expressionNode.Rhs) parents, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
childPath := expressionNode.Operation.Value childPath := pathNode.Operation.Value
log.Debug("childPath to remove %v", childPath) log.Debug("childPath to remove %v", childPath)
for el := parents.MatchingNodes.Front(); el != nil; el = el.Next() { for el := parents.Front(); el != nil; el = el.Next() {
parent := el.Value.(*CandidateNode) parent := el.Value.(*CandidateNode)
parentNode := unwrapDoc(parent.Node) parentNode := UnwrapDoc(parent.Node)
if parentNode.Kind == yaml.MappingNode { if parentNode.Kind == yaml.MappingNode {
deleteFromMap(parent, childPath) deleteFromMap(parent, childPath)
} else if parentNode.Kind == yaml.SequenceNode { } else if parentNode.Kind == yaml.SequenceNode {
deleteFromArray(parent, childPath) deleteFromArray(parent, childPath)
} else { } else {
return Context{}, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag) return nil, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
} }
} }
return context, nil return matchingNodes, nil
} }
func deleteFromMap(candidate *CandidateNode, childPath interface{}) { func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
log.Debug("deleteFromMap") log.Debug("deleteFromMap")
node := unwrapDoc(candidate.Node) node := UnwrapDoc(candidate.Node)
contents := node.Content contents := node.Content
newContents := make([]*yaml.Node, 0) newContents := make([]*yaml.Node, 0)
@@ -74,7 +72,11 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
key := contents[index] key := contents[index]
value := contents[index+1] value := contents[index+1]
childCandidate := candidate.CreateChild(key.Value, value) childCandidate := &CandidateNode{
Node: value,
Document: candidate.Document,
Path: candidate.CreateChildPath(key.Value),
}
shouldDelete := key.Value == childPath shouldDelete := key.Value == childPath
@@ -89,7 +91,7 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
func deleteFromArray(candidate *CandidateNode, childPath interface{}) { func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
log.Debug("deleteFromArray") log.Debug("deleteFromArray")
node := unwrapDoc(candidate.Node) node := UnwrapDoc(candidate.Node)
contents := node.Content contents := node.Content
newContents := make([]*yaml.Node, 0) newContents := make([]*yaml.Node, 0)

View File

@@ -37,38 +37,6 @@ 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, context Context, expressionNode *ExpressionNode) (Context, error) { func GetDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"}
scalar := candidate.CreateChild(nil, node) scalar := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(scalar) results.PushBack(scalar)
} }
return context.ChildContext(results), nil return results, nil
} }

View File

@@ -1,24 +1,24 @@
package yqlib package yqlib
import ( import (
"fmt" "container/list"
"os" "os"
"strings" "strings"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type envOpPreferences struct { type EnvOpPreferences struct {
StringValue bool StringValue bool
} }
func envOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func EnvOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
envName := expressionNode.Operation.CandidateNode.Node.Value envName := pathNode.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 := pathNode.Operation.Preferences.(*EnvOpPreferences)
var node *yaml.Node var node *yaml.Node
if preferences.StringValue { if preferences.StringValue {
@@ -27,23 +27,26 @@ func envOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
Tag: "!!str", Tag: "!!str",
Value: rawValue, Value: rawValue,
} }
} else if rawValue == "" {
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 Context{}, errorReading return nil, errorReading
} }
//first node is a doc //first node is a doc
node = unwrapDoc(&dataBucket) node = UnwrapDoc(&dataBucket)
} }
log.Debug("ENV tag", node.Tag) log.Debug("ENV tag", node.Tag)
log.Debug("ENV value", node.Value) log.Debug("ENV value", node.Value)
log.Debug("ENV Kind", node.Kind) log.Debug("ENV Kind", node.Kind)
target := &CandidateNode{Node: node} target := &CandidateNode{
Path: make([]interface{}, 0),
return context.SingleChildContext(target), nil Document: 0,
Filename: "",
Node: node,
}
return nodeToMap(target), nil
} }

View File

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

@@ -5,23 +5,6 @@ import (
) )
var equalsOperatorScenarios = []expressionScenario{ var equalsOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: "cat",
document2: "dog",
expression: "select(fi==0) == select(fi==1)",
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
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]`,
@@ -31,18 +14,7 @@ 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)`,
@@ -51,18 +23,7 @@ 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, context Context, expressionNode *ExpressionNode) (Context, error) { func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetFilename") log.Debugf("GetFilename")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }
func getFileIndexOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetFileIndex") log.Debugf("GetFileIndex")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }

View File

@@ -29,14 +29,6 @@ var fileOperatorScenarios = []expressionScenario{
"D0, P[], (!!int)::0\n", "D0, P[], (!!int)::0\n",
}, },
}, },
{
skipDoc: true,
document: "a: cat\nb: dog",
expression: `.. lineComment |= filename`,
expected: []string{
"D0, P[], (!!map)::a: cat # sample.yml\nb: dog # sample.yml\n",
},
},
} }
func TestFileOperatorsScenarios(t *testing.T) { func TestFileOperatorsScenarios(t *testing.T) {

View File

@@ -7,26 +7,25 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func hasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- hasOperation") log.Debugf("-- hasOperation")
var results = list.New() var results = list.New()
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
wanted := rhs.MatchingNodes.Front().Value.(*CandidateNode).Node wanted := rhs.Front().Value.(*CandidateNode).Node
wantedKey := wanted.Value wantedKey := wanted.Value
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
// grab the first value // grab the first value
candidateNode := unwrapDoc(candidate.Node) var contents = candidate.Node.Content
var contents = candidateNode.Content switch candidate.Node.Kind {
switch candidateNode.Kind {
case yaml.MappingNode: case yaml.MappingNode:
candidateHasKey := false candidateHasKey := false
for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 { for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 {
@@ -41,7 +40,7 @@ func hasOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
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 Context{}, errParsingInt return nil, errParsingInt
} }
candidateHasKey = int64(len(contents)) > number candidateHasKey = int64(len(contents)) > number
} }
@@ -50,5 +49,5 @@ func hasOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
results.PushBack(createBooleanCandidate(candidate, false)) results.PushBack(createBooleanCandidate(candidate, false))
} }
} }
return context.ChildContext(results), nil return results, nil
} }

View File

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

View File

@@ -1,54 +0,0 @@
package yqlib
import (
"container/list"
"fmt"
"gopkg.in/yaml.v3"
)
func keysOperator(d *dataTreeNavigator, 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

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

View File

@@ -7,21 +7,17 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func lengthOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- lengthOperation") log.Debugf("-- lengthOperation")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
targetNode := unwrapDoc(candidate.Node) targetNode := UnwrapDoc(candidate.Node)
var length int var length int
switch targetNode.Kind { switch targetNode.Kind {
case yaml.ScalarNode: case yaml.ScalarNode:
if targetNode.Tag == "!!null" {
length = 0
} else {
length = len(targetNode.Value) 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,9 +27,9 @@ func lengthOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
} }
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }

View File

@@ -14,30 +14,6 @@ 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,72 +2,92 @@ package yqlib
import ( import (
"fmt" "fmt"
"strconv"
"container/list" "container/list"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type multiplyPreferences struct { type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, calculation CrossFunctionCalculation) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
log.Debugf("crossFunction LHS len: %v", lhs.Len())
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.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 {
AppendArrays bool AppendArrays bool
TraversePrefs traversePreferences
} }
func multiplyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- MultiplyOperator") log.Debugf("-- MultiplyOperator")
return crossFunction(d, context, expressionNode, multiply(expressionNode.Operation.Preferences.(multiplyPreferences))) return crossFunction(d, matchingNodes, pathNode, multiply(pathNode.Operation.Preferences.(*MultiplyPreferences)))
} }
func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = unwrapDoc(lhs.Node) lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = unwrapDoc(rhs.Node) rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("Multipling LHS: %v", lhs.Node.Tag) log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag) log.Debugf("- RHS: %v", rhs.Node.Tag)
shouldAppendArrays := preferences.AppendArrays
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { (lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
var newBlank = lhs.CreateChild(nil, &yaml.Node{}) var newBlank = &CandidateNode{
var newThing, err = mergeObjects(d, context, newBlank, lhs, multiplyPreferences{}) Path: lhs.Path,
Document: lhs.Document,
Filename: lhs.Filename,
Node: &yaml.Node{},
}
var newThing, err = mergeObjects(d, newBlank, lhs, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return mergeObjects(d, context, newThing, rhs, preferences) return mergeObjects(d, newThing, rhs, shouldAppendArrays)
} else if lhs.Node.Tag == "!!int" && rhs.Node.Tag == "!!int" {
return multiplyIntegers(lhs, rhs)
} }
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
} }
} }
func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) { func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
target := lhs.CreateChild(nil, &yaml.Node{})
target.Node.Kind = yaml.ScalarNode
target.Node.Style = lhs.Node.Style
target.Node.Tag = "!!int"
lhsNum, err := strconv.Atoi(lhs.Node.Value)
if err != nil {
return nil, err
}
rhsNum, err := strconv.Atoi(rhs.Node.Value)
if err != nil {
return nil, err
}
target.Node.Value = fmt.Sprintf("%v", lhsNum*rhsNum)
return target, nil
}
func mergeObjects(d *dataTreeNavigator, 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{DontFollowAlias: true, IncludeMapKeys: true}} TraversePreferences: &TraversePreferences{FollowAlias: false}}
err := recursiveDecent(d, results, context.SingleChildContext(rhs), prefs) err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -78,12 +98,7 @@ func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs
} }
for el := results.Front(); el != nil; el = el.Next() { for el := results.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
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
} }
@@ -91,24 +106,24 @@ func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs
return lhs, nil return lhs, nil
} }
func applyAssignment(d *dataTreeNavigator, context Context, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error { func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
shouldAppendArrays := preferences.AppendArrays
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", lhs.GetKey(), rhs.GetKey()) log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
lhsPath := rhs.Path[pathIndexToStartFrom:] lhsPath := rhs.Path[pathIndexToStartFrom:]
assignmentOp := &Operation{OperationType: assignAttributesOpType} assignmentOp := &Operation{OperationType: AssignAttributes}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = assignOpType assignmentOp.OperationType = Assign
assignmentOp.UpdateAssign = false assignmentOp.UpdateAssign = false
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode { } else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = addAssignOpType assignmentOp.OperationType = AddAssign
} }
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs} rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath, preferences.TraversePrefs, rhs.IsMapKey), Rhs: &ExpressionNode{Operation: rhsOp}} assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &PathTreeNode{Operation: rhsOp}}
_, err := d.GetMatchingNodes(context.SingleChildContext(lhs), assignmentOpNode) _, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode)
return err return err
} }

View File

@@ -13,51 +13,6 @@ var multiplyOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n", "D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
}, },
}, },
{
skipDoc: true,
document: "# 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"}`,
@@ -124,10 +79,16 @@ var multiplyOperatorScenarios = []expressionScenario{
{ {
description: "Merge keeps style of LHS", description: "Merge keeps style of LHS",
dontFormatInputForDoc: true, dontFormatInputForDoc: true,
document: "a: {things: great}\nb:\n 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\"}\nb:\n also: \"me\"\n", `D0, P[], (!!map)::a: {things: great, also: "me"}
b:
also: "me"
`,
}, },
}, },
{ {
@@ -146,30 +107,6 @@ var multiplyOperatorScenarios = []expressionScenario{
"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}}`,
@@ -178,14 +115,6 @@ var multiplyOperatorScenarios = []expressionScenario{
"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}`,
@@ -203,11 +132,11 @@ var multiplyOperatorScenarios = []expressionScenario{
}, },
}, },
{ {
description: "Merge copies anchor names", description: "Merge does not copy anchor names",
document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`, document: `{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}`,
expression: `.c * .a`, expression: `.c * .a`,
expected: []string{ expected: []string{
"D0, P[c], (!!map)::{g: thongs, c: &cat frog}\n", "D0, P[c], (!!map)::{g: thongs, c: frog}\n",
}, },
}, },
{ {

View File

@@ -16,12 +16,12 @@ func createPathNodeFor(pathElement interface{}) *yaml.Node {
} }
} }
func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func GetPathOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetPath") log.Debugf("GetPath")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
@@ -31,9 +31,9 @@ func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
content[pathIndex] = createPathNodeFor(path) content[pathIndex] = createPathNodeFor(path)
} }
node.Content = content node.Content = content
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }

View File

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

View File

@@ -6,28 +6,28 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type recursiveDescentPreferences struct { type RecursiveDescentPreferences struct {
TraversePreferences traversePreferences TraversePreferences *TraversePreferences
RecurseArray bool RecurseArray bool
} }
func recursiveDescentOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
var results = list.New() var results = list.New()
preferences := expressionNode.Operation.Preferences.(recursiveDescentPreferences) preferences := pathNode.Operation.Preferences.(*RecursiveDescentPreferences)
err := recursiveDecent(d, results, context, preferences) err := recursiveDecent(d, results, matchMap, preferences)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
return context.ChildContext(results), nil return results, nil
} }
func recursiveDecent(d *dataTreeNavigator, results *list.List, context Context, preferences recursiveDescentPreferences) error { func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences *RecursiveDescentPreferences) error {
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
candidate.Node = unwrapDoc(candidate.Node) candidate.Node = UnwrapDoc(candidate.Node)
log.Debugf("Recursive Decent, added %v", NodeToString(candidate)) log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
results.PushBack(candidate) results.PushBack(candidate)
@@ -35,7 +35,7 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, context Context,
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, context.SingleChildContext(candidate), preferences.TraversePreferences) children, err := Splat(d, nodeToMap(candidate), preferences.TraversePreferences)
if err != nil { if err != nil {
return err return err

View File

@@ -64,11 +64,11 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
}, },
{ {
description: "Recursively find nodes with keys", description: "Recursively find nodes with keys",
subdescription: "Note that this example has wrapped the expression in `[]` to show that there are two matches returned. You do not have to wrap in `[]` in your path expression.",
document: `{a: {name: frog, b: {name: blog, age: 12}}}`, document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
expression: `[.. | select(has("name"))]`, expression: `.. | select(has("name"))`,
expected: []string{ expected: []string{
"D0, P[], (!!seq)::- {name: frog, b: {name: blog, age: 12}}\n- {name: blog, age: 12}\n", "D0, P[a], (!!map)::{name: frog, b: {name: blog, age: 12}}\n",
"D0, P[a b], (!!map)::{name: blog, age: 12}\n",
}, },
}, },
{ {
@@ -165,7 +165,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
document: `{a: &cat {c: frog}, b: *cat}`, document: `{a: &cat {c: frog}, b: *cat}`,
expression: `[..]`, expression: `[..]`,
expected: []string{ expected: []string{
"D0, P[], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n", "D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
}, },
}, },
{ {
@@ -187,7 +187,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
document: mergeDocSample, document: mergeDocSample,
expression: `.foobar | [..]`, expression: `.foobar | [..]`,
expected: []string{ expected: []string{
"D0, P[], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n", "D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
}, },
}, },
{ {
@@ -195,7 +195,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
document: mergeDocSample, document: mergeDocSample,
expression: `.foobar | [...]`, expression: `.foobar | [...]`,
expected: []string{ expected: []string{
"D0, P[], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- c\n- foobar_c\n- !!merge <<\n- *foo\n- thing\n- foobar_thing\n", "D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- c\n- foobar_c\n- !!merge <<\n- *foo\n- thing\n- foobar_thing\n",
}, },
}, },
{ {
@@ -233,5 +233,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) {
for _, tt := range recursiveDescentOperatorScenarios { for _, tt := range recursiveDescentOperatorScenarios {
testScenario(t, &tt) testScenario(t, &tt)
} }
documentScenarios(t, "Recursive Descent (Glob)", recursiveDescentOperatorScenarios) documentScenarios(t, "Recursive Descent", recursiveDescentOperatorScenarios)
} }

View File

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

View File

@@ -1,22 +0,0 @@
package yqlib
import (
"testing"
)
var reduceOperatorScenarios = []expressionScenario{
{
document: `[10,2, 5, 3]`,
expression: `.[] as $item ireduce (0; . + $item)`,
expected: []string{
"D0, P[], (!!int)::20\n",
},
},
}
func TestReduceOperatorScenarios(t *testing.T) {
for _, tt := range reduceOperatorScenarios {
testScenario(t, &tt)
}
// documentScenarios(t, "Reduce", reduceOperatorScenarios)
}

View File

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

View File

@@ -14,14 +14,6 @@ var selectOperatorScenarios = []expressionScenario{
"D0, P[1], (!!str)::goat\n", "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,5 +1,7 @@
package yqlib package yqlib
func selfOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { import "container/list"
return context, nil
func SelfOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
return matchMap, nil
} }

View File

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

View File

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

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

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

@@ -1,52 +0,0 @@
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, context Context, expressionNode *ExpressionNode) (Context, error) { func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignStyleOperator: %v") log.Debugf("AssignStyleOperator: %v")
var style yaml.Style var style yaml.Style
if !expressionNode.Operation.UpdateAssign { if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
style, err = parseStyle(rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value) style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
} }
} }
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) 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 pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
style, err = parseStyle(rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value) style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
} }
} }
@@ -70,15 +70,15 @@ func assignStyleOperator(d *dataTreeNavigator, context Context, expressionNode *
candidate.Node.Style = style candidate.Node.Style = style
} }
return context, nil return matchingNodes, nil
} }
func getStyleOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func GetStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetStyleOperator") log.Debugf("GetStyleOperator")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
var style string var style string
switch candidate.Node.Style { switch candidate.Node.Style {
@@ -100,9 +100,9 @@ func getStyleOperator(d *dataTreeNavigator, context Context, expressionNode *Exp
style = "<unknown>" style = "<unknown>"
} }
node := &yaml.Node{Kind: yaml.ScalarNode, Value: style, Tag: "!!str"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: style, Tag: "!!str"}
result := candidate.CreateChild(nil, node) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }

View File

@@ -6,58 +6,58 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func assignTagOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignTagOperator: %v") log.Debugf("AssignTagOperator: %v")
tag := "" tag := ""
if !expressionNode.Operation.UpdateAssign { if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
tag = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value tag = rhs.Front().Value.(*CandidateNode).Node.Value
} }
} }
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) 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 pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs) rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
if rhs.MatchingNodes.Front() != nil { if rhs.Front() != nil {
tag = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value tag = rhs.Front().Value.(*CandidateNode).Node.Value
} }
} }
unwrapDoc(candidate.Node).Tag = tag candidate.Node.Tag = tag
} }
return context, nil return matchingNodes, nil
} }
func getTagOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetTagOperator") log.Debugf("GetTagOperator")
var results = list.New() var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) 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) lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(result) results.PushBack(lengthCand)
} }
return context.ChildContext(results), nil return results, nil
} }

View File

@@ -26,14 +26,6 @@ var tagOperatorScenarios = []expressionScenario{
"D0, P[], (!!str)::'!!map'\n", "D0, P[], (!!str)::'!!map'\n",
}, },
}, },
{
skipDoc: true,
document: `32`,
expression: `. tag= "!!str"`,
expected: []string{
"D0, P[], (doc)::\"32\"\n",
},
},
{ {
description: "Set custom tag", description: "Set custom tag",
document: `{a: str}`, document: `{a: str}`,

View File

@@ -9,33 +9,31 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type traversePreferences struct { type TraversePreferences struct {
DontFollowAlias bool FollowAlias bool
IncludeMapKeys bool IncludeMapKeys bool
DontAutoCreate bool // by default, we automatically create entries on the fly.
DontIncludeMapValues bool
} }
func splat(d *dataTreeNavigator, context Context, prefs traversePreferences) (Context, error) { func Splat(d *dataTreeNavigator, matches *list.List, prefs *TraversePreferences) (*list.List, error) {
return traverseNodesWithArrayIndices(context, make([]*yaml.Node, 0), prefs) return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
} }
func traversePathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- traversePathOperator") log.Debugf("-- Traversing")
var matches = list.New() var matchingNodeMap = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
newNodes, err := traverse(d, context, el.Value.(*CandidateNode), expressionNode.Operation) newNodes, err := traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
matches.PushBackList(newNodes) matchingNodeMap.PushBackList(newNodes)
} }
return context.ChildContext(matches), nil return matchingNodeMap, nil
} }
func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, operation *Operation) (*list.List, error) { func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
log.Debug("Traversing %v", NodeToString(matchingNode)) log.Debug("Traversing %v", NodeToString(matchingNode))
value := matchingNode.Node value := matchingNode.Node
@@ -56,7 +54,8 @@ func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode
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)
return traverseMap(context, matchingNode, operation.StringValue, operation.Preferences.(traversePreferences), false) prefs := &TraversePreferences{FollowAlias: true}
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))
@@ -65,59 +64,47 @@ func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode
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, context, matchingNode, operation) return traverse(d, matchingNode, operation)
case yaml.DocumentNode: case yaml.DocumentNode:
log.Debug("digging into doc node") log.Debug("digging into doc node")
return traverse(d, &CandidateNode{
return traverse(d, context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), operation) Node: matchingNode.Node.Content[0],
Filename: matchingNode.Filename,
FileIndex: matchingNode.FileIndex,
Document: matchingNode.Document}, operation)
default: default:
return list.New(), nil return list.New(), nil
} }
} }
func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func TraverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
//lhs may update the variable context, we should pass that into the RHS
// BUT we still return the original context back (see jq)
// https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
if err != nil {
return Context{}, err
}
// rhs is a collect expression that will yield indexes to retreive of the arrays // rhs is a collect expression that will yield indexes to retreive of the arrays
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs) rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
var indicesToTraverse = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Content var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
prefs := &TraversePreferences{FollowAlias: true}
//now we traverse the result of the lhs against the indices we found return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs)
result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, traversePreferences{})
if err != nil {
return Context{}, err
}
return context.ChildContext(result.MatchingNodes), nil
} }
func traverseNodesWithArrayIndices(context Context, indicesToTraverse []*yaml.Node, prefs traversePreferences) (Context, error) { func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
var matchingNodeMap = list.New() var matchingNodeMap = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
newNodes, err := traverseArrayIndices(context, candidate, indicesToTraverse, prefs) newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs)
if err != nil { if err != nil {
return Context{}, err return nil, err
} }
matchingNodeMap.PushBackList(newNodes) matchingNodeMap.PushBackList(newNodes)
} }
return context.ChildContext(matchingNodeMap), nil return matchingNodeMap, nil
} }
func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
node := matchingNode.Node 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")
@@ -128,28 +115,32 @@ func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesT
if node.Kind == yaml.AliasNode { if node.Kind == yaml.AliasNode {
matchingNode.Node = node.Alias matchingNode.Node = node.Alias
return traverseArrayIndices(context, matchingNode, indicesToTraverse, prefs) return traverseArrayIndices(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(context, matchingNode, indicesToTraverse, prefs) return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.DocumentNode { } else if node.Kind == yaml.DocumentNode {
return traverseArrayIndices(context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs) return traverseArrayIndices(&CandidateNode{
Node: matchingNode.Node.Content[0],
Filename: matchingNode.Filename,
FileIndex: matchingNode.FileIndex,
Document: matchingNode.Document}, indicesToTraverse, prefs)
} }
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag) log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
return list.New(), nil return list.New(), nil
} }
func traverseMapWithIndices(context Context, candidate *CandidateNode, indices []*yaml.Node, prefs traversePreferences) (*list.List, error) { func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
if len(indices) == 0 { if len(indices) == 0 {
return traverseMap(context, candidate, "", prefs, true) return traverseMap(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(context, candidate, indexNode.Value, prefs, false) newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -162,13 +153,17 @@ func traverseMapWithIndices(context Context, candidate *CandidateNode, indices [
func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) { func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) {
log.Debug("traverseArrayWithIndices") log.Debug("traverseArrayWithIndices")
var newMatches = list.New() var newMatches = list.New()
node := unwrapDoc(candidate.Node) node := UnwrapDoc(candidate.Node)
if len(indices) == 0 { if len(indices) == 0 {
log.Debug("splatting") log.Debug("splatting")
var index int64 var index int64
for index = 0; index < int64(len(node.Content)); index = index + 1 { for index = 0; index < int64(len(node.Content)); index = index + 1 {
newMatches.PushBack(candidate.CreateChild(index, node.Content[index])) newMatches.PushBack(&CandidateNode{
Document: candidate.Document,
Path: candidate.CreateChildPath(index),
Node: node.Content[index],
})
} }
return newMatches, nil return newMatches, nil
@@ -195,16 +190,20 @@ func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*
return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength) return nil, fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
} }
newMatches.PushBack(candidate.CreateChild(index, node.Content[indexToUse])) newMatches.PushBack(&CandidateNode{
Node: node.Content[indexToUse],
Document: candidate.Document,
Path: candidate.CreateChildPath(index),
})
} }
return newMatches, nil return newMatches, nil
} }
func keyMatches(key *yaml.Node, wantedKey string) bool { func keyMatches(key *yaml.Node, wantedKey string) bool {
return matchKey(key.Value, wantedKey) return Match(key.Value, wantedKey)
} }
func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs traversePreferences, splat bool) (*list.List, error) { func traverseMap(matchingNode *CandidateNode, key string, prefs *TraversePreferences, splat bool) (*list.List, error) {
var newMatches = orderedmap.NewOrderedMap() var newMatches = orderedmap.NewOrderedMap()
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat) err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
@@ -212,24 +211,18 @@ func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs
return nil, err return nil, err
} }
if !prefs.DontAutoCreate && !context.DontAutoCreate && newMatches.Len() == 0 { if 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, keyNode, valueNode) node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode)
candidateNode := &CandidateNode{
if prefs.IncludeMapKeys { Node: valueNode,
log.Debug("including key") Path: append(matchingNode.Path, key),
candidateNode := matchingNode.CreateChild(key, keyNode) Document: matchingNode.Document,
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) newMatches.Set(candidateNode.GetKey(), candidateNode)
}
} }
results := list.New() results := list.New()
@@ -241,7 +234,7 @@ func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs
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
@@ -256,7 +249,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.DontFollowAlias { if key.Tag == "!!merge" && prefs.FollowAlias {
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 {
@@ -265,26 +258,33 @@ 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 := &CandidateNode{
candidateNode := candidate.CreateChild(key.Value, key) Node: key,
candidateNode.IsMapKey = true Path: candidate.CreateChildPath(key.Value),
Document: candidate.Document,
}
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode) newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
} }
if !prefs.DontIncludeMapValues { candidateNode := &CandidateNode{
log.Debug("including value") Node: value,
candidateNode := candidate.CreateChild(key.Value, value) Path: candidate.CreateChildPath(key.Value),
newMatches.Set(candidateNode.GetKey(), candidateNode) Document: candidate.Document,
} }
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 := &CandidateNode{
Node: value.Alias,
Path: originalCandidate.Path,
Document: originalCandidate.Document,
}
return doTraverseMap(newMatches, candidateNode, wantedKey, prefs, splat) return doTraverseMap(newMatches, candidateNode, wantedKey, prefs, splat)
case yaml.SequenceNode: case yaml.SequenceNode:
for _, childValue := range value.Content { for _, childValue := range value.Content {

View File

@@ -47,30 +47,13 @@ var traversePathOperatorScenarios = []expressionScenario{
}, },
{ {
description: "Special characters", description: "Special characters",
subdescription: "Use quotes with brackets around path elements with special characters", subdescription: "Use quotes around path elements with special characters",
document: `{"{}": frog}`, 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`,
@@ -80,14 +63,6 @@ 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",
@@ -108,7 +83,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",
}, },
@@ -179,7 +154,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",
}, },
@@ -261,7 +236,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",
@@ -323,7 +298,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",
@@ -369,7 +344,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",
@@ -386,7 +361,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",
}, },
@@ -402,7 +377,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",
}, },
@@ -420,7 +395,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",
@@ -443,5 +418,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) {
for _, tt := range traversePathOperatorScenarios { for _, tt := range traversePathOperatorScenarios {
testScenario(t, &tt) testScenario(t, &tt)
} }
documentScenarios(t, "Traverse (Read)", traversePathOperatorScenarios) documentScenarios(t, "Traverse", traversePathOperatorScenarios)
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,80 +4,20 @@ import (
"container/list" "container/list"
"fmt" "fmt"
"github.com/jinzhu/copier"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
type operatorHandler func(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) type OperatorHandler func(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, 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 {
return node.Content[0] return node.Content[0]
} }
return node return node
} }
func emptyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { func EmptyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
context.MatchingNodes = list.New() return list.New(), nil
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 {
@@ -86,28 +26,23 @@ func createBooleanCandidate(owner *CandidateNode, value bool) *CandidateNode {
valString = "false" valString = "false"
} }
node := &yaml.Node{Kind: yaml.ScalarNode, Value: valString, Tag: "!!bool"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: valString, Tag: "!!bool"}
return owner.CreateChild(nil, node) return &CandidateNode{Node: node, Document: owner.Document, Path: owner.Path}
} }
func createTraversalTree(path []interface{}, traversePrefs traversePreferences, targetKey bool) *ExpressionNode { func nodeToMap(candidate *CandidateNode) *list.List {
elMap := list.New()
elMap.PushBack(candidate)
return elMap
}
func createTraversalTree(path []interface{}) *PathTreeNode {
if len(path) == 0 { if len(path) == 0 {
return &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}} return &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}
} else if len(path) == 1 { } else if len(path) == 1 {
lastPrefs := traversePrefs return &PathTreeNode{Operation: &Operation{OperationType: TraversePath, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
if targetKey {
err := copier.Copy(&lastPrefs, traversePrefs)
if err != nil {
panic(err)
}
lastPrefs.IncludeMapKeys = true
lastPrefs.DontIncludeMapValues = true
}
return &ExpressionNode{Operation: &Operation{OperationType: traversePathOpType, Preferences: lastPrefs, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}}
}
return &ExpressionNode{
Operation: &Operation{OperationType: shortPipeOpType},
Lhs: createTraversalTree(path[0:1], traversePrefs, false),
Rhs: createTraversalTree(path[1:], traversePrefs, targetKey),
} }
return &PathTreeNode{
Operation: &Operation{OperationType: ShortPipe},
Lhs: createTraversalTree(path[0:1]),
Rhs: createTraversalTree(path[1:])}
} }

View File

@@ -27,9 +27,10 @@ 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 := treeCreator.ParsePath(s.expression)
if err != nil { if err != nil {
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err)) t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
return return
@@ -54,7 +55,7 @@ func testScenario(t *testing.T, s *expressionScenario) {
candidateNode := &CandidateNode{ candidateNode := &CandidateNode{
Document: 0, Document: 0,
Filename: "", Filename: "",
Node: &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode}, Node: &yaml.Node{Tag: "!!null"},
FileIndex: 0, FileIndex: 0,
} }
inputs.PushBack(candidateNode) inputs.PushBack(candidateNode)
@@ -65,13 +66,13 @@ func testScenario(t *testing.T, s *expressionScenario) {
os.Setenv("myenv", s.environmentVariable) os.Setenv("myenv", s.environmentVariable)
} }
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node) results, err = treeNavigator.GetMatchingNodes(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(context.MatchingNodes), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document)) test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
} }
func resultsToString(results *list.List) []string { func resultsToString(results *list.List) []string {
@@ -109,7 +110,7 @@ func formatYaml(yaml string, filename string) string {
var output bytes.Buffer var output bytes.Buffer
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true) printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
node, err := NewExpressionParser().ParseExpression(".. style= \"\"") node, err := treeCreator.ParsePath(".. style= \"\"")
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -218,7 +219,7 @@ func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formatt
var err error var err error
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true) printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
node, err := NewExpressionParser().ParseExpression(s.expression) node, err := treeCreator.ParsePath(s.expression)
if err != nil { if err != nil {
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err)) t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
return return
@@ -244,19 +245,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", Kind: yaml.ScalarNode}, Node: &yaml.Node{Tag: "!!null"},
FileIndex: 0, FileIndex: 0,
} }
inputs.PushBack(candidateNode) inputs.PushBack(candidateNode)
} }
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node) results, err := treeNavigator.GetMatchingNodes(inputs, node)
if err != nil { if err != nil {
t.Error(err, s.expression) t.Error(err, s.expression)
} }
err = printer.PrintResults(context.MatchingNodes) err = printer.PrintResults(results)
if err != nil { if err != nil {
t.Error(err, s.expression) t.Error(err, s.expression)
} }

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