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

Compare commits

...

19 Commits

Author SHA1 Message Date
Mike Farah
8e1ce4ca70 Updated todo 2020-11-19 22:12:34 +11:00
Mike Farah
9bd9468526 Minor fixes 2020-11-19 22:11:26 +11:00
Mike Farah
75044e480c Added plain assignment 2020-11-19 17:08:13 +11:00
Mike Farah
36084a60a9 Added tag operator 2020-11-19 16:45:05 +11:00
Mike Farah
9b48cf80e0 updated todo 2020-11-18 20:43:36 +11:00
Mike Farah
bb3b08e648 wip style docs and test 2020-11-18 20:42:37 +11:00
Mike Farah
dcacad1e7e docs 2020-11-18 10:32:30 +11:00
Mike Farah
3356061e1e select doc 2020-11-18 09:52:03 +11:00
Mike Farah
2c062bc2a5 Added printer test 2020-11-18 09:52:03 +11:00
Mike Farah
088ec36acd include docs for tracking 2020-11-18 09:50:52 +11:00
Mike Farah
83cb6421df added test to ensure json keys remain in order 2020-11-17 16:17:38 +11:00
Mike Farah
a57944d123 Fixed printer 2020-11-16 12:09:57 +11:00
Mike Farah
79867473d5 updating release 2020-11-16 10:28:57 +11:00
Mike Farah
b3efcdc202 more docs 2020-11-15 10:58:47 +11:00
Mike Farah
af2aa9ad91 more docs 2020-11-15 10:50:30 +11:00
Mike Farah
db4762ef7c more docs 2020-11-14 13:38:44 +11:00
Mike Farah
860655b4cd Better documentation generation 2020-11-13 21:34:43 +11:00
Mike Farah
d91b25840a Better documentation generation 2020-11-13 21:22:05 +11:00
Mike Farah
019acfe456 Better documentation generation 2020-11-13 20:58:01 +11:00
71 changed files with 2969 additions and 1065 deletions

View File

@@ -14,6 +14,5 @@ var noDocSeparators = false
var nullInput = false
var verbose = false
var version = false
var shellCompletion = ""
// var log = logging.MustGetLogger("yq")

View File

@@ -1,7 +1,6 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
@@ -18,20 +17,6 @@ func New() *cobra.Command {
cmd.Print(GetVersionDisplay())
return nil
}
if shellCompletion != "" {
switch shellCompletion {
case "bash", "":
return cmd.GenBashCompletion(os.Stdout)
case "zsh":
return cmd.GenZshCompletion(os.Stdout)
case "fish":
return cmd.GenFishCompletion(os.Stdout, true)
case "powershell":
return cmd.GenPowerShellCompletion(os.Stdout)
default:
return fmt.Errorf("Unknown variant %v", shellCompletion)
}
}
cmd.Println(cmd.UsageString())
return nil
@@ -62,10 +47,12 @@ func New() *cobra.Command {
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.Flags().StringVarP(&shellCompletion, "shellCompletion", "", "", "[bash/zsh/powershell/fish] prints shell completion script")
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors")
rootCmd.AddCommand(createEvaluateSequenceCommand(), createEvaluateAllCommand())
rootCmd.AddCommand(
createEvaluateSequenceCommand(),
createEvaluateAllCommand(),
completionCmd,
)
return rootCmd
}

61
cmd/shell-completion.go Normal file
View File

@@ -0,0 +1,61 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "shell-completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: `To load completions:
Bash:
$ source <(yq shell-completion bash)
# To load completions for each session, execute once:
Linux:
$ yq shell-completion bash > /etc/bash_completion.d/yq
MacOS:
$ yq shell-completion bash > /usr/local/etc/bash_completion.d/yq
Zsh:
# If shell completion is not already enabled in your environment you will need
# to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ yq shell-completion zsh > "${fpath[1]}/_yq"
# You will need to start a new shell for this setup to take effect.
Fish:
$ yq shell-completion fish | source
# To load completions for each session, execute once:
$ yq shell-completion fish > ~/.config/fish/completions/yq.fish
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var err error = nil
switch args[0] {
case "bash":
err = cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
err = cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
err = cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
err = cmd.Root().GenPowerShellCompletion(os.Stdout)
}
return err
},
}

View File

@@ -1,13 +0,0 @@
GREEN='\033[0;32m'
NC='\033[0m'
echo "${GREEN}---Old---${NC}"
yq $@ > /tmp/yq-old-output
cat /tmp/yq-old-output
echo "${GREEN}---New---${NC}"
./yq $@ > /tmp/yq-new-output
cat /tmp/yq-new-output
echo "${GREEN}---Diff---${NC}"
colordiff /tmp/yq-old-output /tmp/yq-new-output

View File

