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

Compare commits

..

17 Commits

Author SHA1 Message Date
Mike Farah
0a66bb797d 4 alpha2 2020-11-25 13:32:32 +11:00
Mike Farah
1ce30b25dc Add operator! 2020-11-24 13:07:19 +11:00
Mike Farah
3d6a231722 Added has operator 2020-11-24 11:38:39 +11:00
Mike Farah
3f04a1b52e Fixed empty array op 2020-11-22 13:50:32 +11:00
Mike Farah
aed598c736 Fixing docs 2020-11-22 13:16:54 +11:00
Mike Farah
e9fa873af8 path operator singular 2020-11-22 12:22:15 +11:00
Mike Farah
064cff1341 added path operator! 2020-11-22 12:19:57 +11:00
Mike Farah
fc3af441e5 Extracted out evaluators 2020-11-22 11:56:28 +11:00
Mike Farah
e451119014 Added File operators 2020-11-20 23:08:12 +11:00
Mike Farah
d38caf6bc2 Added File operators 2020-11-20 22:57:32 +11:00
Mike Farah
4e385a1b93 get file wip 2020-11-20 15:50:15 +11:00
Mike Farah
356aac5a1f fixed boolean example 2020-11-20 15:33:21 +11:00
Mike Farah
663413cd7a Fixed typo 2020-11-20 15:31:49 +11:00
Mike Farah
f03005f86d Fixed boolean ops 2020-11-20 15:29:53 +11:00
Mike Farah
bc87aca8d7 wip 2020-11-20 14:35:34 +11:00
Mike Farah
c08980e70f Set entrypoint to yq 2020-11-20 13:53:44 +11:00
Mike Farah
9674acf684 Fixed docker file, fixed doco 2020-11-19 22:53:05 +11:00
92 changed files with 1326 additions and 713 deletions

View File

@@ -22,4 +22,4 @@ LABEL version=${VERSION}
WORKDIR /workdir
ENTRYPOINT [/usr/bin/yq]
ENTRYPOINT ["/usr/bin/yq"]

View File

@@ -40,23 +40,23 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
colorsEnabled = true
}
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
switch len(args) {
case 0:
if pipingStdIn {
err = yqlib.EvaluateAllFileStreams("", []string{"-"}, printer)
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
} else {
cmd.Println(cmd.UsageString())
return nil
}
case 1:
if nullInput {
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer)
} else {
err = yqlib.EvaluateAllFileStreams("", []string{args[0]}, printer)
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
}
default:
err = yqlib.EvaluateAllFileStreams(args[0], args[1:], printer)
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
}
cmd.SilenceUsage = true

View File

@@ -41,22 +41,25 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
}
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
streamEvaluator := yqlib.NewStreamEvaluator()
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
switch len(args) {
case 0:
if pipingStdIn {
err = yqlib.EvaluateFileStreamsSequence("", []string{"-"}, printer)
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
} else {
cmd.Println(cmd.UsageString())
return nil
}
case 1:
if nullInput {
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer)
} else {
err = yqlib.EvaluateFileStreamsSequence("", []string{args[0]}, printer)
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
}
default:
err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:], printer)
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
}
cmd.SilenceUsage = true

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "4.0.0-alpha1"
Version = "4.0.0-alpha2"
// 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

View File

@@ -0,0 +1,45 @@
package yqlib
import "container/list"
/**
Loads all yaml documents of all files given into memory, then runs the given expression once.
**/
type Evaluator interface {
EvaluateFiles(expression string, filenames []string, printer Printer) error
}
type allAtOnceEvaluator struct {
treeNavigator DataTreeNavigator
treeCreator PathTreeCreator
}
func NewAllAtOnceEvaluator() Evaluator {
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
}
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
fileIndex := 0
node, err := treeCreator.ParsePath(expression)
if err != nil {
return err
}
var allDocuments *list.List = list.New()
for _, filename := range filenames {
reader, err := readStream(filename)
if err != nil {
return err
}
fileDocuments, err := readDocuments(reader, filename, fileIndex)
if err != nil {
return err
}
allDocuments.PushBackList(fileDocuments)
fileIndex = fileIndex + 1
}
matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
if err != nil {
return err
}
return printer.PrintResults(matches)
}

View File

