mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a55869745 | ||
|
|
15e18bb98b | ||
|
|
7798a141cf | ||
|
|
b7583a538d | ||
|
|
529e88b630 | ||
|
|
658006eb93 | ||
|
|
2743a7e058 | ||
|
|
f95862fba3 | ||
|
|
3f58c4bc38 | ||
|
|
a7975df7cd | ||
|
|
3d5a5e0360 | ||
|
|
155755ae2f | ||
|
|
601c13531c | ||
|
|
69d00c89df | ||
|
|
2faff7b05f | ||
|
|
165949041d | ||
|
|
dbd7ab0f13 | ||
|
|
6d512ad718 | ||
|
|
4fef4a7ab1 | ||
|
|
dcb17b51a9 | ||
|
|
385417556d |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
github: mikefarah
|
||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -40,7 +40,17 @@ jobs:
|
||||
IMAGE_NAME: mikefarah/yq
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get latest release tag
|
||||
uses: oprypin/find-latest-tag@v1
|
||||
with:
|
||||
repository: mikefarah/yq # The repository to scan.
|
||||
releases-only: true # We know that all relevant tags have a GitHub release for them.
|
||||
id: yq
|
||||
|
||||
- name: Clone source code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ steps.yq.outputs.tag }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
@@ -90,7 +90,7 @@ docker run --rm -v "${PWD}":/workdir mikefarah/yq <command> [flags] [expression
|
||||
#### Run commands interactively:
|
||||
|
||||
```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:
|
||||
@@ -103,7 +103,7 @@ yq() {
|
||||
|
||||
### Go Get:
|
||||
```
|
||||
GO111MODULE=on go get github.com/mikefarah/yq/v4
|
||||
GO111MODULE=on go get github.com/mikefarah/yq
|
||||
```
|
||||
|
||||
## Community Supported Installation methods
|
||||
|
||||
@@ -42,21 +42,14 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
||||
colorsEnabled = true
|
||||
}
|
||||
|
||||
firstFileIndex := -1
|
||||
if !nullInput && len(args) == 1 {
|
||||
firstFileIndex = 0
|
||||
} else if len(args) > 1 {
|
||||
firstFileIndex = 1
|
||||
}
|
||||
|
||||
if writeInplace && (firstFileIndex == -1) {
|
||||
if writeInplace && len(args) < 2 {
|
||||
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
||||
}
|
||||
|
||||
if writeInplace {
|
||||
// only use colors if its forced
|
||||
colorsEnabled = forceColor
|
||||
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[firstFileIndex])
|
||||
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
|
||||
out, err = writeInPlaceHandler.CreateTempFile()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -62,21 +62,14 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||
colorsEnabled = true
|
||||
}
|
||||
|
||||
firstFileIndex := -1
|
||||
if !nullInput && len(args) == 1 {
|
||||
firstFileIndex = 0
|
||||
} else if len(args) > 1 {
|
||||
firstFileIndex = 1
|
||||
}
|
||||
|
||||
if writeInplace && (firstFileIndex == -1) {
|
||||
if writeInplace && len(args) < 2 {
|
||||
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
||||
}
|
||||
|
||||
if writeInplace {
|
||||
// only use colors if its forced
|
||||
colorsEnabled = forceColor
|
||||
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[firstFileIndex])
|
||||
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
|
||||
out, err = writeInPlaceHandler.CreateTempFile()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -109,7 +102,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||
err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = streamEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
|
||||
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
}
|
||||
completedSuccessfully = err == nil
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "4.6.1"
|
||||
Version = "4.3.0"
|
||||
|
||||
// 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
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
a:
|
||||
key1: "value1"
|
||||
key2: 2.6
|
||||
ab:
|
||||
key1: 6
|
||||
key2: "h"
|
||||
a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mikefarah/yq:4.6.1
|
||||
FROM mikefarah/yq:4.3.0
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
||||
8
go.mod
8
go.mod
@@ -1,14 +1,14 @@
|
||||
module github.com/mikefarah/yq/v4
|
||||
|
||||
require (
|
||||
github.com/elliotchance/orderedmap v1.4.0
|
||||
github.com/elliotchance/orderedmap v1.3.0
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/goccy/go-yaml v1.8.8
|
||||
github.com/jinzhu/copier v0.2.3
|
||||
github.com/goccy/go-yaml v1.8.4
|
||||
github.com/jinzhu/copier v0.1.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/timtadh/data-structures v0.5.3 // indirect
|
||||
github.com/timtadh/lexmachine v0.2.2
|
||||
golang.org/x/sys v0.0.0-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/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
20
go.sum
20
go.sum
@@ -36,8 +36,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A=
|
||||
github.com/elliotchance/orderedmap v1.4.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
|
||||
github.com/elliotchance/orderedmap v1.3.0 h1:k6m77/d0zCXTjsk12nX40TkEBkSICq8T4s6R6bpCqU0=
|
||||
github.com/elliotchance/orderedmap v1.3.0/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
@@ -52,8 +52,8 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-yaml v1.8.8 h1:MGfRB1GeSn/hWXYWS2Pt67iC2GJNnebdIro01ddyucA=
|
||||
github.com/goccy/go-yaml v1.8.8/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M=
|
||||
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -101,8 +101,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jinzhu/copier v0.2.3 h1:Oe09ju+9qft7TffZ7l/04AB2f8u1+V4ZMxmp/nnqeOs=
|
||||
github.com/jinzhu/copier v0.2.3/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||
github.com/jinzhu/copier v0.1.0 h1:Vh8xALtH3rrKGB/XIRe5d0yCTHPZFauWPLvdpDAbi88=
|
||||
github.com/jinzhu/copier v0.1.0/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@@ -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.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/timtadh/data-structures v0.5.3 h1:F2tEjoG9qWIyUjbvXVgJqEOGJPMIiYn7U5W5mE+i/vQ=
|
||||
github.com/timtadh/data-structures v0.5.3/go.mod h1:9R4XODhJ8JdWFEI8P/HJKqxuJctfBQw6fDibMQny2oU=
|
||||
@@ -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-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-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@@ -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-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
@@ -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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -1,54 +1,29 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
import "container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// A yaml expression evaluator that runs the expression once against all files/nodes in memory.
|
||||
/**
|
||||
Loads all yaml documents of all files given into memory, then runs the given expression once.
|
||||
**/
|
||||
type Evaluator interface {
|
||||
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 {
|
||||
treeNavigator DataTreeNavigator
|
||||
treeCreator ExpressionParser
|
||||
treeCreator PathTreeCreator
|
||||
}
|
||||
|
||||
func NewAllAtOnceEvaluator() Evaluator {
|
||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewExpressionParser()}
|
||||
}
|
||||
|
||||
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
|
||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
||||
}
|
||||
|
||||
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||
fileIndex := 0
|
||||
|
||||
node, err := treeCreator.ParsePath(expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var allDocuments *list.List = list.New()
|
||||
for _, filename := range filenames {
|
||||
reader, err := readStream(filename)
|
||||
@@ -62,7 +37,7 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
|
||||
allDocuments.PushBackList(fileDocuments)
|
||||
fileIndex = fileIndex + 1
|
||||
}
|
||||
matches, err := e.EvaluateCandidateNodes(expression, allDocuments)
|
||||
matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
@@ -13,37 +15,13 @@ type CandidateNode struct {
|
||||
Document uint // the document index of this node
|
||||
Filename string
|
||||
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 {
|
||||
keyPrefix := ""
|
||||
if n.IsMapKey {
|
||||
keyPrefix = "key-"
|
||||
}
|
||||
return fmt.Sprintf("%v%v - %v", keyPrefix, n.Document, n.Path)
|
||||
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
||||
}
|
||||
|
||||
func (n *CandidateNode) CreateChild(path interface{}, node *yaml.Node) *CandidateNode {
|
||||
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!
|
||||
newPath := make([]interface{}, len(n.Path)+1)
|
||||
copy(newPath, n.Path)
|
||||
@@ -66,10 +44,10 @@ func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
|
||||
n.UpdateAttributesFrom(other)
|
||||
n.Node.Content = other.Node.Content
|
||||
n.Node.Value = other.Node.Value
|
||||
n.Node.Alias = other.Node.Alias
|
||||
}
|
||||
|
||||
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
||||
log.Debug("UpdateAttributesFrom: n: %v other: %v", n.GetKey(), other.GetKey())
|
||||
if n.Node.Kind != other.Node.Kind {
|
||||
// clear out the contents when switching to a different type
|
||||
// e.g. map to array
|
||||
@@ -78,8 +56,6 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
||||
}
|
||||
n.Node.Kind = other.Node.Kind
|
||||
n.Node.Tag = other.Node.Tag
|
||||
n.Node.Alias = other.Node.Alias
|
||||
n.Node.Anchor = other.Node.Anchor
|
||||
|
||||
// merge will pickup the style of the new thing
|
||||
// when autocreating nodes
|
||||
@@ -90,3 +66,46 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode) {
|
||||
n.Node.HeadComment = n.Node.HeadComment + other.Node.HeadComment
|
||||
n.Node.LineComment = n.Node.LineComment + other.Node.LineComment
|
||||
}
|
||||
|
||||
func (n *CandidateNode) PathStackToString() string {
|
||||
return mergePathStackToString(n.Path)
|
||||
}
|
||||
|
||||
func mergePathStackToString(pathStack []interface{}) string {
|
||||
var sb strings.Builder
|
||||
for index, path := range pathStack {
|
||||
switch path.(type) {
|
||||
case int, int64:
|
||||
// if arrayMergeStrategy == AppendArrayMergeStrategy {
|
||||
// sb.WriteString("[+]")
|
||||
// } else {
|
||||
sb.WriteString(fmt.Sprintf("[%v]", path))
|
||||
// }
|
||||
|
||||
default:
|
||||
s := fmt.Sprintf("%v", path)
|
||||
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
|
||||
|
||||
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
|
||||
hasDoubleQuotes := strings.Contains(s, "\"")
|
||||
wrappingCharacterStart := "\""
|
||||
wrappingCharacterEnd := "\""
|
||||
if hasDoubleQuotes {
|
||||
wrappingCharacterStart = "("
|
||||
wrappingCharacterEnd = ")"
|
||||
}
|
||||
if hasSpecial || errParsingInt == nil {
|
||||
sb.WriteString(wrappingCharacterStart)
|
||||
}
|
||||
sb.WriteString(s)
|
||||
if hasSpecial || errParsingInt == nil {
|
||||
sb.WriteString(wrappingCharacterEnd)
|
||||
}
|
||||
}
|
||||
|
||||
if index < len(pathStack)-1 {
|
||||
sb.WriteString(".")
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ func format(attr color.Attribute) string {
|
||||
return fmt.Sprintf("%s[%dm", escape, attr)
|
||||
}
|
||||
|
||||
func colorizeAndPrint(yamlBytes []byte, writer io.Writer) error {
|
||||
tokens := lexer.Tokenize(string(yamlBytes))
|
||||
func ColorizeAndPrint(bytes []byte, writer io.Writer) error {
|
||||
tokens := lexer.Tokenize(string(bytes))
|
||||
var p printer.Printer
|
||||
p.Bool = func() *printer.Property {
|
||||
return &printer.Property{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -3,14 +3,16 @@ package yqlib
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"container/list"
|
||||
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
type DataTreeNavigator interface {
|
||||
// given the context and a expressionNode,
|
||||
// this will process the against the given expressionNode and return
|
||||
// a new context of matching candidates
|
||||
GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error)
|
||||
// given a list of CandidateEntities and a pathNode,
|
||||
// this will process the list against the given pathNode and return
|
||||
// a new list of matching candidates
|
||||
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
|
||||
}
|
||||
|
||||
type dataTreeNavigator struct {
|
||||
@@ -20,22 +22,22 @@ func NewDataTreeNavigator() DataTreeNavigator {
|
||||
return &dataTreeNavigator{}
|
||||
}
|
||||
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
if expressionNode == nil {
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
if pathNode == nil {
|
||||
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) {
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
log.Debug(NodeToString(el.Value.(*CandidateNode)))
|
||||
}
|
||||
}
|
||||
log.Debug(">>")
|
||||
handler := expressionNode.Operation.OperationType.Handler
|
||||
handler := pathNode.Operation.OperationType.Handler
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Add behaves differently according to the type of the LHS:
|
||||
- arrays: concatenate
|
||||
- number scalars: arithmetic addition
|
||||
- string scalars: concatenate
|
||||
- number scalars: arithmetic addition (soon)
|
||||
- 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
|
||||
Given a sample.yml file of:
|
||||
@@ -67,19 +67,23 @@ will output
|
||||
- 2
|
||||
```
|
||||
|
||||
## Add new object to array
|
||||
## Add object to array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- dog: woof
|
||||
- 1
|
||||
- 2
|
||||
c:
|
||||
cat: meow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a + {"cat": "meow"}' sample.yml
|
||||
yq eval '.a + .c' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- dog: woof
|
||||
- 1
|
||||
- 2
|
||||
- cat: meow
|
||||
```
|
||||
|
||||
@@ -127,97 +131,3 @@ b:
|
||||
- 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
|
||||
```
|
||||
|
||||
|
||||
@@ -78,22 +78,17 @@ b: dog # leave this
|
||||
```
|
||||
|
||||
## Remove all comments
|
||||
Note the use of `...` to ensure key nodes are included.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat # comment
|
||||
# great
|
||||
b: # key comment
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '... comments=""' sample.yml
|
||||
yq eval '.. comments=""' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat
|
||||
b:
|
||||
```
|
||||
|
||||
## Get line comment
|
||||
|
||||
@@ -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
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
This operator is used to handle environment variables usage in path expressions. While environment variables can, of course, be passed in via your CLI with string interpolation, this often comes with complex quote escaping and can be tricky to write and read. Note that there are two forms, `env` which will parse the environment variable as a yaml (be it a map, array, string, number of boolean) and `strenv` which will always parse the argument as a string.
|
||||
|
||||
|
||||
## Read string environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="cat meow" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat meow
|
||||
```
|
||||
|
||||
## Read boolean environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="true" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: true
|
||||
```
|
||||
|
||||
## Read numeric environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="12" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 12
|
||||
```
|
||||
|
||||
## Read yaml environment variable
|
||||
Running
|
||||
```bash
|
||||
myenv="{b: fish}" yq eval --null-input '.a = env(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: {b: fish}
|
||||
```
|
||||
|
||||
## Read boolean environment variable as a string
|
||||
Running
|
||||
```bash
|
||||
myenv="true" yq eval --null-input '.a = strenv(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: "true"
|
||||
```
|
||||
|
||||
## Read numeric environment variable as a string
|
||||
Running
|
||||
```bash
|
||||
myenv="12" yq eval --null-input '.a = strenv(myenv)'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: "12"
|
||||
```
|
||||
|
||||
## Dynamic key lookup with environment variable
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cat: meow
|
||||
dog: woof
|
||||
```
|
||||
then
|
||||
```bash
|
||||
myenv="cat" yq eval '.[env(myenv)]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
meow
|
||||
```
|
||||
|
||||
@@ -29,24 +29,6 @@ true
|
||||
false
|
||||
```
|
||||
|
||||
## Don't match string
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- cat
|
||||
- goat
|
||||
- dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] | (. != "*at")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
false
|
||||
true
|
||||
```
|
||||
|
||||
## Match number
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -65,24 +47,6 @@ true
|
||||
false
|
||||
```
|
||||
|
||||
## Dont match number
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] | (. != 4)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
true
|
||||
false
|
||||
true
|
||||
```
|
||||
|
||||
## Match nulls
|
||||
Running
|
||||
```bash
|
||||
|
||||
@@ -35,26 +35,6 @@ will output
|
||||
0
|
||||
```
|
||||
|
||||
## Get file indices of multiple documents
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
And another sample another.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval-all 'fileIndex' sample.yml another.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
0
|
||||
---
|
||||
1
|
||||
```
|
||||
|
||||
## Get file index alias
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -16,20 +16,6 @@ will output
|
||||
3
|
||||
```
|
||||
|
||||
## null length
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: null
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a | length' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
0
|
||||
```
|
||||
|
||||
## Map length
|
||||
returns number of entries
|
||||
|
||||
|
||||
@@ -1,34 +1,18 @@
|
||||
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
|
||||
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
||||
|
||||
## Objects and arrays - merging
|
||||
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
|
||||
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.
|
||||
|
||||
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||
|
||||
### Merge Flags
|
||||
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
|
||||
|
||||
- `+` to append arrays
|
||||
- `?` to only merge existing fields
|
||||
- `d` to deeply merge arrays
|
||||
|
||||
### Merging files
|
||||
Note the use of `eval-all` to ensure all documents are loaded into memory.
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||
```
|
||||
|
||||
## Multiply integers
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '3 * 4'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
12
|
||||
```
|
||||
|
||||
## Merge objects together, returning merged result only
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -86,6 +70,7 @@ Given a sample.yml file of:
|
||||
a: {things: great}
|
||||
b:
|
||||
also: "me"
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@@ -126,26 +111,6 @@ b:
|
||||
- 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
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -178,59 +143,6 @@ array:
|
||||
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, deeply merging arrays
|
||||
Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- name: fred
|
||||
age: 12
|
||||
- name: bob
|
||||
age: 32
|
||||
b:
|
||||
- name: fred
|
||||
age: 34
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a *d .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- name: fred
|
||||
age: 34
|
||||
- name: bob
|
||||
age: 32
|
||||
```
|
||||
|
||||
## Merge to prefix an element
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -268,7 +180,7 @@ g: thongs
|
||||
f: *cat
|
||||
```
|
||||
|
||||
## Merge copies anchor names
|
||||
## Merge does not copy anchor names
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -285,7 +197,7 @@ yq eval '.c * .a' sample.yml
|
||||
will output
|
||||
```yaml
|
||||
g: thongs
|
||||
c: &cat frog
|
||||
c: frog
|
||||
```
|
||||
|
||||
## Merge with merge anchors
|
||||
|
||||
@@ -33,8 +33,6 @@ frog
|
||||
```
|
||||
|
||||
## Recursively find nodes with keys
|
||||
Note that this example has wrapped the expression in `[]` to show that there are two matches returned. You do not have to wrap in `[]` in your path expression.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -45,16 +43,16 @@ a:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[.. | select(has("name"))]' sample.yml
|
||||
yq eval '.. | select(has("name"))' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- name: frog
|
||||
b:
|
||||
name: blog
|
||||
age: 12
|
||||
- name: blog
|
||||
name: frog
|
||||
b:
|
||||
name: blog
|
||||
age: 12
|
||||
name: blog
|
||||
age: 12
|
||||
```
|
||||
|
||||
## Recursively find nodes with values
|
||||
@@ -1,76 +0,0 @@
|
||||
Reduce is a powerful way to process a collection of data into a new form.
|
||||
|
||||
```
|
||||
<exp> as $<name> ireduce (<init>; <block>)
|
||||
```
|
||||
|
||||
e.g.
|
||||
|
||||
```
|
||||
.[] as $item ireduce (0; . + $item)
|
||||
```
|
||||
|
||||
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
|
||||
|
||||
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
|
||||
|
||||
## yq vs jq syntax
|
||||
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
|
||||
|
||||
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.
|
||||
|
||||
|
||||
## Sum numbers
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 10
|
||||
- 2
|
||||
- 5
|
||||
- 3
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] as $item ireduce (0; . + $item)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
20
|
||||
```
|
||||
|
||||
## Merge all yaml files together
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
And another sample another.yml file of:
|
||||
```yaml
|
||||
b: dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval-all '. as $item ireduce ({}; . * $item )' sample.yml another.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat
|
||||
b: dog
|
||||
```
|
||||
|
||||
## Convert an array to an object
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- name: Cathy
|
||||
has: apples
|
||||
- name: Bob
|
||||
has: bananas
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
Cathy: apples
|
||||
Bob: bananas
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -33,7 +33,7 @@ c: banana
|
||||
```
|
||||
|
||||
## Special characters
|
||||
Use quotes with brackets around path elements with special characters
|
||||
Use quotes around path elements with special characters
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -41,47 +41,13 @@ Given a sample.yml file of:
|
||||
```
|
||||
then
|
||||
```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
|
||||
```yaml
|
||||
frog
|
||||
```
|
||||
|
||||
## Dynamic keys
|
||||
Expressions within [] can be used to dynamically lookup / calculate keys
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
b: apple
|
||||
apple: crispy yum
|
||||
banana: soft yum
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[.b]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
crispy yum
|
||||
```
|
||||
|
||||
## Children don't exist
|
||||
Nodes are added dynamically while traversing
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Add behaves differently according to the type of the LHS:
|
||||
- arrays: concatenate
|
||||
- number scalars: arithmetic addition
|
||||
- string scalars: concatenate
|
||||
- number scalars: arithmetic addition (soon)
|
||||
- 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`.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Keys
|
||||
|
||||
Use the `keys` operator to return map keys or array indices.
|
||||
@@ -1,19 +1,13 @@
|
||||
Like the multiple operator in jq, depending on the operands, this multiply operator will do different things. Currently numbers, arrays and objects are supported.
|
||||
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
|
||||
|
||||
## Objects and arrays - merging
|
||||
Objects are merged deeply matching on matching keys. By default, array values override and are not deeply merged.
|
||||
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.
|
||||
|
||||
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||
|
||||
### Merge Flags
|
||||
You can control how objects are merged by using one or more of the following flags. Multiple flags can be used together, e.g. `.a *+? .b`. See examples below
|
||||
|
||||
- `+` to append arrays
|
||||
- `?` to only merge existing fields
|
||||
- `d` to deeply merge arrays
|
||||
|
||||
### Merging files
|
||||
Note the use of `eval-all` to ensure all documents are loaded into memory.
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
Reduce is a powerful way to process a collection of data into a new form.
|
||||
|
||||
```
|
||||
<exp> as $<name> ireduce (<init>; <block>)
|
||||
```
|
||||
|
||||
e.g.
|
||||
|
||||
```
|
||||
.[] as $item ireduce (0; . + $item)
|
||||
```
|
||||
|
||||
On the LHS we are configuring the collection of items that will be reduced `<exp>` as well as what each element will be called `$<name>`. Note that the array has been splatted into its individual elements.
|
||||
|
||||
On the RHS there is `<init>`, the starting value of the accumulator and `<block>`, the expression that will update the accumulator for each element in the collection. Note that within the block expression, `.` will evaluate to the current value of the accumulator.
|
||||
|
||||
## yq vs jq syntax
|
||||
Reduce syntax in `yq` is a little different from `jq` - as `yq` (currently) isn't as sophisticated as `jq` and its only supports infix notation (e.g. a + b, where the operator is in the middle of the two parameters) - where as `jq` uses a mix of infix notation with _prefix_ notation (e.g. `reduce a b` is like writing `+ a b`).
|
||||
|
||||
To that end, the reduce operator is called `ireduce` for backwards compatability if a `jq` like prefix version of `reduce` is ever added.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Split into Documents
|
||||
|
||||
This operator splits all matches into separate documents
|
||||
@@ -1 +0,0 @@
|
||||
# String Operators
|
||||
@@ -1 +0,0 @@
|
||||
For more complex scenarios, variables can be used to hold values of expression to be used in other expressions.
|
||||
@@ -50,7 +50,7 @@ func (ye *yamlEncoder) Encode(node *yaml.Node) error {
|
||||
}
|
||||
|
||||
if ye.colorise {
|
||||
return colorizeAndPrint(tempBuffer.Bytes(), ye.destination)
|
||||
return ColorizeAndPrint(tempBuffer.Bytes(), ye.destination)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -76,8 +76,6 @@ func mapKeysToStrings(node *yaml.Node) {
|
||||
|
||||
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
||||
var encoder = json.NewEncoder(destination)
|
||||
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
||||
|
||||
var indentString = ""
|
||||
|
||||
for index := 0; index < indent; index++ {
|
||||
@@ -155,15 +153,11 @@ func (o *orderedMap) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
|
||||
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
||||
if o.kv == nil {
|
||||
return json.Marshal(o.altVal)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
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('{')
|
||||
for idx, el := range o.kv {
|
||||
if err := enc.Encode(el.K); err != nil {
|
||||
|
||||
@@ -9,11 +9,29 @@ import (
|
||||
"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
|
||||
writer := bufio.NewWriter(&output)
|
||||
|
||||
var jsonEncoder = NewJsonEncoder(writer, indent)
|
||||
var jsonEncoder = NewJsonEncoder(writer, 2)
|
||||
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -24,33 +42,6 @@ func yamlToJson(sampleYaml string, indent int) string {
|
||||
panic(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,439 +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}
|
||||
}
|
||||
if strings.Contains(options, "d") {
|
||||
prefs.DeepMergeArrays = true
|
||||
}
|
||||
op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: prefs}
|
||||
return &token{TokenType: operationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
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:]
|
||||
|
||||
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(`\*[\+|\?d]*`), 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.CompileNFA()
|
||||
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
|
||||
}
|
||||
122
pkg/yqlib/lib.go
122
pkg/yqlib/lib.go
@@ -1,5 +1,3 @@
|
||||
// Use the top level Evaluator or StreamEvaluator to evaluate expressions and return matches.
|
||||
//
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -13,95 +11,85 @@ import (
|
||||
|
||||
var log = logging.MustGetLogger("yq-lib")
|
||||
|
||||
type operationType struct {
|
||||
type OperationType struct {
|
||||
Type string
|
||||
NumArgs uint // number of arguments to the op
|
||||
Precedence uint
|
||||
Handler operatorHandler
|
||||
Handler OperatorHandler
|
||||
}
|
||||
|
||||
// operators TODO:
|
||||
// - keys operator for controlling key metadata (particularly anchors/aliases)
|
||||
// - 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 andOpType = &operationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: andOperator}
|
||||
var reduceOpType = &operationType{Type: "REDUCE", NumArgs: 2, Precedence: 35, Handler: reduceOperator}
|
||||
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
||||
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
||||
|
||||
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 addAssignOpType = &operationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: addAssignOperator}
|
||||
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
||||
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 assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
|
||||
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator}
|
||||
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
|
||||
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
|
||||
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
|
||||
var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precedence: 40, Handler: assignAliasOperator}
|
||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
|
||||
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
|
||||
var Alternative = &OperationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 45, Handler: AlternativeOperator}
|
||||
|
||||
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
|
||||
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
|
||||
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
|
||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||
|
||||
var equalsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: equalsOperator}
|
||||
var notEqualsOpType = &operationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: notEqualsOperator}
|
||||
var createMapOpType = &operationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: createMapOperator}
|
||||
var ShortPipe = &OperationType{Type: "SHORT_PIPE", NumArgs: 2, Precedence: 45, Handler: PipeOperator}
|
||||
|
||||
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 collectOpType = &operationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: collectOperator}
|
||||
var splitDocumentOpType = &operationType{Type: "SPLIT_DOC", NumArgs: 0, Precedence: 50, Handler: splitDocumentOperator}
|
||||
var getVariableOpType = &operationType{Type: "GET_VARIABLE", NumArgs: 0, Precedence: 55, Handler: getVariableOperator}
|
||||
var getStyleOpType = &operationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: getStyleOperator}
|
||||
var getTagOpType = &operationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: getTagOperator}
|
||||
var getCommentOpType = &operationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: getCommentsOperator}
|
||||
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 Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
||||
var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator}
|
||||
|
||||
var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator}
|
||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
||||
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
|
||||
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
||||
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||
var TraverseArray = &OperationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: TraverseArrayOperator}
|
||||
|
||||
var 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 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 traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 55, Handler: traversePathOperator}
|
||||
var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Precedence: 50, Handler: traverseArrayOperator}
|
||||
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
|
||||
|
||||
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator}
|
||||
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
|
||||
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
|
||||
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
|
||||
var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
|
||||
|
||||
var 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}
|
||||
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
|
||||
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
||||
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
||||
var DeleteImmediateChild = &OperationType{Type: "DELETE_IMMEDIATE_CHILD", NumArgs: 1, Precedence: 40, Handler: DeleteImmediateChildOperator}
|
||||
|
||||
type Operation struct {
|
||||
OperationType *operationType
|
||||
OperationType *OperationType
|
||||
Value interface{}
|
||||
StringValue string
|
||||
CandidateNode *CandidateNode // used for Value Path elements
|
||||
Preferences interface{}
|
||||
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs
|
||||
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}
|
||||
node.Value = stringValue
|
||||
|
||||
@@ -119,7 +107,7 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
}
|
||||
|
||||
return &Operation{
|
||||
OperationType: valueOpType,
|
||||
OperationType: ValueOp,
|
||||
Value: value,
|
||||
StringValue: stringValue,
|
||||
CandidateNode: &CandidateNode{Node: &node},
|
||||
@@ -128,11 +116,13 @@ func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
|
||||
// debugging purposes only
|
||||
func (p *Operation) toString() string {
|
||||
if p.OperationType == traversePathOpType {
|
||||
if p.OperationType == TraversePath {
|
||||
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"
|
||||
} else if p.OperationType == valueOpType {
|
||||
} else if p.OperationType == ValueOp {
|
||||
return fmt.Sprintf("%v (%T)", p.Value, p.Value)
|
||||
} else {
|
||||
return fmt.Sprintf("%v", p.OperationType.Type)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package yqlib
|
||||
|
||||
func matchKey(name string, pattern string) (matched bool) {
|
||||
func Match(name string, pattern string) (matched bool) {
|
||||
if pattern == "" {
|
||||
return name == pattern
|
||||
}
|
||||
|
||||
@@ -3,23 +3,23 @@ package yqlib
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"strconv"
|
||||
"container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
|
||||
return &ExpressionNode{Operation: &Operation{OperationType: addOpType},
|
||||
Lhs: lhs,
|
||||
func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
|
||||
return &PathTreeNode{Operation: &Operation{OperationType: Add},
|
||||
Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}},
|
||||
Rhs: rhs}
|
||||
}
|
||||
|
||||
func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
assignmentOp := &Operation{OperationType: assignOpType}
|
||||
assignmentOp.UpdateAssign = false
|
||||
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
assignmentOp := &Operation{OperationType: Assign}
|
||||
assignmentOp.UpdateAssign = true
|
||||
|
||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(expressionNode.Lhs, expressionNode.Rhs)}
|
||||
return d.GetMatchingNodes(context, assignmentOpNode)
|
||||
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
|
||||
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
|
||||
}
|
||||
|
||||
func toNodes(candidate *CandidateNode) []*yaml.Node {
|
||||
@@ -36,23 +36,23 @@ 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")
|
||||
|
||||
return crossFunction(d, context, expressionNode, add)
|
||||
return crossFunction(d, matchingNodes, pathNode, add)
|
||||
}
|
||||
|
||||
func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
func add(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
|
||||
lhsNode := lhs.Node
|
||||
|
||||
if lhsNode.Tag == "!!null" {
|
||||
return lhs.CreateChild(nil, rhs.Node), nil
|
||||
target := &CandidateNode{
|
||||
Path: lhs.Path,
|
||||
Document: lhs.Document,
|
||||
Filename: lhs.Filename,
|
||||
Node: &yaml.Node{},
|
||||
}
|
||||
|
||||
target := lhs.CreateChild(nil, &yaml.Node{})
|
||||
lhsNode := lhs.Node
|
||||
|
||||
switch lhsNode.Kind {
|
||||
case yaml.MappingNode:
|
||||
@@ -63,48 +63,7 @@ func add(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *Candida
|
||||
target.Node.Tag = "!!seq"
|
||||
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||
case yaml.ScalarNode:
|
||||
if rhs.Node.Kind != yaml.ScalarNode {
|
||||
return nil, fmt.Errorf("%v (%v) cannot be added to a %v", rhs.Node.Tag, rhs.Path, lhsNode.Tag)
|
||||
}
|
||||
target.Node.Kind = yaml.ScalarNode
|
||||
target.Node.Style = lhsNode.Style
|
||||
return 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 nil, fmt.Errorf("Scalars not yet supported for addition")
|
||||
}
|
||||
|
||||
return target, nil
|
||||
|
||||
@@ -5,15 +5,6 @@ import (
|
||||
)
|
||||
|
||||
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",
|
||||
document: `{a: {val: thing, b: [cat,dog]}}`,
|
||||
@@ -47,11 +38,11 @@ var addOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Add new object to array",
|
||||
document: `a: [{dog: woof}]`,
|
||||
expression: `.a + {"cat": "meow"}`,
|
||||
description: "Add object to array",
|
||||
document: `{a: [1,2], c: {cat: meow}}`,
|
||||
expression: `.a + .c`,
|
||||
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",
|
||||
},
|
||||
},
|
||||
{
|
||||
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) {
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
// corssFunction no matches
|
||||
// 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")
|
||||
return crossFunction(d, context, expressionNode, alternativeFunc)
|
||||
return crossFunction(d, matchingNodes, pathNode, alternativeFunc)
|
||||
}
|
||||
|
||||
func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
func alternativeFunc(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
|
||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||
|
||||
|
||||
@@ -6,146 +6,146 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignAlias operator!")
|
||||
|
||||
aliasName := ""
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
if rhs.Front() != nil {
|
||||
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting aliasName : %v", candidate.GetKey())
|
||||
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
aliasName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
if rhs.Front() != nil {
|
||||
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
candidate.Node.Kind = yaml.AliasNode
|
||||
candidate.Node.Value = aliasName
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func getAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func GetAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetAlias operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Value, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
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!")
|
||||
|
||||
anchorName := ""
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
if rhs.Front() != nil {
|
||||
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting anchorName of : %v", candidate.GetKey())
|
||||
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
anchorName = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
if rhs.Front() != nil {
|
||||
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
candidate.Node.Anchor = anchorName
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func getAnchorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func GetAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetAnchor operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
anchor := candidate.Node.Anchor
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: anchor, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
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")
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
for childEl := rhs.MatchingNodes.Front(); childEl != nil; childEl = childEl.Next() {
|
||||
err = explodeNode(childEl.Value.(*CandidateNode).Node, context)
|
||||
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
|
||||
err = explodeNode(childEl.Value.(*CandidateNode).Node)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return context, nil
|
||||
return matchMap, nil
|
||||
}
|
||||
|
||||
func explodeNode(node *yaml.Node, context Context) error {
|
||||
func explodeNode(node *yaml.Node) error {
|
||||
node.Anchor = ""
|
||||
switch node.Kind {
|
||||
case yaml.SequenceNode, yaml.DocumentNode:
|
||||
for index, contentNode := range node.Content {
|
||||
log.Debugf("exploding index %v", index)
|
||||
errorInContent := explodeNode(contentNode, context)
|
||||
errorInContent := explodeNode(contentNode)
|
||||
if errorInContent != nil {
|
||||
return errorInContent
|
||||
}
|
||||
@@ -169,7 +169,7 @@ func explodeNode(node *yaml.Node, context Context) error {
|
||||
valueNode := node.Content[index+1]
|
||||
log.Debugf("traversing %v", keyNode.Value)
|
||||
if keyNode.Value != "<<" {
|
||||
err := overrideEntry(node, keyNode, valueNode, index, context.ChildContext(newContent))
|
||||
err := overrideEntry(node, keyNode, valueNode, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,14 +178,14 @@ func explodeNode(node *yaml.Node, context Context) error {
|
||||
log.Debugf("an alias merge list!")
|
||||
for index := 0; index < len(valueNode.Content); index = index + 1 {
|
||||
aliasNode := valueNode.Content[index]
|
||||
err := applyAlias(node, aliasNode.Alias, index, context.ChildContext(newContent))
|
||||
err := applyAlias(node, aliasNode.Alias, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Debugf("an alias merge!")
|
||||
err := applyAlias(node, valueNode.Alias, index, context.ChildContext(newContent))
|
||||
err := applyAlias(node, valueNode.Alias, index, newContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -205,7 +205,7 @@ func explodeNode(node *yaml.Node, context Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent Context) error {
|
||||
func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent *list.List) error {
|
||||
if alias == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -221,15 +221,15 @@ func applyAlias(node *yaml.Node, alias *yaml.Node, aliasIndex int, newContent Co
|
||||
return nil
|
||||
}
|
||||
|
||||
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent Context) error {
|
||||
func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex int, newContent *list.List) error {
|
||||
|
||||
err := explodeNode(value, newContent)
|
||||
err := explodeNode(value)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for newEl := newContent.MatchingNodes.Front(); newEl != nil; newEl = newEl.Next() {
|
||||
for newEl := newContent.Front(); newEl != nil; newEl = newEl.Next() {
|
||||
valueEl := newEl.Next() // move forward twice
|
||||
keyNode := newEl.Value.(*yaml.Node)
|
||||
log.Debugf("checking new content %v:%v", keyNode.Value, valueEl.Value.(*yaml.Node).Value)
|
||||
@@ -250,12 +250,12 @@ func overrideEntry(node *yaml.Node, key *yaml.Node, value *yaml.Node, startIndex
|
||||
}
|
||||
}
|
||||
|
||||
err = explodeNode(key, newContent)
|
||||
err = explodeNode(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("adding %v:%v", key.Value, value.Value)
|
||||
newContent.MatchingNodes.PushBack(key)
|
||||
newContent.MatchingNodes.PushBack(value)
|
||||
newContent.PushBack(key)
|
||||
newContent.PushBack(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,61 +1,66 @@
|
||||
package yqlib
|
||||
|
||||
func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
import "container/list"
|
||||
|
||||
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
var rhs Context
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
var rhs *list.List
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
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)
|
||||
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// grab the first value
|
||||
first := rhs.MatchingNodes.Front()
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
rhsCandidate := first.Value.(*CandidateNode)
|
||||
rhsCandidate.Node = unwrapDoc(rhsCandidate.Node)
|
||||
rhsCandidate.Node = UnwrapDoc(rhsCandidate.Node)
|
||||
candidate.UpdateFrom(rhsCandidate)
|
||||
}
|
||||
}
|
||||
|
||||
return context, nil
|
||||
// // if there was nothing given, perhaps we are creating a new yaml doc
|
||||
// if matchingNodes.Len() == 0 {
|
||||
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
|
||||
// return lhs, nil
|
||||
// }
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
// does not update content or values
|
||||
func assignAttributesOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debug("getting lhs matching nodes for update")
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
func AssignAttributesOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// grab the first value
|
||||
first := rhs.MatchingNodes.Front()
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
candidate.UpdateAttributesFrom(first.Value.(*CandidateNode))
|
||||
}
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func isTruthy(c *CandidateNode) (bool, error) {
|
||||
node := unwrapDoc(c.Node)
|
||||
node := UnwrapDoc(c.Node)
|
||||
value := true
|
||||
|
||||
if node.Tag == "!!null" {
|
||||
@@ -25,10 +25,10 @@ func isTruthy(c *CandidateNode) (bool, error) {
|
||||
|
||||
type boolOp func(bool, bool) bool
|
||||
|
||||
func performBoolOp(op boolOp) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
func performBoolOp(op boolOp) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
|
||||
lhsTrue, errDecoding := isTruthy(lhs)
|
||||
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")
|
||||
return crossFunction(d, context, expressionNode, performBoolOp(
|
||||
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
|
||||
func(b1 bool, b2 bool) bool {
|
||||
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")
|
||||
return crossFunction(d, context, expressionNode, performBoolOp(
|
||||
return crossFunction(d, matchingNodes, pathNode, performBoolOp(
|
||||
func(b1 bool, b2 bool) bool {
|
||||
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")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debug("notOperation checking %v", candidate)
|
||||
truthy, errDecoding := isTruthy(candidate)
|
||||
if errDecoding != nil {
|
||||
return Context{}, errDecoding
|
||||
return nil, errDecoding
|
||||
}
|
||||
result := createBooleanCandidate(candidate, !truthy)
|
||||
results.PushBack(result)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -6,35 +6,34 @@ import (
|
||||
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")
|
||||
|
||||
if context.MatchingNodes.Len() == 0 {
|
||||
if matchMap.Len() == 0 {
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return context.SingleChildContext(candidate), nil
|
||||
return nodeToMap(candidate), nil
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
var collectC *CandidateNode
|
||||
if context.MatchingNodes.Front() != nil {
|
||||
collectC = context.MatchingNodes.Front().Value.(*CandidateNode).CreateChild(nil, node)
|
||||
if 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)
|
||||
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)
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
if context.MatchingNodes.Len() == 0 {
|
||||
if matchMap.Len() == 0 {
|
||||
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return context.SingleChildContext(candidate), nil
|
||||
return nodeToMap(candidate), nil
|
||||
}
|
||||
first := context.MatchingNodes.Front().Value.(*CandidateNode)
|
||||
first := matchMap.Front().Value.(*CandidateNode)
|
||||
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
||||
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
rotated[i] = list.New()
|
||||
}
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidateNode := el.Value.(*CandidateNode)
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
rotated[i].PushBack(candidateNode.CreateChild(i, candidateNode.Node.Content[i]))
|
||||
rotated[i].PushBack(createChildCandidate(candidateNode, i))
|
||||
}
|
||||
}
|
||||
|
||||
newObject := list.New()
|
||||
for i := 0; i < len(first.Node.Content); i++ {
|
||||
additions, err := collect(d, context.ChildContext(list.New()), rotated[i])
|
||||
additions, err := collect(d, list.New(), rotated[i])
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
newObject.PushBackList(additions.MatchingNodes)
|
||||
newObject.PushBackList(additions)
|
||||
}
|
||||
|
||||
return context.ChildContext(newObject), nil
|
||||
return newObject, nil
|
||||
|
||||
}
|
||||
|
||||
func collect(d *dataTreeNavigator, context Context, remainingMatches *list.List) (Context, error) {
|
||||
func 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 {
|
||||
return context, nil
|
||||
return aggregate, nil
|
||||
}
|
||||
|
||||
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
|
||||
|
||||
splatted, err := splat(d, context.SingleChildContext(candidate),
|
||||
traversePreferences{DontFollowAlias: true, IncludeMapKeys: false})
|
||||
splatted, err := Splat(d, nodeToMap(candidate),
|
||||
&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
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if context.MatchingNodes.Len() == 0 {
|
||||
if aggregate.Len() == 0 {
|
||||
return collect(d, splatted, remainingMatches)
|
||||
}
|
||||
|
||||
newAgg := list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := aggregate.Front(); el != nil; el = el.Next() {
|
||||
aggCandidate := el.Value.(*CandidateNode)
|
||||
for splatEl := splatted.MatchingNodes.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
|
||||
splatCandidate := splatEl.Value.(*CandidateNode)
|
||||
newCandidate, err := aggCandidate.Copy()
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newCandidate.Path = nil
|
||||
|
||||
newCandidate, err = multiply(multiplyPreferences{AppendArrays: false})(d, context, newCandidate, splatCandidate)
|
||||
newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
newAgg.PushBack(newCandidate)
|
||||
}
|
||||
}
|
||||
return collect(d, context.ChildContext(newAgg), remainingMatches)
|
||||
return collect(d, newAgg, remainingMatches)
|
||||
|
||||
}
|
||||
|
||||
@@ -13,15 +13,6 @@ var collectOperatorScenarios = []expressionScenario{
|
||||
"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,
|
||||
document: ``,
|
||||
|
||||
@@ -7,47 +7,47 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type commentOpPreferences struct {
|
||||
type CommentOpPreferences struct {
|
||||
LineComment bool
|
||||
HeadComment 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!")
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||
|
||||
comment := ""
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
if rhs.Front() != nil {
|
||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
comment = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
if rhs.Front() != nil {
|
||||
comment = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,15 +63,15 @@ func assignCommentsOperator(d *dataTreeNavigator, context Context, expressionNod
|
||||
}
|
||||
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
preferences := expressionNode.Operation.Preferences.(commentOpPreferences)
|
||||
func GetCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
|
||||
log.Debugf("GetComments operator!")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
comment := ""
|
||||
if preferences.LineComment {
|
||||
@@ -84,8 +84,8 @@ func getCommentsOperator(d *dataTreeNavigator, context Context, expressionNode *
|
||||
comment = strings.Replace(comment, "# ", "", 1)
|
||||
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: comment, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -71,12 +71,11 @@ var commentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
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\nb: # key comment",
|
||||
expression: `... comments=""`,
|
||||
description: "Remove all comments",
|
||||
document: "# hi\n\na: cat # comment\n\n# great\n",
|
||||
expression: `.. comments=""`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat\nb:\n",
|
||||
"D0, P[], (!!map)::a: cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func CreateMapOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- createMapOperation")
|
||||
|
||||
//each matchingNodes entry should turn into a sequence of keys to create.
|
||||
@@ -18,29 +18,29 @@ func createMapOperator(d *dataTreeNavigator, context Context, expressionNode *Ex
|
||||
|
||||
sequences := list.New()
|
||||
|
||||
if context.MatchingNodes.Len() > 0 {
|
||||
if matchingNodes.Len() > 0 {
|
||||
|
||||
for matchingNodeEl := context.MatchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
|
||||
for matchingNodeEl := matchingNodes.Front(); matchingNodeEl != nil; matchingNodeEl = matchingNodeEl.Next() {
|
||||
matchingNode := matchingNodeEl.Value.(*CandidateNode)
|
||||
sequenceNode, err := sequenceFor(d, context, matchingNode, expressionNode)
|
||||
sequenceNode, err := sequenceFor(d, matchingNode, pathNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
sequences.PushBack(sequenceNode)
|
||||
}
|
||||
} else {
|
||||
sequenceNode, err := sequenceFor(d, context, nil, expressionNode)
|
||||
sequenceNode, err := sequenceFor(d, nil, pathNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
sequences.PushBack(sequenceNode)
|
||||
}
|
||||
|
||||
return context.SingleChildContext(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
|
||||
return nodeToMap(&CandidateNode{Node: listToNodeSeq(sequences), Document: document, Path: path}), nil
|
||||
|
||||
}
|
||||
|
||||
func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, expressionNode *ExpressionNode) (*CandidateNode, error) {
|
||||
func sequenceFor(d *dataTreeNavigator, matchingNode *CandidateNode, pathNode *PathTreeNode) (*CandidateNode, error) {
|
||||
var path []interface{}
|
||||
var document uint = 0
|
||||
var matches = list.New()
|
||||
@@ -48,17 +48,17 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
|
||||
if matchingNode != nil {
|
||||
path = matchingNode.Path
|
||||
document = matchingNode.Document
|
||||
matches.PushBack(matchingNode)
|
||||
matches = nodeToMap(matchingNode)
|
||||
}
|
||||
|
||||
mapPairs, err := crossFunction(d, context.ChildContext(matches), expressionNode,
|
||||
func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
mapPairs, err := crossFunction(d, matches, pathNode,
|
||||
func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
node := yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||
log.Debugf("LHS:", NodeToString(lhs))
|
||||
log.Debugf("RHS:", NodeToString(rhs))
|
||||
node.Content = []*yaml.Node{
|
||||
unwrapDoc(lhs.Node),
|
||||
unwrapDoc(rhs.Node),
|
||||
UnwrapDoc(lhs.Node),
|
||||
UnwrapDoc(rhs.Node),
|
||||
}
|
||||
|
||||
return &CandidateNode{Node: &node, Document: document, Path: path}, nil
|
||||
@@ -67,7 +67,7 @@ func sequenceFor(d *dataTreeNavigator, context Context, matchingNode *CandidateN
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
innerList := listToNodeSeq(mapPairs.MatchingNodes)
|
||||
innerList := listToNodeSeq(mapPairs)
|
||||
innerList.Style = yaml.FlowStyle
|
||||
return &CandidateNode{Node: innerList, Document: document, Path: path}, nil
|
||||
}
|
||||
|
||||
@@ -1,72 +1,70 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
contextToUse := context.Clone()
|
||||
contextToUse.DontAutoCreate = true
|
||||
nodesToDelete, err := d.GetMatchingNodes(contextToUse, expressionNode.Rhs)
|
||||
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
nodesToDelete, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
//need to iterate backwards to ensure correct indices when deleting multiple
|
||||
for el := nodesToDelete.MatchingNodes.Back(); el != nil; el = el.Prev() {
|
||||
|
||||
for el := nodesToDelete.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
if len(candidate.Path) > 0 {
|
||||
deleteImmediateChildOp := &Operation{
|
||||
OperationType: deleteImmediateChildOpType,
|
||||
Value: candidate.Path[len(candidate.Path)-1],
|
||||
}
|
||||
deleteImmediateChildOp := &Operation{
|
||||
OperationType: DeleteImmediateChild,
|
||||
Value: candidate.Path[len(candidate.Path)-1],
|
||||
}
|
||||
|
||||
deleteImmediateChildOpNode := &ExpressionNode{
|
||||
Operation: deleteImmediateChildOp,
|
||||
Rhs: createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}, false),
|
||||
}
|
||||
deleteImmediateChildOpNode := &PathTreeNode{
|
||||
Operation: deleteImmediateChildOp,
|
||||
Rhs: createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]),
|
||||
}
|
||||
|
||||
_, err := d.GetMatchingNodes(contextToUse, deleteImmediateChildOpNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func deleteImmediateChildOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
parents, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
func DeleteImmediateChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
parents, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
childPath := expressionNode.Operation.Value
|
||||
childPath := pathNode.Operation.Value
|
||||
|
||||
log.Debug("childPath to remove %v", childPath)
|
||||
|
||||
for el := parents.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := parents.Front(); el != nil; el = el.Next() {
|
||||
parent := el.Value.(*CandidateNode)
|
||||
parentNode := unwrapDoc(parent.Node)
|
||||
parentNode := UnwrapDoc(parent.Node)
|
||||
if parentNode.Kind == yaml.MappingNode {
|
||||
deleteFromMap(parent, childPath)
|
||||
} else if parentNode.Kind == yaml.SequenceNode {
|
||||
deleteFromArray(parent, childPath)
|
||||
} else {
|
||||
return Context{}, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
|
||||
return nil, fmt.Errorf("Cannot delete nodes from parent of tag %v", parentNode.Tag)
|
||||
}
|
||||
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
||||
log.Debug("deleteFromMap")
|
||||
node := unwrapDoc(candidate.Node)
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
contents := node.Content
|
||||
newContents := make([]*yaml.Node, 0)
|
||||
|
||||
@@ -74,7 +72,11 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
||||
key := contents[index]
|
||||
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
|
||||
|
||||
@@ -89,7 +91,7 @@ func deleteFromMap(candidate *CandidateNode, childPath interface{}) {
|
||||
|
||||
func deleteFromArray(candidate *CandidateNode, childPath interface{}) {
|
||||
log.Debug("deleteFromArray")
|
||||
node := unwrapDoc(candidate.Node)
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
contents := node.Content
|
||||
newContents := make([]*yaml.Node, 0)
|
||||
|
||||
|
||||
@@ -37,38 +37,6 @@ var deleteOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::[1, 3]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: [1,2,3]`,
|
||||
expression: `del(.a[])`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: []\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: [10,x,10, 10, x, 10]`,
|
||||
expression: `del(.a[] | select(. == 10))`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: [x, x]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: {thing1: yep, thing2: cool, thing3: hi, b: {thing1: cool, great: huh}}`,
|
||||
expression: `del(..)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: {thing1: yep, thing2: cool, thing3: hi, b: {thing1: cool, great: huh}}`,
|
||||
expression: `del(.. | select(tag == "!!map") | (.b.thing1,.thing2))`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: {thing1: yep, thing3: hi, b: {great: huh}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Delete nested entry in array",
|
||||
document: `[{a: cat, b: dog}]`,
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func getDocumentIndexOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func GetDocumentIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.Document), Tag: "!!int"}
|
||||
scalar := candidate.CreateChild(nil, node)
|
||||
scalar := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(scalar)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type envOpPreferences struct {
|
||||
StringValue bool
|
||||
}
|
||||
|
||||
func envOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
envName := expressionNode.Operation.CandidateNode.Node.Value
|
||||
log.Debug("EnvOperator, env name:", envName)
|
||||
|
||||
rawValue := os.Getenv(envName)
|
||||
|
||||
preferences := expressionNode.Operation.Preferences.(envOpPreferences)
|
||||
|
||||
var node *yaml.Node
|
||||
if preferences.StringValue {
|
||||
node = &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: rawValue,
|
||||
}
|
||||
} else if rawValue == "" {
|
||||
return Context{}, fmt.Errorf("Value for env variable '%v' not provided in env()", envName)
|
||||
} else {
|
||||
var dataBucket yaml.Node
|
||||
decoder := yaml.NewDecoder(strings.NewReader(rawValue))
|
||||
errorReading := decoder.Decode(&dataBucket)
|
||||
if errorReading != nil {
|
||||
return Context{}, errorReading
|
||||
}
|
||||
//first node is a doc
|
||||
node = unwrapDoc(&dataBucket)
|
||||
}
|
||||
log.Debug("ENV tag", node.Tag)
|
||||
log.Debug("ENV value", node.Value)
|
||||
log.Debug("ENV Kind", node.Kind)
|
||||
|
||||
target := &CandidateNode{Node: node}
|
||||
|
||||
return context.SingleChildContext(target), nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var envOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Read string environment variable",
|
||||
environmentVariable: "cat meow",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: cat meow\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read boolean environment variable",
|
||||
environmentVariable: "true",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read numeric environment variable",
|
||||
environmentVariable: "12",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: 12\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read yaml environment variable",
|
||||
environmentVariable: "{b: fish}",
|
||||
expression: `.a = env(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: {b: fish}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read boolean environment variable as a string",
|
||||
environmentVariable: "true",
|
||||
expression: `.a = strenv(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: \"true\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read numeric environment variable as a string",
|
||||
environmentVariable: "12",
|
||||
expression: `.a = strenv(myenv)`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: \"12\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Dynamic key lookup with environment variable",
|
||||
environmentVariable: "cat",
|
||||
document: `{cat: meow, dog: woof}`,
|
||||
expression: `.[env(myenv)]`,
|
||||
expected: []string{
|
||||
"D0, P[cat], (!!str)::meow\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEnvOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range envOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Env Variable Operators", envOperatorScenarios)
|
||||
}
|
||||
@@ -1,33 +1,22 @@
|
||||
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")
|
||||
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) {
|
||||
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
value := false
|
||||
func isEquals(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
value := false
|
||||
|
||||
lhsNode := unwrapDoc(lhs.Node)
|
||||
rhsNode := unwrapDoc(rhs.Node)
|
||||
|
||||
if lhsNode.Tag == "!!null" {
|
||||
value = (rhsNode.Tag == "!!null")
|
||||
} else if lhsNode.Kind == yaml.ScalarNode && rhsNode.Kind == yaml.ScalarNode {
|
||||
value = matchKey(lhsNode.Value, rhsNode.Value)
|
||||
}
|
||||
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
|
||||
if flip {
|
||||
value = !value
|
||||
}
|
||||
return createBooleanCandidate(lhs, value), nil
|
||||
if lhs.Node.Tag == "!!null" {
|
||||
value = (rhs.Node.Tag == "!!null")
|
||||
} else {
|
||||
value = Match(lhs.Node.Value, rhs.Node.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func notEqualsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- equalsOperation")
|
||||
return crossFunction(d, context, expressionNode, isEquals(true))
|
||||
log.Debugf("%v == %v ? %v", NodeToString(lhs), NodeToString(rhs), value)
|
||||
return createBooleanCandidate(lhs, value), nil
|
||||
}
|
||||
|
||||
@@ -5,23 +5,6 @@ import (
|
||||
)
|
||||
|
||||
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",
|
||||
document: `[cat,goat,dog]`,
|
||||
@@ -31,18 +14,7 @@ var equalsOperatorScenarios = []expressionScenario{
|
||||
"D0, P[1], (!!bool)::true\n",
|
||||
"D0, P[2], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Don't match string",
|
||||
document: `[cat,goat,dog]`,
|
||||
expression: `.[] | (. != "*at")`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::false\n",
|
||||
"D0, P[1], (!!bool)::false\n",
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
}, {
|
||||
description: "Match number",
|
||||
document: `[3, 4, 5]`,
|
||||
expression: `.[] | (. == 4)`,
|
||||
@@ -51,18 +23,7 @@ var equalsOperatorScenarios = []expressionScenario{
|
||||
"D0, P[1], (!!bool)::true\n",
|
||||
"D0, P[2], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Dont match number",
|
||||
document: `[3, 4, 5]`,
|
||||
expression: `.[] | (. != 4)`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::true\n",
|
||||
"D0, P[1], (!!bool)::false\n",
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
}, {
|
||||
skipDoc: true,
|
||||
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
|
||||
expression: `.a | (.[].b == "apple")`,
|
||||
|
||||
@@ -7,32 +7,32 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func getFilenameOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetFilename")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
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")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -21,16 +21,6 @@ var fileOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!int)::0\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get file indices of multiple documents",
|
||||
document: `{a: cat}`,
|
||||
document2: `{a: cat}`,
|
||||
expression: `fileIndex`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::0\n",
|
||||
"D0, P[], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get file index alias",
|
||||
document: `{a: cat}`,
|
||||
@@ -39,14 +29,6 @@ var fileOperatorScenarios = []expressionScenario{
|
||||
"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) {
|
||||
|
||||
@@ -7,26 +7,25 @@ import (
|
||||
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")
|
||||
var results = list.New()
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
wanted := rhs.MatchingNodes.Front().Value.(*CandidateNode).Node
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
wanted := rhs.Front().Value.(*CandidateNode).Node
|
||||
wantedKey := wanted.Value
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
// grab the first value
|
||||
candidateNode := unwrapDoc(candidate.Node)
|
||||
var contents = candidateNode.Content
|
||||
switch candidateNode.Kind {
|
||||
var contents = candidate.Node.Content
|
||||
switch candidate.Node.Kind {
|
||||
case yaml.MappingNode:
|
||||
candidateHasKey := false
|
||||
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" {
|
||||
var number, errParsingInt = strconv.ParseInt(wantedKey, 10, 64) // nolint
|
||||
if errParsingInt != nil {
|
||||
return Context{}, errParsingInt
|
||||
return nil, errParsingInt
|
||||
}
|
||||
candidateHasKey = int64(len(contents)) > number
|
||||
}
|
||||
@@ -50,5 +49,5 @@ func hasOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
|
||||
results.PushBack(createBooleanCandidate(candidate, false))
|
||||
}
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -5,14 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var hasOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: hello`,
|
||||
expression: `has("a")`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Has map key",
|
||||
document: `- a: "yes"
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -7,21 +7,17 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func lengthOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func LengthOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- lengthOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
targetNode := unwrapDoc(candidate.Node)
|
||||
targetNode := UnwrapDoc(candidate.Node)
|
||||
var length int
|
||||
switch targetNode.Kind {
|
||||
case yaml.ScalarNode:
|
||||
if targetNode.Tag == "!!null" {
|
||||
length = 0
|
||||
} else {
|
||||
length = len(targetNode.Value)
|
||||
}
|
||||
length = len(targetNode.Value)
|
||||
case yaml.MappingNode:
|
||||
length = len(targetNode.Content) / 2
|
||||
case yaml.SequenceNode:
|
||||
@@ -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"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -14,30 +14,6 @@ var lengthOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a], (!!int)::3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "null length",
|
||||
document: `{a: null}`,
|
||||
expression: `.a | length`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!int)::0\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: ~}`,
|
||||
expression: `.a | length`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!int)::0\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: key no exist}`,
|
||||
expression: `.b | length`,
|
||||
expected: []string{
|
||||
"D0, P[b], (!!int)::0\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Map length",
|
||||
subdescription: "returns number of entries",
|
||||
|
||||
@@ -2,93 +2,92 @@ package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type multiplyPreferences struct {
|
||||
AppendArrays bool
|
||||
DeepMergeArrays bool
|
||||
TraversePrefs traversePreferences
|
||||
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
|
||||
}
|
||||
|
||||
func multiplyOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
type MultiplyPreferences struct {
|
||||
AppendArrays bool
|
||||
}
|
||||
|
||||
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
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) {
|
||||
return func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||
|
||||
shouldAppendArrays := preferences.AppendArrays
|
||||
|
||||
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||
|
||||
var newBlank = lhs.CreateChild(nil, &yaml.Node{})
|
||||
var newThing, err = mergeObjects(d, context, newBlank, lhs, multiplyPreferences{})
|
||||
var newBlank = &CandidateNode{
|
||||
Path: lhs.Path,
|
||||
Document: lhs.Document,
|
||||
Filename: lhs.Filename,
|
||||
Node: &yaml.Node{},
|
||||
}
|
||||
var newThing, err = mergeObjects(d, newBlank, lhs, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mergeObjects(d, context, newThing, rhs, preferences)
|
||||
} else if lhs.Node.Tag == "!!int" && rhs.Node.Tag == "!!int" {
|
||||
return multiplyIntegers(lhs, rhs)
|
||||
} else if (lhs.Node.Tag == "!!int" || lhs.Node.Tag == "!!float") && (rhs.Node.Tag == "!!int" || rhs.Node.Tag == "!!float") {
|
||||
return multiplyFloats(lhs, rhs)
|
||||
return mergeObjects(d, newThing, rhs, shouldAppendArrays)
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
func multiplyFloats(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
target := lhs.CreateChild(nil, &yaml.Node{})
|
||||
target.Node.Kind = yaml.ScalarNode
|
||||
target.Node.Style = lhs.Node.Style
|
||||
target.Node.Tag = "!!float"
|
||||
|
||||
lhsNum, err := strconv.ParseFloat(lhs.Node.Value, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhsNum, err := strconv.ParseFloat(rhs.Node.Value, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target.Node.Value = fmt.Sprintf("%v", lhsNum*rhsNum)
|
||||
return target, nil
|
||||
}
|
||||
|
||||
func multiplyIntegers(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
target := lhs.CreateChild(nil, &yaml.Node{})
|
||||
target.Node.Kind = yaml.ScalarNode
|
||||
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
|
||||
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
|
||||
var results = list.New()
|
||||
|
||||
// shouldn't recurse arrays if appending
|
||||
prefs := recursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
|
||||
TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}}
|
||||
err := recursiveDecent(d, results, context.SingleChildContext(rhs), prefs)
|
||||
prefs := &RecursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
|
||||
TraversePreferences: &TraversePreferences{FollowAlias: false}}
|
||||
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,12 +98,7 @@ func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs
|
||||
}
|
||||
|
||||
for el := results.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
if candidate.Node.Tag == "!!merge" {
|
||||
continue
|
||||
}
|
||||
|
||||
err := applyAssignment(d, context, pathIndexToStartFrom, lhs, candidate, preferences)
|
||||
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -112,25 +106,24 @@ func mergeObjects(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs
|
||||
return lhs, nil
|
||||
}
|
||||
|
||||
func applyAssignment(d *dataTreeNavigator, context Context, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences multiplyPreferences) error {
|
||||
shouldAppendArrays := preferences.AppendArrays
|
||||
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", lhs.GetKey(), rhs.GetKey())
|
||||
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
|
||||
|
||||
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
||||
|
||||
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
||||
|
||||
assignmentOp := &Operation{OperationType: assignAttributesOpType}
|
||||
if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
|
||||
assignmentOp.OperationType = addAssignOpType
|
||||
} else if !preferences.DeepMergeArrays && rhs.Node.Kind == yaml.SequenceNode ||
|
||||
(rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode) {
|
||||
assignmentOp.OperationType = assignOpType
|
||||
assignmentOp := &Operation{OperationType: AssignAttributes}
|
||||
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
||||
assignmentOp.OperationType = Assign
|
||||
assignmentOp.UpdateAssign = false
|
||||
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -5,27 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var multiplyOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Multiply integers",
|
||||
expression: `3 * 4`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::12\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `3 * 4.5`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!float)::13.5\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `4.5 * 3`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!float)::13.5\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {also: [1]}, b: {also: me}}`,
|
||||
@@ -34,51 +13,6 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "# b\nb:\n # a\n a: cat",
|
||||
expression: "{} * .",
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::# b\nb:\n # a\n a: cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "# b\nb:\n # a\n a: cat",
|
||||
expression: ". * {}",
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::# b\nb:\n # a\n a: cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: &a { b: &b { c: &c cat } } }`,
|
||||
expression: `{} * .`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: &a {b: &b {c: &c cat}}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: 2, b: 5}`,
|
||||
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,
|
||||
expression: `{} * {"cat":"dog"}`,
|
||||
@@ -137,7 +71,7 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {things: great}, b: {also: me}}`,
|
||||
expression: `. * {"a": .b}`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {things: great, also: me}, b: {also: me}}\n",
|
||||
},
|
||||
@@ -145,10 +79,16 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Merge keeps style of LHS",
|
||||
dontFormatInputForDoc: true,
|
||||
document: "a: {things: great}\nb:\n also: \"me\"",
|
||||
expression: `. * {"a":.b}`,
|
||||
document: `a: {things: great}
|
||||
b:
|
||||
also: "me"
|
||||
`,
|
||||
expression: `. * {"a":.b}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: {things: great, also: \"me\"}\nb:\n also: \"me\"\n",
|
||||
`D0, P[], (!!map)::a: {things: great, also: "me"}
|
||||
b:
|
||||
also: "me"
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -167,30 +107,6 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
"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 *?d .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",
|
||||
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
|
||||
@@ -199,23 +115,6 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
"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, deeply merging arrays",
|
||||
subdescription: "Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.",
|
||||
document: `{a: [{name: fred, age: 12}, {name: bob, age: 32}], b: [{name: fred, age: 34}]}`,
|
||||
expression: `.a *d .b`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!seq)::[{name: fred, age: 34}, {name: bob, age: 32}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge to prefix an element",
|
||||
document: `{a: cat, b: dog}`,
|
||||
@@ -233,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}}`,
|
||||
expression: `.c * .a`,
|
||||
expected: []string{
|
||||
"D0, P[c], (!!map)::{g: thongs, c: &cat frog}\n",
|
||||
"D0, P[c], (!!map)::{g: thongs, c: frog}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
|
||||
@@ -31,9 +31,9 @@ func getPathOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
|
||||
content[pathIndex] = createPathNodeFor(path)
|
||||
}
|
||||
node.Content = content
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
package yqlib
|
||||
|
||||
func pipeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
import "container/list"
|
||||
|
||||
//lhs may update the variable context, we should pass that into the RHS
|
||||
// BUT we still return the original context back (see jq)
|
||||
// https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
func PipeOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(lhs, expressionNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
return context.ChildContext(rhs.MatchingNodes), nil
|
||||
return d.GetMatchingNodes(lhs, pathNode.Rhs)
|
||||
}
|
||||
|
||||
@@ -6,28 +6,28 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type recursiveDescentPreferences struct {
|
||||
TraversePreferences traversePreferences
|
||||
type RecursiveDescentPreferences struct {
|
||||
TraversePreferences *TraversePreferences
|
||||
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()
|
||||
|
||||
preferences := expressionNode.Operation.Preferences.(recursiveDescentPreferences)
|
||||
err := recursiveDecent(d, results, context, preferences)
|
||||
preferences := pathNode.Operation.Preferences.(*RecursiveDescentPreferences)
|
||||
err := recursiveDecent(d, results, matchMap, preferences)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, context Context, preferences recursiveDescentPreferences) error {
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences *RecursiveDescentPreferences) error {
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
candidate.Node = unwrapDoc(candidate.Node)
|
||||
candidate.Node = UnwrapDoc(candidate.Node)
|
||||
|
||||
log.Debugf("Recursive Decent, added %v", NodeToString(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 &&
|
||||
(preferences.RecurseArray || candidate.Node.Kind != yaml.SequenceNode) {
|
||||
|
||||
children, err := splat(d, context.SingleChildContext(candidate), preferences.TraversePreferences)
|
||||
children, err := Splat(d, nodeToMap(candidate), preferences.TraversePreferences)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -63,12 +63,12 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
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}}}`,
|
||||
expression: `[.. | select(has("name"))]`,
|
||||
description: "Recursively find nodes with keys",
|
||||
document: `{a: {name: frog, b: {name: blog, age: 12}}}`,
|
||||
expression: `.. | select(has("name"))`,
|
||||
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}`,
|
||||
expression: `[..]`,
|
||||
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,
|
||||
expression: `.foobar | [..]`,
|
||||
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,
|
||||
expression: `.foobar | [...]`,
|
||||
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 {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Recursive Descent (Glob)", recursiveDescentOperatorScenarios)
|
||||
documentScenarios(t, "Recursive Descent", recursiveDescentOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var reduceOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Sum numbers",
|
||||
document: `[10,2, 5, 3]`,
|
||||
expression: `.[] as $item ireduce (0; . + $item)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::20\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge all yaml files together",
|
||||
document: `a: cat`,
|
||||
document2: `b: dog`,
|
||||
expression: `. as $item ireduce ({}; . * $item )`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat\nb: dog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Convert an array to an object",
|
||||
document: `[{name: Cathy, has: apples},{name: Bob, has: bananas}]`,
|
||||
expression: `.[] as $item ireduce ({}; .[$item | .name] = ($item | .has) )`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Cathy: apples\nBob: bananas\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestReduceOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range reduceOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Reduce", reduceOperatorScenarios)
|
||||
}
|
||||
@@ -4,29 +4,28 @@ import (
|
||||
"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")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
childContext := context.SingleChildContext(candidate)
|
||||
childContext.DontAutoCreate = true
|
||||
rhs, err := d.GetMatchingNodes(childContext, expressionNode.Rhs)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// grab the first value
|
||||
first := rhs.MatchingNodes.Front()
|
||||
first := rhs.Front()
|
||||
|
||||
if first != nil {
|
||||
result := first.Value.(*CandidateNode)
|
||||
includeResult, errDecoding := isTruthy(result)
|
||||
if errDecoding != nil {
|
||||
return Context{}, errDecoding
|
||||
return nil, errDecoding
|
||||
}
|
||||
|
||||
if includeResult {
|
||||
@@ -34,5 +33,5 @@ func selectOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
|
||||
}
|
||||
}
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -14,14 +14,6 @@ var selectOperatorScenarios = []expressionScenario{
|
||||
"D0, P[1], (!!str)::goat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{animal: cat, legs: {cool: true}}, {animal: fish}]`,
|
||||
expression: `(.[] | select(.legs.cool == true).canWalk) = true | (.[] | .alive.things) = "yes"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::[{animal: cat, legs: {cool: true}, canWalk: true, alive: {things: yes}}, {animal: fish, alive: {things: yes}}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[hot, fot, dog]`,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package yqlib
|
||||
|
||||
func selfOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
return context, nil
|
||||
import "container/list"
|
||||
|
||||
func SelfOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
return matchMap, nil
|
||||
}
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sort"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func sortKeysOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func SortKeysOperator(d *dataTreeNavigator, matchingNodes *list.List, 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)
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for childEl := rhs.MatchingNodes.Front(); childEl != nil; childEl = childEl.Next() {
|
||||
node := unwrapDoc(childEl.Value.(*CandidateNode).Node)
|
||||
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
|
||||
node := UnwrapDoc(childEl.Value.(*CandidateNode).Node)
|
||||
if node.Kind == yaml.MappingNode {
|
||||
sortKeys(node)
|
||||
}
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func sortKeys(node *yaml.Node) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -26,43 +26,43 @@ func parseStyle(customStyle string) (yaml.Style, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func assignStyleOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignStyleOperator: %v")
|
||||
var style yaml.Style
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
style, err = parseStyle(rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value)
|
||||
if rhs.Front() != nil {
|
||||
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting style of : %v", candidate.GetKey())
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
style, err = parseStyle(rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value)
|
||||
if rhs.Front() != nil {
|
||||
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,15 +70,15 @@ func assignStyleOperator(d *dataTreeNavigator, context Context, expressionNode *
|
||||
candidate.Node.Style = style
|
||||
}
|
||||
|
||||
return context, nil
|
||||
return matchingNodes, nil
|
||||
}
|
||||
|
||||
func getStyleOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func GetStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetStyleOperator")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
var style string
|
||||
switch candidate.Node.Style {
|
||||
@@ -100,9 +100,9 @@ func getStyleOperator(d *dataTreeNavigator, context Context, expressionNode *Exp
|
||||
style = "<unknown>"
|
||||
}
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: style, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -6,58 +6,58 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func assignTagOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("AssignTagOperator: %v")
|
||||
tag := ""
|
||||
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
if !pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
tag = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
if rhs.Front() != nil {
|
||||
tag = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debugf("Setting tag of : %v", candidate.GetKey())
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if pathNode.Operation.UpdateAssign {
|
||||
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rhs.MatchingNodes.Front() != nil {
|
||||
tag = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
if rhs.Front() != nil {
|
||||
tag = rhs.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
}
|
||||
unwrapDoc(candidate.Node).Tag = tag
|
||||
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")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: unwrapDoc(candidate.Node).Tag, Tag: "!!str"}
|
||||
result := candidate.CreateChild(nil, node)
|
||||
results.PushBack(result)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: UnwrapDoc(candidate.Node).Tag, Tag: "!!str"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -26,14 +26,6 @@ var tagOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!str)::'!!map'\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `32`,
|
||||
expression: `. tag= "!!str"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::\"32\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set custom tag",
|
||||
document: `{a: str}`,
|
||||
|
||||
@@ -9,33 +9,31 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type traversePreferences struct {
|
||||
DontFollowAlias bool
|
||||
IncludeMapKeys bool
|
||||
DontAutoCreate bool // by default, we automatically create entries on the fly.
|
||||
DontIncludeMapValues bool
|
||||
type TraversePreferences struct {
|
||||
FollowAlias bool
|
||||
IncludeMapKeys bool
|
||||
}
|
||||
|
||||
func splat(d *dataTreeNavigator, context Context, prefs traversePreferences) (Context, error) {
|
||||
return traverseNodesWithArrayIndices(context, make([]*yaml.Node, 0), prefs)
|
||||
func Splat(d *dataTreeNavigator, matches *list.List, prefs *TraversePreferences) (*list.List, error) {
|
||||
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
|
||||
}
|
||||
|
||||
func traversePathOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- traversePathOperator")
|
||||
var matches = list.New()
|
||||
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- Traversing")
|
||||
var matchingNodeMap = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
newNodes, err := traverse(d, context, el.Value.(*CandidateNode), expressionNode.Operation)
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
newNodes, err := traverse(d, el.Value.(*CandidateNode), pathNode.Operation)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
matches.PushBackList(newNodes)
|
||||
matchingNodeMap.PushBackList(newNodes)
|
||||
}
|
||||
|
||||
return context.ChildContext(matches), nil
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
|
||||
func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Operation) (*list.List, error) {
|
||||
log.Debug("Traversing %v", NodeToString(matchingNode))
|
||||
value := matchingNode.Node
|
||||
|
||||
@@ -56,7 +54,8 @@ func traverse(d *dataTreeNavigator, context Context, matchingNode *CandidateNode
|
||||
switch value.Kind {
|
||||
case yaml.MappingNode:
|
||||
log.Debug("its a map with %v entries", len(value.Content)/2)
|
||||
return traverseMap(context, matchingNode, operation.StringValue, operation.Preferences.(traversePreferences), false)
|
||||
prefs := &TraversePreferences{FollowAlias: true}
|
||||
return traverseMap(matchingNode, operation.StringValue, prefs, false)
|
||||
|
||||
case yaml.SequenceNode:
|
||||
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:
|
||||
log.Debug("its an alias!")
|
||||
matchingNode.Node = matchingNode.Node.Alias
|
||||
return traverse(d, context, matchingNode, operation)
|
||||
return traverse(d, matchingNode, operation)
|
||||
case yaml.DocumentNode:
|
||||
log.Debug("digging into doc node")
|
||||
|
||||
return traverse(d, context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), operation)
|
||||
return traverse(d, &CandidateNode{
|
||||
Node: matchingNode.Node.Content[0],
|
||||
Filename: matchingNode.Filename,
|
||||
FileIndex: matchingNode.FileIndex,
|
||||
Document: matchingNode.Document}, operation)
|
||||
default:
|
||||
return list.New(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func traverseArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
//lhs may update the variable context, we should pass that into the RHS
|
||||
// BUT we still return the original context back (see jq)
|
||||
// https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
func TraverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
// rhs is a collect expression that will yield indexes to retreive of the arrays
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context, expressionNode.Rhs)
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var indicesToTraverse = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Content
|
||||
|
||||
//now we traverse the result of the lhs against the indices we found
|
||||
result, err := traverseNodesWithArrayIndices(lhs, indicesToTraverse, traversePreferences{})
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
return context.ChildContext(result.MatchingNodes), nil
|
||||
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
|
||||
prefs := &TraversePreferences{FollowAlias: true}
|
||||
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs)
|
||||
}
|
||||
|
||||
func traverseNodesWithArrayIndices(context Context, indicesToTraverse []*yaml.Node, prefs traversePreferences) (Context, error) {
|
||||
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
|
||||
var matchingNodeMap = list.New()
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
newNodes, err := traverseArrayIndices(context, candidate, indicesToTraverse, prefs)
|
||||
newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
matchingNodeMap.PushBackList(newNodes)
|
||||
}
|
||||
|
||||
return context.ChildContext(matchingNodeMap), nil
|
||||
return matchingNodeMap, nil
|
||||
}
|
||||
|
||||
func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
|
||||
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
|
||||
node := matchingNode.Node
|
||||
if node.Tag == "!!null" {
|
||||
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
|
||||
@@ -128,28 +115,32 @@ func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesT
|
||||
|
||||
if node.Kind == yaml.AliasNode {
|
||||
matchingNode.Node = node.Alias
|
||||
return traverseArrayIndices(context, matchingNode, indicesToTraverse, prefs)
|
||||
return traverseArrayIndices(matchingNode, indicesToTraverse, prefs)
|
||||
} else if node.Kind == yaml.SequenceNode {
|
||||
return traverseArrayWithIndices(matchingNode, indicesToTraverse)
|
||||
} else if node.Kind == yaml.MappingNode {
|
||||
return traverseMapWithIndices(context, matchingNode, indicesToTraverse, prefs)
|
||||
return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs)
|
||||
} else if node.Kind == yaml.DocumentNode {
|
||||
return traverseArrayIndices(context, matchingNode.CreateChild(nil, matchingNode.Node.Content[0]), indicesToTraverse, prefs)
|
||||
return traverseArrayIndices(&CandidateNode{
|
||||
Node: matchingNode.Node.Content[0],
|
||||
Filename: matchingNode.Filename,
|
||||
FileIndex: matchingNode.FileIndex,
|
||||
Document: matchingNode.Document}, indicesToTraverse, prefs)
|
||||
}
|
||||
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
|
||||
return list.New(), nil
|
||||
}
|
||||
|
||||
func traverseMapWithIndices(context Context, candidate *CandidateNode, indices []*yaml.Node, prefs traversePreferences) (*list.List, error) {
|
||||
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
|
||||
if len(indices) == 0 {
|
||||
return traverseMap(context, candidate, "", prefs, true)
|
||||
return traverseMap(candidate, "", prefs, true)
|
||||
}
|
||||
|
||||
var matchingNodeMap = list.New()
|
||||
|
||||
for _, indexNode := range indices {
|
||||
log.Debug("traverseMapWithIndices: %v", indexNode.Value)
|
||||
newNodes, err := traverseMap(context, candidate, indexNode.Value, prefs, false)
|
||||
newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -162,13 +153,17 @@ func traverseMapWithIndices(context Context, candidate *CandidateNode, indices [
|
||||
func traverseArrayWithIndices(candidate *CandidateNode, indices []*yaml.Node) (*list.List, error) {
|
||||
log.Debug("traverseArrayWithIndices")
|
||||
var newMatches = list.New()
|
||||
node := unwrapDoc(candidate.Node)
|
||||
node := UnwrapDoc(candidate.Node)
|
||||
if len(indices) == 0 {
|
||||
log.Debug("splatting")
|
||||
var index int64
|
||||
for index = 0; index < int64(len(node.Content)); index = index + 1 {
|
||||
|
||||
newMatches.PushBack(candidate.CreateChild(index, node.Content[index]))
|
||||
newMatches.PushBack(&CandidateNode{
|
||||
Document: candidate.Document,
|
||||
Path: candidate.CreateChildPath(index),
|
||||
Node: node.Content[index],
|
||||
})
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
|
||||
|
||||
@@ -212,24 +211,18 @@ func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !prefs.DontAutoCreate && !context.DontAutoCreate && newMatches.Len() == 0 {
|
||||
if newMatches.Len() == 0 {
|
||||
//no matches, create one automagically
|
||||
valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"}
|
||||
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: key}
|
||||
node := matchingNode.Node
|
||||
node.Content = append(node.Content, keyNode, valueNode)
|
||||
node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: key}, valueNode)
|
||||
candidateNode := &CandidateNode{
|
||||
Node: valueNode,
|
||||
Path: append(matchingNode.Path, key),
|
||||
Document: matchingNode.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
|
||||
if prefs.IncludeMapKeys {
|
||||
log.Debug("including key")
|
||||
candidateNode := matchingNode.CreateChild(key, keyNode)
|
||||
candidateNode.IsMapKey = true
|
||||
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
|
||||
}
|
||||
if !prefs.DontIncludeMapValues {
|
||||
log.Debug("including value")
|
||||
candidateNode := matchingNode.CreateChild(key, valueNode)
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
}
|
||||
}
|
||||
|
||||
results := list.New()
|
||||
@@ -241,7 +234,7 @@ func traverseMap(context Context, matchingNode *CandidateNode, key string, prefs
|
||||
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,
|
||||
// so keys are in the even indexes, values in odd.
|
||||
// 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)
|
||||
//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")
|
||||
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
|
||||
if err != nil {
|
||||
@@ -265,26 +258,33 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
|
||||
} else if splat || keyMatches(key, wantedKey) {
|
||||
log.Debug("MATCHED")
|
||||
if prefs.IncludeMapKeys {
|
||||
log.Debug("including key")
|
||||
candidateNode := candidate.CreateChild(key.Value, key)
|
||||
candidateNode.IsMapKey = true
|
||||
candidateNode := &CandidateNode{
|
||||
Node: key,
|
||||
Path: candidate.CreateChildPath(key.Value),
|
||||
Document: candidate.Document,
|
||||
}
|
||||
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
|
||||
}
|
||||
if !prefs.DontIncludeMapValues {
|
||||
log.Debug("including value")
|
||||
candidateNode := candidate.CreateChild(key.Value, value)
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
candidateNode := &CandidateNode{
|
||||
Node: value,
|
||||
Path: candidate.CreateChildPath(key.Value),
|
||||
Document: candidate.Document,
|
||||
}
|
||||
newMatches.Set(candidateNode.GetKey(), candidateNode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs traversePreferences, splat bool) error {
|
||||
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *TraversePreferences, splat bool) error {
|
||||
switch value.Kind {
|
||||
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)
|
||||
case yaml.SequenceNode:
|
||||
for _, childValue := range value.Content {
|
||||
|
||||
@@ -47,47 +47,13 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
description: "Special characters",
|
||||
subdescription: "Use quotes with brackets around path elements with special characters",
|
||||
subdescription: "Use quotes around path elements with special characters",
|
||||
document: `{"{}": frog}`,
|
||||
expression: `.["{}"]`,
|
||||
expression: `."{}"`,
|
||||
expected: []string{
|
||||
"D0, P[{}], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Keys with spaces",
|
||||
subdescription: "Use quotes with brackets around path elements with special characters",
|
||||
document: `{"red rabbit": frog}`,
|
||||
expression: `.["red rabbit"]`,
|
||||
expected: []string{
|
||||
"D0, P[red rabbit], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{"flying fox": frog}`,
|
||||
expression: `.["flying fox"]`,
|
||||
expected: []string{
|
||||
"D0, P[flying fox], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Dynamic keys",
|
||||
subdescription: `Expressions within [] can be used to dynamically lookup / calculate keys`,
|
||||
document: `{b: apple, apple: crispy yum, banana: soft yum}`,
|
||||
expression: `.[.b]`,
|
||||
expected: []string{
|
||||
"D0, P[apple], (!!str)::crispy yum\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{b: apple, fruit: {apple: yum, banana: smooth}}`,
|
||||
expression: `.fruit[.b]`,
|
||||
expected: []string{
|
||||
"D0, P[fruit apple], (!!str)::yum\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Children don't exist",
|
||||
subdescription: "Nodes are added dynamically while traversing",
|
||||
@@ -108,7 +74,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: `.a[1]`,
|
||||
expression: `.a.[1]`,
|
||||
expected: []string{
|
||||
"D0, P[a 1], (!!null)::null\n",
|
||||
},
|
||||
@@ -179,7 +145,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `.b[]`,
|
||||
expression: `.b.[]`,
|
||||
expected: []string{
|
||||
"D0, P[b c], (!!str)::frog\n",
|
||||
},
|
||||
@@ -261,7 +227,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar[]`,
|
||||
expression: `.foobar.[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobar c], (!!str)::foo_c\n",
|
||||
"D0, P[foobar a], (!!str)::foo_a\n",
|
||||
@@ -323,7 +289,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList[]`,
|
||||
expression: `.foobarList.[]`,
|
||||
expected: []string{
|
||||
"D0, P[foobarList b], (!!str)::bar_b\n",
|
||||
"D0, P[foobarList a], (!!str)::foo_a\n",
|
||||
@@ -369,7 +335,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[0, 2]`,
|
||||
expression: `.a.[0, 2]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 2], (!!str)::c\n",
|
||||
@@ -386,7 +352,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[-1]`,
|
||||
expression: `.a.[-1]`,
|
||||
expected: []string{
|
||||
"D0, P[a -1], (!!str)::c\n",
|
||||
},
|
||||
@@ -402,7 +368,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[-2]`,
|
||||
expression: `.a.[-2]`,
|
||||
expected: []string{
|
||||
"D0, P[a -2], (!!str)::b\n",
|
||||
},
|
||||
@@ -420,7 +386,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: [a,b,c]}`,
|
||||
expression: `.a[]`,
|
||||
expression: `.a.[]`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::a\n",
|
||||
"D0, P[a 1], (!!str)::b\n",
|
||||
@@ -443,5 +409,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range traversePathOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Traverse (Read)", traversePathOperatorScenarios)
|
||||
documentScenarios(t, "Traverse", traversePathOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package yqlib
|
||||
|
||||
func unionOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
import "container/list"
|
||||
|
||||
func UnionOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
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 {
|
||||
return Context{}, err
|
||||
return nil, err
|
||||
}
|
||||
for el := rhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
for el := rhs.Front(); el != nil; el = el.Next() {
|
||||
node := el.Value.(*CandidateNode)
|
||||
lhs.MatchingNodes.PushBack(node)
|
||||
lhs.PushBack(node)
|
||||
}
|
||||
return lhs, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package yqlib
|
||||
|
||||
func valueOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debug("value = %v", expressionNode.Operation.CandidateNode.Node.Value)
|
||||
return context.SingleChildContext(expressionNode.Operation.CandidateNode), nil
|
||||
import "container/list"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user