mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
36 Commits
path-tree
...
4.0.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a66bb797d | ||
|
|
1ce30b25dc | ||
|
|
3d6a231722 | ||
|
|
3f04a1b52e | ||
|
|
aed598c736 | ||
|
|
e9fa873af8 | ||
|
|
064cff1341 | ||
|
|
fc3af441e5 | ||
|
|
e451119014 | ||
|
|
d38caf6bc2 | ||
|
|
4e385a1b93 | ||
|
|
356aac5a1f | ||
|
|
663413cd7a | ||
|
|
f03005f86d | ||
|
|
bc87aca8d7 | ||
|
|
c08980e70f | ||
|
|
9674acf684 | ||
|
|
8e1ce4ca70 | ||
|
|
9bd9468526 | ||
|
|
75044e480c | ||
|
|
36084a60a9 | ||
|
|
9b48cf80e0 | ||
|
|
bb3b08e648 | ||
|
|
dcacad1e7e | ||
|
|
3356061e1e | ||
|
|
2c062bc2a5 | ||
|
|
088ec36acd | ||
|
|
83cb6421df | ||
|
|
a57944d123 | ||
|
|
79867473d5 | ||
|
|
b3efcdc202 | ||
|
|
af2aa9ad91 | ||
|
|
db4762ef7c | ||
|
|
860655b4cd | ||
|
|
d91b25840a | ||
|
|
019acfe456 |
@@ -22,4 +22,4 @@ LABEL version=${VERSION}
|
||||
|
||||
WORKDIR /workdir
|
||||
|
||||
ENTRYPOINT [/usr/bin/yq]
|
||||
ENTRYPOINT ["/usr/bin/yq"]
|
||||
|
||||
@@ -14,6 +14,5 @@ var noDocSeparators = false
|
||||
var nullInput = false
|
||||
var verbose = false
|
||||
var version = false
|
||||
var shellCompletion = ""
|
||||
|
||||
// var log = logging.MustGetLogger("yq")
|
||||
|
||||
@@ -40,23 +40,23 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
||||
colorsEnabled = true
|
||||
}
|
||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||
|
||||
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = yqlib.EvaluateAllFileStreams("", []string{"-"}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer)
|
||||
} else {
|
||||
err = yqlib.EvaluateAllFileStreams("", []string{args[0]}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], args[1:], printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
}
|
||||
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
@@ -41,22 +41,25 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||
|
||||
streamEvaluator := yqlib.NewStreamEvaluator()
|
||||
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = yqlib.EvaluateFileStreamsSequence("", []string{"-"}, printer)
|
||||
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer)
|
||||
} else {
|
||||
err = yqlib.EvaluateFileStreamsSequence("", []string{args[0]}, printer)
|
||||
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:], printer)
|
||||
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
}
|
||||
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
23
cmd/root.go
23
cmd/root.go
@@ -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
61
cmd/shell-completion.go
Normal 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
|
||||
|
||||
},
|
||||
}
|
||||
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "4.0.0-alpha1"
|
||||
Version = "4.0.0-alpha2"
|
||||
|
||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
|
||||
13
compare.sh
13
compare.sh
@@ -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
|
||||
45
pkg/yqlib/all_at_once_evaluator.go
Normal file
45
pkg/yqlib/all_at_once_evaluator.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
/**
|
||||
Loads all yaml documents of all files given into memory, then runs the given expression once.
|
||||
**/
|
||||
type Evaluator interface {
|
||||
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
||||
}
|
||||
|
||||
type allAtOnceEvaluator struct {
|
||||
treeNavigator DataTreeNavigator
|
||||
treeCreator PathTreeCreator
|
||||
}
|
||||
|
||||
func NewAllAtOnceEvaluator() Evaluator {
|
||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
||||
}
|
||||
|
||||
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||
fileIndex := 0
|
||||
node, err := treeCreator.ParsePath(expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var allDocuments *list.List = list.New()
|
||||
for _, filename := range filenames {
|
||||
reader, err := readStream(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileDocuments, err := readDocuments(reader, filename, fileIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allDocuments.PushBackList(fileDocuments)
|
||||
fileIndex = fileIndex + 1
|
||||
}
|
||||
matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintResults(matches)
|
||||
}
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CandidateNode struct {
|
||||
Node *yaml.Node // the actual node
|
||||
Path []interface{} /// the path we took to get to this node
|
||||
Document uint // the document index of this node
|
||||
Filename string
|
||||
Node *yaml.Node // the actual node
|
||||
Path []interface{} /// the path we took to get to this node
|
||||
Document uint // the document index of this node
|
||||
Filename string
|
||||
FileIndex int
|
||||
}
|
||||
|
||||
func (n *CandidateNode) GetKey() string {
|
||||
|
||||
@@ -5,17 +5,9 @@ import (
|
||||
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
type dataTreeNavigator struct {
|
||||
navigationPrefs NavigationPrefs
|
||||
}
|
||||
|
||||
type NavigationPrefs struct {
|
||||
FollowAlias bool
|
||||
}
|
||||
|
||||
type DataTreeNavigator interface {
|
||||
// given a list of CandidateEntities and a pathNode,
|
||||
// this will process the list against the given pathNode and return
|
||||
@@ -23,8 +15,11 @@ type DataTreeNavigator interface {
|
||||
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
|
||||
}
|
||||
|
||||
func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator {
|
||||
return &dataTreeNavigator{navigationPrefs}
|
||||
type dataTreeNavigator struct {
|
||||
}
|
||||
|
||||
func NewDataTreeNavigator() DataTreeNavigator {
|
||||
return &dataTreeNavigator{}
|
||||
}
|
||||
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
@@ -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))
|
||||
// }
|
||||
1
pkg/yqlib/doc/.gitignore
vendored
1
pkg/yqlib/doc/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
*.md
|
||||
107
pkg/yqlib/doc/Add.md
Normal file
107
pkg/yqlib/doc/Add.md
Normal file
@@ -0,0 +1,107 @@
|
||||
Add behaves differently according to the type of the LHS:
|
||||
- arrays: concatenate
|
||||
- number scalars: arithmetic addition (soon)
|
||||
- string scalars: concatenate (soon)
|
||||
## Concatenate arrays
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- 1
|
||||
- 2
|
||||
b:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a + .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
||||
## Concatenate null to array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- 1
|
||||
- 2
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a + null' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
```
|
||||
|
||||
## Add object to array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- 1
|
||||
- 2
|
||||
c:
|
||||
cat: meow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a + .c' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- cat: meow
|
||||
```
|
||||
|
||||
## Add string to array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- 1
|
||||
- 2
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a + "hello"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- hello
|
||||
```
|
||||
|
||||
## Update array (append)
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- 1
|
||||
- 2
|
||||
b:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a = .a + .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
b:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
||||
157
pkg/yqlib/doc/Assign.md
Normal file
157
pkg/yqlib/doc/Assign.md
Normal file
@@ -0,0 +1,157 @@
|
||||
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.
|
||||
## Update node 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 node 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
|
||||
{}
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a.b |= "bogs"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
{a: {b: bogs}}
|
||||
```
|
||||
|
||||
## Update empty object and array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
{}
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a.b[0] |= "bogs"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
{a: {b: [bogs]}}
|
||||
```
|
||||
|
||||
113
pkg/yqlib/doc/Boolean Operators.md
Normal file
113
pkg/yqlib/doc/Boolean Operators.md
Normal file
@@ -0,0 +1,113 @@
|
||||
The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes.
|
||||
## OR example
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'true or false'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
true
|
||||
```
|
||||
|
||||
## AND example
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'true and false'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
## Matching nodes with select, equals and or
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- a: bird
|
||||
b: dog
|
||||
- a: frog
|
||||
b: bird
|
||||
- a: cat
|
||||
b: fly
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[.[] | select(.a == "cat" or .b == "dog")]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a: bird
|
||||
b: dog
|
||||
- a: cat
|
||||
b: fly
|
||||
```
|
||||
|
||||
## Not true is false
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'true | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
## Not false is true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'false | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
true
|
||||
```
|
||||
|
||||
## String values considered to be true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '"cat" | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
## Empty string value considered to be true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '"" | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
## Numbers are considered to be true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '1 | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
## Zero is considered to be true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '0 | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
## Null is considered to be false
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '~ | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
true
|
||||
```
|
||||
|
||||
41
pkg/yqlib/doc/Collect into Array.md
Normal file
41
pkg/yqlib/doc/Collect into Array.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Collect into Array
|
||||
|
||||
This creates an array using the expression between the square brackets.
|
||||
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
79
pkg/yqlib/doc/Collect into Object.md
Normal file
79
pkg/yqlib/doc/Collect into Object.md
Normal 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.
|
||||
## 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
|
||||
```
|
||||
|
||||
119
pkg/yqlib/doc/Comment Operators.md
Normal file
119
pkg/yqlib/doc/Comment Operators.md
Normal file
@@ -0,0 +1,119 @@
|
||||
Use these comment operators to set or retrieve comments.
|
||||
## 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
|
||||
|
||||
```
|
||||
|
||||
65
pkg/yqlib/doc/Delete.md
Normal file
65
pkg/yqlib/doc/Delete.md
Normal file
@@ -0,0 +1,65 @@
|
||||
Deletes matching entries in maps or arrays.
|
||||
## 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
|
||||
```
|
||||
|
||||
54
pkg/yqlib/doc/Document Index.md
Normal file
54
pkg/yqlib/doc/Document Index.md
Normal file
@@ -0,0 +1,54 @@
|
||||
Use the `documentIndex` operator to select nodes of a particular document.
|
||||
## 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
|
||||
```
|
||||
|
||||
59
pkg/yqlib/doc/Equals.md
Normal file
59
pkg/yqlib/doc/Equals.md
Normal file
@@ -0,0 +1,59 @@
|
||||
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)
|
||||
```
|
||||
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
98
pkg/yqlib/doc/Explode.md
Normal file
98
pkg/yqlib/doc/Explode.md
Normal file
@@ -0,0 +1,98 @@
|
||||
Explodes (or dereferences) aliases and anchors.
|
||||
## 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
|
||||
```
|
||||
|
||||
35
pkg/yqlib/doc/File Operators.md
Normal file
35
pkg/yqlib/doc/File Operators.md
Normal file
@@ -0,0 +1,35 @@
|
||||
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
||||
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
```
|
||||
## Get filename
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'filename' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
sample.yaml
|
||||
```
|
||||
|
||||
## Get file index
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval 'fileIndex' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
0
|
||||
```
|
||||
|
||||
44
pkg/yqlib/doc/Has.md
Normal file
44
pkg/yqlib/doc/Has.md
Normal file
@@ -0,0 +1,44 @@
|
||||
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
## Has map key
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- a: yes
|
||||
- a: ~
|
||||
- a:
|
||||
- b: nope
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] | has("a")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
true
|
||||
true
|
||||
true
|
||||
false
|
||||
```
|
||||
|
||||
## Has array index
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- []
|
||||
- [1]
|
||||
- [1, 2]
|
||||
- [1, null]
|
||||
- [1, 2, 3]
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.[] | has(1)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
false
|
||||
true
|
||||
true
|
||||
true
|
||||
```
|
||||
|
||||
204
pkg/yqlib/doc/Multiply.md
Normal file
204
pkg/yqlib/doc/Multiply.md
Normal file
@@ -0,0 +1,204 @@
|
||||
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.
|
||||
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||
```
|
||||
|
||||
## Merge objects together, returning merged result only
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
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
|
||||
```
|
||||
|
||||
58
pkg/yqlib/doc/Path.md
Normal file
58
pkg/yqlib/doc/Path.md
Normal file
@@ -0,0 +1,58 @@
|
||||
The path operator can be used to get the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.
|
||||
## Map path
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
b: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a.b | path' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a
|
||||
- b
|
||||
```
|
||||
|
||||
## Array path
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- cat
|
||||
- dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a.[] | select(. == "dog") | path' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a
|
||||
- 1
|
||||
```
|
||||
|
||||
## Print path and value
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- cat
|
||||
- dog
|
||||
- frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a.[] | select(. == "*og") | [{"path":path, "value":.}]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- path:
|
||||
- a
|
||||
- 1
|
||||
value: dog
|
||||
- path:
|
||||
- a
|
||||
- 2
|
||||
value: frog
|
||||
```
|
||||
|
||||
63
pkg/yqlib/doc/Recursive Descent.md
Normal file
63
pkg/yqlib/doc/Recursive Descent.md
Normal file
@@ -0,0 +1,63 @@
|
||||
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
|
||||
```
|
||||
## 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
|
||||
```
|
||||
|
||||
38
pkg/yqlib/doc/Select.md
Normal file
38
pkg/yqlib/doc/Select.md
Normal file
@@ -0,0 +1,38 @@
|
||||
Select is used to filter arrays and maps by a boolean expression.
|
||||
## 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
|
||||
```
|
||||
|
||||
165
pkg/yqlib/doc/Style.md
Normal file
165
pkg/yqlib/doc/Style.md
Normal 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)
|
||||
## 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}
|
||||
```
|
||||
|
||||
## Pretty print
|
||||
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
|
||||
flow
|
||||
double
|
||||
single
|
||||
```
|
||||
|
||||
44
pkg/yqlib/doc/Tag.md
Normal file
44
pkg/yqlib/doc/Tag.md
Normal file
@@ -0,0 +1,44 @@
|
||||
The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!int`, `!!bool`).
|
||||
## Get tag
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
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
|
||||
```
|
||||
|
||||
368
pkg/yqlib/doc/Traverse.md
Normal file
368
pkg/yqlib/doc/Traverse.md
Normal file
@@ -0,0 +1,368 @@
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
|
||||
## Simple map navigation
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
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
|
||||
```
|
||||
|
||||
## Special characters
|
||||
Use quotes around path elements with special characters
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
"{}": frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '."{}"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
frog
|
||||
```
|
||||
|
||||
## Children don't exist
|
||||
Nodes are added dynamically while traversing
|
||||
|
||||
Given a sample.yml file of:
|
||||
```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
|
||||
```
|
||||
|
||||
30
pkg/yqlib/doc/Union.md
Normal file
30
pkg/yqlib/doc/Union.md
Normal file
@@ -0,0 +1,30 @@
|
||||
This operator is used to combine different results together.
|
||||
## 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
|
||||
```
|
||||
|
||||
4
pkg/yqlib/doc/headers/Add.md
Normal file
4
pkg/yqlib/doc/headers/Add.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Add behaves differently according to the type of the LHS:
|
||||
- arrays: concatenate
|
||||
- number scalars: arithmetic addition (soon)
|
||||
- string scalars: concatenate (soon)
|
||||
7
pkg/yqlib/doc/headers/Assign.md
Normal file
7
pkg/yqlib/doc/headers/Assign.md
Normal 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.
|
||||
1
pkg/yqlib/doc/headers/Boolean Operators.md
Normal file
1
pkg/yqlib/doc/headers/Boolean Operators.md
Normal file
@@ -0,0 +1 @@
|
||||
The `or` and `and` operators take two parameters and return a boolean result. `not` flips a boolean from true to false, or vice versa. These are most commonly used with the `select` operator to filter particular nodes.
|
||||
4
pkg/yqlib/doc/headers/Collect into Array.md
Normal file
4
pkg/yqlib/doc/headers/Collect into Array.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Collect into Array
|
||||
|
||||
This creates an array using the expression between the square brackets.
|
||||
|
||||
1
pkg/yqlib/doc/headers/Collect into Object.md
Normal file
1
pkg/yqlib/doc/headers/Collect into Object.md
Normal 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.
|
||||
1
pkg/yqlib/doc/headers/Comment Operators.md
Normal file
1
pkg/yqlib/doc/headers/Comment Operators.md
Normal file
@@ -0,0 +1 @@
|
||||
Use these comment operators to set or retrieve comments.
|
||||
1
pkg/yqlib/doc/headers/Delete.md
Normal file
1
pkg/yqlib/doc/headers/Delete.md
Normal file
@@ -0,0 +1 @@
|
||||
Deletes matching entries in maps or arrays.
|
||||
1
pkg/yqlib/doc/headers/Document Index.md
Normal file
1
pkg/yqlib/doc/headers/Document Index.md
Normal file
@@ -0,0 +1 @@
|
||||
Use the `documentIndex` operator to select nodes of a particular document.
|
||||
12
pkg/yqlib/doc/headers/Equals.md
Normal file
12
pkg/yqlib/doc/headers/Equals.md
Normal file
@@ -0,0 +1,12 @@
|
||||
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)
|
||||
```
|
||||
|
||||
1
pkg/yqlib/doc/headers/Explode.md
Normal file
1
pkg/yqlib/doc/headers/Explode.md
Normal file
@@ -0,0 +1 @@
|
||||
Explodes (or dereferences) aliases and anchors.
|
||||
7
pkg/yqlib/doc/headers/File Operators.md
Normal file
7
pkg/yqlib/doc/headers/File Operators.md
Normal file
@@ -0,0 +1,7 @@
|
||||
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
|
||||
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
|
||||
```
|
||||
1
pkg/yqlib/doc/headers/Has.md
Normal file
1
pkg/yqlib/doc/headers/Has.md
Normal file
@@ -0,0 +1 @@
|
||||
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
12
pkg/yqlib/doc/headers/Multiply.md
Normal file
12
pkg/yqlib/doc/headers/Multiply.md
Normal file
@@ -0,0 +1,12 @@
|
||||
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.
|
||||
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||
```
|
||||
1
pkg/yqlib/doc/headers/Path.md
Normal file
1
pkg/yqlib/doc/headers/Path.md
Normal file
@@ -0,0 +1 @@
|
||||
The path operator can be used to get the traversal paths of matching nodes in an expression. The path is returned as an array, which if traversed in order will lead to the matching node.
|
||||
5
pkg/yqlib/doc/headers/Recursive Descent.md
Normal file
5
pkg/yqlib/doc/headers/Recursive Descent.md
Normal 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
|
||||
```
|
||||
1
pkg/yqlib/doc/headers/Select.md
Normal file
1
pkg/yqlib/doc/headers/Select.md
Normal file
@@ -0,0 +1 @@
|
||||
Select is used to filter arrays and maps by a boolean expression.
|
||||
1
pkg/yqlib/doc/headers/Style.md
Normal file
1
pkg/yqlib/doc/headers/Style.md
Normal file
@@ -0,0 +1 @@
|
||||
The style operator can be used to get or set the style of nodes (e.g. string style, yaml style)
|
||||
1
pkg/yqlib/doc/headers/Tag.md
Normal file
1
pkg/yqlib/doc/headers/Tag.md
Normal file
@@ -0,0 +1 @@
|
||||
The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!int`, `!!bool`).
|
||||
1
pkg/yqlib/doc/headers/Traverse.md
Normal file
1
pkg/yqlib/doc/headers/Traverse.md
Normal file
@@ -0,0 +1 @@
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
|
||||
1
pkg/yqlib/doc/headers/Union.md
Normal file
1
pkg/yqlib/doc/headers/Union.md
Normal file
@@ -0,0 +1 @@
|
||||
This operator is used to combine different results together.
|
||||
47
pkg/yqlib/encoder_test.go
Normal file
47
pkg/yqlib/encoder_test.go
Normal 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", 0)
|
||||
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())
|
||||
|
||||
}
|
||||
@@ -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,9 @@ 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)
|
||||
// - 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,11 +30,14 @@ 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}
|
||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
|
||||
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
|
||||
|
||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||
@@ -50,8 +46,12 @@ 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}
|
||||
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
|
||||
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
|
||||
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
|
||||
|
||||
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
||||
|
||||
@@ -66,11 +66,9 @@ 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 Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
||||
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
||||
|
||||
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}
|
||||
// filters matches if they have the existing path
|
||||
|
||||
69
pkg/yqlib/operator_add.go
Normal file
69
pkg/yqlib/operator_add.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func toNodes(candidates *list.List) []*yaml.Node {
|
||||
|
||||
if candidates.Len() == 0 {
|
||||
return []*yaml.Node{}
|
||||
}
|
||||
candidate := candidates.Front().Value.(*CandidateNode)
|
||||
|
||||
if candidate.Node.Tag == "!!null" {
|
||||
return []*yaml.Node{}
|
||||
}
|
||||
|
||||
switch candidate.Node.Kind {
|
||||
case yaml.SequenceNode:
|
||||
return candidate.Node.Content
|
||||
default:
|
||||
return []*yaml.Node{candidate.Node}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("Add operator")
|
||||
var results = list.New()
|
||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||
lhsCandidate := el.Value.(*CandidateNode)
|
||||
lhsNode := UnwrapDoc(lhsCandidate.Node)
|
||||
|
||||
var newBlank = &CandidateNode{
|
||||
Path: lhsCandidate.Path,
|
||||
Document: lhsCandidate.Document,
|
||||
Filename: lhsCandidate.Filename,
|
||||
Node: &yaml.Node{},
|
||||
}
|
||||
|
||||
switch lhsNode.Kind {
|
||||
case yaml.MappingNode:
|
||||
return nil, fmt.Errorf("Maps not yet supported for addition")
|
||||
case yaml.SequenceNode:
|
||||
newBlank.Node.Kind = yaml.SequenceNode
|
||||
newBlank.Node.Style = lhsNode.Style
|
||||
newBlank.Node.Tag = "!!seq"
|
||||
newBlank.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||
results.PushBack(newBlank)
|
||||
case yaml.ScalarNode:
|
||||
return nil, fmt.Errorf("Scalars not yet supported for addition")
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
55
pkg/yqlib/operator_add_test.go
Normal file
55
pkg/yqlib/operator_add_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var addOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Concatenate arrays",
|
||||
document: `{a: [1,2], b: [3,4]}`,
|
||||
expression: `.a + .b`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!seq)::[1, 2, 3, 4]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Concatenate null to array",
|
||||
document: `{a: [1,2]}`,
|
||||
expression: `.a + null`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!seq)::[1, 2]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Add object to array",
|
||||
document: `{a: [1,2], c: {cat: meow}}`,
|
||||
expression: `.a + .c`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!seq)::[1, 2, {cat: meow}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Add string to array",
|
||||
document: `{a: [1,2]}`,
|
||||
expression: `.a + "hello"`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!seq)::[1, 2, hello]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update array (append)",
|
||||
document: `{a: [1,2], b: [3,4]}`,
|
||||
expression: `.a = .a + .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: [1, 2, 3, 4], b: [3, 4]}\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAddOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range addOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Add", addOperatorScenarios)
|
||||
}
|
||||
@@ -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
|
||||
122
pkg/yqlib/operator_assign_test.go
Normal file
122
pkg/yqlib/operator_assign_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Update node to be the child value",
|
||||
document: `{a: {b: {g: foof}}}`,
|
||||
expression: `.a |= .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {g: foof}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update node 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",
|
||||
dontFormatInputForDoc: true,
|
||||
document: `{}`,
|
||||
expression: `.a.b |= "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: bogs}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update empty object and array",
|
||||
dontFormatInputForDoc: true,
|
||||
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", assignOperatorScenarios)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package yqlib
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func isTruthy(c *CandidateNode) (bool, error) {
|
||||
@@ -25,9 +25,42 @@ func isTruthy(c *CandidateNode) (bool, error) {
|
||||
|
||||
type boolOp func(bool, bool) bool
|
||||
|
||||
func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
|
||||
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
||||
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
||||
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return errDecoding
|
||||
}
|
||||
|
||||
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
|
||||
rhsCandidate := rhsChild.Value.(*CandidateNode)
|
||||
rhsTrue, errDecoding := isTruthy(rhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return errDecoding
|
||||
}
|
||||
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
|
||||
results.PushBack(boolResult)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
if matchingNodes.Len() == 0 {
|
||||
lhs, err := d.GetMatchingNodes(list.New(), pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(list.New(), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return results, performBoolOp(results, lhs, rhs, op)
|
||||
}
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
|
||||
@@ -39,23 +72,9 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTre
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
||||
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
||||
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
|
||||
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
|
||||
rhsCandidate := rhsChild.Value.(*CandidateNode)
|
||||
rhsTrue, errDecoding := isTruthy(rhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
|
||||
|
||||
results.PushBack(boolResult)
|
||||
}
|
||||
err = performBoolOp(results, lhs, rhs, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,3 +94,20 @@ func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
|
||||
return b1 && b2
|
||||
})
|
||||
}
|
||||
|
||||
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- notOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debug("notOperation checking %v", candidate)
|
||||
truthy, errDecoding := isTruthy(candidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
result := createBooleanCandidate(candidate, !truthy)
|
||||
results.PushBack(result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -6,18 +6,36 @@ import (
|
||||
|
||||
var booleanOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `true or false`,
|
||||
description: "OR example",
|
||||
expression: `true or false`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
}, {
|
||||
document: `{}`,
|
||||
},
|
||||
{
|
||||
description: "AND example",
|
||||
expression: `true and false`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: "[{a: bird, b: dog}, {a: frog, b: bird}, {a: cat, b: fly}]",
|
||||
description: "Matching nodes with select, equals and or",
|
||||
expression: `[.[] | select(.a == "cat" or .b == "dog")]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- {a: bird, b: dog}\n- {a: cat, b: fly}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `false or false`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: true, b: false}`,
|
||||
expression: `.[] or (false, true)`,
|
||||
expected: []string{
|
||||
@@ -27,10 +45,61 @@ var booleanOperatorScenarios = []expressionScenario{
|
||||
"D0, P[b], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Not true is false",
|
||||
expression: `true | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Not false is true",
|
||||
expression: `false | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "String values considered to be true",
|
||||
expression: `"cat" | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Empty string value considered to be true",
|
||||
expression: `"" | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Numbers are considered to be true",
|
||||
expression: `1 | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Zero is considered to be true",
|
||||
expression: `0 | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
description: "Null is considered to be false",
|
||||
expression: `~ | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestBooleanOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range booleanOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Boolean Operators", booleanOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,18 @@ package yqlib
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- collectOperation")
|
||||
|
||||
if matchMap.Len() == 0 {
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return nodeToMap(candidate), nil
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
|
||||
@@ -2,6 +2,8 @@ package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -19,7 +21,9 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
|
||||
log.Debugf("-- collectObjectOperation")
|
||||
|
||||
if matchMap.Len() == 0 {
|
||||
return list.New(), nil
|
||||
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return nodeToMap(candidate), nil
|
||||
}
|
||||
first := matchMap.Front().Value.(*CandidateNode)
|
||||
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
||||
|
||||
@@ -6,18 +6,23 @@ import (
|
||||
|
||||
var collectObjectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: ``,
|
||||
expression: `{}`,
|
||||
expected: []string{},
|
||||
description: `Collect empty object`,
|
||||
document: ``,
|
||||
expression: `{}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
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 +31,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{name: Mike, age: 32}`,
|
||||
expression: `{.name: .age}`,
|
||||
expected: []string{
|
||||
@@ -33,16 +39,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,8 +60,9 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
||||
expression: `{.name: .pets[], "f":.food[]}`,
|
||||
expression: `{.name: .pets.[], "f":.food.[]}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Mike: cat\nf: hotdog\n",
|
||||
"D0, P[], (!!map)::Mike: cat\nf: burger\n",
|
||||
@@ -61,6 +71,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{name: Mike, pets: {cows: [apl, bba]}}`,
|
||||
expression: `{"a":.name, "b":.pets}`,
|
||||
expected: []string{
|
||||
@@ -70,13 +81,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 +97,7 @@ b: {cows: [apl, bba]}
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{name: Mike}`,
|
||||
expression: `{"wrap": {"further": .}} | (.. style= "flow")`,
|
||||
expected: []string{
|
||||
|
||||
@@ -6,43 +6,56 @@ import (
|
||||
|
||||
var collectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: ``,
|
||||
expression: `[]`,
|
||||
expected: []string{},
|
||||
description: "Collect empty",
|
||||
document: ``,
|
||||
expression: `[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
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[]]`,
|
||||
expression: `[.a.b.[]]`,
|
||||
skipDoc: true,
|
||||
expected: []string{
|
||||
"D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n",
|
||||
},
|
||||
|
||||
@@ -75,5 +75,5 @@ func TestCommentOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range commentOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Comments Operator", commentOperatorScenarios)
|
||||
documentScenarios(t, "Comment Operators", commentOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@ var createMapOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: [cat, dog]}`,
|
||||
expression: `.name: .pets[]`,
|
||||
expression: `.name: .pets.[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
||||
expression: `.name: .pets[], "f":.food[]`,
|
||||
expression: `.name: .pets.[], "f":.food.[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
||||
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n",
|
||||
@@ -36,7 +36,7 @@ var createMapOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
document: "{name: Mike, pets: [cat, dog], food: [hotdog, burger]}\n---\n{name: Fred, pets: [mouse], food: [pizza, onion, apple]}",
|
||||
expression: `.name: .pets[], "f":.food[]`,
|
||||
expression: `.name: .pets.[], "f":.food.[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n",
|
||||
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
47
pkg/yqlib/operator_delete_test.go
Normal file
47
pkg/yqlib/operator_delete_test.go
Normal 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", deleteOperatorScenarios)
|
||||
}
|
||||
@@ -37,5 +37,5 @@ func TestDocumentIndexScenarios(t *testing.T) {
|
||||
for _, tt := range documentIndexScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Document Index Operator", documentIndexScenarios)
|
||||
documentScenarios(t, "Document Index", documentIndexScenarios)
|
||||
}
|
||||
@@ -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",
|
||||
},
|
||||
@@ -49,5 +54,5 @@ func TestEqualOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range equalsOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Equals Operator", equalsOperatorScenarios)
|
||||
documentScenarios(t, "Equals", equalsOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -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", explodeTest)
|
||||
}
|
||||
|
||||
38
pkg/yqlib/operator_file.go
Normal file
38
pkg/yqlib/operator_file.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetFilename")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetFileIndex")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
31
pkg/yqlib/operator_file_test.go
Normal file
31
pkg/yqlib/operator_file_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var fileOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Get filename",
|
||||
document: `{a: cat}`,
|
||||
expression: `filename`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::sample.yml\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get file index",
|
||||
document: `{a: cat}`,
|
||||
expression: `fileIndex`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::0\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestFileOperatorsScenarios(t *testing.T) {
|
||||
for _, tt := range fileOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "File Operators", fileOperatorScenarios)
|
||||
}
|
||||
53
pkg/yqlib/operator_has.go
Normal file
53
pkg/yqlib/operator_has.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"strconv"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("-- hasOperation")
|
||||
var results = list.New()
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
wanted := rhs.Front().Value.(*CandidateNode).Node
|
||||
wantedKey := wanted.Value
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
// grab the first value
|
||||
var contents = candidate.Node.Content
|
||||
switch candidate.Node.Kind {
|
||||
case yaml.MappingNode:
|
||||
candidateHasKey := false
|
||||
for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 {
|
||||
key := contents[index]
|
||||
if key.Value == wantedKey {
|
||||
candidateHasKey = true
|
||||
}
|
||||
}
|
||||
results.PushBack(createBooleanCandidate(candidate, candidateHasKey))
|
||||
case yaml.SequenceNode:
|
||||
candidateHasKey := false
|
||||
if wanted.Tag == "!!int" {
|
||||
var number, errParsingInt = strconv.ParseInt(wantedKey, 10, 64) // nolint
|
||||
if errParsingInt != nil {
|
||||
return nil, errParsingInt
|
||||
}
|
||||
candidateHasKey = int64(len(contents)) > number
|
||||
}
|
||||
results.PushBack(createBooleanCandidate(candidate, candidateHasKey))
|
||||
default:
|
||||
results.PushBack(createBooleanCandidate(candidate, false))
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
48
pkg/yqlib/operator_has_test.go
Normal file
48
pkg/yqlib/operator_has_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var hasOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Has map key",
|
||||
document: `- a: "yes"
|
||||
- a: ~
|
||||
- a:
|
||||
- b: nope
|
||||
`,
|
||||
expression: `.[] | has("a")`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::true\n",
|
||||
"D0, P[1], (!!bool)::true\n",
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
"D0, P[3], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
dontFormatInputForDoc: true,
|
||||
description: "Has array index",
|
||||
document: `- []
|
||||
- [1]
|
||||
- [1, 2]
|
||||
- [1, null]
|
||||
- [1, 2, 3]
|
||||
`,
|
||||
expression: `.[] | has(1)`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::false\n",
|
||||
"D0, P[1], (!!bool)::false\n",
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
"D0, P[3], (!!bool)::true\n",
|
||||
"D0, P[4], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestHasOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range hasOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Has", hasOperatorScenarios)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
|
||||
@@ -15,12 +15,14 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("crossFunction LHS len: %v", lhs.Len())
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("crossFunction RHS len: %v", rhs.Len())
|
||||
|
||||
var results = list.New()
|
||||
|
||||
@@ -28,6 +30,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
||||
lhsCandidate := el.Value.(*CandidateNode)
|
||||
|
||||
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
|
||||
log.Debugf("Applying calc")
|
||||
rhsCandidate := rightEl.Value.(*CandidateNode)
|
||||
resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
|
||||
if err != nil {
|
||||
@@ -48,8 +51,8 @@ func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *
|
||||
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
log.Debugf("Multipling LHS: %v", NodeToString(lhs))
|
||||
log.Debugf("- RHS: %v", NodeToString(rhs))
|
||||
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||
|
||||
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||
@@ -67,7 +70,7 @@ func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*Ca
|
||||
return mergeObjects(d, newThing, rhs)
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("Cannot multiply %v with %v", NodeToString(lhs), NodeToString(rhs))
|
||||
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
||||
}
|
||||
|
||||
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
@@ -112,6 +115,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}
|
||||
|
||||
@@ -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", multiplyOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- notOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debug("notOperation checking %v", candidate)
|
||||
truthy, errDecoding := isTruthy(candidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
result := createBooleanCandidate(candidate, !truthy)
|
||||
results.PushBack(result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
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`,
|
||||
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)
|
||||
}
|
||||
}
|
||||
39
pkg/yqlib/operator_path.go
Normal file
39
pkg/yqlib/operator_path.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func createPathNodeFor(pathElement interface{}) *yaml.Node {
|
||||
switch pathElement := pathElement.(type) {
|
||||
case string:
|
||||
return &yaml.Node{Kind: yaml.ScalarNode, Value: pathElement, Tag: "!!str"}
|
||||
default:
|
||||
return &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", pathElement), Tag: "!!int"}
|
||||
}
|
||||
}
|
||||
|
||||
func GetPathOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetPath")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
|
||||
content := make([]*yaml.Node, len(candidate.Path))
|
||||
for pathIndex := 0; pathIndex < len(candidate.Path); pathIndex++ {
|
||||
path := candidate.Path[pathIndex]
|
||||
content[pathIndex] = createPathNodeFor(path)
|
||||
}
|
||||
node.Content = content
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
45
pkg/yqlib/operator_path_test.go
Normal file
45
pkg/yqlib/operator_path_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var pathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Map path",
|
||||
document: `{a: {b: cat}}`,
|
||||
expression: `.a.b | path`,
|
||||
expected: []string{
|
||||
"D0, P[a b], (!!seq)::- a\n- b\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Array path",
|
||||
document: `{a: [cat, dog]}`,
|
||||
expression: `.a.[] | select(. == "dog") | path`,
|
||||
expected: []string{
|
||||
"D0, P[a 1], (!!seq)::- a\n- 1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Print path and value",
|
||||
document: `{a: [cat, dog, frog]}`,
|
||||
expression: `.a.[] | select(. == "*og") | [{"path":path, "value":.}]`,
|
||||
expected: []string{`D0, P[], (!!seq)::- path:
|
||||
- a
|
||||
- 1
|
||||
value: dog
|
||||
- path:
|
||||
- a
|
||||
- 2
|
||||
value: frog
|
||||
`},
|
||||
},
|
||||
}
|
||||
|
||||
func TestPathOperatorsScenarios(t *testing.T) {
|
||||
for _, tt := range pathOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Path", pathOperatorScenarios)
|
||||
}
|
||||
@@ -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,6 +23,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
@@ -30,6 +33,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[1,2,3]`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
@@ -40,6 +44,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{a: cat},2,true]`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
@@ -51,26 +56,23 @@ 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",
|
||||
"D0, P[a c], (!!str)::frog\n",
|
||||
"D0, P[b], (alias)::*cat\n",
|
||||
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
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",
|
||||
"D0, P[foobar <<], (alias)::*foo\n",
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList | ..`,
|
||||
expected: []string{
|
||||
@@ -88,4 +90,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range recursiveDescentOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Recursive Descent", recursiveDescentOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -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")`,
|
||||
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)`,
|
||||
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", selectOperatorScenarios)
|
||||
}
|
||||
|
||||
116
pkg/yqlib/operator_style_test.go
Normal file
116
pkg/yqlib/operator_style_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
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: "Pretty print",
|
||||
subdescription: "Set empty (default) quote style",
|
||||
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
||||
expression: `.. style=""`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
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'}`,
|
||||
dontFormatInputForDoc: true,
|
||||
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", styleOperatorScenarios)
|
||||
}
|
||||
51
pkg/yqlib/operator_tag.go
Normal file
51
pkg/yqlib/operator_tag.go
Normal 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
|
||||
}
|
||||
36
pkg/yqlib/operator_tag_test.go
Normal file
36
pkg/yqlib/operator_tag_test.go
Normal 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", tagOperatorScenarios)
|
||||
}
|
||||
@@ -28,28 +28,43 @@ 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: "Special characters",
|
||||
subdescription: "Use quotes around path elements with special characters",
|
||||
document: `{"{}": frog}`,
|
||||
expression: `."{}"`,
|
||||
expected: []string{
|
||||
"D0, P[{}], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Children don't exist",
|
||||
subdescription: "Nodes are added dynamically while traversing",
|
||||
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 +72,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: `.a.[1]`,
|
||||
expected: []string{
|
||||
@@ -64,14 +80,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 +99,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {cat: apple, mad: things}}`,
|
||||
expression: `.a | (.cat, .mad)`,
|
||||
expected: []string{
|
||||
@@ -89,6 +108,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {cat: apple, mad: things}}`,
|
||||
expression: `.a | (.cat, .mad, .fad)`,
|
||||
expected: []string{
|
||||
@@ -98,6 +118,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: {cat: apple, mad: things}}`,
|
||||
expression: `.a | (.cat, .mad, .fad) | select( (. == null) | not)`,
|
||||
expected: []string{
|
||||
@@ -106,40 +127,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 +181,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar`,
|
||||
expected: []string{
|
||||
@@ -154,29 +189,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 +223,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList`,
|
||||
expected: []string{
|
||||
@@ -191,6 +231,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.a`,
|
||||
expected: []string{
|
||||
@@ -198,13 +239,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 +256,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeDocSample,
|
||||
expression: `.foobarList.b`,
|
||||
expected: []string{
|
||||
@@ -219,8 +264,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 +280,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range traversePathOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Traverse", traversePathOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -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", unionOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -26,13 +29,13 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
||||
|
||||
node, err := treeCreator.ParsePath(s.expression)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
|
||||
return
|
||||
}
|
||||
inputs := list.New()
|
||||
|
||||
if s.document != "" {
|
||||
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml")
|
||||
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
@@ -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,35 +67,87 @@ 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)
|
||||
}
|
||||
streamEvaluator := NewStreamEvaluator()
|
||||
err = streamEvaluator.Evaluate("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()
|
||||
w := bufio.NewWriter(f)
|
||||
writeOrPanic(w, fmt.Sprintf("# %v\n", title))
|
||||
writeOrPanic(w, "## Examples\n")
|
||||
|
||||
for index, s := range scenarios {
|
||||
err = copyFromHeader(title, f)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
w := bufio.NewWriter(f)
|
||||
writeOrPanic(w, "\n")
|
||||
|
||||
for _, s := range scenarios {
|
||||
if !s.skipDoc {
|
||||
|
||||
if s.description != "" {
|
||||
writeOrPanic(w, fmt.Sprintf("### %v\n", s.description))
|
||||
} else {
|
||||
writeOrPanic(w, fmt.Sprintf("### Example %v\n", index))
|
||||
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||
|
||||
if s.subdescription != "" {
|
||||
writeOrPanic(w, s.subdescription)
|
||||
writeOrPanic(w, "\n\n")
|
||||
}
|
||||
formattedDoc := ""
|
||||
if s.document != "" {
|
||||
writeOrPanic(w, "sample.yml:\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.document))
|
||||
}
|
||||
if s.expression != "" {
|
||||
writeOrPanic(w, "Expression\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```bash\nyq '%v' < sample.yml\n```\n", s.expression))
|
||||
if s.dontFormatInputForDoc {
|
||||
formattedDoc = s.document + "\n"
|
||||
} 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,18 +158,20 @@ 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)
|
||||
streamEvaluator := NewStreamEvaluator()
|
||||
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
} else {
|
||||
err = EvaluateAllFileStreams(s.expression, []string{}, printer)
|
||||
allAtOnceEvaluator := NewAllAtOnceEvaluator()
|
||||
err = allAtOnceEvaluator.EvaluateFiles(s.expression, []string{}, printer)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", output.String()))
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -37,8 +37,18 @@ var pathTests = []struct {
|
||||
// {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")},
|
||||
// {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")},
|
||||
// {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")},
|
||||
// {`.a[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")},
|
||||
// {`.a.[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")},
|
||||
// {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")},
|
||||
// {
|
||||
// `["cat"]`,
|
||||
// append(make([]interface{}, 0), "[", "cat (string)", "]"),
|
||||
// append(make([]interface{}, 0), "cat (string)", "COLLECT", "PIPE"),
|
||||
// },
|
||||
{
|
||||
`[]`,
|
||||
append(make([]interface{}, 0), "[", "]"),
|
||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"),
|
||||
},
|
||||
{
|
||||
`d0.a`,
|
||||
append(make([]interface{}, 0), "d0", "PIPE", "a"),
|
||||
@@ -85,7 +95,7 @@ var pathTests = []struct {
|
||||
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"),
|
||||
},
|
||||
{
|
||||
`{.a: .c, .b[]: .f.g[]}`,
|
||||
`{.a: .c, .b.[]: .f.g.[]}`,
|
||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"),
|
||||
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"),
|
||||
},
|
||||
@@ -99,6 +109,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", ")"),
|
||||
|
||||
@@ -3,7 +3,7 @@ package yqlib
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
type PathPostFixer interface {
|
||||
|
||||
@@ -24,14 +24,16 @@ 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 {
|
||||
if t.TokenType == OperationToken {
|
||||
log.Debug("toString, its an op")
|
||||
return t.Operation.toString()
|
||||
} else if t.TokenType == OpenBracket {
|
||||
return "("
|
||||
@@ -57,13 +59,7 @@ func pathToken(wrapped bool) lex.Action {
|
||||
if wrapped {
|
||||
value = unwrap(value)
|
||||
}
|
||||
op := &Operation{OperationType: TraversePath, Value: value, StringValue: value}
|
||||
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func literalPathToken(value string) lex.Action {
|
||||
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
|
||||
log.Debug("PathToken %v", value)
|
||||
op := &Operation{OperationType: TraversePath, Value: value, StringValue: value}
|
||||
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
|
||||
}
|
||||
@@ -77,20 +73,30 @@ func documentToken() lex.Action {
|
||||
if errParsingInt != nil {
|
||||
return nil, errParsingInt
|
||||
}
|
||||
log.Debug("documentToken %v", string(m.Bytes))
|
||||
op := &Operation{OperationType: DocumentFilter, Value: number, StringValue: numberString}
|
||||
return &Token{TokenType: OperationToken, Operation: op, CheckForPostTraverse: true}, nil
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
log.Debug("opTokenWithPrefs %v", string(m.Bytes))
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,40 +184,44 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte(`\(`), literalToken(OpenBracket, false))
|
||||
lexer.Add([]byte(`\)`), literalToken(CloseBracket, true))
|
||||
|
||||
lexer.Add([]byte(`\.?\[\]`), literalPathToken("[]"))
|
||||
lexer.Add([]byte(`\.\[\]`), pathToken(false))
|
||||
lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent))
|
||||
|
||||
lexer.Add([]byte(`,`), opToken(Union))
|
||||
lexer.Add([]byte(`:\s*`), opToken(CreateMap))
|
||||
lexer.Add([]byte(`length`), opToken(Length))
|
||||
lexer.Add([]byte(`select`), opToken(Select))
|
||||
lexer.Add([]byte(`has`), opToken(Has))
|
||||
lexer.Add([]byte(`explode`), opToken(Explode))
|
||||
lexer.Add([]byte(`or`), opToken(Or))
|
||||
lexer.Add([]byte(`and`), opToken(And))
|
||||
lexer.Add([]byte(`not`), opToken(Not))
|
||||
|
||||
lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))
|
||||
|
||||
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(`filename`), opToken(GetFilename))
|
||||
lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex))
|
||||
lexer.Add([]byte(`path`), opToken(GetPath))
|
||||
|
||||
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))
|
||||
@@ -242,6 +252,7 @@ func initLexer() (*lex.Lexer, error) {
|
||||
lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false))
|
||||
lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true))
|
||||
lexer.Add([]byte(`\*`), opToken(Multiply))
|
||||
lexer.Add([]byte(`\+`), opToken(Add))
|
||||
|
||||
err := lexer.Compile()
|
||||
if err != nil {
|
||||
@@ -286,15 +297,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})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
99
pkg/yqlib/printer_test.go
Normal 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", 0)
|
||||
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", 0)
|
||||
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", 0)
|
||||
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())
|
||||
}
|
||||
85
pkg/yqlib/stream_evaluator.go
Normal file
85
pkg/yqlib/stream_evaluator.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type StreamEvaluator interface {
|
||||
Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error
|
||||
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
||||
}
|
||||
|
||||
type streamEvaluator struct {
|
||||
treeNavigator DataTreeNavigator
|
||||
treeCreator PathTreeCreator
|
||||
fileIndex int
|
||||
}
|
||||
|
||||
func NewStreamEvaluator() StreamEvaluator {
|
||||
return &streamEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
||||
}
|
||||
|
||||
func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||
|
||||
node, err := treeCreator.ParsePath(expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, filename := range filenames {
|
||||
reader, err := readStream(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.Evaluate(filename, reader, node, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch reader := reader.(type) {
|
||||
case *os.File:
|
||||
safelyCloseFile(reader)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *streamEvaluator) Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error {
|
||||
|
||||
var currentIndex uint
|
||||
|
||||
decoder := yaml.NewDecoder(reader)
|
||||
for {
|
||||
var dataBucket yaml.Node
|
||||
errorReading := decoder.Decode(&dataBucket)
|
||||
|
||||
if errorReading == io.EOF {
|
||||
s.fileIndex = s.fileIndex + 1
|
||||
return nil
|
||||
} else if errorReading != nil {
|
||||
return errorReading
|
||||
}
|
||||
candidateNode := &CandidateNode{
|
||||
Document: currentIndex,
|
||||
Filename: filename,
|
||||
Node: &dataBucket,
|
||||
FileIndex: s.fileIndex,
|
||||
}
|
||||
inputList := list.New()
|
||||
inputList.PushBack(candidateNode)
|
||||
|
||||
matches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node)
|
||||
if errorParsing != nil {
|
||||
return errorParsing
|
||||
}
|
||||
err := printer.PrintResults(matches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentIndex = currentIndex + 1
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user