@@ -6,14 +6,15 @@ import (
"strings"
"github.com/jinzhu/copier"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
type CandidateNode struct {
Node *yaml.Node // the actual node
Path []interface{} /// the path we took to get to this node
Document uint // the document index of this node
Filename string
Node *yaml.Node // the actual node
Path []interface{} /// the path we took to get to this node
Document uint // the document index of this node
Filename string
FileIndex int
}
func (n *CandidateNode) GetKey() string {

View File

@@ -5,17 +5,9 @@ import (
"container/list"
"gopkg.in/op/go-logging.v1"
logging "gopkg.in/op/go-logging.v1"
)
type dataTreeNavigator struct {
navigationPrefs NavigationPrefs
}
type NavigationPrefs struct {
FollowAlias bool
}
type DataTreeNavigator interface {
// given a list of CandidateEntities and a pathNode,
// this will process the list against the given pathNode and return
@@ -23,8 +15,11 @@ type DataTreeNavigator interface {
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
}
func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator {
return &dataTreeNavigator{navigationPrefs}
type dataTreeNavigator struct {
}
func NewDataTreeNavigator() DataTreeNavigator {
return &dataTreeNavigator{}
}
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {

107
pkg/yqlib/doc/Add.md Normal file
View File

@@ -0,0 +1,107 @@
Add behaves differently according to the type of the LHS:
- arrays: concatenate
- number scalars: arithmetic addition (soon)
- string scalars: concatenate (soon)
## Concatenate arrays
Given a sample.yml file of:
```yaml
a:
- 1
- 2
b:
- 3
- 4
```
then
```bash
yq eval '.a + .b' sample.yml
```
will output
```yaml
- 1
- 2
- 3
- 4
```
## Concatenate null to array
Given a sample.yml file of:
```yaml
a:
- 1
- 2
```
then
```bash
yq eval '.a + null' sample.yml
```
will output
```yaml
- 1
- 2
```
## Add object to array
Given a sample.yml file of:
```yaml
a:
- 1
- 2
c:
cat: meow
```
then
```bash
yq eval '.a + .c' sample.yml
```
will output
```yaml
- 1
- 2
- cat: meow
```
## Add string to array
Given a sample.yml file of:
```yaml
a:
- 1
- 2
```
then
```bash
yq eval '.a + "hello"' sample.yml
```
will output
```yaml
- 1
- 2
- hello
```
## Update array (append)
Given a sample.yml file of:
```yaml
a:
- 1
- 2
b:
- 3
- 4
```
then
```bash
yq eval '.a = .a + .b' sample.yml
```
will output
```yaml
a:
- 1
- 2
- 3
- 4
b:
- 3
- 4
```

View File

@@ -5,8 +5,7 @@ Which will assign the LHS node values to the RHS node values. The RHS expression
### relative form: `|=`
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
## Examples
### Update parent to be the child value
## Update node to be the child value
Given a sample.yml file of:
```yaml
a:
@@ -23,7 +22,7 @@ a:
g: foof
```
### Update to be the sibling value
## Update node to be the sibling value
Given a sample.yml file of:
```yaml
a:
@@ -40,7 +39,7 @@ a: sibling
b: sibling
```
### Updated multiple paths
## Updated multiple paths
Given a sample.yml file of:
```yaml
a: fieldA
@@ -58,7 +57,7 @@ b: fieldB
c: potatoe
```
### Update string value
## Update string value
Given a sample.yml file of:
```yaml
a:
@@ -74,7 +73,7 @@ a:
b: frog
```
### Update string value via |=
## Update string value via |=
Note there is no difference between `=` and `|=` when the RHS is a scalar
Given a sample.yml file of:
@@ -92,7 +91,7 @@ a:
b: frog
```
### Update selected results
## Update selected results
Given a sample.yml file of:
```yaml
a:
@@ -101,7 +100,7 @@ a:
```
then
```bash
yq eval '.a[] | select(. == "apple") |= "frog"' sample.yml
yq eval '.a.[] | select(. == "apple") |= "frog"' sample.yml
```
will output
```yaml
@@ -110,7 +109,7 @@ a:
c: cactus
```
### Update array values
## Update array values
Given a sample.yml file of:
```yaml
- candy
@@ -128,10 +127,10 @@ will output
- bogs
```
### Update empty object
## Update empty object
Given a sample.yml file of:
```yaml
'': null
{}
```
then
```bash
@@ -139,15 +138,13 @@ yq eval '.a.b |= "bogs"' sample.yml
```
will output
```yaml
'': null
a:
b: bogs
{a: {b: bogs}}
```
### Update empty object and array
## Update empty object and array
Given a sample.yml file of:
```yaml
'': null
{}
```
then
```bash
@@ -155,9 +152,6 @@ yq eval '.a.b[0] |= "bogs"' sample.yml
```
will output
```yaml
'': null
a:
b:
- bogs
{a: {b: [bogs]}}
```

View File

@@ -0,0 +1,113 @@
The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes.
## OR example
Running
```bash
yq eval --null-input 'true or false'
```
will output
```yaml
true
```
## AND example
Running
```bash
yq eval --null-input 'true and false'
```
will output
```yaml
false
```
## Matching nodes with select, equals and or
Given a sample.yml file of:
```yaml
- a: bird
b: dog
- a: frog
b: bird
- a: cat
b: fly
```
then
```bash
yq eval '[.[] | select(.a == "cat" or .b == "dog")]' sample.yml
```
will output
```yaml
- a: bird
b: dog
- a: cat
b: fly
```
## Not true is false
Running
```bash
yq eval --null-input 'true | not'
```
will output
```yaml
false
```
## Not false is true
Running
```bash
yq eval --null-input 'false | not'
```
will output
```yaml
true
```
## String values considered to be true
Running
```bash
yq eval --null-input '"cat" | not'
```
will output
```yaml
false
```
## Empty string value considered to be true
Running
```bash
yq eval --null-input '"" | not'
```
will output
```yaml
false
```
## Numbers are considered to be true
Running
```bash
yq eval --null-input '1 | not'
```
will output
```yaml
false
```
## Zero is considered to be true
Running
```bash
yq eval --null-input '0 | not'
```
will output
```yaml
false
```
## Null is considered to be false
Running
```bash
yq eval --null-input '~ | not'
```
will output
```yaml
true
```

View File

@@ -3,17 +3,17 @@
This creates an array using the expression between the square brackets.
## Examples
### Collect empty
## Collect empty
Running
```bash
yq eval --null-input '[]'
```
will output
```yaml
[]
```
### Collect single
## Collect single
Running
```bash
yq eval --null-input '["cat"]'
@@ -23,7 +23,7 @@ will output
- cat
```
### Collect many
## Collect many
Given a sample.yml file of:
```yaml
a: cat

View File

@@ -1,15 +1,15 @@
This is used to construct objects (or maps). This can be used against existing yaml, or to create fresh yaml documents.
## Examples
### Collect empty object
## Collect empty object
Running
```bash
yq eval --null-input '{}'
```
will output
```yaml
{}
```
### Wrap (prefix) existing object
## Wrap (prefix) existing object
Given a sample.yml file of:
```yaml
name: Mike
@@ -24,7 +24,7 @@ wrap:
name: Mike
```
### Using splat to create multiple objects
## Using splat to create multiple objects
Given a sample.yml file of:
```yaml
name: Mike
@@ -34,7 +34,7 @@ pets:
```
then
```bash
yq eval '{.name: .pets[]}' sample.yml
yq eval '{.name: .pets.[]}' sample.yml
```
will output
```yaml
@@ -42,7 +42,7 @@ Mike: cat
Mike: dog
```
### Working with multiple documents
## Working with multiple documents
Given a sample.yml file of:
```yaml
name: Mike
@@ -57,7 +57,7 @@ pets:
```
then
```bash
yq eval '{.name: .pets[]}' sample.yml
yq eval '{.name: .pets.[]}' sample.yml
```
will output
```yaml
@@ -67,7 +67,7 @@ Rosey: monkey
Rosey: sheep
```
### Creating yaml from scratch
## Creating yaml from scratch
Running
```bash
yq eval --null-input '{"wrap": "frog"}'

View File

@@ -1,6 +1,5 @@
Use these comment operators to set or retrieve comments.
## Examples
### Set line comment
## Set line comment
Given a sample.yml file of:
```yaml
a: cat
@@ -14,7 +13,7 @@ will output
a: cat # single
```
### Set head comment
## Set head comment
Given a sample.yml file of:
```yaml
a: cat
@@ -30,7 +29,7 @@ will output
a: cat
```
### Set foot comment, using an expression
## Set foot comment, using an expression
Given a sample.yml file of:
```yaml
a: cat
@@ -46,7 +45,7 @@ a: cat
# cat
```
### Remove comment
## Remove comment
Given a sample.yml file of:
```yaml
a: cat # comment
@@ -62,7 +61,7 @@ a: cat
b: dog # leave this
```
### Remove all comments
## Remove all comments
Given a sample.yml file of:
```yaml
a: cat # comment
@@ -76,7 +75,7 @@ will output
a: cat
```
### Get line comment
## Get line comment
Given a sample.yml file of:
```yaml
a: cat # meow
@@ -90,7 +89,7 @@ will output
meow
```
### Get head comment
## Get head comment
Given a sample.yml file of:
```yaml
a: cat # meow
@@ -104,7 +103,7 @@ will output
```
### Get foot comment
## Get foot comment
Given a sample.yml file of:
```yaml
a: cat # meow

View File

@@ -1,6 +1,5 @@
Deletes matching entries in maps or arrays.
## Examples
### Delete entry in map
## Delete entry in map
Given a sample.yml file of:
```yaml
a: cat
@@ -15,7 +14,7 @@ will output
a: cat
```
### Delete entry in array
## Delete entry in array
Given a sample.yml file of:
```yaml
- 1
@@ -32,7 +31,7 @@ will output
- 3
```
### Delete no matches
## Delete no matches
Given a sample.yml file of:
```yaml
a: cat
@@ -48,7 +47,7 @@ a: cat
b: dog
```
### Delete matching entries
## Delete matching entries
Given a sample.yml file of:
```yaml
a: cat

View File

@@ -1,6 +1,5 @@
## Examples
### Retrieve a document index
Use the `documentIndex` operator to select nodes of a particular document.
## Retrieve a document index
Given a sample.yml file of:
```yaml
a: cat
@@ -18,7 +17,7 @@ will output
1
```
### Filter by document index
## Filter by document index
Given a sample.yml file of:
```yaml
a: cat
@@ -34,7 +33,7 @@ will output
a: frog
```
### Print Document Index with matches
## Print Document Index with matches
Given a sample.yml file of:
```yaml
a: cat

View File

@@ -1,5 +1,3 @@
## Equals Operator
This is a boolean operator that will return ```true``` if the LHS is equal to the RHS and ``false`` otherwise.
```
@@ -13,8 +11,7 @@ select(.a == .b)
```
## Examples
### Match string
## Match string
Given a sample.yml file of:
```yaml
- cat
@@ -32,7 +29,7 @@ true
false
```
### Match number
## Match number
Given a sample.yml file of:
```yaml
- 3
@@ -50,7 +47,7 @@ true
false
```
### Match nulls
## Match nulls
Running
```bash
yq eval --null-input 'null == ~'

View File

@@ -1,6 +1,5 @@
Explodes (or dereferences) aliases and anchors.
## Examples
### Explode alias and anchor
## Explode alias and anchor
Given a sample.yml file of:
```yaml
f:
@@ -18,7 +17,7 @@ f:
b: cat
```
### Explode with no aliases or anchors
## Explode with no aliases or anchors
Given a sample.yml file of:
```yaml
a: mike
@@ -32,7 +31,7 @@ will output
a: mike
```
### Explode with alias keys
## Explode with alias keys
Given a sample.yml file of:
```yaml
f:
@@ -50,7 +49,7 @@ f:
cat: b
```
### Explode with merge anchors
## Explode with merge anchors
Given a sample.yml file of:
```yaml
foo: &foo

View File

@@ -0,0 +1,35 @@
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
```
## Get filename
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval 'filename' sample.yml
```
will output
```yaml
sample.yaml
```
## Get file index
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval 'fileIndex' sample.yml
```
will output
```yaml
0
```

44
pkg/yqlib/doc/Has.md Normal file
View File

@@ -0,0 +1,44 @@
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.
## Has map key
Given a sample.yml file of:
```yaml
- a: yes
- a: ~
- a:
- b: nope
```
then
```bash
yq eval '.[] | has("a")' sample.yml
```
will output
```yaml
true
true
true
false
```
## Has array index
Given a sample.yml file of:
```yaml
- []
- [1]
- [1, 2]
- [1, null]
- [1, 2, 3]
```
then
```bash
yq eval '.[] | has(1)' sample.yml
```
will output
```yaml
false
false
true
true
true
```

View File

@@ -1,121 +0,0 @@
# Mulitply Operator
## Examples
### Merge objects together
sample.yml:
```yaml
{a: {also: me}, b: {also: {g: wizz}}}
```
Expression
```bash
yq '. * {"a":.b}' < sample.yml
```
Result
```yaml
{a: {also: {g: wizz}}, b: {also: {g: wizz}}}
```
### Merge keeps style of LHS
sample.yml:
```yaml
a: {things: great}
b:
also: "me"
```
Expression
```bash
yq '. * {"a":.b}' < sample.yml
```
Result
```yaml
a: {things: great, also: "me"}
b:
also: "me"
```
### Merge arrays
sample.yml:
```yaml
{a: [1,2,3], b: [3,4,5]}
```
Expression
```bash
yq '. * {"a":.b}' < sample.yml
```
Result
```yaml
{a: [3, 4, 5], b: [3, 4, 5]}
```
### Merge to prefix an element
sample.yml:
```yaml
{a: cat, b: dog}
```
Expression
```bash
yq '. * {"a": {"c": .a}}' < sample.yml
```
Result
```yaml
{a: {c: cat}, b: dog}
```
### Merge with simple aliases
sample.yml:
```yaml
{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}}
```
Expression
```bash
yq '.c * .b' < sample.yml
```
Result
```yaml
{g: thongs, f: *cat}
```
### Merge does not copy anchor names
sample.yml:
```yaml
{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}
```
Expression
```bash
yq '.c * .a' < sample.yml
```
Result
```yaml
{g: thongs, c: frog}
```
### Merge with merge anchors
sample.yml:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
<<: [*foo,*bar]
c: foobarList_c
foobar:
c: foobar_c
<<: *foo
thing: foobar_thing
```
Expression
```bash
yq '.foobar * .foobarList' < sample.yml
```
Result
```yaml
c: foobarList_c
<<: [*foo, *bar]
thing: foobar_thing
b: foobarList_b
```

View File

@@ -3,8 +3,15 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
## Examples
### Merge objects together, returning merged result only
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
```
## Merge objects together, returning merged result only
Given a sample.yml file of:
```yaml
a:
@@ -27,7 +34,7 @@ fieldA: cat
fieldB: dog
```
### Merge objects together, returning parent object
## Merge objects together, returning parent object
Given a sample.yml file of:
```yaml
a:
@@ -55,12 +62,13 @@ b:
fieldB: dog
```
### Merge keeps style of LHS
## Merge keeps style of LHS
Given a sample.yml file of:
```yaml
a: {things: great}
b:
also: "me"
```
then
```bash
@@ -73,7 +81,7 @@ b:
also: "me"
```
### Merge arrays
## Merge arrays
Given a sample.yml file of:
```yaml
a:
@@ -101,7 +109,7 @@ b:
- 5
```
### Merge to prefix an element
## Merge to prefix an element
Given a sample.yml file of:
```yaml
a: cat
@@ -118,7 +126,7 @@ a:
b: dog
```
### Merge with simple aliases
## Merge with simple aliases
Given a sample.yml file of:
```yaml
a: &cat
@@ -138,7 +146,7 @@ g: thongs
f: *cat
```
### Merge does not copy anchor names
## Merge does not copy anchor names
Given a sample.yml file of:
```yaml
a:
@@ -158,7 +166,7 @@ g: thongs
c: frog
```
### Merge with merge anchors
## Merge with merge anchors
Given a sample.yml file of:
```yaml
foo: &foo

View File

@@ -1,72 +0,0 @@
This is a boolean operator and will return `true` when given a `false` value (including null), and `false` otherwise.
## Examples
### Not true is false
Running
```bash
yq eval --null-input 'true | not'
```
will output
```yaml
false
```
### Not false is true
Running
```bash
yq eval --null-input 'false | not'
```
will output
```yaml
true
```
### String values considered to be true
Running
```bash
yq eval --null-input '"cat" | not'
```
will output
```yaml
false
```
### Empty string value considered to be true
Running
```bash
yq eval --null-input '"" | not'
```
will output
```yaml
false
```
### Numbers are considered to be true
Running
```bash
yq eval --null-input '1 | not'
```
will output
```yaml
false
```
### Zero is considered to be true
Running
```bash
yq eval --null-input '0 | not'
```
will output
```yaml
false
```
### Null is considered to be false
Running
```bash
yq eval --null-input '~ | not'
```
will output
```yaml
true
```

58
pkg/yqlib/doc/Path.md Normal file
View File

@@ -0,0 +1,58 @@
The path operator can be used to get the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.
## Map path
Given a sample.yml file of:
```yaml
a:
b: cat
```
then
```bash
yq eval '.a.b | path' sample.yml
```
will output
```yaml
- a
- b
```
## Array path
Given a sample.yml file of:
```yaml
a:
- cat
- dog
```
then
```bash
yq eval '.a.[] | select(. == "dog") | path' sample.yml
```
will output
```yaml
- a
- 1
```
## Print path and value
Given a sample.yml file of:
```yaml
a:
- cat
- dog
- frog
```
then
```bash
yq eval '.a.[] | select(. == "*og") | [{"path":path, "value":.}]' sample.yml
```
will output
```yaml
- path:
- a
- 1
value: dog
- path:
- a
- 2
value: frog
```

View File

@@ -3,69 +3,7 @@ This operator recursively matches all children nodes given of a particular eleme
```bash
yq eval '.. style= "flow"' file.yaml
```
## Examples
### Map
Given a sample.yml file of:
```yaml
a:
b: apple
```
then
```bash
yq eval '..' sample.yml
```
will output
```yaml
a:
b: apple
b: apple
apple
```
### Array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq eval '..' sample.yml
```
will output
```yaml
- 1
- 2
- 3
1
2
3
```
### Array of maps
Given a sample.yml file of:
```yaml
- a: cat
- 2
- true
```
then
```bash
yq eval '..' sample.yml
```
will output
```yaml
- a: cat
- 2
- true
a: cat
cat
2
true
```
### Aliases are not traversed
## Aliases are not traversed
Given a sample.yml file of:
```yaml
a: &cat
@@ -74,20 +12,20 @@ b: *cat
```
then
```bash
yq eval '..' sample.yml
yq eval '[..]' sample.yml
```
will output
```yaml
a: &cat
- a: &cat
c: frog
b: *cat
- &cat
c: frog
b: *cat
&cat
c: frog
frog
*cat
- frog
- *cat
```
### Merge docs are not traversed
## Merge docs are not traversed
Given a sample.yml file of:
```yaml
foo: &foo
@@ -111,15 +49,15 @@ foobar:
```
then
```bash
yq eval '.foobar | ..' sample.yml
yq eval '.foobar | [..]' sample.yml
```
will output
```yaml
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
foobar_c
*foo
foobar_thing
- c: foobar_c
!!merge <<: *foo
thing: foobar_thing
- foobar_c
- *foo
- foobar_thing
```

View File

@@ -1,6 +1,5 @@
Select is used to filter arrays and maps by a boolean expression.
## Examples
### Select elements from array
## Select elements from array
Given a sample.yml file of:
```yaml
- cat
@@ -17,7 +16,7 @@ cat
goat
```
### Select and update matching values in map
## Select and update matching values in map
Given a sample.yml file of:
```yaml
a:
@@ -27,7 +26,7 @@ a:
```
then
```bash
yq eval '(.a[] | select(. == "*at")) |= "rabbit"' sample.yml
yq eval '(.a.[] | select(. == "*at")) |= "rabbit"' sample.yml
```
will output
```yaml

View File

@@ -1,6 +1,5 @@
The style operator can be used to get or set the style of nodes (e.g. string style, yaml style)
## Examples
### Set tagged style
## Set tagged style
Given a sample.yml file of:
```yaml
a: cat
@@ -21,7 +20,7 @@ c: !!float 3.2
e: !!bool true
```
### Set double quote style
## Set double quote style
Given a sample.yml file of:
```yaml
a: cat
@@ -41,7 +40,7 @@ c: "3.2"
e: "true"
```
### Set single quote style
## Set single quote style
Given a sample.yml file of:
```yaml
a: cat
@@ -61,7 +60,7 @@ c: '3.2'
e: 'true'
```
### Set literal quote style
## Set literal quote style
Given a sample.yml file of:
```yaml
a: cat
@@ -85,7 +84,7 @@ e: |-
true
```
### Set folded quote style
## Set folded quote style
Given a sample.yml file of:
```yaml
a: cat
@@ -109,7 +108,7 @@ e: >-
true
```
### Set flow quote style
## Set flow quote style
Given a sample.yml file of:
```yaml
a: cat
@@ -126,7 +125,9 @@ will output
{a: cat, b: 5, c: 3.2, e: true}
```
### Set empty (default) quote style
## Pretty print
Set empty (default) quote style
Given a sample.yml file of:
```yaml
a: cat
@@ -146,11 +147,10 @@ c: 3.2
e: true
```
### Read style
## Read style
Given a sample.yml file of:
```yaml
a: cat
b: thing
{a: "cat", b: 'thing'}
```
then
```bash
@@ -158,8 +158,8 @@ yq eval '.. | style' sample.yml
```
will output
```yaml
flow
double
single
```

View File

@@ -1,6 +1,5 @@
The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`).
## Examples
### Get tag
The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!int`, `!!bool`).
## Get tag
Given a sample.yml file of:
```yaml
a: cat
@@ -23,7 +22,7 @@ will output
!!seq
```
### Convert numbers to strings
## Convert numbers to strings
Given a sample.yml file of:
```yaml
a: cat

View File

@@ -1,6 +1,5 @@
This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
## Examples
### Simple map navigation
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
a:
@@ -15,7 +14,7 @@ will output
b: apple
```
### Splat
## Splat
Often used to pipe children into other operators
Given a sample.yml file of:
@@ -33,7 +32,23 @@ b: apple
c: banana
```
### Children don't exist
## Special characters
Use quotes around path elements with special characters
Given a sample.yml file of:
```yaml
"{}": frog
```
then
```bash
yq eval '."{}"' sample.yml
```
will output
```yaml
frog
```
## Children don't exist
Nodes are added dynamically while traversing
Given a sample.yml file of:
@@ -49,7 +64,7 @@ will output
null
```
### Wildcard matching
## Wildcard matching
Given a sample.yml file of:
```yaml
a:
@@ -66,7 +81,7 @@ apple
things
```
### Aliases
## Aliases
Given a sample.yml file of:
```yaml
a: &cat
@@ -82,7 +97,7 @@ will output
*cat
```
### Traversing aliases with splat
## Traversing aliases with splat
Given a sample.yml file of:
```yaml
a: &cat
@@ -98,7 +113,7 @@ will output
frog
```
### Traversing aliases explicitly
## Traversing aliases explicitly
Given a sample.yml file of:
```yaml
a: &cat
@@ -114,7 +129,7 @@ will output
frog
```
### Traversing arrays by index
## Traversing arrays by index
Given a sample.yml file of:
```yaml
- 1
@@ -130,7 +145,7 @@ will output
1
```
### Maps with numeric keys
## Maps with numeric keys
Given a sample.yml file of:
```yaml
2: cat
@@ -144,7 +159,7 @@ will output
cat
```
### Maps with non existing numeric keys
## Maps with non existing numeric keys
Given a sample.yml file of:
```yaml
a: b
@@ -158,7 +173,7 @@ will output
null
```
### Traversing merge anchors
## Traversing merge anchors
Given a sample.yml file of:
```yaml
foo: &foo
@@ -189,7 +204,7 @@ will output
foo_a
```
### Traversing merge anchors with override
## Traversing merge anchors with override
Given a sample.yml file of:
```yaml
foo: &foo
@@ -220,7 +235,7 @@ will output
foo_c
```
### Traversing merge anchors with local override
## Traversing merge anchors with local override
Given a sample.yml file of:
```yaml
foo: &foo
@@ -251,7 +266,7 @@ will output
foobar_thing
```
### Splatting merge anchors
## Splatting merge anchors
Given a sample.yml file of:
```yaml
foo: &foo
@@ -284,7 +299,7 @@ foo_a
foobar_thing
```
### Traversing merge anchor lists
## Traversing merge anchor lists
Note that the later merge anchors override previous
Given a sample.yml file of:
@@ -317,7 +332,7 @@ will output
bar_thing
```
### Splatting merge anchor lists
## Splatting merge anchor lists
Given a sample.yml file of:
```yaml
foo: &foo

View File

@@ -1,6 +1,5 @@
This operator is used to combine different results together.
## Examples
### Combine scalars
## Combine scalars
Running
```bash
yq eval --null-input '1, true, "cat"'
@@ -12,7 +11,7 @@ true
cat
```
### Combine selected paths
## Combine selected paths
Given a sample.yml file of:
```yaml
a: fieldA

View File

@@ -0,0 +1,4 @@
Add behaves differently according to the type of the LHS:
- arrays: concatenate
- number scalars: arithmetic addition (soon)
- string scalars: concatenate (soon)

View File

@@ -0,0 +1 @@
The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes.

View File

@@ -0,0 +1 @@
Use the `documentIndex` operator to select nodes of a particular document.

View File

@@ -1,5 +1,3 @@
## Equals Operator
This is a boolean operator that will return ```true``` if the LHS is equal to the RHS and ``false`` otherwise.
```

View File

@@ -0,0 +1,7 @@
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
```

View File

@@ -0,0 +1 @@
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.

View File

@@ -2,4 +2,11 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
## Merging files
Note the use of eval-all to ensure all documents are loaded into memory.
```bash
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
```

View File

@@ -1 +0,0 @@
This is a boolean operator and will return `true` when given a `false` value (including null), and `false` otherwise.

View File

@@ -0,0 +1 @@
The path operator can be used to get the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.

View File

@@ -1 +0,0 @@
The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`).

View File

@@ -0,0 +1 @@
The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!int`, `!!bool`).

View File

@@ -1 +0,0 @@
This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.

View File

@@ -0,0 +1 @@
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.

View File

@@ -32,7 +32,7 @@ func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
writer := bufio.NewWriter(&output)
var jsonEncoder = NewJsonEncoder(writer, 2)
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml")
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
if err != nil {
panic(err)
}

View File

@@ -17,9 +17,6 @@ type OperationType struct {
}
// operators TODO:
// - get path operator (like doc index)
// - get file index op (like doc index)
// - get file name op (like doc index)
// - write in place
// - mergeAppend (merges and appends arrays)
// - mergeEmpty (sets only if the document is empty, do I do that now?)
@@ -39,7 +36,8 @@ var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 4
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator}
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
@@ -51,6 +49,9 @@ var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Han
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
@@ -66,6 +67,7 @@ var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}

69
pkg/yqlib/operator_add.go Normal file
View File

@@ -0,0 +1,69 @@
package yqlib
import (
"fmt"
"container/list"
yaml "gopkg.in/yaml.v3"
)
func toNodes(candidates *list.List) []*yaml.Node {
if candidates.Len() == 0 {
return []*yaml.Node{}
}
candidate := candidates.Front().Value.(*CandidateNode)
if candidate.Node.Tag == "!!null" {
return []*yaml.Node{}
}
switch candidate.Node.Kind {
case yaml.SequenceNode:
return candidate.Node.Content
default:
return []*yaml.Node{candidate.Node}
}
}
func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("Add operator")
var results = list.New()
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
for el := lhs.Front(); el != nil; el = el.Next() {
lhsCandidate := el.Value.(*CandidateNode)
lhsNode := UnwrapDoc(lhsCandidate.Node)
var newBlank = &CandidateNode{
Path: lhsCandidate.Path,
Document: lhsCandidate.Document,
Filename: lhsCandidate.Filename,
Node: &yaml.Node{},
}
switch lhsNode.Kind {
case yaml.MappingNode:
return nil, fmt.Errorf("Maps not yet supported for addition")
case yaml.SequenceNode:
newBlank.Node.Kind = yaml.SequenceNode
newBlank.Node.Style = lhsNode.Style
newBlank.Node.Tag = "!!seq"
newBlank.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
results.PushBack(newBlank)
case yaml.ScalarNode:
return nil, fmt.Errorf("Scalars not yet supported for addition")
}
}
return results, nil
}

View File

@@ -0,0 +1,55 @@
package yqlib
import (
"testing"
)
var addOperatorScenarios = []expressionScenario{
{
description: "Concatenate arrays",
document: `{a: [1,2], b: [3,4]}`,
expression: `.a + .b`,
expected: []string{
"D0, P[a], (!!seq)::[1, 2, 3, 4]\n",
},
},
{
description: "Concatenate null to array",
document: `{a: [1,2]}`,
expression: `.a + null`,
expected: []string{
"D0, P[a], (!!seq)::[1, 2]\n",
},
},
{
description: "Add object to array",
document: `{a: [1,2], c: {cat: meow}}`,
expression: `.a + .c`,
expected: []string{
"D0, P[a], (!!seq)::[1, 2, {cat: meow}]\n",
},
},
{
description: "Add string to array",
document: `{a: [1,2]}`,
expression: `.a + "hello"`,
expected: []string{
"D0, P[a], (!!seq)::[1, 2, hello]\n",
},
},
{
description: "Update array (append)",
document: `{a: [1,2], b: [3,4]}`,
expression: `.a = .a + .b`,
expected: []string{
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
},
},
}
func TestAddOperatorScenarios(t *testing.T) {
for _, tt := range addOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Add", addOperatorScenarios)
}

View File

@@ -6,7 +6,7 @@ import (
var assignOperatorScenarios = []expressionScenario{
{
description: "Update parent to be the child value",
description: "Update node to be the child value",
document: `{a: {b: {g: foof}}}`,
expression: `.a |= .b`,
expected: []string{
@@ -14,7 +14,7 @@ var assignOperatorScenarios = []expressionScenario{
},
},
{
description: "Update to be the sibling value",
description: "Update node to be the sibling value",
document: `{a: {b: child}, b: sibling}`,
expression: `.a = .b`,
expected: []string{
@@ -73,7 +73,7 @@ var assignOperatorScenarios = []expressionScenario{
{
description: "Update selected results",
document: `{a: {b: apple, c: cactus}}`,
expression: `.a[] | select(. == "apple") |= "frog"`,
expression: `.a.[] | select(. == "apple") |= "frog"`,
expected: []string{
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
},
@@ -87,17 +87,19 @@ var assignOperatorScenarios = []expressionScenario{
},
},
{
description: "Update empty object",
document: `{}`,
expression: `.a.b |= "bogs"`,
description: "Update empty object",
dontFormatInputForDoc: true,
document: `{}`,
expression: `.a.b |= "bogs"`,
expected: []string{
"D0, P[], (doc)::{a: {b: bogs}}\n",
},
},
{
description: "Update empty object and array",
document: `{}`,
expression: `.a.b[0] |= "bogs"`,
description: "Update empty object and array",
dontFormatInputForDoc: true,
document: `{}`,
expression: `.a.b[0] |= "bogs"`,
expected: []string{
"D0, P[], (doc)::{a: {b: [bogs]}}\n",
},
@@ -116,5 +118,5 @@ func TestAssignOperatorScenarios(t *testing.T) {
for _, tt := range assignOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Assign Operator", assignOperatorScenarios)
documentScenarios(t, "Assign", assignOperatorScenarios)
}

View File

@@ -3,7 +3,7 @@ package yqlib
import (
"container/list"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
func isTruthy(c *CandidateNode) (bool, error) {
@@ -25,9 +25,42 @@ func isTruthy(c *CandidateNode) (bool, error) {
type boolOp func(bool, bool) bool
func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
lhsCandidate := lhsChild.Value.(*CandidateNode)
lhsTrue, errDecoding := isTruthy(lhsCandidate)
if errDecoding != nil {
return errDecoding
}
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
rhsCandidate := rhsChild.Value.(*CandidateNode)
rhsTrue, errDecoding := isTruthy(rhsCandidate)
if errDecoding != nil {
return errDecoding
}
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
results.PushBack(boolResult)
}
}
return nil
}
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
var results = list.New()
if matchingNodes.Len() == 0 {
lhs, err := d.GetMatchingNodes(list.New(), pathNode.Lhs)
if err != nil {
return nil, err
}
rhs, err := d.GetMatchingNodes(list.New(), pathNode.Rhs)
if err != nil {
return nil, err
}
return results, performBoolOp(results, lhs, rhs, op)
}
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
@@ -39,23 +72,9 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTre
return nil, err
}
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
lhsCandidate := lhsChild.Value.(*CandidateNode)
lhsTrue, errDecoding := isTruthy(lhsCandidate)
if errDecoding != nil {
return nil, errDecoding
}
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
rhsCandidate := rhsChild.Value.(*CandidateNode)
rhsTrue, errDecoding := isTruthy(rhsCandidate)
if errDecoding != nil {
return nil, errDecoding
}
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
results.PushBack(boolResult)
}
err = performBoolOp(results, lhs, rhs, op)
if err != nil {
return nil, err
}
}
@@ -75,3 +94,20 @@ func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
return b1 && b2
})
}
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- notOperation")
var results = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debug("notOperation checking %v", candidate)
truthy, errDecoding := isTruthy(candidate)
if errDecoding != nil {
return nil, errDecoding
}
result := createBooleanCandidate(candidate, !truthy)
results.PushBack(result)
}
return results, nil
}

View File

@@ -6,18 +6,36 @@ import (
var booleanOperatorScenarios = []expressionScenario{
{
document: `{}`,
expression: `true or false`,
description: "OR example",
expression: `true or false`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
}, {
document: `{}`,
},
{
description: "AND example",
expression: `true and false`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
document: "[{a: bird, b: dog}, {a: frog, b: bird}, {a: cat, b: fly}]",
description: "Matching nodes with select, equals and or",
expression: `[.[] | select(.a == "cat" or .b == "dog")]`,
expected: []string{
"D0, P[], (!!seq)::- {a: bird, b: dog}\n- {a: cat, b: fly}\n",
},
},
{
skipDoc: true,
expression: `false or false`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
}, {
},
{
skipDoc: true,
document: `{a: true, b: false}`,
expression: `.[] or (false, true)`,
expected: []string{
@@ -27,10 +45,61 @@ var booleanOperatorScenarios = []expressionScenario{
"D0, P[b], (!!bool)::true\n",
},
},
{
description: "Not true is false",
expression: `true | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Not false is true",
expression: `false | not`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{
description: "String values considered to be true",
expression: `"cat" | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Empty string value considered to be true",
expression: `"" | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Numbers are considered to be true",
expression: `1 | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Zero is considered to be true",
expression: `0 | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Null is considered to be false",
expression: `~ | not`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
}
func TestBooleanOperatorScenarios(t *testing.T) {
for _, tt := range booleanOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Boolean Operators", booleanOperatorScenarios)
}

View File

@@ -3,12 +3,18 @@ package yqlib
import (
"container/list"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- collectOperation")
if matchMap.Len() == 0 {
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
candidate := &CandidateNode{Node: node}
return nodeToMap(candidate), nil
}
var results = list.New()
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}

View File

@@ -2,6 +2,8 @@ package yqlib
import (
"container/list"
yaml "gopkg.in/yaml.v3"
)
/*
@@ -19,7 +21,9 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
log.Debugf("-- collectObjectOperation")
if matchMap.Len() == 0 {
return list.New(), nil
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"}
candidate := &CandidateNode{Node: node}
return nodeToMap(candidate), nil
}
first := matchMap.Front().Value.(*CandidateNode)
var rotated []*list.List = make([]*list.List, len(first.Node.Content))

View File

@@ -9,7 +9,9 @@ var collectObjectOperatorScenarios = []expressionScenario{
description: `Collect empty object`,
document: ``,
expression: `{}`,
expected: []string{},
expected: []string{
"D0, P[], (!!map)::{}\n",
},
},
{
description: `Wrap (prefix) existing object`,
@@ -39,7 +41,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
{
description: `Using splat to create multiple objects`,
document: `{name: Mike, pets: [cat, dog]}`,
expression: `{.name: .pets[]}`,
expression: `{.name: .pets.[]}`,
expected: []string{
"D0, P[], (!!map)::Mike: cat\n",
"D0, P[], (!!map)::Mike: dog\n",
@@ -49,7 +51,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
description: `Working with multiple documents`,
dontFormatInputForDoc: false,
document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}",
expression: `{.name: .pets[]}`,
expression: `{.name: .pets.[]}`,
expected: []string{
"D0, P[], (!!map)::Mike: cat\n",
"D0, P[], (!!map)::Mike: dog\n",
@@ -60,7 +62,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
expression: `{.name: .pets[], "f":.food[]}`,
expression: `{.name: .pets.[], "f":.food.[]}`,
expected: []string{
"D0, P[], (!!map)::Mike: cat\nf: hotdog\n",
"D0, P[], (!!map)::Mike: cat\nf: burger\n",

View File

@@ -9,7 +9,9 @@ var collectOperatorScenarios = []expressionScenario{
description: "Collect empty",
document: ``,
expression: `[]`,
expected: []string{},
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
description: "Collect single",
@@ -52,7 +54,7 @@ var collectOperatorScenarios = []expressionScenario{
},
{
document: `a: {b: [1,2,3]}`,
expression: `[.a.b[]]`,
expression: `[.a.b.[]]`,
skipDoc: true,
expected: []string{
"D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n",

View File

@@ -75,5 +75,5 @@ func TestCommentOperatorScenarios(t *testing.T) {
for _, tt := range commentOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Comments Operator", commentOperatorScenarios)
documentScenarios(t, "Comment Operators", commentOperatorScenarios)
}

View File

@@ -21,14 +21,14 @@ var createMapOperatorScenarios = []expressionScenario{
},
{
document: `{name: Mike, pets: [cat, dog]}`,
expression: `.name: .pets[]`,
expression: `.name: .pets.[]`,
expected: []string{
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
},
},
{
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
expression: `.name: .pets[], "f":.food[]`,
expression: `.name: .pets.[], "f":.food.[]`,
expected: []string{
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n",
@@ -36,7 +36,7 @@ var createMapOperatorScenarios = []expressionScenario{
},
{
document: "{name: Mike, pets: [cat, dog], food: [hotdog, burger]}\n---\n{name: Fred, pets: [mouse], food: [pizza, onion, apple]}",
expression: `.name: .pets[], "f":.food[]`,
expression: `.name: .pets.[], "f":.food.[]`,
expected: []string{
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n",
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n",

View File

@@ -43,5 +43,5 @@ func TestDeleteOperatorScenarios(t *testing.T) {
for _, tt := range deleteOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Delete Operator", deleteOperatorScenarios)
documentScenarios(t, "Delete", deleteOperatorScenarios)
}

View File

@@ -37,5 +37,5 @@ func TestDocumentIndexScenarios(t *testing.T) {
for _, tt := range documentIndexScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Document Index Operator", documentIndexScenarios)
documentScenarios(t, "Document Index", documentIndexScenarios)
}

View File

@@ -54,5 +54,5 @@ func TestEqualOperatorScenarios(t *testing.T) {
for _, tt := range equalsOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Equals Operator", equalsOperatorScenarios)
documentScenarios(t, "Equals", equalsOperatorScenarios)
}

View File

@@ -86,5 +86,5 @@ func TestExplodeOperatorScenarios(t *testing.T) {
for _, tt := range explodeTest {
testScenario(t, &tt)
}
documentScenarios(t, "Explode Operator", explodeTest)
documentScenarios(t, "Explode", explodeTest)
}

View File

@@ -0,0 +1,38 @@
package yqlib
import (
"container/list"
"fmt"
yaml "gopkg.in/yaml.v3"
)
func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetFilename")
var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}
func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetFileIndex")
var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}

View File

@@ -0,0 +1,31 @@
package yqlib
import (
"testing"
)
var fileOperatorScenarios = []expressionScenario{
{
description: "Get filename",
document: `{a: cat}`,
expression: `filename`,
expected: []string{
"D0, P[], (!!str)::sample.yml\n",
},
},
{
description: "Get file index",
document: `{a: cat}`,
expression: `fileIndex`,
expected: []string{
"D0, P[], (!!int)::0\n",
},
},
}
func TestFileOperatorsScenarios(t *testing.T) {
for _, tt := range fileOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "File Operators", fileOperatorScenarios)
}

53
pkg/yqlib/operator_has.go Normal file
View File

@@ -0,0 +1,53 @@
package yqlib
import (
"container/list"
"strconv"
yaml "gopkg.in/yaml.v3"
)
func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- hasOperation")
var results = list.New()
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
wanted := rhs.Front().Value.(*CandidateNode).Node
wantedKey := wanted.Value
if err != nil {
return nil, err
}
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
// grab the first value
var contents = candidate.Node.Content
switch candidate.Node.Kind {
case yaml.MappingNode:
candidateHasKey := false
for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 {
key := contents[index]
if key.Value == wantedKey {
candidateHasKey = true
}
}
results.PushBack(createBooleanCandidate(candidate, candidateHasKey))
case yaml.SequenceNode:
candidateHasKey := false
if wanted.Tag == "!!int" {
var number, errParsingInt = strconv.ParseInt(wantedKey, 10, 64) // nolint
if errParsingInt != nil {
return nil, errParsingInt
}
candidateHasKey = int64(len(contents)) > number
}
results.PushBack(createBooleanCandidate(candidate, candidateHasKey))
default:
results.PushBack(createBooleanCandidate(candidate, false))
}
}
return results, nil
}

View File

@@ -0,0 +1,48 @@
package yqlib
import (
"testing"
)
var hasOperatorScenarios = []expressionScenario{
{
description: "Has map key",
document: `- a: "yes"
- a: ~
- a:
- b: nope
`,
expression: `.[] | has("a")`,
expected: []string{
"D0, P[0], (!!bool)::true\n",
"D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::true\n",
"D0, P[3], (!!bool)::false\n",
},
},
{
dontFormatInputForDoc: true,
description: "Has array index",
document: `- []
- [1]
- [1, 2]
- [1, null]
- [1, 2, 3]
`,
expression: `.[] | has(1)`,
expected: []string{
"D0, P[0], (!!bool)::false\n",
"D0, P[1], (!!bool)::false\n",
"D0, P[2], (!!bool)::true\n",
"D0, P[3], (!!bool)::true\n",
"D0, P[4], (!!bool)::true\n",
},
},
}
func TestHasOperatorScenarios(t *testing.T) {
for _, tt := range hasOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Has", hasOperatorScenarios)
}

View File

@@ -5,7 +5,7 @@ import (
"container/list"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
@@ -15,12 +15,14 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
if err != nil {
return nil, err
}
log.Debugf("crossFunction LHS len: %v", lhs.Len())
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
log.Debugf("crossFunction RHS len: %v", rhs.Len())
var results = list.New()
@@ -28,6 +30,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
lhsCandidate := el.Value.(*CandidateNode)
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
log.Debugf("Applying calc")
rhsCandidate := rightEl.Value.(*CandidateNode)
resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
if err != nil {
@@ -48,8 +51,8 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
lhs.Node = UnwrapDoc(lhs.Node)
rhs.Node = UnwrapDoc(rhs.Node)
log.Debugf("Multipling LHS: %v", NodeToString(lhs))
log.Debugf("- RHS: %v", NodeToString(rhs))
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
log.Debugf("- RHS: %v", rhs.Node.Tag)
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
@@ -67,7 +70,7 @@ func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Ca
return mergeObjects(d, newThing, rhs)
}
return nil, fmt.Errorf("Cannot multiply %v with %v", NodeToString(lhs), NodeToString(rhs))
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
}
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {

View File

@@ -130,5 +130,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) {
for _, tt := range multiplyOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Multiply Operator", multiplyOperatorScenarios)
documentScenarios(t, "Multiply", multiplyOperatorScenarios)
}

View File

@@ -1,20 +0,0 @@
package yqlib
import "container/list"
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("-- notOperation")
var results = list.New()
for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debug("notOperation checking %v", candidate)
truthy, errDecoding := isTruthy(candidate)
if errDecoding != nil {
return nil, errDecoding
}
result := createBooleanCandidate(candidate, !truthy)
results.PushBack(result)
}
return results, nil
}

View File

@@ -1,65 +0,0 @@
package yqlib
import (
"testing"
)
var notOperatorScenarios = []expressionScenario{
{
description: "Not true is false",
expression: `true | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Not false is true",
expression: `false | not`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
{
description: "String values considered to be true",
expression: `"cat" | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Empty string value considered to be true",
expression: `"" | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Numbers are considered to be true",
expression: `1 | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Zero is considered to be true",
expression: `0 | not`,
expected: []string{
"D0, P[], (!!bool)::false\n",
},
},
{
description: "Null is considered to be false",
expression: `~ | not`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},
},
}
func TestNotOperatorScenarios(t *testing.T) {
for _, tt := range notOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Not Operator", notOperatorScenarios)
}

View File

@@ -0,0 +1,39 @@
package yqlib
import (
"container/list"
"fmt"
yaml "gopkg.in/yaml.v3"
)
func createPathNodeFor(pathElement interface{}) *yaml.Node {
switch pathElement := pathElement.(type) {
case string:
return &yaml.Node{Kind: yaml.ScalarNode, Value: pathElement, Tag: "!!str"}
default:
return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", pathElement), Tag: "!!int"}
}
}
func GetPathOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetPath")
var results = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
content := make([]*yaml.Node, len(candidate.Path))
for pathIndex := 0; pathIndex < len(candidate.Path); pathIndex++ {
path := candidate.Path[pathIndex]
content[pathIndex] = createPathNodeFor(path)
}
node.Content = content
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}

View File

@@ -0,0 +1,45 @@
package yqlib
import (
"testing"
)
var pathOperatorScenarios = []expressionScenario{
{
description: "Map path",
document: `{a: {b: cat}}`,
expression: `.a.b | path`,
expected: []string{
"D0, P[a b], (!!seq)::- a\n- b\n",
},
},
{
description: "Array path",
document: `{a: [cat, dog]}`,
expression: `.a.[] | select(. == "dog") | path`,
expected: []string{
"D0, P[a 1], (!!seq)::- a\n- 1\n",
},
},
{
description: "Print path and value",
document: `{a: [cat, dog, frog]}`,
expression: `.a.[] | select(. == "*og") | [{"path":path, "value":.}]`,
expected: []string{`D0, P[], (!!seq)::- path:
- a
- 1
value: dog
- path:
- a
- 2
value: frog
`},
},
}
func TestPathOperatorsScenarios(t *testing.T) {
for _, tt := range pathOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Path", pathOperatorScenarios)
}

View File

@@ -23,9 +23,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
description: "Map",
document: `{a: {b: apple}}`,
expression: `..`,
skipDoc: true,
document: `{a: {b: apple}}`,
expression: `..`,
expected: []string{
"D0, P[], (!!map)::{a: {b: apple}}\n",
"D0, P[a], (!!map)::{b: apple}\n",
@@ -33,9 +33,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
description: "Array",
document: `[1,2,3]`,
expression: `..`,
skipDoc: true,
document: `[1,2,3]`,
expression: `..`,
expected: []string{
"D0, P[], (!!seq)::[1, 2, 3]\n",
"D0, P[0], (!!int)::1\n",
@@ -44,9 +44,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
description: "Array of maps",
document: `[{a: cat},2,true]`,
expression: `..`,
skipDoc: true,
document: `[{a: cat},2,true]`,
expression: `..`,
expected: []string{
"D0, P[], (!!seq)::[{a: cat}, 2, true]\n",
"D0, P[0], (!!map)::{a: cat}\n",
@@ -58,23 +58,17 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
{
description: "Aliases are not traversed",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `..`,
expression: `[..]`,
expected: []string{
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
"D0, P[a], (!!map)::&cat {c: frog}\n",
"D0, P[a c], (!!str)::frog\n",
"D0, P[b], (alias)::*cat\n",
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
},
},
{
description: "Merge docs are not traversed",
document: mergeDocSample,
expression: `.foobar | ..`,
expression: `.foobar | [..]`,
expected: []string{
"D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n",
"D0, P[foobar c], (!!str)::foobar_c\n",
"D0, P[foobar <<], (alias)::*foo\n",
"D0, P[foobar thing], (!!str)::foobar_thing\n",
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
},
},
{
@@ -96,5 +90,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) {
for _, tt := range recursiveDescentOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Recursive Descent Operator", recursiveDescentOperatorScenarios)
documentScenarios(t, "Recursive Descent", recursiveDescentOperatorScenarios)
}

View File

@@ -23,7 +23,7 @@ var selectOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `a: [cat,goat,dog]`,
expression: `.a[] | select(. == "*at")`,
expression: `.a.[] | select(. == "*at")`,
expected: []string{
"D0, P[a 0], (!!str)::cat\n",
"D0, P[a 1], (!!str)::goat\n"},
@@ -31,7 +31,7 @@ var selectOperatorScenarios = []expressionScenario{
{
description: "Select and update matching values in map",
document: `a: { things: cat, bob: goat, horse: dog }`,
expression: `(.a[] | select(. == "*at")) |= "rabbit"`,
expression: `(.a.[] | select(. == "*at")) |= "rabbit"`,
expected: []string{
"D0, P[], (doc)::a: {things: rabbit, bob: rabbit, horse: dog}\n",
},
@@ -39,7 +39,7 @@ var selectOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`,
expression: `.a[] | select(.include)`,
expression: `.a.[] | select(.include)`,
expected: []string{
"D0, P[a things], (!!map)::{include: true}\n",
"D0, P[a andMe], (!!map)::{include: fold}\n",
@@ -59,5 +59,5 @@ func TestSelectOperatorScenarios(t *testing.T) {
for _, tt := range selectOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Select Operator", selectOperatorScenarios)
documentScenarios(t, "Select", selectOperatorScenarios)
}

View File

@@ -70,9 +70,10 @@ e: >-
},
},
{
description: "Set empty (default) quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style=""`,
description: "Pretty print",
subdescription: "Set empty (default) quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style=""`,
expected: []string{
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
},
@@ -86,9 +87,10 @@ e: >-
},
},
{
description: "Read style",
document: `{a: "cat", b: 'thing'}`,
expression: `.. | style`,
description: "Read style",
document: `{a: "cat", b: 'thing'}`,
dontFormatInputForDoc: true,
expression: `.. | style`,
expected: []string{
"D0, P[], (!!str)::flow\n",
"D0, P[a], (!!str)::double\n",
@@ -110,5 +112,5 @@ func TestStyleOperatorScenarios(t *testing.T) {
for _, tt := range styleOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Style Operator", styleOperatorScenarios)
documentScenarios(t, "Style", styleOperatorScenarios)
}

View File

@@ -32,5 +32,5 @@ func TestTagOperatorScenarios(t *testing.T) {
for _, tt := range tagOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Tag Operator", tagOperatorScenarios)
documentScenarios(t, "Tag", tagOperatorScenarios)
}

View File

@@ -45,6 +45,15 @@ var traversePathOperatorScenarios = []expressionScenario{
"D0, P[1], (!!map)::{c: banana}\n",
},
},
{
description: "Special characters",
subdescription: "Use quotes around path elements with special characters",
document: `{"{}": frog}`,
expression: `."{}"`,
expected: []string{
"D0, P[{}], (!!str)::frog\n",
},
},
{
description: "Children don't exist",
subdescription: "Nodes are added dynamically while traversing",
@@ -271,5 +280,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) {
for _, tt := range traversePathOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Traverse Operator", traversePathOperatorScenarios)
documentScenarios(t, "Traverse", traversePathOperatorScenarios)
}

View File

@@ -29,5 +29,5 @@ func TestUnionOperatorScenarios(t *testing.T) {
for _, tt := range unionOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Union Operator", unionOperatorScenarios)
documentScenarios(t, "Union", unionOperatorScenarios)
}

View File

@@ -29,13 +29,13 @@ func testScenario(t *testing.T, s *expressionScenario) {
node, err := treeCreator.ParsePath(s.expression)
if err != nil {
t.Error(err)
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
return
}
inputs := list.New()
if s.document != "" {
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml")
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
if err != nil {
t.Error(err)
return
@@ -90,7 +90,8 @@ func formatYaml(yaml string) string {
if err != nil {
panic(err)
}
err = EvaluateStream("sample.yaml", strings.NewReader(yaml), node, printer)
streamEvaluator := NewStreamEvaluator()
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(yaml), node, printer)
if err != nil {
panic(err)
}
@@ -113,17 +114,13 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
}
w := bufio.NewWriter(f)
writeOrPanic(w, "\n")
writeOrPanic(w, "\n## Examples\n")
for index, s := range scenarios {
for _, s := range scenarios {
if !s.skipDoc {
if s.description != "" {
writeOrPanic(w, fmt.Sprintf("### %v\n", s.description))
} else {
writeOrPanic(w, fmt.Sprintf("### Example %v\n", index))
}
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
@@ -131,7 +128,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
formattedDoc := ""
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document
formattedDoc = s.document + "\n"
} else {
formattedDoc = formatYaml(s.document)
}
@@ -161,12 +158,14 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
if err != nil {
t.Error(err)
}
err = EvaluateStream("sample.yaml", strings.NewReader(formattedDoc), node, printer)
streamEvaluator := NewStreamEvaluator()
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
if err != nil {
t.Error(err)
}
} else {
err = EvaluateAllFileStreams(s.expression, []string{}, printer)
allAtOnceEvaluator := NewAllAtOnceEvaluator()
err = allAtOnceEvaluator.EvaluateFiles(s.expression, []string{}, printer)
if err != nil {
t.Error(err)
}

View File

@@ -37,8 +37,18 @@ var pathTests = []struct {
// {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")},
// {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")},
// {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")},
// {`.a[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")},
// {`.a.[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")},
// {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")},
// {
// `["cat"]`,
// append(make([]interface{}, 0), "[", "cat (string)", "]"),
// append(make([]interface{}, 0), "cat (string)", "COLLECT", "PIPE"),
// },
{
`[]`,
append(make([]interface{}, 0), "[", "]"),
append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"),
},
{
`d0.a`,
append(make([]interface{}, 0), "d0", "PIPE", "a"),
@@ -85,7 +95,7 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"),
},
{
`{.a: .c, .b[]: .f.g[]}`,
`{.a: .c, .b.[]: .f.g.[]}`,
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"),
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"),
},

View File

@@ -3,7 +3,7 @@ package yqlib
import (
"errors"
"gopkg.in/op/go-logging.v1"
logging "gopkg.in/op/go-logging.v1"
)
type PathPostFixer interface {

View File

@@ -33,6 +33,7 @@ type Token struct {
func (t *Token) toString() string {
if t.TokenType == OperationToken {
log.Debug("toString, its an op")
return t.Operation.toString()
} else if t.TokenType == OpenBracket {
return "("
@@ -58,13 +59,7 @@ func pathToken(wrapped bool) lex.Action {
if wrapped {
value = unwrap(value)
}
op := &Operation{OperationType: TraversePath, Value: value, StringValue: value}
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
}
}
func literalPathToken(value string) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("PathToken %v", value)
op := &Operation{OperationType: TraversePath, Value: value, StringValue: value}
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
}
@@ -78,6 +73,7 @@ func documentToken() lex.Action {
if errParsingInt != nil {
return nil, errParsingInt
}
log.Debug("documentToken %v", string(m.Bytes))
op := &Operation{OperationType: DocumentFilter, Value: number, StringValue: numberString}
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
}
@@ -93,6 +89,7 @@ func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.A
func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences}
var assign *Operation
@@ -187,15 +184,17 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\(`), literalToken(OpenBracket, false))
lexer.Add([]byte(`\)`), literalToken(CloseBracket, true))
lexer.Add([]byte(`\.?\[\]`), literalPathToken("[]"))
lexer.Add([]byte(`\.\[\]`), pathToken(false))
lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent))
lexer.Add([]byte(`,`), opToken(Union))
lexer.Add([]byte(`:\s*`), opToken(CreateMap))
lexer.Add([]byte(`length`), opToken(Length))
lexer.Add([]byte(`select`), opToken(Select))
lexer.Add([]byte(`has`), opToken(Has))
lexer.Add([]byte(`explode`), opToken(Explode))
lexer.Add([]byte(`or`), opToken(Or))
lexer.Add([]byte(`and`), opToken(And))
lexer.Add([]byte(`not`), opToken(Not))
lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))
@@ -203,6 +202,9 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle))
lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag))
lexer.Add([]byte(`filename`), opToken(GetFilename))
lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex))
lexer.Add([]byte(`path`), opToken(GetPath))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true}))
@@ -250,6 +252,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false))
lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true))
lexer.Add([]byte(`\*`), opToken(Multiply))
lexer.Add([]byte(`\+`), opToken(Add))
err := lexer.Compile()
if err != nil {

View File

@@ -21,7 +21,7 @@ func TestPrinterMultipleDocsInSequence(t *testing.T) {
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml")
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}
@@ -60,7 +60,7 @@ func TestPrinterMultipleDocsInSinglePrint(t *testing.T) {
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, false, true, false, 2, true)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml")
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}
@@ -79,7 +79,7 @@ func TestPrinterMultipleDocsJson(t *testing.T) {
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, true, true, false, 0, false)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml")
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0)
if err != nil {
panic(err)
}

View File

@@ -0,0 +1,85 @@
package yqlib
import (
"container/list"
"io"
"os"
yaml "gopkg.in/yaml.v3"
)
type StreamEvaluator interface {
Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error
EvaluateFiles(expression string, filenames []string, printer Printer) error
}
type streamEvaluator struct {
treeNavigator DataTreeNavigator
treeCreator PathTreeCreator
fileIndex int
}
func NewStreamEvaluator() StreamEvaluator {
return &streamEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
}
func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
node, err := treeCreator.ParsePath(expression)
if err != nil {
return err
}
for _, filename := range filenames {
reader, err := readStream(filename)
if err != nil {
return err
}
err = s.Evaluate(filename, reader, node, printer)
if err != nil {
return err
}
switch reader := reader.(type) {
case *os.File:
safelyCloseFile(reader)
}
}
return nil
}
func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error {
var currentIndex uint
decoder := yaml.NewDecoder(reader)
for {
var dataBucket yaml.Node
errorReading := decoder.Decode(&dataBucket)
if errorReading == io.EOF {
s.fileIndex = s.fileIndex + 1
return nil
} else if errorReading != nil {
return errorReading
}
candidateNode := &CandidateNode{
Document: currentIndex,
Filename: filename,
Node: &dataBucket,
FileIndex: s.fileIndex,
}
inputList := list.New()
inputList.PushBack(candidateNode)
matches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node)
if errorParsing != nil {
return errorParsing
}
err := printer.PrintResults(matches)
if err != nil {
return err
}
currentIndex = currentIndex + 1
}
}

View File

@@ -9,7 +9,9 @@ import (
yaml "gopkg.in/yaml.v3"
)
var treeNavigator = NewDataTreeNavigator(NavigationPrefs{})
//TODO: convert to interface + struct
var treeNavigator = NewDataTreeNavigator()
var treeCreator = NewPathTreeCreator()
func readStream(filename string) (io.Reader, error) {
@@ -20,41 +22,7 @@ func readStream(filename string) (io.Reader, error) {
}
}
func EvaluateStream(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error {
var currentIndex uint = 0
decoder := yaml.NewDecoder(reader)
for {
var dataBucket yaml.Node
errorReading := decoder.Decode(&dataBucket)
if errorReading == io.EOF {
return nil
} else if errorReading != nil {
return errorReading
}
candidateNode := &CandidateNode{
Document: currentIndex,
Filename: filename,
Node: &dataBucket,
}
inputList := list.New()
inputList.PushBack(candidateNode)
matches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node)
if errorParsing != nil {
return errorParsing
}
err := printer.PrintResults(matches)
if err != nil {
return err
}
currentIndex = currentIndex + 1
}
}
func readDocuments(reader io.Reader, filename string) (*list.List, error) {
func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List, error) {
decoder := yaml.NewDecoder(reader)
inputList := list.New()
var currentIndex uint = 0
@@ -73,9 +41,10 @@ func readDocuments(reader io.Reader, filename string) (*list.List, error) {
return nil, errorReading
}
candidateNode := &CandidateNode{
Document: currentIndex,
Filename: filename,
Node: &dataBucket,
Document: currentIndex,
Filename: filename,
Node: &dataBucket,
FileIndex: fileIndex,
}
inputList.PushBack(candidateNode)
@@ -84,55 +53,6 @@ func readDocuments(reader io.Reader, filename string) (*list.List, error) {
}
}
func EvaluateAllFileStreams(expression string, filenames []string, printer Printer) error {
node, err := treeCreator.ParsePath(expression)
if err != nil {
return err
}
var allDocuments *list.List = list.New()
for _, filename := range filenames {
reader, err := readStream(filename)
if err != nil {
return err
}
fileDocuments, err := readDocuments(reader, filename)
if err != nil {
return err
}
allDocuments.PushBackList(fileDocuments)
}
matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
if err != nil {
return err
}
return printer.PrintResults(matches)
}
func EvaluateFileStreamsSequence(expression string, filenames []string, printer Printer) error {
node, err := treeCreator.ParsePath(expression)
if err != nil {
return err
}
for _, filename := range filenames {
reader, err := readStream(filename)
if err != nil {
return err
}
err = EvaluateStream(filename, reader, node, printer)
if err != nil {
return err
}
switch reader := reader.(type) {
case *os.File:
safelyCloseFile(reader)
}
}
return nil
}
// func safelyRenameFile(from string, to string) {
// if renameError := os.Rename(from, to); renameError != nil {
// log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)

View File

@@ -1,5 +1,5 @@
name: yq
version: '4.0.0-alpha1'
version: '4.0.0-alpha2'
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.