@@ -1,664 +0,0 @@
package yqlib
import (
"container/list"
)
func resultsToString(results *list.List) []string {
var pretty []string = make([]string, 0)
for el := results.Front(); el != nil; el = el.Next() {
n := el.Value.(*CandidateNode)
pretty = append(pretty, NodeToString(n))
}
return pretty
}
// func TestDataTreeNavigatorDeleteSimple(t *testing.T) {
// nodes := readDoc(t, `a:
// b: apple
// c: camel`)
// path, errPath := treeCreator.ParsePath("a .- b")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a]
// Tag: !!map, Kind: MappingNode, Anchor:
// c: camel
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorDeleteTwice(t *testing.T) {
// nodes := readDoc(t, `a:
// b: apple
// c: camel
// d: dingo`)
// path, errPath := treeCreator.ParsePath("a .- b OR a .- c")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a]
// Tag: !!map, Kind: MappingNode, Anchor:
// d: dingo
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorDeleteWithUnion(t *testing.T) {
// nodes := readDoc(t, `a:
// b: apple
// c: camel
// d: dingo`)
// path, errPath := treeCreator.ParsePath("a .- (b OR c)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a]
// Tag: !!map, Kind: MappingNode, Anchor:
// d: dingo
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorDeleteByIndex(t *testing.T) {
// nodes := readDoc(t, `a:
// - b: apple
// - b: sdfsd
// - b: apple`)
// path, errPath := treeCreator.ParsePath("(a .- (0 or 1))")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a]
// Tag: !!seq, Kind: SequenceNode, Anchor:
// - b: apple
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorDeleteByFind(t *testing.T) {
// nodes := readDoc(t, `a:
// - b: apple
// - b: sdfsd
// - b: apple`)
// path, errPath := treeCreator.ParsePath("(a .- (* == apple))")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a]
// Tag: !!seq, Kind: SequenceNode, Anchor:
// - b: sdfsd
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorDeleteArrayByFind(t *testing.T) {
// nodes := readDoc(t, `a:
// - apple
// - sdfsd
// - apple`)
// path, errPath := treeCreator.ParsePath("(a .- (. == apple))")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a]
// Tag: !!seq, Kind: SequenceNode, Anchor:
// - sdfsd
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorDeleteViaSelf(t *testing.T) {
// nodes := readDoc(t, `- apple
// - sdfsd
// - apple`)
// path, errPath := treeCreator.ParsePath(". .- (. == apple)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: []
// Tag: !!seq, Kind: SequenceNode, Anchor:
// - sdfsd
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorFilterWithSplat(t *testing.T) {
// nodes := readDoc(t, `f:
// a: frog
// b: dally
// c: log`)
// path, errPath := treeCreator.ParsePath(".f | .[] == \"frog\"")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [f]
// Tag: !!int, Kind: ScalarNode, Anchor:
// 2
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCountAndCollectWithFilterCmd(t *testing.T) {
// nodes := readDoc(t, `f:
// a: frog
// b: dally
// c: log`)
// path, errPath := treeCreator.ParsePath(".f | .[] == *og ")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [f]
// Tag: !!int, Kind: ScalarNode, Anchor:
// 2
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCollectWithFilter(t *testing.T) {
// nodes := readDoc(t, `f:
// a: frog
// b: dally
// c: log`)
// path, errPath := treeCreator.ParsePath("f(collect(. == *og))")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [f]
// Tag: , Kind: SequenceNode, Anchor:
// - frog
// - log
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCountWithFilter2(t *testing.T) {
// nodes := readDoc(t, `f:
// a: frog
// b: dally
// c: log`)
// path, errPath := treeCreator.ParsePath("count(f(. == *og))")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: []
// Tag: !!int, Kind: ScalarNode, Anchor:
// 2
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCollectWithFilter2(t *testing.T) {
// nodes := readDoc(t, `f:
// a: frog
// b: dally
// c: log`)
// path, errPath := treeCreator.ParsePath("collect(f(. == *og))")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: []
// Tag: , Kind: SequenceNode, Anchor:
// - frog
// - log
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCountMultipleMatchesInside(t *testing.T) {
// nodes := readDoc(t, `f:
// a: [1,2]
// b: dally
// c: [3,4,5]`)
// path, errPath := treeCreator.ParsePath("f | count(a or c)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [f]
// Tag: !!int, Kind: ScalarNode, Anchor:
// 2
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCollectMultipleMatchesInside(t *testing.T) {
// nodes := readDoc(t, `f:
// a: [1,2]
// b: dally
// c: [3,4,5]`)
// path, errPath := treeCreator.ParsePath("f | collect(a or c)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [f]
// Tag: , Kind: SequenceNode, Anchor:
// - [1, 2]
// - [3, 4, 5]
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCountMultipleMatchesInsideSplat(t *testing.T) {
// nodes := readDoc(t, `f:
// a: [1,2,3]
// b: [1,2,3,4]
// c: [1,2,3,4,5]`)
// path, errPath := treeCreator.ParsePath("f(count( (a or c)*))")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [f]
// Tag: !!int, Kind: ScalarNode, Anchor:
// 8
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCountMultipleMatchesOutside(t *testing.T) {
// nodes := readDoc(t, `f:
// a: [1,2,3]
// b: [1,2,3,4]
// c: [1,2,3,4,5]`)
// path, errPath := treeCreator.ParsePath("f(a or c)(count(*))")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [f a]
// Tag: !!int, Kind: ScalarNode, Anchor:
// 3
// -- Node --
// Document 0, path: [f c]
// Tag: !!int, Kind: ScalarNode, Anchor:
// 5
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCountOfResults(t *testing.T) {
// nodes := readDoc(t, `- apple
// - sdfsd
// - apple`)
// path, errPath := treeCreator.ParsePath("count(*)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: []
// Tag: !!int, Kind: ScalarNode, Anchor:
// 3
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorCountNoMatches(t *testing.T) {
// nodes := readDoc(t, `- apple
// - sdfsd
// - apple`)
// path, errPath := treeCreator.ParsePath("count(5)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: []
// Tag: !!int, Kind: ScalarNode, Anchor:
// 0
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorDeleteAndWrite(t *testing.T) {
// nodes := readDoc(t, `a:
// - b: apple
// - b: sdfsd
// - { b: apple, c: cat }`)
// path, errPath := treeCreator.ParsePath("(a .- (0 or 1)) or (a[0].b := frog)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a]
// Tag: !!seq, Kind: SequenceNode, Anchor:
// - {b: frog, c: cat}
// -- Node --
// Document 0, path: [a 0 b]
// Tag: !!str, Kind: ScalarNode, Anchor:
// frog
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorDeleteArray(t *testing.T) {
// nodes := readDoc(t, `a:
// - b: apple
// - b: sdfsd
// - b: apple`)
// path, errPath := treeCreator.ParsePath("a .- (b == a*)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a]
// Tag: !!seq, Kind: SequenceNode, Anchor:
// - b: sdfsd
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorArraySimple(t *testing.T) {
// nodes := readDoc(t, `- b: apple`)
// path, errPath := treeCreator.ParsePath("[0]")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [0]
// Tag: !!map, Kind: MappingNode, Anchor:
// b: apple
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorSimpleAssignByFind(t *testing.T) {
// nodes := readDoc(t, `a:
// b: apple`)
// path, errPath := treeCreator.ParsePath("a(. == apple) := frog")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a b]
// Tag: !!str, Kind: ScalarNode, Anchor:
// frog
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorOrDeDupes(t *testing.T) {
// nodes := readDoc(t, `a:
// cat: apple
// mad: things`)
// path, errPath := treeCreator.ParsePath("a.(cat or cat)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a cat]
// Tag: !!str, Kind: ScalarNode, Anchor:
// apple
// `
// test.AssertResult(t, expected, resultsToString(results))
// }
// func TestDataTreeNavigatorAnd(t *testing.T) {
// nodes := readDoc(t, `a:
// cat: apple
// pat: apple
// cow: apple
// mad: things`)
// path, errPath := treeCreator.ParsePath("a.(*t and c*)")
// if errPath != nil {
// t.Error(errPath)
// }
// results, errNav := treeNavigator.GetMatchingNodes(nodes, path)
// if errNav != nil {
// t.Error(errNav)
// }
// expected := `
// -- Node --
// Document 0, path: [a cat]
// Tag: !!str, Kind: ScalarNode, Anchor:
// apple
// `
// test.AssertResult(t, expected, resultsToString(results))
// }

View File

@@ -1 +0,0 @@
*.md

View File

@@ -0,0 +1,163 @@
This operator is used to update node values. It can be used in either the:
### plain form: `=`
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
### 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
Given a sample.yml file of:
```yaml
a:
b:
g: foof
```
then
```bash
yq eval '.a |= .b' sample.yml
```
will output
```yaml
a:
g: foof
```
### Update to be the sibling value
Given a sample.yml file of:
```yaml
a:
b: child
b: sibling
```
then
```bash
yq eval '.a = .b' sample.yml
```
will output
```yaml
a: sibling
b: sibling
```
### Updated multiple paths
Given a sample.yml file of:
```yaml
a: fieldA
b: fieldB
c: fieldC
```
then
```bash
yq eval '(.a, .c) |= "potatoe"' sample.yml
```
will output
```yaml
a: potatoe
b: fieldB
c: potatoe
```
### Update string value
Given a sample.yml file of:
```yaml
a:
b: apple
```
then
```bash
yq eval '.a.b = "frog"' sample.yml
```
will output
```yaml
a:
b: frog
```
### Update string value via |=
Note there is no difference between `=` and `|=` when the RHS is a scalar
Given a sample.yml file of:
```yaml
a:
b: apple
```
then
```bash
yq eval '.a.b |= "frog"' sample.yml
```
will output
```yaml
a:
b: frog
```
### Update selected results
Given a sample.yml file of:
```yaml
a:
b: apple
c: cactus
```
then
```bash
yq eval '.a[] | select(. == "apple") |= "frog"' sample.yml
```
will output
```yaml
a:
b: frog
c: cactus
```
### Update array values
Given a sample.yml file of:
```yaml
- candy
- apple
- sandy
```
then
```bash
yq eval '.[] | select(. == "*andy") |= "bogs"' sample.yml
```
will output
```yaml
- bogs
- apple
- bogs
```
### Update empty object
Given a sample.yml file of:
```yaml
'': null
```
then
```bash
yq eval '.a.b |= "bogs"' sample.yml
```
will output
```yaml
'': null
a:
b: bogs
```
### Update empty object and array
Given a sample.yml file of:
```yaml
'': null
```
then
```bash
yq eval '.a.b[0] |= "bogs"' sample.yml
```
will output
```yaml
'': null
a:
b:
- bogs
```

View File

@@ -0,0 +1,41 @@
# Collect into Array
This creates an array using the expression between the square brackets.
## Examples
### Collect empty
Running
```bash
yq eval --null-input '[]'
```
will output
```yaml
```
### Collect single
Running
```bash
yq eval --null-input '["cat"]'
```
will output
```yaml
- cat
```
### Collect many
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq eval '[.a, .b]' sample.yml
```
will output
```yaml
- cat
- dog
```

View File

@@ -0,0 +1,79 @@
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
Running
```bash
yq eval --null-input '{}'
```
will output
```yaml
```
### Wrap (prefix) existing object
Given a sample.yml file of:
```yaml
name: Mike
```
then
```bash
yq eval '{"wrap": .}' sample.yml
```
will output
```yaml
wrap:
name: Mike
```
### Using splat to create multiple objects
Given a sample.yml file of:
```yaml
name: Mike
pets:
- cat
- dog
```
then
```bash
yq eval '{.name: .pets[]}' sample.yml
```
will output
```yaml
Mike: cat
Mike: dog
```
### Working with multiple documents
Given a sample.yml file of:
```yaml
name: Mike
pets:
- cat
- dog
---
name: Rosey
pets:
- monkey
- sheep
```
then
```bash
yq eval '{.name: .pets[]}' sample.yml
```
will output
```yaml
Mike: cat
Mike: dog
Rosey: monkey
Rosey: sheep
```
### Creating yaml from scratch
Running
```bash
yq eval --null-input '{"wrap": "frog"}'
```
will output
```yaml
wrap: frog
```

View File

@@ -0,0 +1,120 @@
Use these comment operators to set or retrieve comments.
## Examples
### Set line comment
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval '.a lineComment="single"' sample.yml
```
will output
```yaml
a: cat # single
```
### Set head comment
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval '. headComment="single"' sample.yml
```
will output
```yaml
# single
a: cat
```
### Set foot comment, using an expression
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval '. footComment=.a' sample.yml
```
will output
```yaml
a: cat
# cat
```
### Remove comment
Given a sample.yml file of:
```yaml
a: cat # comment
b: dog # leave this
```
then
```bash
yq eval '.a lineComment=""' sample.yml
```
will output
```yaml
a: cat
b: dog # leave this
```
### Remove all comments
Given a sample.yml file of:
```yaml
a: cat # comment
```
then
```bash
yq eval '.. comments=""' sample.yml
```
will output
```yaml
a: cat
```
### Get line comment
Given a sample.yml file of:
```yaml
a: cat # meow
```
then
```bash
yq eval '.a | lineComment' sample.yml
```
will output
```yaml
meow
```
### Get head comment
Given a sample.yml file of:
```yaml
a: cat # meow
```
then
```bash
yq eval '. | headComment' sample.yml
```
will output
```yaml
```
### Get foot comment
Given a sample.yml file of:
```yaml
a: cat # meow
```
then
```bash
yq eval '. | footComment' sample.yml
```
will output
```yaml
```

View File

@@ -0,0 +1,66 @@
Deletes matching entries in maps or arrays.
## Examples
### Delete entry in map
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq eval 'del(.b)' sample.yml
```
will output
```yaml
a: cat
```
### Delete entry in array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq eval 'del(.[1])' sample.yml
```
will output
```yaml
- 1
- 3
```
### Delete no matches
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq eval 'del(.c)' sample.yml
```
will output
```yaml
a: cat
b: dog
```
### Delete matching entries
Given a sample.yml file of:
```yaml
a: cat
b: dog
c: bat
```
then
```bash
yq eval 'del( .[] | select(. == "*at") )' sample.yml
```
will output
```yaml
b: dog
```

View File

@@ -0,0 +1,55 @@
## Examples
### Retrieve a document index
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval '.a | documentIndex' sample.yml
```
will output
```yaml
0
---
1
```
### Filter by document index
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval 'select(. | documentIndex == 1)' sample.yml
```
will output
```yaml
a: frog
```
### Print Document Index with matches
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml
```
will output
```yaml
match: cat
doc: 0
match: frog
doc: 1
```

View File

@@ -0,0 +1,62 @@
## Equals Operator
This is a boolean operator that will return ```true``` if the LHS is equal to the RHS and ``false`` otherwise.
```
.a == .b
```
It is most often used with the select operator to find particular nodes:
```
select(.a == .b)
```
## Examples
### Match string
Given a sample.yml file of:
```yaml
- cat
- goat
- dog
```
then
```bash
yq eval '.[] | (. == "*at")' sample.yml
```
will output
```yaml
true
true
false
```
### Match number
Given a sample.yml file of:
```yaml
- 3
- 4
- 5
```
then
```bash
yq eval '.[] | (. == 4)' sample.yml
```
will output
```yaml
false
true
false
```
### Match nulls
Running
```bash
yq eval --null-input 'null == ~'
```
will output
```yaml
true
```

View File

@@ -0,0 +1,99 @@
Explodes (or dereferences) aliases and anchors.
## Examples
### Explode alias and anchor
Given a sample.yml file of:
```yaml
f:
a: &a cat
b: *a
```
then
```bash
yq eval 'explode(.f)' sample.yml
```
will output
```yaml
f:
a: cat
b: cat
```
### Explode with no aliases or anchors
Given a sample.yml file of:
```yaml
a: mike
```
then
```bash
yq eval 'explode(.a)' sample.yml
```
will output
```yaml
a: mike
```
### Explode with alias keys
Given a sample.yml file of:
```yaml
f:
a: &a cat
*a: b
```
then
```bash
yq eval 'explode(.f)' sample.yml
```
will output
```yaml
f:
a: cat
cat: b
```
### Explode with merge anchors
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval 'explode(.)' sample.yml
```
will output
```yaml
foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
a: foo_a
thing: bar_thing
c: foobarList_c
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
```

View File

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

@@ -0,0 +1,196 @@
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
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
Given a sample.yml file of:
```yaml
a:
field: me
fieldA: cat
b:
field:
g: wizz
fieldB: dog
```
then
```bash
yq eval '.a * .b' sample.yml
```
will output
```yaml
field:
g: wizz
fieldA: cat
fieldB: dog
```
### Merge objects together, returning parent object
Given a sample.yml file of:
```yaml
a:
field: me
fieldA: cat
b:
field:
g: wizz
fieldB: dog
```
then
```bash
yq eval '. * {"a":.b}' sample.yml
```
will output
```yaml
a:
field:
g: wizz
fieldA: cat
fieldB: dog
b:
field:
g: wizz
fieldB: dog
```
### Merge keeps style of LHS
Given a sample.yml file of:
```yaml
a: {things: great}
b:
also: "me"
```
then
```bash
yq eval '. * {"a":.b}' sample.yml
```
will output
```yaml
a: {things: great, also: "me"}
b:
also: "me"
```
### Merge arrays
Given a sample.yml file of:
```yaml
a:
- 1
- 2
- 3
b:
- 3
- 4
- 5
```
then
```bash
yq eval '. * {"a":.b}' sample.yml
```
will output
```yaml
a:
- 3
- 4
- 5
b:
- 3
- 4
- 5
```
### Merge to prefix an element
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq eval '. * {"a": {"c": .a}}' sample.yml
```
will output
```yaml
a:
c: cat
b: dog
```
### Merge with simple aliases
Given a sample.yml file of:
```yaml
a: &cat
c: frog
b:
f: *cat
c:
g: thongs
```
then
```bash
yq eval '.c * .b' sample.yml
```
will output
```yaml
g: thongs
f: *cat
```
### Merge does not copy anchor names
Given a sample.yml file of:
```yaml
a:
c: &cat frog
b:
f: *cat
c:
g: thongs
```
then
```bash
yq eval '.c * .a' sample.yml
```
will output
```yaml
g: thongs
c: frog
```
### Merge with merge anchors
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval '.foobar * .foobarList' sample.yml
```
will output
```yaml
c: foobarList_c
<<:
- *foo
- *bar
thing: foobar_thing
b: foobarList_b
```

View File

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

View File

@@ -0,0 +1,125 @@
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches, for instance to set the `style` of all nodes in a yaml doc:
```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
Given a sample.yml file of:
```yaml
a: &cat
c: frog
b: *cat
```
then
```bash
yq eval '..' sample.yml
```
will output
```yaml
a: &cat
c: frog
b: *cat
&cat
c: frog
frog
*cat
```
### Merge docs are not traversed
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval '.foobar | ..' sample.yml
```
will output
```yaml
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
foobar_c
*foo
foobar_thing
```

View File

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

View File

@@ -0,0 +1,165 @@
The style operator can be used to get or set the style of nodes (e.g. string style, yaml style)
## Examples
### Set tagged style
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '.. style="tagged"' sample.yml
```
will output
```yaml
!!map
a: !!str cat
b: !!int 5
c: !!float 3.2
e: !!bool true
```
### Set double quote style
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '.. style="double"' sample.yml
```
will output
```yaml
a: "cat"
b: "5"
c: "3.2"
e: "true"
```
### Set single quote style
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '.. style="single"' sample.yml
```
will output
```yaml
a: 'cat'
b: '5'
c: '3.2'
e: 'true'
```
### Set literal quote style
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '.. style="literal"' sample.yml
```
will output
```yaml
a: |-
cat
b: |-
5
c: |-
3.2
e: |-
true
```
### Set folded quote style
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '.. style="folded"' sample.yml
```
will output
```yaml
a: >-
cat
b: >-
5
c: >-
3.2
e: >-
true
```
### Set flow quote style
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '.. style="flow"' sample.yml
```
will output
```yaml
{a: cat, b: 5, c: 3.2, e: true}
```
### Set empty (default) quote style
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '.. style=""' sample.yml
```
will output
```yaml
a: cat
b: 5
c: 3.2
e: true
```
### Read style
Given a sample.yml file of:
```yaml
a: cat
b: thing
```
then
```bash
yq eval '.. | style' sample.yml
```
will output
```yaml
```

View File

@@ -0,0 +1,45 @@
The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`).
## Examples
### Get tag
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
f: []
```
then
```bash
yq eval '.. | tag' sample.yml
```
will output
```yaml
!!map
!!str
!!int
!!float
!!bool
!!seq
```
### Convert numbers to strings
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '(.. | select(tag == "!!int")) tag= "!!str"' sample.yml
```
will output
```yaml
a: cat
b: "5"
c: 3.2
e: true
```

View File

@@ -0,0 +1,353 @@
This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
## Examples
### Simple map navigation
Given a sample.yml file of:
```yaml
a:
b: apple
```
then
```bash
yq eval '.a' sample.yml
```
will output
```yaml
b: apple
```
### Splat
Often used to pipe children into other operators
Given a sample.yml file of:
```yaml
- b: apple
- c: banana
```
then
```bash
yq eval '.[]' sample.yml
```
will output
```yaml
b: apple
c: banana
```
### Children don't exist
Nodes are added dynamically while traversing
Given a sample.yml file of:
```yaml
c: banana
```
then
```bash
yq eval '.a.b' sample.yml
```
will output
```yaml
null
```
### Wildcard matching
Given a sample.yml file of:
```yaml
a:
cat: apple
mad: things
```
then
```bash
yq eval '.a."*a*"' sample.yml
```
will output
```yaml
apple
things
```
### Aliases
Given a sample.yml file of:
```yaml
a: &cat
c: frog
b: *cat
```
then
```bash
yq eval '.b' sample.yml
```
will output
```yaml
*cat
```
### Traversing aliases with splat
Given a sample.yml file of:
```yaml
a: &cat
c: frog
b: *cat
```
then
```bash
yq eval '.b.[]' sample.yml
```
will output
```yaml
frog
```
### Traversing aliases explicitly
Given a sample.yml file of:
```yaml
a: &cat
c: frog
b: *cat
```
then
```bash
yq eval '.b.c' sample.yml
```
will output
```yaml
frog
```
### Traversing arrays by index
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq eval '[0]' sample.yml
```
will output
```yaml
1
```
### Maps with numeric keys
Given a sample.yml file of:
```yaml
2: cat
```
then
```bash
yq eval '[2]' sample.yml
```
will output
```yaml
cat
```
### Maps with non existing numeric keys
Given a sample.yml file of:
```yaml
a: b
```
then
```bash
yq eval '[0]' sample.yml
```
will output
```yaml
null
```
### Traversing merge anchors
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval '.foobar.a' sample.yml
```
will output
```yaml
foo_a
```
### Traversing merge anchors with override
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval '.foobar.c' sample.yml
```
will output
```yaml
foo_c
```
### Traversing merge anchors with local override
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval '.foobar.thing' sample.yml
```
will output
```yaml
foobar_thing
```
### Splatting merge anchors
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval '.foobar.[]' sample.yml
```
will output
```yaml
foo_c
foo_a
foobar_thing
```
### Traversing merge anchor lists
Note that the later merge anchors override previous
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval '.foobarList.thing' sample.yml
```
will output
```yaml
bar_thing
```
### Splatting merge anchor lists
Given a sample.yml file of:
```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
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq eval '.foobarList.[]' sample.yml
```
will output
```yaml
bar_b
foo_a
bar_thing
foobarList_c
```

View File

@@ -0,0 +1,31 @@
This operator is used to combine different results together.
## Examples
### Combine scalars
Running
```bash
yq eval --null-input '1, true, "cat"'
```
will output
```yaml
1
true
cat
```
### Combine selected paths
Given a sample.yml file of:
```yaml
a: fieldA
b: fieldB
c: fieldC
```
then
```bash
yq eval '.a, .c' sample.yml
```
will output
```yaml
fieldA
fieldC
```

View File

@@ -0,0 +1,7 @@
This operator is used to update node values. It can be used in either the:
### plain form: `=`
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
### 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.

View File

@@ -0,0 +1,4 @@
# Collect into Array
This creates an array using the expression between the square brackets.

View File

@@ -0,0 +1 @@
This is used to construct objects (or maps). This can be used against existing yaml, or to create fresh yaml documents.

View File

@@ -0,0 +1 @@
Use these comment operators to set or retrieve comments.

View File

@@ -0,0 +1 @@
Deletes matching entries in maps or arrays.

View File

@@ -0,0 +1,14 @@
## Equals Operator
This is a boolean operator that will return ```true``` if the LHS is equal to the RHS and ``false`` otherwise.
```
.a == .b
```
It is most often used with the select operator to find particular nodes:
```
select(.a == .b)
```

View File

@@ -0,0 +1 @@
Explodes (or dereferences) aliases and anchors.

View File

@@ -0,0 +1,5 @@
Like the multiple operator in `jq`, depending on the operands, this multiply operator will do different things. Currently only objects are supported, which have the effect of merging the RHS into the LHS.
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.

View File

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

View File

@@ -0,0 +1,5 @@
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches, for instance to set the `style` of all nodes in a yaml doc:
```bash
yq eval '.. style= "flow"' file.yaml
```

View File

@@ -0,0 +1 @@
Select is used to filter arrays and maps by a boolean expression.

View File

@@ -0,0 +1 @@
The style operator can be used to get or set the style of nodes (e.g. string style, yaml style)

View File

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

View File

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

View File

@@ -0,0 +1 @@
This operator is used to combine different results together.

47
pkg/yqlib/encoder_test.go Normal file
View File

@@ -0,0 +1,47 @@
package yqlib
import (
"bufio"
"bytes"
"strings"
"testing"
"github.com/mikefarah/yq/v4/test"
)
var sampleYaml = `zabbix: winner
apple: great
banana:
- {cobra: kai, angus: bob}
`
var expectedJson = `{
"zabbix": "winner",
"apple": "great",
"banana": [
{
"cobra": "kai",
"angus": "bob"
}
]
}
`
func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
var output bytes.Buffer
writer := bufio.NewWriter(&output)
var jsonEncoder = NewJsonEncoder(writer, 2)
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml")
if err != nil {
panic(err)
}
node := inputs.Front().Value.(*CandidateNode).Node
err = jsonEncoder.Encode(node)
if err != nil {
panic(err)
}
writer.Flush()
test.AssertResult(t, expectedJson, output.String())
}

View File

@@ -5,8 +5,8 @@ import (
"container/list"
"fmt"
"gopkg.in/op/go-logging.v1"
"gopkg.in/yaml.v3"
logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
)
type OperationType struct {
@@ -17,16 +17,12 @@ type OperationType struct {
}
// operators TODO:
// - generator doc from operator tests
// - slurp - stdin, read in sequence, vs read all
// - write in place
// - 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?)
// - updateTag - not recursive
// - get tag (tag)
// - compare ??
// - validate ??
// - exists
@@ -37,8 +33,10 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator}
@@ -50,6 +48,7 @@ var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: Pip
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
@@ -66,11 +65,8 @@ var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
// not sure yet
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator}
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}
// filters matches if they have the existing path

View File

@@ -2,15 +2,28 @@ package yqlib
import "container/list"
type AssignOpPreferences struct {
UpdateAssign bool
}
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
var rhs *list.List
if !preferences.UpdateAssign {
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if preferences.UpdateAssign {
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
}
if err != nil {
return nil, err

View File

@@ -0,0 +1,120 @@
package yqlib
import (
"testing"
)
var assignOperatorScenarios = []expressionScenario{
{
description: "Update parent to be the child value",
document: `{a: {b: {g: foof}}}`,
expression: `.a |= .b`,
expected: []string{
"D0, P[], (doc)::{a: {g: foof}}\n",
},
},
{
description: "Update to be the sibling value",
document: `{a: {b: child}, b: sibling}`,
expression: `.a = .b`,
expected: []string{
"D0, P[], (doc)::{a: sibling, b: sibling}\n",
},
},
{
description: "Updated multiple paths",
document: `{a: fieldA, b: fieldB, c: fieldC}`,
expression: `(.a, .c) |= "potatoe"`,
expected: []string{
"D0, P[], (doc)::{a: potatoe, b: fieldB, c: potatoe}\n",
},
},
{
description: "Update string value",
document: `{a: {b: apple}}`,
expression: `.a.b = "frog"`,
expected: []string{
"D0, P[], (doc)::{a: {b: frog}}\n",
},
},
{
description: "Update string value via |=",
subdescription: "Note there is no difference between `=` and `|=` when the RHS is a scalar",
document: `{a: {b: apple}}`,
expression: `.a.b |= "frog"`,
expected: []string{
"D0, P[], (doc)::{a: {b: frog}}\n",
},
},
{
skipDoc: true,
document: `{a: {b: apple}}`,
expression: `.a.b | (. |= "frog")`,
expected: []string{
"D0, P[a b], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `{a: {b: apple}}`,
expression: `.a.b |= 5`,
expected: []string{
"D0, P[], (doc)::{a: {b: 5}}\n",
},
},
{
skipDoc: true,
document: `{a: {b: apple}}`,
expression: `.a.b |= 3.142`,
expected: []string{
"D0, P[], (doc)::{a: {b: 3.142}}\n",
},
},
{
description: "Update selected results",
document: `{a: {b: apple, c: cactus}}`,
expression: `.a[] | select(. == "apple") |= "frog"`,
expected: []string{
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
},
},
{
description: "Update array values",
document: `[candy, apple, sandy]`,
expression: `.[] | select(. == "*andy") |= "bogs"`,
expected: []string{
"D0, P[], (doc)::[bogs, apple, bogs]\n",
},
},
{
description: "Update empty object",
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"`,
expected: []string{
"D0, P[], (doc)::{a: {b: [bogs]}}\n",
},
},
{
skipDoc: true,
document: `{}`,
expression: `.a.b[1].c |= "bogs"`,
expected: []string{
"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n",
},
},
}
func TestAssignOperatorScenarios(t *testing.T) {
for _, tt := range assignOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Assign Operator", assignOperatorScenarios)
}

View File

@@ -1,84 +0,0 @@
package yqlib
import (
"testing"
)
var assignOperatorScenarios = []expressionScenario{
{
document: `{a: {b: apple}}`,
expression: `.a.b |= "frog"`,
expected: []string{
"D0, P[], (doc)::{a: {b: frog}}\n",
},
},
{
document: `{a: {b: apple}}`,
expression: `.a.b | (. |= "frog")`,
expected: []string{
"D0, P[a b], (!!str)::frog\n",
},
},
{
document: `{a: {b: apple}}`,
expression: `.a.b |= 5`,
expected: []string{
"D0, P[], (doc)::{a: {b: 5}}\n",
},
},
{
document: `{a: {b: apple}}`,
expression: `.a.b |= 3.142`,
expected: []string{
"D0, P[], (doc)::{a: {b: 3.142}}\n",
},
},
{
document: `{a: {b: {g: foof}}}`,
expression: `.a |= .b`,
expected: []string{
"D0, P[], (doc)::{a: {g: foof}}\n",
},
},
{
document: `{a: {b: apple, c: cactus}}`,
expression: `.a[] | select(. == "apple") |= "frog"`,
expected: []string{
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
},
},
{
document: `[candy, apple, sandy]`,
expression: `.[] | select(. == "*andy") |= "bogs"`,
expected: []string{
"D0, P[], (doc)::[bogs, apple, bogs]\n",
},
},
{
document: `{}`,
expression: `.a.b |= "bogs"`,
expected: []string{
"D0, P[], (doc)::{a: {b: bogs}}\n",
},
},
{
document: `{}`,
expression: `.a.b[0] |= "bogs"`,
expected: []string{
"D0, P[], (doc)::{a: {b: [bogs]}}\n",
},
},
{
document: `{}`,
expression: `.a.b[1].c |= "bogs"`,
expected: []string{
"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n",
},
},
}
func TestAssignOperatorScenarios(t *testing.T) {
for _, tt := range assignOperatorScenarios {
testScenario(t, &tt)
}
}

View File

@@ -6,18 +6,21 @@ import (
var collectObjectOperatorScenarios = []expressionScenario{
{
document: ``,
expression: `{}`,
expected: []string{},
description: `Collect empty object`,
document: ``,
expression: `{}`,
expected: []string{},
},
{
document: "{name: Mike}\n",
expression: `{"wrap": .}`,
description: `Wrap (prefix) existing object`,
document: "{name: Mike}\n",
expression: `{"wrap": .}`,
expected: []string{
"D0, P[], (!!map)::wrap: {name: Mike}\n",
},
},
{
skipDoc: true,
document: "{name: Mike}\n---\n{name: Bob}",
expression: `{"wrap": .}`,
expected: []string{
@@ -26,6 +29,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{name: Mike, age: 32}`,
expression: `{.name: .age}`,
expected: []string{
@@ -33,16 +37,19 @@ var collectObjectOperatorScenarios = []expressionScenario{
},
},
{
document: `{name: Mike, pets: [cat, dog]}`,
expression: `{.name: .pets[]}`,
description: `Using splat to create multiple objects`,
document: `{name: Mike, pets: [cat, dog]}`,
expression: `{.name: .pets[]}`,
expected: []string{
"D0, P[], (!!map)::Mike: cat\n",
"D0, P[], (!!map)::Mike: dog\n",
},
},
{
document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}",
expression: `{.name: .pets[]}`,
description: `Working with multiple documents`,
dontFormatInputForDoc: false,
document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}",
expression: `{.name: .pets[]}`,
expected: []string{
"D0, P[], (!!map)::Mike: cat\n",
"D0, P[], (!!map)::Mike: dog\n",
@@ -51,6 +58,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
expression: `{.name: .pets[], "f":.food[]}`,
expected: []string{
@@ -61,6 +69,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{name: Mike, pets: {cows: [apl, bba]}}`,
expression: `{"a":.name, "b":.pets}`,
expected: []string{
@@ -70,13 +79,15 @@ b: {cows: [apl, bba]}
},
},
{
document: ``,
expression: `{"wrap": "frog"}`,
description: "Creating yaml from scratch",
document: ``,
expression: `{"wrap": "frog"}`,
expected: []string{
"D0, P[], (!!map)::wrap: frog\n",
},
},
{
skipDoc: true,
document: `{name: Mike}`,
expression: `{"wrap": .}`,
expected: []string{
@@ -84,6 +95,7 @@ b: {cows: [apl, bba]}
},
},
{
skipDoc: true,
document: `{name: Mike}`,
expression: `{"wrap": {"further": .}} | (.. style= "flow")`,
expected: []string{

View File

@@ -6,43 +6,54 @@ import (
var collectOperatorScenarios = []expressionScenario{
{
document: ``,
expression: `[]`,
expected: []string{},
description: "Collect empty",
document: ``,
expression: `[]`,
expected: []string{},
},
{
document: ``,
expression: `["cat"]`,
description: "Collect single",
document: ``,
expression: `["cat"]`,
expected: []string{
"D0, P[], (!!seq)::- cat\n",
},
}, {
document: ``,
skipDoc: true,
expression: `[true]`,
expected: []string{
"D0, P[], (!!seq)::- true\n",
},
}, {
document: ``,
expression: `["cat", "dog"]`,
},
{
description: "Collect many",
document: `{a: cat, b: dog}`,
expression: `[.a, .b]`,
expected: []string{
"D0, P[], (!!seq)::- cat\n- dog\n",
},
}, {
},
{
document: ``,
skipDoc: true,
expression: `1 | collect`,
expected: []string{
"D0, P[], (!!seq)::- 1\n",
},
}, {
},
{
document: `[1,2,3]`,
skipDoc: true,
expression: `[.[]]`,
expected: []string{
"D0, P[], (!!seq)::- 1\n- 2\n- 3\n",
},
}, {
},
{
document: `a: {b: [1,2,3]}`,
expression: `[.a.b[]]`,
skipDoc: true,
expected: []string{
"D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n",
},

View File

@@ -3,19 +3,15 @@ package yqlib
import (
"container/list"
"gopkg.in/yaml.v3"
yaml "gopkg.in/yaml.v3"
)
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
// for each lhs, splat the node,
// the intersect it against the rhs expression
// recreate the contents using only the intersection result.
for el := lhs.Front(); el != nil; el = el.Next() {
for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
elMap := list.New()
elMap.PushBack(candidate)
@@ -25,20 +21,22 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
return nil, err
}
if candidate.Node.Kind == yaml.SequenceNode {
realNode := UnwrapDoc(candidate.Node)
if realNode.Kind == yaml.SequenceNode {
deleteFromArray(candidate, nodesToDelete)
} else if candidate.Node.Kind == yaml.MappingNode {
} else if realNode.Kind == yaml.MappingNode {
deleteFromMap(candidate, nodesToDelete)
} else {
log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate))
}
}
return lhs, nil
return matchingNodes, nil
}
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
log.Debug("deleteFromMap")
node := candidate.Node
node := UnwrapDoc(candidate.Node)
contents := node.Content
newContents := make([]*yaml.Node, 0)
@@ -51,8 +49,13 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
Document: candidate.Document,
Path: append(candidate.Path, key.Value),
}
// _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
shouldDelete := true
shouldDelete := false
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
shouldDelete = true
}
}
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
@@ -65,21 +68,26 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
log.Debug("deleteFromArray")
node := candidate.Node
node := UnwrapDoc(candidate.Node)
contents := node.Content
newContents := make([]*yaml.Node, 0)
for index := 0; index < len(contents); index = index + 1 {
value := contents[index]
// childCandidate := &CandidateNode{
// Node: value,
// Document: candidate.Document,
// Path: append(candidate.Path, index),
// }
childCandidate := &CandidateNode{
Node: value,
Document: candidate.Document,
Path: append(candidate.Path, index),
}
shouldDelete := false
for el := nodesToDelete.Front(); el != nil && !shouldDelete; el = el.Next() {
if el.Value.(*CandidateNode).GetKey() == childCandidate.GetKey() {
shouldDelete = true
}
}
// _, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
shouldDelete := true
if !shouldDelete {
newContents = append(newContents, value)
}

View File

@@ -0,0 +1,47 @@
package yqlib
import (
"testing"
)
var deleteOperatorScenarios = []expressionScenario{
{
description: "Delete entry in map",
document: `{a: cat, b: dog}`,
expression: `del(.b)`,
expected: []string{
"D0, P[], (doc)::{a: cat}\n",
},
},
{
description: "Delete entry in array",
document: `[1,2,3]`,
expression: `del(.[1])`,
expected: []string{
"D0, P[], (doc)::[1, 3]\n",
},
},
{
description: "Delete no matches",
document: `{a: cat, b: dog}`,
expression: `del(.c)`,
expected: []string{
"D0, P[], (doc)::{a: cat, b: dog}\n",
},
},
{
description: "Delete matching entries",
document: `{a: cat, b: dog, c: bat}`,
expression: `del( .[] | select(. == "*at") )`,
expected: []string{
"D0, P[], (doc)::{b: dog}\n",
},
},
}
func TestDeleteOperatorScenarios(t *testing.T) {
for _, tt := range deleteOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Delete Operator", deleteOperatorScenarios)
}

View File

@@ -6,22 +6,25 @@ import (
var equalsOperatorScenarios = []expressionScenario{
{
document: `[cat,goat,dog]`,
expression: `.[] | (. == "*at")`,
description: "Match string",
document: `[cat,goat,dog]`,
expression: `.[] | (. == "*at")`,
expected: []string{
"D0, P[0], (!!bool)::true\n",
"D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\n",
},
}, {
document: `[3, 4, 5]`,
expression: `.[] | (. == 4)`,
description: "Match number",
document: `[3, 4, 5]`,
expression: `.[] | (. == 4)`,
expected: []string{
"D0, P[0], (!!bool)::false\n",
"D0, P[1], (!!bool)::true\n",
"D0, P[2], (!!bool)::false\n",
},
}, {
skipDoc: true,
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
expression: `.a | (.[].b == "apple")`,
expected: []string{
@@ -30,6 +33,7 @@ var equalsOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: ``,
expression: `null == null`,
expected: []string{
@@ -37,8 +41,9 @@ var equalsOperatorScenarios = []expressionScenario{
},
},
{
document: ``,
expression: `null == ~`,
description: "Match nulls",
document: ``,
expression: `null == ~`,
expected: []string{
"D0, P[], (!!bool)::true\n",
},

View File

@@ -6,27 +6,54 @@ import (
var explodeTest = []expressionScenario{
{
document: `{a: mike}`,
expression: `explode(.a)`,
expected: []string{
"D0, P[], (doc)::{a: mike}\n",
},
},
{
document: `{f : {a: &a cat, b: *a}}`,
expression: `explode(.f)`,
description: "Explode alias and anchor",
document: `{f : {a: &a cat, b: *a}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[], (doc)::{f: {a: cat, b: cat}}\n",
},
},
{
document: `{f : {a: &a cat, *a: b}}`,
expression: `explode(.f)`,
description: "Explode with no aliases or anchors",
document: `a: mike`,
expression: `explode(.a)`,
expected: []string{
"D0, P[], (doc)::a: mike\n",
},
},
{
description: "Explode with alias keys",
document: `{f : {a: &a cat, *a: b}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[], (doc)::{f: {a: cat, cat: b}}\n",
},
},
{
description: "Explode with merge anchors",
document: mergeDocSample,
expression: `explode(.)`,
expected: []string{`D0, P[], (doc)::foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
a: foo_a
thing: bar_thing
c: foobarList_c
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
`},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(.) | (. style="flow")`,
expected: []string{
@@ -36,6 +63,7 @@ var explodeTest = []expressionScenario{
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
expected: []string{
@@ -45,6 +73,7 @@ var explodeTest = []expressionScenario{
},
},
{
skipDoc: true,
document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`,
expression: `explode(.f)`,
expected: []string{
@@ -57,4 +86,5 @@ func TestExplodeOperatorScenarios(t *testing.T) {
for _, tt := range explodeTest {
testScenario(t, &tt)
}
documentScenarios(t, "Explode Operator", explodeTest)
}

View File

@@ -112,6 +112,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
assignmentOp := &Operation{OperationType: AssignAttributes}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = Assign
assignmentOp.Preferences = &AssignOpPreferences{false}
}
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}

View File

@@ -22,11 +22,19 @@ var multiplyOperatorScenarios = []expressionScenario{
},
},
{
description: "Merge objects together",
document: `{a: {also: me}, b: {also: {g: wizz}}}`,
description: "Merge objects together, returning merged result only",
document: `{a: {field: me, fieldA: cat}, b: {field: {g: wizz}, fieldB: dog}}`,
expression: `.a * .b`,
expected: []string{
"D0, P[a], (!!map)::{field: {g: wizz}, fieldA: cat, fieldB: dog}\n",
},
},
{
description: "Merge objects together, returning parent object",
document: `{a: {field: me, fieldA: cat}, b: {field: {g: wizz}, fieldB: dog}}`,
expression: `. * {"a":.b}`,
expected: []string{
"D0, P[], (!!map)::{a: {also: {g: wizz}}, b: {also: {g: wizz}}}\n",
"D0, P[], (!!map)::{a: {field: {g: wizz}, fieldA: cat, fieldB: dog}, b: {field: {g: wizz}, fieldB: dog}}\n",
},
},
{
@@ -62,7 +70,8 @@ var multiplyOperatorScenarios = []expressionScenario{
},
},
{
description: "Merge keeps style of LHS",
description: "Merge keeps style of LHS",
dontFormatInputForDoc: true,
document: `a: {things: great}
b:
also: "me"
@@ -121,5 +130,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) {
for _, tt := range multiplyOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Mulitply Operator", multiplyOperatorScenarios)
documentScenarios(t, "Multiply Operator", multiplyOperatorScenarios)
}

View File

@@ -5,52 +5,61 @@ import (
)
var notOperatorScenarios = []expressionScenario{
// {
// document: `cat`,
// expression: `. | not`,
// expected: []string{
// "D0, P[], (!!bool)::false\n",
// },
// },
// {
// document: `1`,
// expression: `. | not`,
// expected: []string{
// "D0, P[], (!!bool)::false\n",
// },
// },
// {
// document: `0`,
// expression: `. | not`,
// expected: []string{
// "D0, P[], (!!bool)::false\n",
// },
// },
{
document: `~`,
expression: `. | not`,
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",
},
},
// {
// document: `false`,
// expression: `. | not`,
// expected: []string{
// "D0, P[], (!!bool)::true\n",
// },
// },
// {
// document: `true`,
// expression: `. | not`,
// expected: []string{
// "D0, P[], (!!bool)::false\n",
// },
// },
}
func TestNotOperatorScenarios(t *testing.T) {
for _, tt := range notOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Not Operator", notOperatorScenarios)
}

View File

@@ -6,6 +6,7 @@ import (
var recursiveDescentOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `cat`,
expression: `..`,
expected: []string{
@@ -13,6 +14,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{a: frog}`,
expression: `..`,
expected: []string{
@@ -21,8 +23,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
document: `{a: {b: apple}}`,
expression: `..`,
description: "Map",
document: `{a: {b: apple}}`,
expression: `..`,
expected: []string{
"D0, P[], (!!map)::{a: {b: apple}}\n",
"D0, P[a], (!!map)::{b: apple}\n",
@@ -30,8 +33,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
document: `[1,2,3]`,
expression: `..`,
description: "Array",
document: `[1,2,3]`,
expression: `..`,
expected: []string{
"D0, P[], (!!seq)::[1, 2, 3]\n",
"D0, P[0], (!!int)::1\n",
@@ -40,8 +44,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
document: `[{a: cat},2,true]`,
expression: `..`,
description: "Array of maps",
document: `[{a: cat},2,true]`,
expression: `..`,
expected: []string{
"D0, P[], (!!seq)::[{a: cat}, 2, true]\n",
"D0, P[0], (!!map)::{a: cat}\n",
@@ -51,8 +56,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `..`,
description: "Aliases are not traversed",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `..`,
expected: []string{
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
"D0, P[a], (!!map)::&cat {c: frog}\n",
@@ -61,8 +67,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
document: mergeDocSample,
expression: `.foobar | ..`,
description: "Merge docs are not traversed",
document: mergeDocSample,
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",
@@ -71,6 +78,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList | ..`,
expected: []string{
@@ -88,4 +96,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) {
for _, tt := range recursiveDescentOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Recursive Descent Operator", recursiveDescentOperatorScenarios)
}

View File

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

View File

@@ -0,0 +1,114 @@
package yqlib
import (
"testing"
)
var styleOperatorScenarios = []expressionScenario{
{
description: "Set tagged style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="tagged"`,
expected: []string{
"D0, P[], (!!map)::!!map\na: !!str cat\nb: !!int 5\nc: !!float 3.2\ne: !!bool true\n",
},
},
{
description: "Set double quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="double"`,
expected: []string{
"D0, P[], (!!map)::a: \"cat\"\nb: \"5\"\nc: \"3.2\"\ne: \"true\"\n",
},
},
{
description: "Set single quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="single"`,
expected: []string{
"D0, P[], (!!map)::a: 'cat'\nb: '5'\nc: '3.2'\ne: 'true'\n",
},
},
{
description: "Set literal quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="literal"`,
expected: []string{
`D0, P[], (!!map)::a: |-
cat
b: |-
5
c: |-
3.2
e: |-
true
`,
},
},
{
description: "Set folded quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="folded"`,
expected: []string{
`D0, P[], (!!map)::a: >-
cat
b: >-
5
c: >-
3.2
e: >-
true
`,
},
},
{
description: "Set flow quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `.. style="flow"`,
expected: []string{
"D0, P[], (!!map)::{a: cat, b: 5, c: 3.2, e: true}\n",
},
},
{
description: "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",
},
},
{
skipDoc: true,
document: `{a: cat, b: double}`,
expression: `.a style=.b`,
expected: []string{
"D0, P[], (doc)::{a: \"cat\", b: double}\n",
},
},
{
description: "Read style",
document: `{a: "cat", b: 'thing'}`,
expression: `.. | style`,
expected: []string{
"D0, P[], (!!str)::flow\n",
"D0, P[a], (!!str)::double\n",
"D0, P[b], (!!str)::single\n",
},
},
{
skipDoc: true,
document: `a: cat`,
expression: `.. | style`,
expected: []string{
"D0, P[], (!!str)::\"\"\n",
"D0, P[a], (!!str)::\"\"\n",
},
},
}
func TestStyleOperatorScenarios(t *testing.T) {
for _, tt := range styleOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Style Operator", styleOperatorScenarios)
}

51
pkg/yqlib/operator_tag.go Normal file
View File

@@ -0,0 +1,51 @@
package yqlib
import (
"container/list"
"gopkg.in/yaml.v3"
)
func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignTagOperator: %v")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
tag := ""
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil {
return nil, err
}
for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
log.Debugf("Setting tag of : %v", candidate.GetKey())
candidate.Node.Tag = tag
}
return matchingNodes, nil
}
func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("GetTagOperator")
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.Node.Tag, Tag: "!!str"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand)
}
return results, nil
}

View File

@@ -0,0 +1,36 @@
package yqlib
import (
"testing"
)
var tagOperatorScenarios = []expressionScenario{
{
description: "Get tag",
document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`,
expression: `.. | tag`,
expected: []string{
"D0, P[], (!!str)::'!!map'\n",
"D0, P[a], (!!str)::'!!str'\n",
"D0, P[b], (!!str)::'!!int'\n",
"D0, P[c], (!!str)::'!!float'\n",
"D0, P[e], (!!str)::'!!bool'\n",
"D0, P[f], (!!str)::'!!seq'\n",
},
},
{
description: "Convert numbers to strings",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
expected: []string{
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
},
},
}
func TestTagOperatorScenarios(t *testing.T) {
for _, tt := range tagOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Tag Operator", tagOperatorScenarios)
}

View File

@@ -28,28 +28,34 @@ foobar:
var traversePathOperatorScenarios = []expressionScenario{
{
document: `{a: {b: apple}}`,
expression: `.a`,
description: "Simple map navigation",
document: `{a: {b: apple}}`,
expression: `.a`,
expected: []string{
"D0, P[a], (!!map)::{b: apple}\n",
},
},
{
document: `[{b: apple}, {c: banana}]`,
expression: `.[]`,
description: "Splat",
subdescription: "Often used to pipe children into other operators",
document: `[{b: apple}, {c: banana}]`,
expression: `.[]`,
expected: []string{
"D0, P[0], (!!map)::{b: apple}\n",
"D0, P[1], (!!map)::{c: banana}\n",
},
},
{
document: `{}`,
expression: `.a.b`,
description: "Children don't exist",
subdescription: "Nodes are added dynamically while traversing",
document: `{c: banana}`,
expression: `.a.b`,
expected: []string{
"D0, P[a b], (!!null)::null\n",
},
},
{
skipDoc: true,
document: `{}`,
expression: `.[1].a`,
expected: []string{
@@ -57,6 +63,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{}`,
expression: `.a.[1]`,
expected: []string{
@@ -64,14 +71,16 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
document: `{a: {cat: apple, mad: things}}`,
expression: `.a."*a*"`,
description: "Wildcard matching",
document: `{a: {cat: apple, mad: things}}`,
expression: `.a."*a*"`,
expected: []string{
"D0, P[a cat], (!!str)::apple\n",
"D0, P[a mad], (!!str)::things\n",
},
},
{
skipDoc: true,
document: `{a: {cat: {b: 3}, mad: {b: 4}, fad: {c: t}}}`,
expression: `.a."*a*".b`,
expected: []string{
@@ -81,6 +90,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{a: {cat: apple, mad: things}}`,
expression: `.a | (.cat, .mad)`,
expected: []string{
@@ -89,6 +99,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{a: {cat: apple, mad: things}}`,
expression: `.a | (.cat, .mad, .fad)`,
expected: []string{
@@ -98,6 +109,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: `{a: {cat: apple, mad: things}}`,
expression: `.a | (.cat, .mad, .fad) | select( (. == null) | not)`,
expected: []string{
@@ -106,40 +118,53 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b`,
description: "Aliases",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b`,
expected: []string{
"D0, P[b], (alias)::*cat\n",
},
},
{
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b.[]`,
description: "Traversing aliases with splat",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b.[]`,
expected: []string{
"D0, P[b c], (!!str)::frog\n",
},
},
{
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b.c`,
description: "Traversing aliases explicitly",
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `.b.c`,
expected: []string{
"D0, P[b c], (!!str)::frog\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
expression: `.b`,
expected: []string{},
},
{
document: `[1,2,3]`,
expression: `[0]`,
description: "Traversing arrays by index",
document: `[1,2,3]`,
expression: `[0]`,
expected: []string{
"D0, P[0], (!!int)::1\n",
},
},
{
description: `Maps can have numbers as keys, so this default to a non-exisiting key behaviour.`,
description: "Maps with numeric keys",
document: `{2: cat}`,
expression: `[2]`,
expected: []string{
"D0, P[2], (!!str)::cat\n",
},
},
{
description: "Maps with non existing numeric keys",
document: `{a: b}`,
expression: `[0]`,
expected: []string{
@@ -147,6 +172,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobar`,
expected: []string{
@@ -154,29 +180,33 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
document: mergeDocSample,
expression: `.foobar.a`,
description: "Traversing merge anchors",
document: mergeDocSample,
expression: `.foobar.a`,
expected: []string{
"D0, P[foobar a], (!!str)::foo_a\n",
},
},
{
document: mergeDocSample,
expression: `.foobar.c`,
description: "Traversing merge anchors with override",
document: mergeDocSample,
expression: `.foobar.c`,
expected: []string{
"D0, P[foobar c], (!!str)::foo_c\n",
},
},
{
document: mergeDocSample,
expression: `.foobar.thing`,
description: "Traversing merge anchors with local override",
document: mergeDocSample,
expression: `.foobar.thing`,
expected: []string{
"D0, P[foobar thing], (!!str)::foobar_thing\n",
},
},
{
document: mergeDocSample,
expression: `.foobar.[]`,
description: "Splatting merge anchors",
document: mergeDocSample,
expression: `.foobar.[]`,
expected: []string{
"D0, P[foobar c], (!!str)::foo_c\n",
"D0, P[foobar a], (!!str)::foo_a\n",
@@ -184,6 +214,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList`,
expected: []string{
@@ -191,6 +222,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList.a`,
expected: []string{
@@ -198,13 +230,16 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
document: mergeDocSample,
expression: `.foobarList.thing`,
description: "Traversing merge anchor lists",
subdescription: "Note that the later merge anchors override previous",
document: mergeDocSample,
expression: `.foobarList.thing`,
expected: []string{
"D0, P[foobarList thing], (!!str)::bar_thing\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList.c`,
expected: []string{
@@ -212,6 +247,7 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList.b`,
expected: []string{
@@ -219,8 +255,9 @@ var traversePathOperatorScenarios = []expressionScenario{
},
},
{
document: mergeDocSample,
expression: `.foobarList.[]`,
description: "Splatting merge anchor lists",
document: mergeDocSample,
expression: `.foobarList.[]`,
expected: []string{
"D0, P[foobarList b], (!!str)::bar_b\n",
"D0, P[foobarList a], (!!str)::foo_a\n",
@@ -234,4 +271,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) {
for _, tt := range traversePathOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Traverse Operator", traversePathOperatorScenarios)
}

View File

@@ -6,20 +6,21 @@ import (
var unionOperatorScenarios = []expressionScenario{
{
document: `{}`,
expression: `"cat", "dog"`,
expected: []string{
"D0, P[], (!!str)::cat\n",
"D0, P[], (!!str)::dog\n",
},
}, {
document: `{a: frog}`,
expression: `1, true, "cat", .a`,
description: "Combine scalars",
expression: `1, true, "cat"`,
expected: []string{
"D0, P[], (!!int)::1\n",
"D0, P[], (!!bool)::true\n",
"D0, P[], (!!str)::cat\n",
"D0, P[a], (!!str)::frog\n",
},
},
{
description: "Combine selected paths",
document: `{a: fieldA, b: fieldB, c: fieldC}`,
expression: `.a, .c`,
expected: []string{
"D0, P[a], (!!str)::fieldA\n",
"D0, P[c], (!!str)::fieldC\n",
},
},
}
@@ -28,4 +29,5 @@ func TestUnionOperatorScenarios(t *testing.T) {
for _, tt := range unionOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Union Operator", unionOperatorScenarios)
}

View File

@@ -5,6 +5,7 @@ import (
"bytes"
"container/list"
"fmt"
"io"
"os"
"strings"
"testing"
@@ -13,11 +14,13 @@ import (
)
type expressionScenario struct {
description string
document string
expression string
expected []string
skipDoc bool
description string
subdescription string
document string
expression string
expected []string
skipDoc bool
dontFormatInputForDoc bool // dont format input doc for documentation generation
}
func testScenario(t *testing.T, s *expressionScenario) {
@@ -48,6 +51,15 @@ func testScenario(t *testing.T, s *expressionScenario) {
test.AssertResultComplexWithContext(t, s.expected, resultsToString(results), fmt.Sprintf("exp: %v\ndoc: %v", s.expression, s.document))
}
func resultsToString(results *list.List) []string {
var pretty []string = make([]string, 0)
for el := results.Front(); el != nil; el = el.Next() {
n := el.Value.(*CandidateNode)
pretty = append(pretty, NodeToString(n))
}
return pretty
}
func writeOrPanic(w *bufio.Writer, text string) {
_, err := w.WriteString(text)
if err != nil {
@@ -55,16 +67,54 @@ func writeOrPanic(w *bufio.Writer, text string) {
}
}
func copyFromHeader(title string, out *os.File) error {
source := fmt.Sprintf("doc/headers/%v.md", title)
_, err := os.Stat(source)
if os.IsNotExist(err) {
return nil
}
in, err := os.Open(source) // nolint gosec
if err != nil {
return err
}
defer safelyCloseFile(in)
_, err = io.Copy(out, in)
return err
}
func formatYaml(yaml string) string {
var output bytes.Buffer
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
node, err := treeCreator.ParsePath(".. style= \"\"")
if err != nil {
panic(err)
}
err = EvaluateStream("sample.yaml", strings.NewReader(yaml), node, printer)
if err != nil {
panic(err)
}
return output.String()
}
func documentScenarios(t *testing.T, title string, scenarios []expressionScenario) {
f, err := os.Create(fmt.Sprintf("doc/%v.md", title))
if err != nil {
t.Error(err)
return
}
defer f.Close()
err = copyFromHeader(title, f)
if err != nil {
t.Error(err)
return
}
w := bufio.NewWriter(f)
writeOrPanic(w, fmt.Sprintf("# %v\n", title))
writeOrPanic(w, "## Examples\n")
writeOrPanic(w, "\n## Examples\n")
for index, s := range scenarios {
if !s.skipDoc {
@@ -74,16 +124,33 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
} else {
writeOrPanic(w, fmt.Sprintf("### Example %v\n", index))
}
if s.document != "" {
writeOrPanic(w, "sample.yml:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.document))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
if s.expression != "" {
writeOrPanic(w, "Expression\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression))
formattedDoc := ""
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document
} else {
formattedDoc = formatYaml(s.document)
}
//TODO: pretty here
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
writeOrPanic(w, "then\n")
if s.expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval '%v' sample.yml\n```\n", s.expression))
} else {
writeOrPanic(w, "```bash\nyq eval sample.yml\n```\n")
}
} else {
writeOrPanic(w, "Running\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval --null-input '%v'\n```\n", s.expression))
}
writeOrPanic(w, "Result\n")
writeOrPanic(w, "will output\n")
var output bytes.Buffer
var err error
@@ -94,7 +161,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
if err != nil {
t.Error(err)
}
err = EvaluateStream("sample.yaml", strings.NewReader(s.document), node, printer)
err = EvaluateStream("sample.yaml", strings.NewReader(formattedDoc), node, printer)
if err != nil {
t.Error(err)
}
@@ -105,7 +172,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
}
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", output.String()))
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
}

View File

@@ -1,54 +0,0 @@
package yqlib
import (
"testing"
)
var styleOperatorScenarios = []expressionScenario{
{
document: `{a: cat}`,
expression: `.a style="single"`,
expected: []string{
"D0, P[], (doc)::{a: 'cat'}\n",
},
},
{
description: "Set style using a path",
document: `{a: cat, b: double}`,
expression: `.a style=.b`,
expected: []string{
"D0, P[], (doc)::{a: \"cat\", b: double}\n",
},
},
{
document: `{a: "cat", b: 'dog'}`,
expression: `.. style=""`,
expected: []string{
"D0, P[], (!!map)::a: cat\nb: dog\n",
},
},
{
document: `{a: "cat", b: 'thing'}`,
expression: `.. | style`,
expected: []string{
"D0, P[], (!!str)::flow\n",
"D0, P[a], (!!str)::double\n",
"D0, P[b], (!!str)::single\n",
},
},
{
document: `a: cat`,
expression: `.. | style`,
expected: []string{
"D0, P[], (!!str)::\"\"\n",
"D0, P[a], (!!str)::\"\"\n",
},
},
}
func TestStyleOperatorScenarios(t *testing.T) {
for _, tt := range styleOperatorScenarios {
testScenario(t, &tt)
}
documentScenarios(t, "Style Operator", styleOperatorScenarios)
}

View File

@@ -99,6 +99,27 @@ var pathTests = []struct {
append(make([]interface{}, 0), "a", "PIPE", "b", "ASSIGN_STYLE", "folded (string)"),
append(make([]interface{}, 0), "a", "b", "PIPE", "folded (string)", "ASSIGN_STYLE"),
},
{
`tag == "str"`,
append(make([]interface{}, 0), "GET_TAG", "EQUALS", "str (string)"),
append(make([]interface{}, 0), "GET_TAG", "str (string)", "EQUALS"),
},
{
`. tag= "str"`,
append(make([]interface{}, 0), "SELF", "ASSIGN_TAG", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_TAG"),
},
{
`lineComment == "str"`,
append(make([]interface{}, 0), "GET_COMMENT", "EQUALS", "str (string)"),
append(make([]interface{}, 0), "GET_COMMENT", "str (string)", "EQUALS"),
},
{
`. lineComment= "str"`,
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
},
// {
// `.a.b tag="!!str"`,
// append(make([]interface{}, 0), "EXPLODE", "(", "a", "PIPE", "b", ")"),

View File

@@ -24,10 +24,11 @@ const (
)
type Token struct {
TokenType TokenType
Operation *Operation
TokenType TokenType
Operation *Operation
AssignOperation *Operation // e.g. tag (GetTag) op becomes AssignTag if '=' follows it
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
CheckForPostTraverse bool // e.g. [1]cat should really be [1].cat
}
func (t *Token) toString() string {
@@ -83,14 +84,22 @@ func documentToken() lex.Action {
}
func opToken(op *OperationType) lex.Action {
return opTokenWithPrefs(op, nil)
return opTokenWithPrefs(op, nil, nil)
}
func opTokenWithPrefs(op *OperationType, preferences interface{}) lex.Action {
func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.Action {
return opTokenWithPrefs(opType, assignOpType, nil)
}
func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
value := string(m.Bytes)
op := &Operation{OperationType: op, Value: op.Type, StringValue: value, Preferences: preferences}
return &Token{TokenType: OperationToken, Operation: op}, nil
var assign *Operation
if assignOpType != nil {
assign = &Operation{OperationType: assignOpType, Value: assignOpType.Type, StringValue: value, Preferences: preferences}
}
return &Token{TokenType: OperationToken, Operation: op, AssignOperation: assign}, nil
}
}
@@ -191,27 +200,26 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))
lexer.Add([]byte(`style\s*=`), opToken(AssignStyle))
lexer.Add([]byte(`style`), opToken(GetStyle))
lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle))
lexer.Add([]byte(`lineComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true}))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{LineComment: true}))
lexer.Add([]byte(`tag`), opAssignableToken(GetTag, AssignTag))
lexer.Add([]byte(`headComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{HeadComment: true}))
lexer.Add([]byte(`headComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{HeadComment: true}))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true}))
lexer.Add([]byte(`footComment\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`headComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{HeadComment: true}))
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true}))
lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, nil, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true}))
lexer.Add([]byte(`collect`), opToken(Collect))
lexer.Add([]byte(`\s*==\s*`), opToken(Equals))
lexer.Add([]byte(`\s*=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{false}))
lexer.Add([]byte(`\s*.-\s*`), opToken(DeleteChild))
lexer.Add([]byte(`del`), opToken(DeleteChild))
lexer.Add([]byte(`\s*\|=\s*`), opToken(Assign))
lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true}))
lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(false))
lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true))
@@ -286,15 +294,28 @@ func (p *pathTokeniser) Tokenise(path string) ([]*Token, error) {
}
var postProcessedTokens = make([]*Token, 0)
skipNextToken := false
for index, token := range tokens {
if skipNextToken {
skipNextToken = false
} else {
postProcessedTokens = append(postProcessedTokens, token)
if index != len(tokens)-1 && token.AssignOperation != nil &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == Assign {
token.Operation = token.AssignOperation
skipNextToken = true
}
if index != len(tokens)-1 && token.CheckForPostTraverse &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == TraversePath {
op := &Operation{OperationType: Pipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
postProcessedTokens = append(postProcessedTokens, token)
if index != len(tokens)-1 && token.CheckForPostTraverse &&
tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == TraversePath {
op := &Operation{OperationType: Pipe, Value: "PIPE"}
postProcessedTokens = append(postProcessedTokens, &Token{TokenType: OperationToken, Operation: op})
}
}
}

View File

@@ -20,6 +20,7 @@ type resultsPrinter struct {
printDocSeparators bool
writer io.Writer
firstTimePrinting bool
previousDocIndex uint
}
func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer {
@@ -53,6 +54,7 @@ func (p *resultsPrinter) writeString(writer io.Writer, txt string) error {
}
func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
log.Debug("PrintResults for %v matches", matchingNodes.Len())
var err error
if p.outputToJSON {
explodeOp := Operation{OperationType: Explode}
@@ -70,13 +72,16 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
log.Debug("no matching results, nothing to print")
return nil
}
previousDocIndex := matchingNodes.Front().Value.(*CandidateNode).Document
if p.firstTimePrinting {
p.previousDocIndex = matchingNodes.Front().Value.(*CandidateNode).Document
p.firstTimePrinting = false
}
for el := matchingNodes.Front(); el != nil; el = el.Next() {
mappedDoc := el.Value.(*CandidateNode)
if (!p.firstTimePrinting || (previousDocIndex != mappedDoc.Document)) && p.printDocSeparators {
log.Debug("-- print sep logic: p.firstTimePrinting: %v, previousDocIndex: %v, mappedDoc.Document: %v, printDocSeparators: %v", p.firstTimePrinting, p.previousDocIndex, mappedDoc.Document, p.printDocSeparators)
if (p.previousDocIndex != mappedDoc.Document) && p.printDocSeparators {
log.Debug("-- writing doc sep")
if err := p.writeString(bufferedWriter, "---\n"); err != nil {
return err
}
@@ -87,9 +92,8 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
return err
}
previousDocIndex = mappedDoc.Document
p.previousDocIndex = mappedDoc.Document
}
p.firstTimePrinting = false
return nil
}

99
pkg/yqlib/printer_test.go Normal file
View File

@@ -0,0 +1,99 @@
package yqlib
import (
"bufio"
"bytes"
"strings"
"testing"
"github.com/mikefarah/yq/v4/test"
)
var multiDocSample = `a: banana
---
a: apple
---
a: coconut
`
func TestPrinterMultipleDocsInSequence(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(multiDocSample), "sample.yml")
if err != nil {
panic(err)
}
el := inputs.Front()
sample1 := nodeToMap(el.Value.(*CandidateNode))
el = el.Next()
sample2 := nodeToMap(el.Value.(*CandidateNode))
el = el.Next()
sample3 := nodeToMap(el.Value.(*CandidateNode))
err = printer.PrintResults(sample1)
if err != nil {
panic(err)
}
err = printer.PrintResults(sample2)
if err != nil {
panic(err)
}
err = printer.PrintResults(sample3)
if err != nil {
panic(err)
}
writer.Flush()
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleDocsInSinglePrint(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(multiDocSample), "sample.yml")
if err != nil {
panic(err)
}
err = printer.PrintResults(inputs)
if err != nil {
panic(err)
}
writer.Flush()
test.AssertResult(t, multiDocSample, output.String())
}
func TestPrinterMultipleDocsJson(t *testing.T) {
var output bytes.Buffer
var writer = bufio.NewWriter(&output)
printer := NewPrinter(writer, true, true, false, 0, false)
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml")
if err != nil {
panic(err)
}
err = printer.PrintResults(inputs)
if err != nil {
panic(err)
}
expected := `{"a":"banana"}
{"a":"apple"}
{"a":"coconut"}
`
writer.Flush()
test.AssertResult(t, expected, output.String())
}

View File

@@ -151,7 +151,7 @@ func EvaluateFileStreamsSequence(expression string, filenames []string, printer
// }
// }
// // thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
// func copyFileContents(src, dst string) (err error) {
// in, err := os.Open(src) // nolint gosec
// if err != nil {

View File

@@ -8,7 +8,8 @@
- make local xcompile (builds binaries for all platforms)
- git release
./scripts/publish.sh
./scripts/release.sh
./scripts/upload.sh
- snapcraft
- will auto create a candidate, test it works then promote

21
scripts/release.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
set -ex
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
CURRENT="$(git describe --tags --abbrev=0)"
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
OWNER="mikefarah"
REPO="yq"
release() {
github-release release \
--user "$OWNER" \
--draft \
--repo "$REPO" \
--tag "$CURRENT"
}
release

View File

@@ -7,14 +7,6 @@ PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
OWNER="mikefarah"
REPO="yq"
release() {
github-release release \
--user "$OWNER" \
--draft \
--repo "$REPO" \
--tag "$CURRENT"
}
upload() {
mkdir -p ./build-done
while IFS= read -r -d $'\0'; do
@@ -32,5 +24,5 @@ upload() {
done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
}
# release
upload

View File

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