mirror of
https://github.com/mikefarah/yq.git
synced 2026-08-24 08:22:13 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac988d9655 |
@@ -1,19 +0,0 @@
|
||||
name: Test Yq Action
|
||||
on: [push]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get test
|
||||
uses: mikefarah/yq@master
|
||||
with:
|
||||
cmd: yq eval '.a' examples/data1.yaml
|
||||
- name: Write inplace test
|
||||
id: lookupSdkVersion
|
||||
uses: mikefarah/yq@master
|
||||
with:
|
||||
cmd: yq eval -i '.a.b = 5' examples/data1.yaml
|
||||
+1
-8
@@ -12,12 +12,7 @@ RUN CGO_ENABLED=0 make local build
|
||||
|
||||
# Choose alpine as a base image to make this useful for CI, as many
|
||||
# CI tools expect an interactive shell inside the container
|
||||
FROM alpine:3.13.5 as production
|
||||
|
||||
RUN mkdir /home/yq/
|
||||
RUN addgroup -g 1000 yq && \
|
||||
adduser -u 1000 -G yq -s /bin/bash -h /home/yq -D yq
|
||||
RUN chown -R yq:yq /home/yq/
|
||||
FROM alpine:3.12.3 as production
|
||||
|
||||
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
||||
RUN chmod +x /usr/bin/yq
|
||||
@@ -25,8 +20,6 @@ RUN chmod +x /usr/bin/yq
|
||||
ARG VERSION=none
|
||||
LABEL version=${VERSION}
|
||||
|
||||
USER yq
|
||||
|
||||
WORKDIR /workdir
|
||||
|
||||
ENTRYPOINT ["/usr/bin/yq"]
|
||||
|
||||
@@ -101,25 +101,6 @@ yq() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Running as root:
|
||||
|
||||
`yq`'s docker image no longer runs under root (https://github.com/mikefarah/yq/pull/860). If you'd like to install more things in the docker image, or you're having permissions issues when attempting to read/write files you'll need to either:
|
||||
|
||||
|
||||
```
|
||||
docker run --user="root" -it --entrypoint sh mikefarah/yq
|
||||
```
|
||||
|
||||
Or, in your docker file:
|
||||
|
||||
```
|
||||
FROM mikefarah/yq
|
||||
|
||||
USER root
|
||||
RUN apk add bash
|
||||
USER yq
|
||||
```
|
||||
|
||||
### Go Get:
|
||||
```
|
||||
GO111MODULE=on go get github.com/mikefarah/yq/v4
|
||||
@@ -187,7 +168,6 @@ Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
|
||||
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/v/v4.x/commands/evaluate)
|
||||
- [General shell completion scripts (bash/zsh/fish/powershell)](https://mikefarah.gitbook.io/yq/v/v4.x/commands/shell-completion)
|
||||
- [Reduce](https://mikefarah.gitbook.io/yq/operators/reduce) to merge multiple files or sum an array or other fancy things.
|
||||
- [Github Action](https://mikefarah.gitbook.io/yq/usage/github-action) to use in your automated pipeline (thanks @devorbitus)
|
||||
|
||||
## [Usage](https://mikefarah.gitbook.io/yq/)
|
||||
|
||||
@@ -229,7 +209,3 @@ yq e '.a.b | length' f1.yml f2.yml
|
||||
|
||||
## Known Issues / Missing Features
|
||||
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
|
||||
- Powershell has its own...opinions: https://mikefarah.gitbook.io/yq/usage/tips-and-tricks#quotes-in-windows-powershell
|
||||
- Running expressions against blank files does not work, because the file is empty, there are no matches for yq to run through the expression pipeline and so nothing happens. Instead, you can do something like `yq e -n '.someNew="content"' > newfile.yml` to create a new file.
|
||||
|
||||
See [tips and tricks](https://mikefarah.gitbook.io/yq/usage/tips-and-tricks) for more common problems and solutions.
|
||||
+2
-6
@@ -1,15 +1,11 @@
|
||||
name: 'yq - portable yaml processor'
|
||||
description: 'create, read, update, delete, merge, validate and do more with yaml'
|
||||
branding:
|
||||
icon: command
|
||||
color: gray-dark
|
||||
icon: command
|
||||
color: gray-dark
|
||||
inputs:
|
||||
cmd:
|
||||
description: 'The Command which should be run'
|
||||
required: true
|
||||
outputs:
|
||||
result:
|
||||
description: "The complete result from the yq command being run"
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'github-action/Dockerfile'
|
||||
|
||||
@@ -15,27 +15,13 @@ func createEvaluateAllCommand() *cobra.Command {
|
||||
Aliases: []string{"ea"},
|
||||
Short: "Loads _all_ yaml documents of _all_ yaml files and runs expression once",
|
||||
Example: `
|
||||
# Merge f2.yml into f1.yml (inplace)
|
||||
yq eval-all --inplace 'select(fileIndex == 0) * select(fileIndex == 1)' f1.yml f2.yml
|
||||
## the same command and expression using shortened names:
|
||||
yq ea -i 'select(fi == 0) * select(fi == 1)' f1.yml f2.yml
|
||||
# merges f2.yml into f1.yml (inplace)
|
||||
yq eval-all --inplace 'select(fileIndex == 0) * select(fileIndex == 1)' f1.yml f2.yml
|
||||
|
||||
|
||||
# Merge all given files
|
||||
yq ea '. as $item ireduce ({}; . * $item )' file1.yml file2.yml ...
|
||||
|
||||
# Read from STDIN
|
||||
## use '-' as a filename to read from STDIN
|
||||
# use '-' as a filename to read from STDIN
|
||||
cat file2.yml | yq ea '.a.b' file1.yml - file3.yml
|
||||
`,
|
||||
Long: `yq is a portable command-line YAML processor (https://github.com/mikefarah/yq/)
|
||||
See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.
|
||||
|
||||
## Evaluate All ##
|
||||
This command loads _all_ yaml documents of _all_ yaml files and runs expression once
|
||||
Useful when you need to run an expression across several yaml documents or files (like merge).
|
||||
Note that it consumes more memory than eval.
|
||||
`,
|
||||
Long: "Evaluate All:\nUseful when you need to run an expression across several yaml documents or files. Consumes more memory than eval",
|
||||
RunE: evaluateAll,
|
||||
}
|
||||
return cmdEvalAll
|
||||
|
||||
@@ -13,31 +13,25 @@ func createEvaluateSequenceCommand() *cobra.Command {
|
||||
var cmdEvalSequence = &cobra.Command{
|
||||
Use: "eval [expression] [yaml_file1]...",
|
||||
Aliases: []string{"e"},
|
||||
Short: "Apply the expression to each document in each yaml file in sequence",
|
||||
Short: "Apply expression to each document in each yaml file given in sequence",
|
||||
Example: `
|
||||
# Reads field under the given path for each file
|
||||
yq e '.a.b' f1.yml f2.yml
|
||||
# runs the expression against each file, in series
|
||||
yq e '.a.b | length' f1.yml f2.yml
|
||||
|
||||
# Prints out the file
|
||||
# prints out the file
|
||||
yq e sample.yaml
|
||||
|
||||
# Read from STDIN
|
||||
## use '-' as a filename to read from STDIN
|
||||
# use '-' as a filename to read from STDIN
|
||||
cat file2.yml | yq e '.a.b' file1.yml - file3.yml
|
||||
|
||||
# Creates a new yaml document
|
||||
## Note that editing an empty file does not work.
|
||||
# prints a new yaml document
|
||||
yq e -n '.a.b.c = "cat"'
|
||||
|
||||
# Update a file inplace
|
||||
|
||||
# updates file.yaml directly
|
||||
yq e '.a.b = "cool"' -i file.yaml
|
||||
`,
|
||||
Long: `yq is a portable command-line YAML processor (https://github.com/mikefarah/yq/)
|
||||
See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.
|
||||
|
||||
## Evaluate Sequence ##
|
||||
This command iterates over each yaml document from each given file, applies the
|
||||
expression and prints the result in sequence.`,
|
||||
Long: "Evaluate Sequence:\nIterate over each yaml document, apply the expression and print the results, in sequence.",
|
||||
RunE: evaluateSequence,
|
||||
}
|
||||
return cmdEvalSequence
|
||||
|
||||
+1
-2
@@ -11,8 +11,7 @@ func New() *cobra.Command {
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "yq",
|
||||
Short: "yq is a lightweight and portable command-line YAML processor.",
|
||||
Long: `yq is a portable command-line YAML processor (https://github.com/mikefarah/yq/)
|
||||
See https://mikefarah.gitbook.io/yq/ for detailed documentation and examples.`,
|
||||
Long: `yq is a lightweight and portable command-line YAML processor. It aims to be the jq or sed of yaml files.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if version {
|
||||
cmd.Print(GetVersionDisplay())
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "4.10.0"
|
||||
Version = "4.9.3"
|
||||
|
||||
// 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
|
||||
@@ -25,7 +25,7 @@ const ProductName = "yq"
|
||||
// GetVersionDisplay composes the parts of the version in a way that's suitable
|
||||
// for displaying to humans.
|
||||
func GetVersionDisplay() string {
|
||||
return fmt.Sprintf("yq (https://github.com/mikefarah/yq/) version %s\n", getHumanVersion())
|
||||
return fmt.Sprintf("%s version %s\n", ProductName, getHumanVersion())
|
||||
}
|
||||
|
||||
func getHumanVersion() string {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package cmd
|
||||
import "testing"
|
||||
|
||||
func TestGetVersionDisplay(t *testing.T) {
|
||||
var expectedVersion = ProductName + " (https://github.com/mikefarah/yq/) version " + Version
|
||||
var expectedVersion = ProductName + " version " + Version
|
||||
if VersionPrerelease != "" {
|
||||
expectedVersion = expectedVersion + "-" + VersionPrerelease
|
||||
}
|
||||
|
||||
Vendored
-9
@@ -1,12 +1,3 @@
|
||||
yq (4.9.6) focal; urgency=medium
|
||||
|
||||
* Added darwin/arm64 build, thanks @alecthomas
|
||||
* Incremented docker alpine base version, thanks @da6d6i7-bronga
|
||||
* Bug fix: multine expression
|
||||
* Bug fix: special character
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Tue, 29 Jun 2021 21:32:14 +0200
|
||||
|
||||
yq (3.3.2) focal; urgency=medium
|
||||
|
||||
* Bug fix: existStatus bug (#459)
|
||||
|
||||
Vendored
+2
-1
@@ -3,7 +3,8 @@ Section: devel
|
||||
Priority: optional
|
||||
Maintainer: Roberto Mier Escandón <rmescandon@gmail.com>
|
||||
Build-Depends: debhelper (>=10),
|
||||
golang-1.15,
|
||||
dh-golang (>=1.34),
|
||||
golang-1.13,
|
||||
rsync
|
||||
Standards-Version: 4.1.4
|
||||
Homepage: https://github.com/mikefarah/yq.git
|
||||
|
||||
Vendored
+2
-2
@@ -17,7 +17,7 @@
|
||||
PROJECT := yq
|
||||
OWNER := mikefarah
|
||||
REPO := github.com
|
||||
GOVERSION := 1.15
|
||||
GOVERSION := 1.13
|
||||
|
||||
export DH_OPTIONS
|
||||
export DH_GOPKG := ${REPO}/${OWNER}/${PROJECT}
|
||||
@@ -34,7 +34,7 @@ BINDIR := /usr/bin
|
||||
ASSETSDIR := /usr/share/${PROJECT}
|
||||
|
||||
%:
|
||||
dh $@
|
||||
dh $@ --builddirectory=${GOPATH} --buildsystem=golang
|
||||
|
||||
override_dh_auto_build:
|
||||
mkdir -p ${SRCDIR}
|
||||
|
||||
+6
-2
@@ -1,2 +1,6 @@
|
||||
---
|
||||
{"a":{"b":1}}
|
||||
a:
|
||||
key1: "value1"
|
||||
key2: 2.6
|
||||
ab:
|
||||
key1: 6
|
||||
key2: "h"
|
||||
@@ -0,0 +1,2 @@
|
||||
# a: apple
|
||||
# b: cat
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
---
|
||||
a: test
|
||||
@@ -1,8 +1,5 @@
|
||||
FROM mikefarah/yq:4.10.0
|
||||
FROM mikefarah/yq:4.9.3
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
# this seems to be the default user in github actions
|
||||
USER 1001
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#!/bin/sh -l
|
||||
|
||||
echo "::debug::\$cmd: $1"
|
||||
RESULT=$(eval "$1")
|
||||
echo "::debug::\$RESULT: $RESULT"
|
||||
echo ::set-output name=result::"$RESULT"
|
||||
echo "$1"
|
||||
eval $1
|
||||
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/timtadh/data-structures v0.5.3 // indirect
|
||||
github.com/timtadh/lexmachine v0.2.2
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
|
||||
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e // indirect
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
)
|
||||
|
||||
@@ -256,8 +256,8 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e h1:XNp2Flc/1eWQGk5BLzqTAN7fQIwIbfyVTuVxXxZh73M=
|
||||
golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/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=
|
||||
|
||||
@@ -18,12 +18,12 @@ type Evaluator interface {
|
||||
}
|
||||
|
||||
type allAtOnceEvaluator struct {
|
||||
treeNavigator DataTreeNavigator
|
||||
treeNavigator dataTreeNavigator
|
||||
treeCreator ExpressionParser
|
||||
}
|
||||
|
||||
func NewAllAtOnceEvaluator() Evaluator {
|
||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewExpressionParser()}
|
||||
return &allAtOnceEvaluator{treeNavigator: newDataTreeNavigator(), treeCreator: NewExpressionParser()}
|
||||
}
|
||||
|
||||
func (e *allAtOnceEvaluator) EvaluateNodes(expression string, nodes ...*yaml.Node) (*list.List, error) {
|
||||
@@ -48,19 +48,13 @@ func (e *allAtOnceEvaluator) EvaluateCandidateNodes(expression string, inputCand
|
||||
|
||||
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||
fileIndex := 0
|
||||
firstFileLeadingSeperator := false
|
||||
|
||||
var allDocuments *list.List = list.New()
|
||||
for _, filename := range filenames {
|
||||
reader, leadingSeperator, err := readStream(filename)
|
||||
reader, err := readStream(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fileIndex == 0 && leadingSeperator {
|
||||
firstFileLeadingSeperator = leadingSeperator
|
||||
}
|
||||
|
||||
fileDocuments, err := readDocuments(reader, filename, fileIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -68,21 +62,9 @@ func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string
|
||||
allDocuments.PushBackList(fileDocuments)
|
||||
fileIndex = fileIndex + 1
|
||||
}
|
||||
|
||||
if allDocuments.Len() == 0 {
|
||||
candidateNode := &CandidateNode{
|
||||
Document: 0,
|
||||
Filename: "",
|
||||
Node: &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode},
|
||||
FileIndex: 0,
|
||||
}
|
||||
allDocuments.PushBack(candidateNode)
|
||||
}
|
||||
|
||||
matches, err := e.EvaluateCandidateNodes(expression, allDocuments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printer.SetPrintLeadingSeperator(firstFileLeadingSeperator)
|
||||
return printer.PrintResults(matches)
|
||||
}
|
||||
|
||||
@@ -66,9 +66,3 @@ func (n *Context) ReadOnlyClone() Context {
|
||||
clone.DontAutoCreate = true
|
||||
return clone
|
||||
}
|
||||
|
||||
func (n *Context) WritableClone() Context {
|
||||
clone := n.Clone()
|
||||
clone.DontAutoCreate = false
|
||||
return clone
|
||||
}
|
||||
|
||||
@@ -6,21 +6,21 @@ import (
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
type DataTreeNavigator interface {
|
||||
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)
|
||||
}
|
||||
|
||||
type dataTreeNavigator struct {
|
||||
type dataTreeNavigatorImpl struct {
|
||||
}
|
||||
|
||||
func NewDataTreeNavigator() DataTreeNavigator {
|
||||
return &dataTreeNavigator{}
|
||||
func newDataTreeNavigator() dataTreeNavigator {
|
||||
return &dataTreeNavigatorImpl{}
|
||||
}
|
||||
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
func (d *dataTreeNavigatorImpl) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
if expressionNode == nil {
|
||||
log.Debugf("getMatchingNodes - nothing to do")
|
||||
return context, nil
|
||||
|
||||
@@ -127,32 +127,6 @@ b:
|
||||
- 4
|
||||
```
|
||||
|
||||
## Append another array using +=
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- 1
|
||||
- 2
|
||||
b:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a += .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
b:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
||||
## Relative append
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -299,33 +299,3 @@ foobar:
|
||||
thing: foobar_thing
|
||||
```
|
||||
|
||||
## Dereference and update a field
|
||||
`Use explode with multiply to dereference an object
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
item_value: &item_value
|
||||
value: true
|
||||
thingOne:
|
||||
name: item_1
|
||||
!!merge <<: *item_value
|
||||
thingTwo:
|
||||
name: item_2
|
||||
!!merge <<: *item_value
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.thingOne |= explode(.) * {"value": false}' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
item_value: &item_value
|
||||
value: true
|
||||
thingOne:
|
||||
name: item_1
|
||||
value: false
|
||||
thingTwo:
|
||||
name: item_2
|
||||
!!merge <<: *item_value
|
||||
```
|
||||
|
||||
|
||||
@@ -28,24 +28,6 @@ a:
|
||||
g: foof
|
||||
```
|
||||
|
||||
## Double elements in an array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] |= . * 2' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 2
|
||||
- 4
|
||||
- 6
|
||||
```
|
||||
|
||||
## Update node from another file
|
||||
Note this will also work when the second file is a scalar (string/number)
|
||||
|
||||
@@ -93,7 +75,7 @@ c: fieldC
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '(.a, .c) = "potatoe"' sample.yml
|
||||
yq eval '(.a, .c) |= "potatoe"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
@@ -98,21 +98,3 @@ KEY_a: 1
|
||||
KEY_b: 2
|
||||
```
|
||||
|
||||
## Use with_entries to filter the map
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
b: bird
|
||||
c:
|
||||
d: dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'with_entries(select(.value | has("b")))' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
b: bird
|
||||
```
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# String Operators
|
||||
|
||||
## RegEx
|
||||
This uses golangs native regex functions under the hood - See https://github.com/google/re2/wiki/Syntax for the supported syntax.
|
||||
|
||||
## Join strings
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -21,171 +18,6 @@ will output
|
||||
cat; meow; 1; ; true
|
||||
```
|
||||
|
||||
## Match string
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo bar foo
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'match("foo")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
string: foo
|
||||
offset: 0
|
||||
length: 3
|
||||
captures: []
|
||||
```
|
||||
|
||||
## Match string, case insensitive
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo bar FOO
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[match("(?i)foo"; "g")]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- string: foo
|
||||
offset: 0
|
||||
length: 3
|
||||
captures: []
|
||||
- string: FOO
|
||||
offset: 8
|
||||
length: 3
|
||||
captures: []
|
||||
```
|
||||
|
||||
## Match with capture groups
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
abc abc
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[match("(abc)+"; "g")]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- string: abc
|
||||
offset: 0
|
||||
length: 3
|
||||
captures:
|
||||
- string: abc
|
||||
offset: 0
|
||||
length: 3
|
||||
- string: abc
|
||||
offset: 4
|
||||
length: 3
|
||||
captures:
|
||||
- string: abc
|
||||
offset: 4
|
||||
length: 3
|
||||
```
|
||||
|
||||
## Match with named capture groups
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo bar foo foo foo
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[match("foo (?P<bar123>bar)? foo"; "g")]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- string: foo bar foo
|
||||
offset: 0
|
||||
length: 11
|
||||
captures:
|
||||
- string: bar
|
||||
offset: 4
|
||||
length: 3
|
||||
name: bar123
|
||||
- string: foo foo
|
||||
offset: 12
|
||||
length: 8
|
||||
captures:
|
||||
- string: null
|
||||
offset: -1
|
||||
length: 0
|
||||
name: bar123
|
||||
```
|
||||
|
||||
## Capture named groups into a map
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
xyzzy-14
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: xyzzy
|
||||
n: "14"
|
||||
```
|
||||
|
||||
## Match without global flag
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cat cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'match("cat")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
string: cat
|
||||
offset: 0
|
||||
length: 3
|
||||
captures: []
|
||||
```
|
||||
|
||||
## Match with global flag
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cat cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[match("cat"; "g")]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- string: cat
|
||||
offset: 0
|
||||
length: 3
|
||||
captures: []
|
||||
- string: cat
|
||||
offset: 4
|
||||
length: 3
|
||||
captures: []
|
||||
```
|
||||
|
||||
## Test using regex
|
||||
Like jq'q equivalant, this works like match but only returns true/false instead of full match details
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- cat
|
||||
- dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] | test("at")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
true
|
||||
false
|
||||
```
|
||||
|
||||
## Substitute / Replace string
|
||||
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
|
||||
Note the use of `|=` to run in context of the current string value.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structures.
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
|
||||
## Simple map navigation
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -63,22 +63,6 @@ will output
|
||||
frog
|
||||
```
|
||||
|
||||
## Nested special characters
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
"key.withdots":
|
||||
"another.key": apple
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a["key.withdots"]["another.key"]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
apple
|
||||
```
|
||||
|
||||
## Keys with spaces
|
||||
Use quotes with brackets around path elements with special characters
|
||||
|
||||
@@ -227,20 +211,6 @@ will output
|
||||
1
|
||||
```
|
||||
|
||||
## Traversing nested arrays by index
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
[[], [cat]]
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[1][0]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
```
|
||||
|
||||
## Maps with numeric keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ dog
|
||||
Both sides have now been evaluated, so now the operator copies across the value from the RHS to the value on the LHS, and it returns the now updated context:
|
||||
|
||||
```yaml
|
||||
a: dog
|
||||
a: cat
|
||||
b: dog
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
# String Operators
|
||||
|
||||
## RegEx
|
||||
This uses golangs native regex functions under the hood - See https://github.com/google/re2/wiki/Syntax for the supported syntax.
|
||||
|
||||
@@ -1 +1 @@
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structures.
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Encoder interface {
|
||||
type encoder interface {
|
||||
Encode(node *yaml.Node) error
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ type yamlEncoder struct {
|
||||
firstDoc bool
|
||||
}
|
||||
|
||||
func NewYamlEncoder(destination io.Writer, indent int, colorise bool) Encoder {
|
||||
func newYamlEncoder(destination io.Writer, indent int, colorise bool) encoder {
|
||||
if indent < 0 {
|
||||
indent = 0
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func mapKeysToStrings(node *yaml.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func NewJsonEncoder(destination io.Writer, indent int) Encoder {
|
||||
func newJsonEncoder(destination io.Writer, indent int) encoder {
|
||||
var encoder = json.NewEncoder(destination)
|
||||
encoder.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
||||
|
||||
|
||||
@@ -7,24 +7,11 @@ import (
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
var variableWithNewLine = `"cat
|
||||
"`
|
||||
|
||||
var pathTests = []struct {
|
||||
path string
|
||||
expectedTokens []interface{}
|
||||
expectedPostFix []interface{}
|
||||
}{
|
||||
{
|
||||
".a\n",
|
||||
append(make([]interface{}, 0), "a"),
|
||||
append(make([]interface{}, 0), "a"),
|
||||
},
|
||||
{
|
||||
variableWithNewLine,
|
||||
append(make([]interface{}, 0), "cat\n (string)"),
|
||||
append(make([]interface{}, 0), "cat\n (string)"),
|
||||
},
|
||||
{
|
||||
`.[0]`,
|
||||
append(make([]interface{}, 0), "SELF", "TRAVERSE_ARRAY", "[", "0 (int64)", "]"),
|
||||
|
||||
@@ -276,9 +276,6 @@ func initLexer() (*lex.Lexer, error) {
|
||||
|
||||
lexer.Add([]byte(`join`), opToken(joinStringOpType))
|
||||
lexer.Add([]byte(`sub`), opToken(subStringOpType))
|
||||
lexer.Add([]byte(`match`), opToken(matchOpType))
|
||||
lexer.Add([]byte(`capture`), opToken(captureOpType))
|
||||
lexer.Add([]byte(`test`), opToken(testOpType))
|
||||
|
||||
lexer.Add([]byte(`any`), opToken(anyOpType))
|
||||
lexer.Add([]byte(`any_c`), opToken(anyConditionOpType))
|
||||
@@ -323,7 +320,7 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
|
||||
|
||||
lexer.Add([]byte(`\."[^ "]+"\??`), pathToken(true))
|
||||
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=\n]+\??`), pathToken(false))
|
||||
lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+\??`), pathToken(false))
|
||||
lexer.Add([]byte(`\.`), selfToken())
|
||||
|
||||
lexer.Add([]byte(`\|`), opToken(pipeOpType))
|
||||
|
||||
@@ -25,10 +25,7 @@ func safelyRenameFile(from string, to string) {
|
||||
|
||||
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
|
||||
func copyFileContents(src, dst string) (err error) {
|
||||
// ignore CWE-22 gosec issue - that's more targetted for http based apps that run in a public directory,
|
||||
// and ensuring that it's not possible to give a path to a file outside thar directory.
|
||||
|
||||
in, err := os.Open(src) // #nosec
|
||||
in, err := os.Open(src) // nolint gosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+4
-15
@@ -83,9 +83,6 @@ var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50,
|
||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||
var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator}
|
||||
var subStringOpType = &operationType{Type: "SUBSTR", NumArgs: 1, Precedence: 50, Handler: substituteStringOperator}
|
||||
var matchOpType = &operationType{Type: "MATCH", NumArgs: 1, Precedence: 50, Handler: matchOperator}
|
||||
var captureOpType = &operationType{Type: "CAPTURE", NumArgs: 1, Precedence: 50, Handler: captureOperator}
|
||||
var testOpType = &operationType{Type: "TEST", NumArgs: 1, Precedence: 50, Handler: testOperator}
|
||||
var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator}
|
||||
|
||||
var keysOpType = &operationType{Type: "KEYS", NumArgs: 0, Precedence: 50, Handler: keysOperator}
|
||||
@@ -117,8 +114,8 @@ type Operation struct {
|
||||
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs
|
||||
}
|
||||
|
||||
func createScalarNode(value interface{}, stringValue string) *yaml.Node {
|
||||
var node = &yaml.Node{Kind: yaml.ScalarNode}
|
||||
func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
var node yaml.Node = yaml.Node{Kind: yaml.ScalarNode}
|
||||
node.Value = stringValue
|
||||
|
||||
switch value.(type) {
|
||||
@@ -133,17 +130,12 @@ func createScalarNode(value interface{}, stringValue string) *yaml.Node {
|
||||
case nil:
|
||||
node.Tag = "!!null"
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
var node *yaml.Node = createScalarNode(value, stringValue)
|
||||
|
||||
return &Operation{
|
||||
OperationType: valueOpType,
|
||||
Value: value,
|
||||
StringValue: stringValue,
|
||||
CandidateNode: &CandidateNode{Node: node},
|
||||
CandidateNode: &CandidateNode{Node: &node},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,10 +179,7 @@ func NodeToString(node *CandidateNode) string {
|
||||
if errorEncoding != nil {
|
||||
log.Error("Error debugging node, %v", errorEncoding.Error())
|
||||
}
|
||||
errorClosingEncoder := encoder.Close()
|
||||
if errorClosingEncoder != nil {
|
||||
log.Error("Error closing encoder: ", errorClosingEncoder.Error())
|
||||
}
|
||||
encoder.Close()
|
||||
tag := value.Tag
|
||||
if value.Kind == yaml.DocumentNode {
|
||||
tag = "doc"
|
||||
|
||||
@@ -15,7 +15,11 @@ func createAddOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode {
|
||||
}
|
||||
|
||||
func addAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
return compoundAssignFunction(d, context, expressionNode, createAddOp)
|
||||
assignmentOp := &Operation{OperationType: assignOpType}
|
||||
assignmentOp.UpdateAssign = true
|
||||
selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
|
||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createAddOp(selfExpression, expressionNode.Rhs)}
|
||||
return d.GetMatchingNodes(context, assignmentOpNode)
|
||||
}
|
||||
|
||||
func toNodes(candidate *CandidateNode) []*yaml.Node {
|
||||
|
||||
@@ -86,14 +86,6 @@ var addOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Append another array using +=",
|
||||
document: `{a: [1,2], b: [3,4]}`,
|
||||
expression: `.a += .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Relative append",
|
||||
document: `a: { a1: {b: [cat]}, a2: {b: [dog]}, a3: {} }`,
|
||||
|
||||
@@ -9,9 +9,6 @@ func alternativeFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode,
|
||||
if lhs == nil {
|
||||
return rhs, nil
|
||||
}
|
||||
if rhs == nil {
|
||||
return lhs, nil
|
||||
}
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
log.Debugf("Alternative LHS: %v", lhs.Node.Tag)
|
||||
|
||||
@@ -5,12 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var alternativeOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `.b // .c`,
|
||||
document: `a: bridge`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `(.b // "hello") as $x`,
|
||||
@@ -19,14 +13,6 @@ var alternativeOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::a: bridge\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `.a // .b`,
|
||||
document: `a: 2`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!int)::2\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "LHS is defined",
|
||||
expression: `.a // "hello"`,
|
||||
|
||||
@@ -12,29 +12,6 @@ var specDocument = `- &CENTER { x: 1, y: 2 }
|
||||
|
||||
var expectedSpecResult = "D0, P[4], (!!map)::x: 1\ny: 2\nr: 10\n"
|
||||
|
||||
var simpleArrayRef = `
|
||||
item_value: &item_value
|
||||
value: true
|
||||
|
||||
thingOne:
|
||||
name: item_1
|
||||
<<: *item_value
|
||||
|
||||
thingTwo:
|
||||
name: item_2
|
||||
<<: *item_value
|
||||
`
|
||||
|
||||
var expectedUpdatedArrayRef = `D0, P[], (doc)::item_value: &item_value
|
||||
value: true
|
||||
thingOne:
|
||||
name: item_1
|
||||
value: false
|
||||
thingTwo:
|
||||
name: item_2
|
||||
!!merge <<: *item_value
|
||||
`
|
||||
|
||||
var anchorOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Merge one map",
|
||||
@@ -220,13 +197,6 @@ foobar:
|
||||
"D0, P[], (doc)::{f: {a: cat, b: {f: cat}, cat: {f: cat}}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Dereference and update a field",
|
||||
subdescription: "`Use explode with multiply to dereference an object",
|
||||
document: simpleArrayRef,
|
||||
expression: `.thingOne |= explode(.) * {"value": false}`,
|
||||
expected: []string{expectedUpdatedArrayRef},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAnchorAliasOperatorScenarios(t *testing.T) {
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
package yqlib
|
||||
|
||||
func assignUpdateFunc(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
lhs.UpdateFrom(rhs)
|
||||
return lhs, nil
|
||||
}
|
||||
|
||||
func assignUpdateOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
var rhs Context
|
||||
if !expressionNode.Operation.UpdateAssign {
|
||||
// this works because we already ran against LHS with an editable context.
|
||||
_, err := crossFunction(d, context.ReadOnlyClone(), expressionNode, assignUpdateFunc, false)
|
||||
return context, err
|
||||
rhs, err = d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.Rhs)
|
||||
}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
if expressionNode.Operation.UpdateAssign {
|
||||
rhs, err = d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.Rhs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
|
||||
@@ -36,14 +36,6 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::{a: {g: foof}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Double elements in an array",
|
||||
document: `[1,2,3]`,
|
||||
expression: `.[] |= . * 2`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::[2, 4, 6]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update node from another file",
|
||||
subdescription: "Note this will also work when the second file is a scalar (string/number)",
|
||||
@@ -65,7 +57,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Updated multiple paths",
|
||||
document: `{a: fieldA, b: fieldB, c: fieldC}`,
|
||||
expression: `(.a, .c) = "potatoe"`,
|
||||
expression: `(.a, .c) |= "potatoe"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: potatoe, b: fieldB, c: potatoe}\n",
|
||||
},
|
||||
|
||||
@@ -12,22 +12,6 @@ var booleanOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "b: hi",
|
||||
expression: `.a or .c`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "b: false",
|
||||
expression: `.b or .c`,
|
||||
expected: []string{
|
||||
"D0, P[b], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "b: hi",
|
||||
|
||||
@@ -52,14 +52,6 @@ var entriesOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!map)::KEY_a: 1\nKEY_b: 2\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Use with_entries to filter the map",
|
||||
document: `{a: { b: bird }, c: { d: dog }}`,
|
||||
expression: `with_entries(select(.value | has("b")))`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: {b: bird}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestEntriesOperatorScenarios(t *testing.T) {
|
||||
|
||||
@@ -5,29 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var equalsOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: ".a == .b",
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: cat`,
|
||||
expression: ".a == .b",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: cat`,
|
||||
expression: ".b == .a",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "cat",
|
||||
|
||||
@@ -32,11 +32,11 @@ func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, contex
|
||||
|
||||
var newBlank = lhs.CreateChild(nil, &yaml.Node{})
|
||||
|
||||
var newThing, err = mergeObjects(d, context.WritableClone(), newBlank, lhs, multiplyPreferences{})
|
||||
var newThing, err = mergeObjects(d, context, newBlank, lhs, multiplyPreferences{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mergeObjects(d, context.WritableClone(), newThing, rhs, preferences)
|
||||
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") {
|
||||
|
||||
@@ -47,20 +47,6 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
docExpected,
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `.x = {"things": "whatever"} * {}`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::x:\n things: whatever\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `.x = {} * {"things": "whatever"}`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::x:\n things: whatever\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `3 * 4.5`,
|
||||
|
||||
@@ -74,232 +74,6 @@ func substituteStringOperator(d *dataTreeNavigator, context Context, expressionN
|
||||
|
||||
}
|
||||
|
||||
func addMatch(original []*yaml.Node, match string, offset int, name string) []*yaml.Node {
|
||||
|
||||
newContent := append(original,
|
||||
createScalarNode("string", "string"))
|
||||
|
||||
if offset < 0 {
|
||||
// offset of -1 means there was no match, force a null value like jq
|
||||
newContent = append(newContent,
|
||||
createScalarNode(nil, "null"),
|
||||
)
|
||||
} else {
|
||||
newContent = append(newContent,
|
||||
createScalarNode(match, match),
|
||||
)
|
||||
}
|
||||
|
||||
newContent = append(newContent,
|
||||
createScalarNode("offset", "offset"),
|
||||
createScalarNode(offset, fmt.Sprintf("%v", offset)),
|
||||
createScalarNode("length", "length"),
|
||||
createScalarNode(len(match), fmt.Sprintf("%v", len(match))))
|
||||
|
||||
if name != "" {
|
||||
newContent = append(newContent,
|
||||
createScalarNode("name", "name"),
|
||||
createScalarNode(name, name),
|
||||
)
|
||||
}
|
||||
return newContent
|
||||
}
|
||||
|
||||
type matchPreferences struct {
|
||||
Global bool
|
||||
}
|
||||
|
||||
func getMatches(matchPrefs matchPreferences, regEx *regexp.Regexp, value string) ([][]string, [][]int) {
|
||||
var allMatches [][]string
|
||||
var allIndices [][]int
|
||||
|
||||
if matchPrefs.Global {
|
||||
allMatches = regEx.FindAllStringSubmatch(value, -1)
|
||||
allIndices = regEx.FindAllStringSubmatchIndex(value, -1)
|
||||
} else {
|
||||
allMatches = [][]string{regEx.FindStringSubmatch(value)}
|
||||
allIndices = [][]int{regEx.FindStringSubmatchIndex(value)}
|
||||
}
|
||||
|
||||
log.Debug("allMatches, %v", allMatches)
|
||||
return allMatches, allIndices
|
||||
}
|
||||
|
||||
func match(matchPrefs matchPreferences, regEx *regexp.Regexp, candidate *CandidateNode, value string, results *list.List) {
|
||||
subNames := regEx.SubexpNames()
|
||||
allMatches, allIndices := getMatches(matchPrefs, regEx, value)
|
||||
|
||||
// if all matches just has an empty array in it,
|
||||
// then nothing matched
|
||||
if len(allMatches) > 0 && len(allMatches[0]) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i, matches := range allMatches {
|
||||
capturesNode := &yaml.Node{Kind: yaml.SequenceNode}
|
||||
match, submatches := matches[0], matches[1:]
|
||||
for j, submatch := range submatches {
|
||||
captureNode := &yaml.Node{Kind: yaml.MappingNode}
|
||||
captureNode.Content = addMatch(capturesNode.Content, submatch, allIndices[i][2+j*2], subNames[j+1])
|
||||
capturesNode.Content = append(capturesNode.Content, captureNode)
|
||||
}
|
||||
|
||||
node := &yaml.Node{Kind: yaml.MappingNode}
|
||||
node.Content = addMatch(node.Content, match, allIndices[i][0], "")
|
||||
node.Content = append(node.Content,
|
||||
createScalarNode("captures", "captures"),
|
||||
capturesNode,
|
||||
)
|
||||
results.PushBack(candidate.CreateChild(nil, node))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func capture(matchPrefs matchPreferences, regEx *regexp.Regexp, candidate *CandidateNode, value string, results *list.List) {
|
||||
subNames := regEx.SubexpNames()
|
||||
allMatches, allIndices := getMatches(matchPrefs, regEx, value)
|
||||
|
||||
// if all matches just has an empty array in it,
|
||||
// then nothing matched
|
||||
if len(allMatches) > 0 && len(allMatches[0]) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i, matches := range allMatches {
|
||||
capturesNode := &yaml.Node{Kind: yaml.MappingNode}
|
||||
|
||||
_, submatches := matches[0], matches[1:]
|
||||
for j, submatch := range submatches {
|
||||
capturesNode.Content = append(capturesNode.Content,
|
||||
createScalarNode(subNames[j+1], subNames[j+1]))
|
||||
|
||||
offset := allIndices[i][2+j*2]
|
||||
// offset of -1 means there was no match, force a null value like jq
|
||||
if offset < 0 {
|
||||
capturesNode.Content = append(capturesNode.Content,
|
||||
createScalarNode(nil, "null"),
|
||||
)
|
||||
} else {
|
||||
capturesNode.Content = append(capturesNode.Content,
|
||||
createScalarNode(submatch, submatch),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
results.PushBack(candidate.CreateChild(nil, capturesNode))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func extractMatchArguments(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (*regexp.Regexp, matchPreferences, error) {
|
||||
regExExpNode := expressionNode.Rhs
|
||||
|
||||
matchPrefs := matchPreferences{}
|
||||
|
||||
// we got given parameters e.g. match(exp; params)
|
||||
if expressionNode.Rhs.Operation.OperationType == blockOpType {
|
||||
block := expressionNode.Rhs
|
||||
regExExpNode = block.Lhs
|
||||
replacementNodes, err := d.GetMatchingNodes(context, block.Rhs)
|
||||
if err != nil {
|
||||
return nil, matchPrefs, err
|
||||
}
|
||||
paramText := ""
|
||||
if replacementNodes.MatchingNodes.Front() != nil {
|
||||
paramText = replacementNodes.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
if strings.Contains(paramText, "g") {
|
||||
paramText = strings.ReplaceAll(paramText, "g", "")
|
||||
matchPrefs.Global = true
|
||||
}
|
||||
if strings.Contains(paramText, "i") {
|
||||
return nil, matchPrefs, fmt.Errorf(`'i' is not a valid option for match. To ignore case, use an expression like match("(?i)cat")`)
|
||||
}
|
||||
if len(paramText) > 0 {
|
||||
return nil, matchPrefs, fmt.Errorf(`Unrecognised match params '%v', please see docs at https://mikefarah.gitbook.io/yq/operators/string-operators`, paramText)
|
||||
}
|
||||
}
|
||||
|
||||
regExNodes, err := d.GetMatchingNodes(context.ReadOnlyClone(), regExExpNode)
|
||||
if err != nil {
|
||||
return nil, matchPrefs, err
|
||||
}
|
||||
log.Debug(NodesToString(regExNodes.MatchingNodes))
|
||||
regExStr := ""
|
||||
if regExNodes.MatchingNodes.Front() != nil {
|
||||
regExStr = regExNodes.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||
}
|
||||
log.Debug("regEx %v", regExStr)
|
||||
regEx, err := regexp.Compile(regExStr)
|
||||
return regEx, matchPrefs, err
|
||||
}
|
||||
|
||||
func matchOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
regEx, matchPrefs, err := extractMatchArguments(d, context, expressionNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := unwrapDoc(candidate.Node)
|
||||
if node.Tag != "!!str" {
|
||||
return Context{}, fmt.Errorf("cannot match with %v, can only match strings. Hint: Most often you'll want to use '|=' over '=' for this operation", node.Tag)
|
||||
}
|
||||
|
||||
match(matchPrefs, regEx, candidate, node.Value, results)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func captureOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
regEx, matchPrefs, err := extractMatchArguments(d, context, expressionNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := unwrapDoc(candidate.Node)
|
||||
if node.Tag != "!!str" {
|
||||
return Context{}, fmt.Errorf("cannot match with %v, can only match strings. Hint: Most often you'll want to use '|=' over '=' for this operation", node.Tag)
|
||||
}
|
||||
capture(matchPrefs, regEx, candidate, node.Value, results)
|
||||
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func testOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
regEx, _, err := extractMatchArguments(d, context, expressionNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := unwrapDoc(candidate.Node)
|
||||
if node.Tag != "!!str" {
|
||||
return Context{}, fmt.Errorf("cannot match with %v, can only match strings. Hint: Most often you'll want to use '|=' over '=' for this operation", node.Tag)
|
||||
}
|
||||
matches := regEx.FindStringSubmatch(node.Value)
|
||||
results.PushBack(createBooleanCandidate(candidate, len(matches) > 0))
|
||||
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func joinStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- joinStringOperator")
|
||||
joinStr := ""
|
||||
|
||||
@@ -13,110 +13,6 @@ var stringsOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!str)::cat; meow; 1; ; true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Match string",
|
||||
document: `foo bar foo`,
|
||||
expression: `match("foo")`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::string: foo\noffset: 0\nlength: 3\ncaptures: []\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Match string, case insensitive",
|
||||
document: `foo bar FOO`,
|
||||
expression: `[match("(?i)foo"; "g")]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- string: foo\n offset: 0\n length: 3\n captures: []\n- string: FOO\n offset: 8\n length: 3\n captures: []\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Match with capture groups",
|
||||
document: `abc abc`,
|
||||
expression: `[match("(abc)+"; "g")]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- string: abc\n offset: 0\n length: 3\n captures:\n - string: abc\n offset: 0\n length: 3\n- string: abc\n offset: 4\n length: 3\n captures:\n - string: abc\n offset: 4\n length: 3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Match with named capture groups",
|
||||
document: `foo bar foo foo foo`,
|
||||
expression: `[match("foo (?P<bar123>bar)? foo"; "g")]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- string: foo bar foo\n offset: 0\n length: 11\n captures:\n - string: bar\n offset: 4\n length: 3\n name: bar123\n- string: foo foo\n offset: 12\n length: 8\n captures:\n - string: null\n offset: -1\n length: 0\n name: bar123\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Capture named groups into a map",
|
||||
document: `xyzzy-14`,
|
||||
expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)")`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: xyzzy\nn: \"14\"\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Capture named groups into a map, with null",
|
||||
document: `xyzzy-14`,
|
||||
expression: `capture("(?P<a>[a-z]+)-(?P<n>[0-9]+)(?P<bar123>bar)?")`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::a: xyzzy\nn: \"14\"\nbar123: null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Match without global flag",
|
||||
document: `cat cat`,
|
||||
expression: `match("cat")`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::string: cat\noffset: 0\nlength: 3\ncaptures: []\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Match with global flag",
|
||||
document: `cat cat`,
|
||||
expression: `[match("cat"; "g")]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- string: cat\n offset: 0\n length: 3\n captures: []\n- string: cat\n offset: 4\n length: 3\n captures: []\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "No match",
|
||||
document: `dog`,
|
||||
expression: `match("cat"; "g")`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "No match",
|
||||
expression: `"dog" | match("cat", "g")`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "No match",
|
||||
expression: `"dog" | match("cat")`,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "Test using regex",
|
||||
subdescription: "Like jq'q equivalant, this works like match but only returns true/false instead of full match details",
|
||||
document: `["cat", "dog"]`,
|
||||
expression: `.[] | test("at")`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::true\n",
|
||||
"D0, P[1], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `["cat*", "cat*", "cat"]`,
|
||||
expression: `.[] | test("cat\*")`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::true\n",
|
||||
"D0, P[1], (!!bool)::true\n",
|
||||
"D0, P[2], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Substitute / Replace string",
|
||||
subdescription: "This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)\nNote the use of `|=` to run in context of the current string value.",
|
||||
|
||||
@@ -15,7 +15,11 @@ func createSubtractOp(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode
|
||||
}
|
||||
|
||||
func subtractAssignOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
return compoundAssignFunction(d, context, expressionNode, createSubtractOp)
|
||||
assignmentOp := &Operation{OperationType: assignOpType}
|
||||
assignmentOp.UpdateAssign = true
|
||||
selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
|
||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: expressionNode.Lhs, Rhs: createSubtractOp(selfExpression, expressionNode.Rhs)}
|
||||
return d.GetMatchingNodes(context, assignmentOpNode)
|
||||
}
|
||||
|
||||
func subtractOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
@@ -127,16 +127,9 @@ func traverseArrayIndices(context Context, matchingNode *CandidateNode, indicesT
|
||||
node := matchingNode.Node
|
||||
if node.Tag == "!!null" {
|
||||
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
|
||||
// auto vivification
|
||||
// auto vivification, make it into an empty array
|
||||
node.Tag = ""
|
||||
node.Kind = yaml.SequenceNode
|
||||
//check that the indices are numeric, if not, then we should create an object
|
||||
if len(indicesToTraverse) != 0 {
|
||||
_, err := strconv.ParseInt(indicesToTraverse[0].Value, 10, 64)
|
||||
if err != nil {
|
||||
node.Kind = yaml.MappingNode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if node.Kind == yaml.AliasNode {
|
||||
@@ -315,6 +308,6 @@ func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *C
|
||||
|
||||
func traverseArray(candidate *CandidateNode, operation *Operation, prefs traversePreferences) (*list.List, error) {
|
||||
log.Debug("operation Value %v", operation.Value)
|
||||
indices := []*yaml.Node{{Value: operation.StringValue}}
|
||||
indices := []*yaml.Node{&yaml.Node{Value: operation.StringValue}}
|
||||
return traverseArrayWithIndices(candidate, indices, prefs)
|
||||
}
|
||||
|
||||
@@ -35,14 +35,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[0 0], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `b: cat`,
|
||||
expression: ".b\n",
|
||||
expected: []string{
|
||||
"D0, P[b], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[[[1]]]`,
|
||||
@@ -51,13 +43,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[0 0 0], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `.["cat"] = "thing"`,
|
||||
expected: []string{
|
||||
"D0, P[], ()::cat: thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Simple map navigation",
|
||||
document: `{a: {b: apple}}`,
|
||||
@@ -92,14 +77,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[{}], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Nested special characters",
|
||||
document: `a: {"key.withdots": {"another.key": apple}}`,
|
||||
expression: `.a["key.withdots"]["another.key"]`,
|
||||
expected: []string{
|
||||
"D0, P[a key.withdots another.key], (!!str)::apple\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Keys with spaces",
|
||||
subdescription: "Use quotes with brackets around path elements with special characters",
|
||||
@@ -267,15 +244,6 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[0], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Traversing nested arrays by index",
|
||||
dontFormatInputForDoc: true,
|
||||
document: `[[], [cat]]`,
|
||||
expression: `.[1][0]`,
|
||||
expected: []string{
|
||||
"D0, P[1 0], (!!str)::cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Maps with numeric keys",
|
||||
document: `{2: cat}`,
|
||||
|
||||
+2
-35
@@ -10,32 +10,6 @@ import (
|
||||
|
||||
type operatorHandler func(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error)
|
||||
|
||||
type compoundCalculation func(lhs *ExpressionNode, rhs *ExpressionNode) *ExpressionNode
|
||||
|
||||
func compoundAssignFunction(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode, calculation compoundCalculation) (Context, error) {
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.Lhs)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
assignmentOp := &Operation{OperationType: assignOpType}
|
||||
valueOp := &Operation{OperationType: valueOpType}
|
||||
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
valueOp.CandidateNode = candidate
|
||||
valueExpression := &ExpressionNode{Operation: valueOp}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: valueExpression, Rhs: calculation(valueExpression, expressionNode.Rhs)}
|
||||
|
||||
_, err = d.GetMatchingNodes(context, assignmentOpNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
}
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func unwrapDoc(node *yaml.Node) *yaml.Node {
|
||||
if node.Kind == yaml.DocumentNode {
|
||||
return node.Content[0]
|
||||
@@ -57,9 +31,7 @@ func resultsForRhs(d *dataTreeNavigator, context Context, lhsCandidate *Candidat
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resultCandidate != nil {
|
||||
results.PushBack(resultCandidate)
|
||||
}
|
||||
results.PushBack(resultCandidate)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,9 +42,7 @@ func resultsForRhs(d *dataTreeNavigator, context Context, lhsCandidate *Candidat
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resultCandidate != nil {
|
||||
results.PushBack(resultCandidate)
|
||||
}
|
||||
results.PushBack(resultCandidate)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -121,12 +91,9 @@ func crossFunction(d *dataTreeNavigator, context Context, expressionNode *Expres
|
||||
}
|
||||
}
|
||||
if evaluateAllTogether {
|
||||
log.Debug("crossFunction evaluateAllTogether!")
|
||||
return doCrossFunc(d, context, expressionNode, calculation, calcWhenEmpty)
|
||||
}
|
||||
|
||||
log.Debug("crossFunction evaluate apart!")
|
||||
|
||||
for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
|
||||
innerResults, err := doCrossFunc(d, context.SingleChildContext(matchEl.Value.(*CandidateNode)), expressionNode, calculation, calcWhenEmpty)
|
||||
if err != nil {
|
||||
|
||||
@@ -65,7 +65,7 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
||||
os.Setenv("myenv", s.environmentVariable)
|
||||
}
|
||||
|
||||
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
|
||||
context, err := newDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
|
||||
|
||||
if err != nil {
|
||||
t.Error(fmt.Errorf("%v: %v", err, s.expression))
|
||||
@@ -114,7 +114,7 @@ func formatYaml(yaml string, filename string) string {
|
||||
panic(err)
|
||||
}
|
||||
streamEvaluator := NewStreamEvaluator()
|
||||
_, err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer)
|
||||
err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -251,7 +251,7 @@ func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formatt
|
||||
|
||||
}
|
||||
|
||||
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
|
||||
context, err := newDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
|
||||
if err != nil {
|
||||
t.Error(err, s.expression)
|
||||
}
|
||||
|
||||
+5
-13
@@ -11,7 +11,6 @@ import (
|
||||
type Printer interface {
|
||||
PrintResults(matchingNodes *list.List) error
|
||||
PrintedAnything() bool
|
||||
SetPrintLeadingSeperator(bool)
|
||||
}
|
||||
|
||||
type resultsPrinter struct {
|
||||
@@ -25,7 +24,7 @@ type resultsPrinter struct {
|
||||
previousDocIndex uint
|
||||
previousFileIndex int
|
||||
printedMatches bool
|
||||
treeNavigator DataTreeNavigator
|
||||
treeNavigator dataTreeNavigator
|
||||
}
|
||||
|
||||
func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer {
|
||||
@@ -37,14 +36,7 @@ func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEn
|
||||
indent: indent,
|
||||
printDocSeparators: !outputToJSON && printDocSeparators,
|
||||
firstTimePrinting: true,
|
||||
treeNavigator: NewDataTreeNavigator(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *resultsPrinter) SetPrintLeadingSeperator(printLeadingSeperator bool) {
|
||||
if printLeadingSeperator {
|
||||
p.firstTimePrinting = false
|
||||
p.previousFileIndex = -1
|
||||
treeNavigator: newDataTreeNavigator(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,14 +48,14 @@ func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error {
|
||||
p.printedMatches = p.printedMatches || (node.Tag != "!!null" &&
|
||||
(node.Tag != "!!bool" || node.Value != "false"))
|
||||
|
||||
var encoder Encoder
|
||||
var encoder encoder
|
||||
if node.Kind == yaml.ScalarNode && p.unwrapScalar && !p.outputToJSON {
|
||||
return p.writeString(writer, node.Value+"\n")
|
||||
}
|
||||
if p.outputToJSON {
|
||||
encoder = NewJsonEncoder(writer, p.indent)
|
||||
encoder = newJsonEncoder(writer, p.indent)
|
||||
} else {
|
||||
encoder = NewYamlEncoder(writer, p.indent, p.colorsEnabled)
|
||||
encoder = newYamlEncoder(writer, p.indent, p.colorsEnabled)
|
||||
}
|
||||
return encoder.Encode(node)
|
||||
}
|
||||
|
||||
@@ -17,36 +17,12 @@ a: apple
|
||||
a: coconut
|
||||
`
|
||||
|
||||
var leadingSeperatorSample = `---
|
||||
a: good doc
|
||||
`
|
||||
|
||||
func nodeToList(candidate *CandidateNode) *list.List {
|
||||
elMap := list.New()
|
||||
elMap.PushBack(candidate)
|
||||
return elMap
|
||||
}
|
||||
|
||||
func TestPrinterWithLeadingSeperator(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
var writer = bufio.NewWriter(&output)
|
||||
printer := NewPrinter(writer, false, true, false, 2, true)
|
||||
|
||||
inputs, err := readDocuments(strings.NewReader(leadingSeperatorSample), "sample.yml", 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
printer.SetPrintLeadingSeperator(true)
|
||||
|
||||
err = printer.PrintResults(inputs)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
writer.Flush()
|
||||
test.AssertResult(t, leadingSeperatorSample, output.String())
|
||||
}
|
||||
|
||||
func TestPrinterMultipleDocsInSequence(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
var writer = bufio.NewWriter(&output)
|
||||
|
||||
@@ -12,19 +12,19 @@ import (
|
||||
// Uses less memory than loading all documents and running the expression once, but this cannot process
|
||||
// cross document expressions.
|
||||
type StreamEvaluator interface {
|
||||
Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer) (uint, error)
|
||||
Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer) error
|
||||
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
||||
EvaluateNew(expression string, printer Printer) error
|
||||
}
|
||||
|
||||
type streamEvaluator struct {
|
||||
treeNavigator DataTreeNavigator
|
||||
treeNavigator dataTreeNavigator
|
||||
treeCreator ExpressionParser
|
||||
fileIndex int
|
||||
}
|
||||
|
||||
func NewStreamEvaluator() StreamEvaluator {
|
||||
return &streamEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewExpressionParser()}
|
||||
return &streamEvaluator{treeNavigator: newDataTreeNavigator(), treeCreator: NewExpressionParser()}
|
||||
}
|
||||
|
||||
func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error {
|
||||
@@ -49,42 +49,34 @@ func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error
|
||||
}
|
||||
|
||||
func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||
var totalProcessDocs uint = 0
|
||||
|
||||
node, err := s.treeCreator.ParseExpression(expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for index, filename := range filenames {
|
||||
reader, leadingSeperator, err := readStream(filename)
|
||||
if index == 0 && leadingSeperator {
|
||||
printer.SetPrintLeadingSeperator(leadingSeperator)
|
||||
}
|
||||
for _, filename := range filenames {
|
||||
reader, err := readStream(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
processedDocs, err := s.Evaluate(filename, reader, node, printer)
|
||||
err = s.Evaluate(filename, reader, node, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
totalProcessDocs = totalProcessDocs + processedDocs
|
||||
|
||||
switch reader := reader.(type) {
|
||||
case *os.File:
|
||||
safelyCloseFile(reader)
|
||||
}
|
||||
}
|
||||
|
||||
if totalProcessDocs == 0 {
|
||||
return s.EvaluateNew(expression, printer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer) (uint, error) {
|
||||
func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *ExpressionNode, printer Printer) error {
|
||||
|
||||
var currentIndex uint
|
||||
|
||||
decoder := yaml.NewDecoder(reader)
|
||||
for {
|
||||
var dataBucket yaml.Node
|
||||
@@ -92,9 +84,9 @@ func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *Expr
|
||||
|
||||
if errorReading == io.EOF {
|
||||
s.fileIndex = s.fileIndex + 1
|
||||
return currentIndex, nil
|
||||
return nil
|
||||
} else if errorReading != nil {
|
||||
return currentIndex, errorReading
|
||||
return errorReading
|
||||
}
|
||||
candidateNode := &CandidateNode{
|
||||
Document: currentIndex,
|
||||
@@ -107,11 +99,11 @@ func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *Expr
|
||||
|
||||
result, errorParsing := s.treeNavigator.GetMatchingNodes(Context{MatchingNodes: inputList}, node)
|
||||
if errorParsing != nil {
|
||||
return currentIndex, errorParsing
|
||||
return errorParsing
|
||||
}
|
||||
err := printer.PrintResults(result.MatchingNodes)
|
||||
if err != nil {
|
||||
return currentIndex, err
|
||||
return err
|
||||
}
|
||||
currentIndex = currentIndex + 1
|
||||
}
|
||||
|
||||
+3
-29
@@ -9,37 +9,11 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func readStream(filename string) (io.Reader, bool, error) {
|
||||
|
||||
func readStream(filename string) (io.Reader, error) {
|
||||
if filename == "-" {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
seperatorBytes, err := reader.Peek(3)
|
||||
|
||||
if err == io.EOF {
|
||||
// EOF are handled else where..
|
||||
return reader, false, nil
|
||||
}
|
||||
|
||||
return reader, string(seperatorBytes) == "---", err
|
||||
return bufio.NewReader(os.Stdin), nil
|
||||
} else {
|
||||
// ignore CWE-22 gosec issue - that's more targetted for http based apps that run in a public directory,
|
||||
// and ensuring that it's not possible to give a path to a file outside thar directory.
|
||||
reader, err := os.Open(filename) // #nosec
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
seperatorBytes := make([]byte, 3)
|
||||
_, err = reader.Read(seperatorBytes)
|
||||
if err == io.EOF {
|
||||
// EOF are handled else where..
|
||||
return reader, false, nil
|
||||
} else if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
_, err = reader.Seek(0, 0)
|
||||
|
||||
return reader, string(seperatorBytes) == "---", err
|
||||
return os.Open(filename) // nolint gosec
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,9 +54,6 @@ func (w *writeInPlaceHandlerImpl) FinishWriteInPlace(evaluatedSuccessfully bool)
|
||||
safelyRenameFile(w.tempFile.Name(), w.inputFilename)
|
||||
} else {
|
||||
log.Debug("removed temp file")
|
||||
removeErr := os.Remove(w.tempFile.Name())
|
||||
if removeErr != nil {
|
||||
log.Errorf("failed removing temp file: %s", w.tempFile.Name())
|
||||
}
|
||||
os.Remove(w.tempFile.Name())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
- snapcraft
|
||||
- will auto create a candidate, test it works then promote
|
||||
- !! need to update v4/stable as well as latest
|
||||
|
||||
sudo snap remove yq
|
||||
sudo snap install --edge yq
|
||||
|
||||
@@ -55,101 +55,6 @@ if [[ $? != 1 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Test: leading seperator logic"
|
||||
expected=$(cat examples/leading-seperator.yaml)
|
||||
|
||||
X=$(cat examples/leading-seperator.yaml | ./yq e '.' -)
|
||||
if [[ $X != $expected ]]; then
|
||||
echo "Pipe into e"
|
||||
echo "Expected $expected but was $X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
X=$(./yq e '.' examples/leading-seperator.yaml)
|
||||
expected=$(cat examples/leading-seperator.yaml)
|
||||
if [[ $X != $expected ]]; then
|
||||
echo "read given file e"
|
||||
echo "Expected $expected but was $X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
X=$(cat examples/leading-seperator.yaml | ./yq ea '.' -)
|
||||
if [[ $X != $expected ]]; then
|
||||
echo "Pipe into e"
|
||||
echo "Expected $expected but was $X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
X=$(./yq ea '.' examples/leading-seperator.yaml)
|
||||
expected=$(cat examples/leading-seperator.yaml)
|
||||
if [[ $X != $expected ]]; then
|
||||
echo "read given file e"
|
||||
echo "Expected $expected but was $X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# multidoc
|
||||
read -r -d '' expected << EOM
|
||||
---
|
||||
a: test
|
||||
---
|
||||
version: 3
|
||||
application: MyApp
|
||||
EOM
|
||||
|
||||
X=$(./yq e '.' examples/leading-seperator.yaml examples/order.yaml)
|
||||
|
||||
if [[ $X != $expected ]]; then
|
||||
echo "Multidoc with leading seperator"
|
||||
echo "Expected $expected but was $X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
X=$(./yq ea '.' examples/leading-seperator.yaml examples/order.yaml)
|
||||
|
||||
if [[ $X != $expected ]]; then
|
||||
echo "Multidoc with leading seperator"
|
||||
echo "Expected $expected but was $X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Test: handle empty files"
|
||||
./yq e '.' examples/empty.yaml
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Expected no error when processing empty file but got one"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat examples/empty.yaml | ./yq e '.' -
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Expected no error when processing empty stdin but got one"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# run expression against empty file
|
||||
touch temp.yaml
|
||||
expected="apple: tree"
|
||||
|
||||
./yq e '.apple = "tree"' temp.yaml -i
|
||||
X=$(cat temp.yaml)
|
||||
rm temp.yaml
|
||||
if [[ $X != $expected ]]; then
|
||||
echo "Write empty doc"
|
||||
echo "Expected $expected but was $X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
touch temp.yaml
|
||||
|
||||
./yq ea '.apple = "tree"' temp.yaml -i
|
||||
X=$(cat temp.yaml)
|
||||
rm temp.yaml
|
||||
if [[ $X != $expected ]]; then
|
||||
echo "Write all empty doc"
|
||||
echo "Expected $expected but was $X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "--success"
|
||||
|
||||
set -e
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
find . \( -path ./vendor \) -prune -o -name "*.go" -exec goimports -w {} \;
|
||||
gofmt -w -s .
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
go mod vendor
|
||||
+2
-2
@@ -5,7 +5,7 @@ set -o pipefail
|
||||
|
||||
if command -v gosec &> /dev/null
|
||||
then
|
||||
gosec ${PWD} ./...
|
||||
gosec ${PWD}
|
||||
else
|
||||
./bin/gosec ${PWD} ./...
|
||||
./bin/gosec ${PWD}
|
||||
fi
|
||||
+4
-5
@@ -4,16 +4,15 @@ set -e
|
||||
# at https://github.com/inconshreveable/gonative
|
||||
|
||||
|
||||
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}" --osarch="darwin/amd64 darwin/arm64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm linux/arm64 linux/mips linux/mips64 linux/mips64le linux/mipsle linux/ppc64 linux/ppc64le linux/s390x netbsd/386 netbsd/amd64 netbsd/arm openbsd/386 openbsd/amd64 windows/386 windows/amd64"
|
||||
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}" --osarch="darwin/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm linux/arm64 linux/mips linux/mips64 linux/mips64le linux/mipsle linux/ppc64 linux/ppc64le linux/s390x netbsd/386 netbsd/amd64 netbsd/arm openbsd/386 openbsd/amd64 windows/386 windows/amd64"
|
||||
|
||||
cd build
|
||||
|
||||
find . -executable -type f | xargs -I {} tar czvf {}.tar.gz {}
|
||||
|
||||
rhash -r -a . -o checksums
|
||||
|
||||
rhash --list-hashes > checksums_hashes_order
|
||||
|
||||
find . -executable -type f | xargs -I {} tar czvf {}.tar.gz {}
|
||||
|
||||
# just in case find thinks this is executable...
|
||||
rm -f checksums_hashes_order.tar.gz
|
||||
rm -f checksums.tar.gz
|
||||
@@ -22,4 +21,4 @@ rm yq_windows_386.exe.tar.gz
|
||||
rm yq_windows_amd64.exe.tar.gz
|
||||
|
||||
zip yq_windows_386.zip yq_windows_386.exe
|
||||
zip yq_windows_amd64.zip yq_windows_amd64.exe
|
||||
zip yq_windows_amd64.zip yq_windows_amd64.exe
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
name: yq
|
||||
version: '4.10.0'
|
||||
version: '4.9.3'
|
||||
summary: A lightweight and portable command-line YAML processor
|
||||
description: |
|
||||
The aim of the project is to be the jq or sed of yaml files.
|
||||
|
||||
+1
-3
@@ -81,9 +81,7 @@ func WriteTempYamlFile(content string) string {
|
||||
}
|
||||
|
||||
func ReadTempYamlFile(name string) string {
|
||||
// ignore CWE-22 gosec issue - that's more targetted for http based apps that run in a public directory,
|
||||
// and ensuring that it's not possible to give a path to a file outside thar directory.
|
||||
content, _ := ioutil.ReadFile(name) // #nosec
|
||||
content, _ := ioutil.ReadFile(name)
|
||||
return string(content)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user