Compare commits

..
Author SHA1 Message Date
Mike Farah ac988d9655 wip 2021-05-28 16:48:36 +10:00
67 changed files with 115 additions and 1239 deletions
-19
View File
@@ -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
View File
@@ -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"]
-24
View File
@@ -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
View File
@@ -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'
+4 -18
View File
@@ -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
+9 -15
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
-9
View File
@@ -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)
+2 -1
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -1,2 +1,6 @@
---
{"a":{"b":1}}
a:
key1: "value1"
key2: 2.6
ab:
key1: 6
key2: "h"
+2
View File
@@ -0,0 +1,2 @@
# a: apple
# b: cat
-2
View File
@@ -1,2 +0,0 @@
---
a: test
+1 -4
View File
@@ -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"]
+2 -4
View File
@@ -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
+1 -1
View File
@@ -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
)
+2 -2
View File
@@ -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=
+3 -21
View File
@@ -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)
}
-6
View File
@@ -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
}
+5 -5
View File
@@ -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
-26
View File
@@ -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
```
+1 -19
View File
@@ -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
-18
View File
@@ -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
```
-168
View File
@@ -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 -31
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+3 -3
View File
@@ -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. &, <, >
-13
View File
@@ -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)", "]"),
+1 -4
View File
@@ -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))
+1 -4
View File
@@ -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
View File
@@ -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"
+5 -1
View File
@@ -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 {
-8
View File
@@ -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: {} }`,
-3
View File
@@ -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)
-14
View File
@@ -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) {
+5 -11
View File
@@ -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
+1 -9
View File
@@ -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",
},
-16
View File
@@ -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",
-8
View File
@@ -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) {
-23
View File
@@ -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",
+2 -2
View File
@@ -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") {
-14
View File
@@ -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`,
-226
View File
@@ -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 := ""
-104
View File
@@ -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.",
+5 -1
View File
@@ -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) {
+2 -9
View File
@@ -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)
}
-32
View File
@@ -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
View File
@@ -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 {
+3 -3
View File
@@ -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
View File
@@ -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)
}
-24
View File
@@ -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)
+13 -21
View File
@@ -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
View File
@@ -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
}
}
+1 -4
View File
@@ -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())
}
}
-1
View File
@@ -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
-95
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}