mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
50 Commits
path-tree
...
4.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cd290c00b | ||
|
|
363fe5d283 | ||
|
|
773b1a3517 | ||
|
|
cf4915d786 | ||
|
|
08f579f4e3 | ||
|
|
c9229439f7 | ||
|
|
9bc66c80b6 | ||
|
|
8de10e550d | ||
|
|
1258fa199e | ||
|
|
3a030651a3 | ||
|
|
3f48201a19 | ||
|
|
3cecb4e383 | ||
|
|
13679e51e2 | ||
|
|
5205f01248 | ||
|
|
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
|
WORKDIR /workdir
|
||||||
|
|
||||||
ENTRYPOINT [/usr/bin/yq]
|
ENTRYPOINT ["/usr/bin/yq"]
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package cmd
|
|||||||
|
|
||||||
var unwrapScalar = true
|
var unwrapScalar = true
|
||||||
|
|
||||||
// var writeInplace = false
|
var writeInplace = false
|
||||||
var outputToJSON = false
|
var outputToJSON = false
|
||||||
|
|
||||||
// var exitStatus = false
|
var exitStatus = false
|
||||||
var forceColor = false
|
var forceColor = false
|
||||||
var forceNoColor = false
|
var forceNoColor = false
|
||||||
var colorsEnabled = false
|
var colorsEnabled = false
|
||||||
@@ -14,6 +14,5 @@ var noDocSeparators = false
|
|||||||
var nullInput = false
|
var nullInput = false
|
||||||
var verbose = false
|
var verbose = false
|
||||||
var version = false
|
var version = false
|
||||||
var shellCompletion = ""
|
|
||||||
|
|
||||||
// var log = logging.MustGetLogger("yq")
|
var completedSuccessfully = false
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||||
@@ -23,6 +25,7 @@ yq es -n '{"a": "b"}'
|
|||||||
return cmdEvalAll
|
return cmdEvalAll
|
||||||
}
|
}
|
||||||
func evaluateAll(cmd *cobra.Command, args []string) error {
|
func evaluateAll(cmd *cobra.Command, args []string) error {
|
||||||
|
cmd.SilenceUsage = true
|
||||||
// 0 args, read std in
|
// 0 args, read std in
|
||||||
// 1 arg, null input, process expression
|
// 1 arg, null input, process expression
|
||||||
// 1 arg, read file in sequence
|
// 1 arg, read file in sequence
|
||||||
@@ -39,26 +42,50 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
|||||||
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
||||||
colorsEnabled = true
|
colorsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if writeInplace && len(args) < 2 {
|
||||||
|
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeInplace {
|
||||||
|
// only use colors if its forced
|
||||||
|
colorsEnabled = forceColor
|
||||||
|
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
|
||||||
|
out, err = writeInPlaceHandler.CreateTempFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// need to indirectly call the function so that completedSuccessfully is
|
||||||
|
// passed when we finish execution as opposed to now
|
||||||
|
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
|
||||||
|
}
|
||||||
|
|
||||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||||
|
|
||||||
|
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
if pipingStdIn {
|
if pipingStdIn {
|
||||||
err = yqlib.EvaluateAllFileStreams("", []string{"-"}, printer)
|
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||||
} else {
|
} else {
|
||||||
cmd.Println(cmd.UsageString())
|
cmd.Println(cmd.UsageString())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
if nullInput {
|
if nullInput {
|
||||||
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
|
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
|
||||||
} else {
|
} else {
|
||||||
err = yqlib.EvaluateAllFileStreams("", []string{args[0]}, printer)
|
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = yqlib.EvaluateAllFileStreams(args[0], args[1:], printer)
|
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||||
|
}
|
||||||
|
|
||||||
|
completedSuccessfully = err == nil
|
||||||
|
|
||||||
|
if err == nil && exitStatus && !printer.PrintedAnything() {
|
||||||
|
return errors.New("no matches found")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.SilenceUsage = true
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||||
@@ -23,6 +25,7 @@ yq es -n '{"a": "b"}'
|
|||||||
return cmdEvalSequence
|
return cmdEvalSequence
|
||||||
}
|
}
|
||||||
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||||
|
cmd.SilenceUsage = true
|
||||||
// 0 args, read std in
|
// 0 args, read std in
|
||||||
// 1 arg, null input, process expression
|
// 1 arg, null input, process expression
|
||||||
// 1 arg, read file in sequence
|
// 1 arg, read file in sequence
|
||||||
@@ -39,26 +42,50 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
|||||||
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
||||||
colorsEnabled = true
|
colorsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if writeInplace && len(args) < 2 {
|
||||||
|
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeInplace {
|
||||||
|
// only use colors if its forced
|
||||||
|
colorsEnabled = forceColor
|
||||||
|
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
|
||||||
|
out, err = writeInPlaceHandler.CreateTempFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// need to indirectly call the function so that completedSuccessfully is
|
||||||
|
// passed when we finish execution as opposed to now
|
||||||
|
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
|
||||||
|
}
|
||||||
|
|
||||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||||
|
|
||||||
|
streamEvaluator := yqlib.NewStreamEvaluator()
|
||||||
|
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
if pipingStdIn {
|
if pipingStdIn {
|
||||||
err = yqlib.EvaluateFileStreamsSequence("", []string{"-"}, printer)
|
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||||
} else {
|
} else {
|
||||||
cmd.Println(cmd.UsageString())
|
cmd.Println(cmd.UsageString())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
if nullInput {
|
if nullInput {
|
||||||
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
|
err = streamEvaluator.EvaluateNew(args[0], printer)
|
||||||
} else {
|
} else {
|
||||||
err = yqlib.EvaluateFileStreamsSequence("", []string{args[0]}, printer)
|
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:], printer)
|
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||||
|
}
|
||||||
|
completedSuccessfully = err == nil
|
||||||
|
|
||||||
|
if err == nil && exitStatus && !printer.PrintedAnything() {
|
||||||
|
return errors.New("no matches found")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.SilenceUsage = true
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
25
cmd/root.go
25
cmd/root.go
@@ -1,7 +1,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -18,20 +17,6 @@ func New() *cobra.Command {
|
|||||||
cmd.Print(GetVersionDisplay())
|
cmd.Print(GetVersionDisplay())
|
||||||
return nil
|
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())
|
cmd.Println(cmd.UsageString())
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
@@ -61,11 +46,15 @@ func New() *cobra.Command {
|
|||||||
|
|
||||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
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().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
|
||||||
rootCmd.Flags().StringVarP(&shellCompletion, "shellCompletion", "", "", "[bash/zsh/powershell/fish] prints shell completion script")
|
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
|
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.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors")
|
||||||
rootCmd.AddCommand(createEvaluateSequenceCommand(), createEvaluateAllCommand())
|
rootCmd.AddCommand(
|
||||||
|
createEvaluateSequenceCommand(),
|
||||||
|
createEvaluateAllCommand(),
|
||||||
|
completionCmd,
|
||||||
|
)
|
||||||
return rootCmd
|
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
|
GitDescribe string
|
||||||
|
|
||||||
// Version is main version number that is being run at the moment.
|
// Version is main version number that is being run at the moment.
|
||||||
Version = "4.0.0-alpha1"
|
Version = "4.0.0-beta1"
|
||||||
|
|
||||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
// 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
|
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||||
|
|||||||
61
cmd/write.go
61
cmd/write.go
@@ -1,61 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "github.com/spf13/cobra"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// func createWriteCmd() *cobra.Command {
|
|
||||||
// var cmdWrite = &cobra.Command{
|
|
||||||
// Use: "write [yaml_file] [path_expression] [value]",
|
|
||||||
// Aliases: []string{"w"},
|
|
||||||
// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue",
|
|
||||||
// Example: `
|
|
||||||
// yq write things.yaml 'a.b.c' true
|
|
||||||
// yq write things.yaml 'a.*.c' true
|
|
||||||
// yq write things.yaml 'a.**' true
|
|
||||||
// yq write things.yaml 'a.(child.subchild==co*).c' true
|
|
||||||
// yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
|
|
||||||
// yq write things.yaml 'a.b.c' --tag '!!float' 3
|
|
||||||
// yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags
|
|
||||||
// yq w -i things.yaml 'a.b.c' cat
|
|
||||||
// yq w -i -s update_script.yaml things.yaml
|
|
||||||
// yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array
|
|
||||||
// yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file
|
|
||||||
// `,
|
|
||||||
// Long: `Updates the yaml file w.r.t the given path and value.
|
|
||||||
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
|
||||||
|
|
||||||
// Append value to array adds the value to the end of array.
|
|
||||||
|
|
||||||
// Update Scripts:
|
|
||||||
// Note that you can give an update script to perform more sophisticated update. Update script
|
|
||||||
// format is list of update commands (update or delete) like so:
|
|
||||||
// ---
|
|
||||||
// - command: update
|
|
||||||
// path: b.c
|
|
||||||
// value:
|
|
||||||
// #great
|
|
||||||
// things: frog # wow!
|
|
||||||
// - command: delete
|
|
||||||
// path: b.d
|
|
||||||
// `,
|
|
||||||
// RunE: writeProperty,
|
|
||||||
// }
|
|
||||||
// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
|
||||||
// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
|
||||||
// return cmdWrite
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func writeProperty(cmd *cobra.Command, args []string) error {
|
|
||||||
// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
|
|
||||||
// if updateCommandsError != nil {
|
|
||||||
// return updateCommandsError
|
|
||||||
// }
|
|
||||||
// return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
|
||||||
// }
|
|
||||||
@@ -1,610 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "fmt"
|
|
||||||
// "runtime"
|
|
||||||
// "strings"
|
|
||||||
// "testing"
|
|
||||||
|
|
||||||
// "github.com/mikefarah/yq/v3/test"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// func TestWriteCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteKeepCommentsCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3 # comment
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7 # comment
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithTaggedStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: !!str cat
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: "cat"
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteUpdateStyleOnlyCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// d: things
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 'dog'
|
|
||||||
// d: 'things'
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteUpdateTagOnlyCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: true
|
|
||||||
// d: false
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: "true"
|
|
||||||
// d: "false"
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithSingleQuotedStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 'cat'
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithLiteralStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: |-
|
|
||||||
// cat
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithFoldedStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: >-
|
|
||||||
// cat
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteEmptyMultiDocCmd(t *testing.T) {
|
|
||||||
// content := `# this is empty
|
|
||||||
// ---
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `c: 7
|
|
||||||
|
|
||||||
// # this is empty
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) {
|
|
||||||
// content := `---
|
|
||||||
// # empty
|
|
||||||
// ---
|
|
||||||
// cat: frog
|
|
||||||
// ---
|
|
||||||
|
|
||||||
// # empty
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `
|
|
||||||
|
|
||||||
// # empty
|
|
||||||
// ---
|
|
||||||
// cat: frog
|
|
||||||
// c: 7
|
|
||||||
// ---
|
|
||||||
|
|
||||||
// # empty
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteFromFileCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// source := `kittens: are cute # sure are!`
|
|
||||||
// fromFilename := test.WriteTempYamlFile(source)
|
|
||||||
// defer test.RemoveTempYamlFile(fromFilename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c:
|
|
||||||
// kittens: are cute # sure are!
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteEmptyCmd(t *testing.T) {
|
|
||||||
// content := ``
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteAutoCreateCmd(t *testing.T) {
|
|
||||||
// content := `applications:
|
|
||||||
// - name: app
|
|
||||||
// env:`
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `applications:
|
|
||||||
// - name: app
|
|
||||||
// env:
|
|
||||||
// hello: world
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmdScript(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// updateScript := `- command: update
|
|
||||||
// path: b.c
|
|
||||||
// value: 7`
|
|
||||||
// scriptFilename := test.WriteTempYamlFile(updateScript)
|
|
||||||
// defer test.RemoveTempYamlFile(scriptFilename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmdEmptyScript(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// updateScript := ``
|
|
||||||
// scriptFilename := test.WriteTempYamlFile(updateScript)
|
|
||||||
// defer test.RemoveTempYamlFile(scriptFilename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteMultiCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// ---
|
|
||||||
// apples: great
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 3
|
|
||||||
// ---
|
|
||||||
// apples: ok
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
// func TestWriteInvalidDocumentIndexCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename))
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected command to fail due to invalid path")
|
|
||||||
// }
|
|
||||||
// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteBadDocumentIndexCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected command to fail due to invalid path")
|
|
||||||
// }
|
|
||||||
// expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
|
||||||
// }
|
|
||||||
// func TestWriteMultiAllCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// ---
|
|
||||||
// apples: great
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 3
|
|
||||||
// apples: ok
|
|
||||||
// ---
|
|
||||||
// apples: ok`
|
|
||||||
// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_EmptyArray(t *testing.T) {
|
|
||||||
// content := `b: 3`
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b: 3
|
|
||||||
// a: []
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_Error(t *testing.T) {
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, "write")
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected command to fail due to missing arg")
|
|
||||||
// }
|
|
||||||
// expectedOutput := `Must provide <filename> <path_to_update> <value>`
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, "write fake-unknown a.b 3")
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected command to fail due to unknown file")
|
|
||||||
// }
|
|
||||||
// var expectedOutput string
|
|
||||||
// if runtime.GOOS == "windows" {
|
|
||||||
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
|
|
||||||
// } else {
|
|
||||||
// expectedOutput = `open fake-unknown: no such file or directory`
|
|
||||||
// }
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_Inplace(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// gotOutput := test.ReadTempYamlFile(filename)
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7`
|
|
||||||
// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_InplaceError(t *testing.T) {
|
|
||||||
// content := `b: cat
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected Error to occur!")
|
|
||||||
// }
|
|
||||||
// gotOutput := test.ReadTempYamlFile(filename)
|
|
||||||
// test.AssertResult(t, content, gotOutput)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_Append(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// - foo
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// - foo
|
|
||||||
// - 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_AppendInline(t *testing.T) {
|
|
||||||
// content := `b: [foo]`
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b: [foo, 7]
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_AppendInlinePretty(t *testing.T) {
|
|
||||||
// content := `b: [foo]`
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// - foo
|
|
||||||
// - 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_AppendEmptyArray(t *testing.T) {
|
|
||||||
// content := `a: 2
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `a: 2
|
|
||||||
// b:
|
|
||||||
// - v
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_SplatArray(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// - c: thing
|
|
||||||
// - c: another thing
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// - c: new
|
|
||||||
// - c: new
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_SplatMap(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: thing
|
|
||||||
// d: another thing
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: new
|
|
||||||
// d: new
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_SplatMapEmpty(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: thing
|
|
||||||
// d: another thing
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: {}
|
|
||||||
// d: another thing
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CandidateNode struct {
|
type CandidateNode struct {
|
||||||
Node *yaml.Node // the actual node
|
Node *yaml.Node // the actual node
|
||||||
Path []interface{} /// the path we took to get to this node
|
Path []interface{} /// the path we took to get to this node
|
||||||
Document uint // the document index of this node
|
Document uint // the document index of this node
|
||||||
Filename string
|
Filename string
|
||||||
|
FileIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *CandidateNode) GetKey() string {
|
func (n *CandidateNode) GetKey() string {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import "gopkg.in/op/go-logging.v1"
|
|
||||||
|
|
||||||
var log = logging.MustGetLogger("yq-lib")
|
|
||||||
@@ -5,17 +5,9 @@ import (
|
|||||||
|
|
||||||
"container/list"
|
"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 {
|
type DataTreeNavigator interface {
|
||||||
// given a list of CandidateEntities and a pathNode,
|
// given a list of CandidateEntities and a pathNode,
|
||||||
// this will process the list against the given pathNode and return
|
// 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)
|
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator {
|
type dataTreeNavigator struct {
|
||||||
return &dataTreeNavigator{navigationPrefs}
|
}
|
||||||
|
|
||||||
|
func NewDataTreeNavigator() DataTreeNavigator {
|
||||||
|
return &dataTreeNavigator{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
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))
|
|
||||||
// }
|
|
||||||
2
pkg/yqlib/doc/.gitignore
vendored
2
pkg/yqlib/doc/.gitignore
vendored
@@ -1 +1 @@
|
|||||||
*.md
|
*.zip
|
||||||
|
|||||||
133
pkg/yqlib/doc/Add.md
Normal file
133
pkg/yqlib/doc/Add.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
Add behaves differently according to the type of the LHS:
|
||||||
|
- arrays: concatenate
|
||||||
|
- number scalars: arithmetic addition (soon)
|
||||||
|
- string scalars: concatenate (soon)
|
||||||
|
|
||||||
|
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
||||||
|
|
||||||
|
## Concatenate and assign arrays
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
val: thing
|
||||||
|
b:
|
||||||
|
- cat
|
||||||
|
- dog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a.b += ["cow"]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
val: thing
|
||||||
|
b:
|
||||||
|
- cat
|
||||||
|
- dog
|
||||||
|
- cow
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
169
pkg/yqlib/doc/Assign.md
Normal file
169
pkg/yqlib/doc/Assign.md
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
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.
|
||||||
|
## Create yaml file
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
yq eval --null-input '(.a.b = "cat") | (.x = "frog")'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
b: cat
|
||||||
|
x: frog
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
238
pkg/yqlib/doc/Multiply.md
Normal file
238
pkg/yqlib/doc/Multiply.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
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).
|
||||||
|
|
||||||
|
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||||
|
|
||||||
|
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, appending arrays
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
array:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- animal: dog
|
||||||
|
value: coconut
|
||||||
|
b:
|
||||||
|
array:
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
- animal: cat
|
||||||
|
value: banana
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a *+ .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
array:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- animal: dog
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
- animal: cat
|
||||||
|
value: banana
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
92
pkg/yqlib/doc/Path.md
Normal file
92
pkg/yqlib/doc/Path.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
You can get the key/index of matching nodes by using the `path` operator to return the path array then piping that through `.[-1]` to get the last element of that array, the key.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get map key
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
b: cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a.b | path | .[-1]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Get array index
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
- cat
|
||||||
|
- dog
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a.[] | select(. == "dog") | path | .[-1]' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
68
pkg/yqlib/doc/Sort Keys.md
Normal file
68
pkg/yqlib/doc/Sort Keys.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
The Sort Keys operator sorts maps by their keys (based on their string value). This operator does not do anything to arrays or scalars (so you can easily recursively apply it to all maps).
|
||||||
|
|
||||||
|
Sort is particularly useful for diffing two different yaml documents:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yq eval -i 'sortKeys(..)' file1.yml
|
||||||
|
yq eval -i 'sortKeys(..)' file2.yml
|
||||||
|
diff file1.yml file2.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sort keys of map
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
c: frog
|
||||||
|
a: blah
|
||||||
|
b: bing
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'sortKeys(.)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a: blah
|
||||||
|
b: bing
|
||||||
|
c: frog
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sort keys recursively
|
||||||
|
Note the array elements are left unsorted, but maps inside arrays are sorted
|
||||||
|
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
bParent:
|
||||||
|
c: dog
|
||||||
|
array:
|
||||||
|
- 3
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
aParent:
|
||||||
|
z: donkey
|
||||||
|
x:
|
||||||
|
- c: yum
|
||||||
|
b: delish
|
||||||
|
- b: ew
|
||||||
|
a: apple
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval 'sortKeys(..)' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
aParent:
|
||||||
|
x:
|
||||||
|
- b: delish
|
||||||
|
c: yum
|
||||||
|
- a: apple
|
||||||
|
b: ew
|
||||||
|
z: donkey
|
||||||
|
bParent:
|
||||||
|
array:
|
||||||
|
- 3
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
c: 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
|
||||||
|
```
|
||||||
|
|
||||||
0
pkg/yqlib/doc/aa.md
Normal file
0
pkg/yqlib/doc/aa.md
Normal file
6
pkg/yqlib/doc/headers/Add.md
Normal file
6
pkg/yqlib/doc/headers/Add.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Add behaves differently according to the type of the LHS:
|
||||||
|
- arrays: concatenate
|
||||||
|
- number scalars: arithmetic addition (soon)
|
||||||
|
- string scalars: concatenate (soon)
|
||||||
|
|
||||||
|
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
||||||
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.
|
||||||
14
pkg/yqlib/doc/headers/Multiply.md
Normal file
14
pkg/yqlib/doc/headers/Multiply.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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).
|
||||||
|
|
||||||
|
To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them.
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
3
pkg/yqlib/doc/headers/Path.md
Normal file
3
pkg/yqlib/doc/headers/Path.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
You can get the key/index of matching nodes by using the `path` operator to return the path array then piping that through `.[-1]` to get the last element of that array, the key.
|
||||||
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.
|
||||||
9
pkg/yqlib/doc/headers/Sort Keys.md
Normal file
9
pkg/yqlib/doc/headers/Sort Keys.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
The Sort Keys operator sorts maps by their keys (based on their string value). This operator does not do anything to arrays or scalars (so you can easily recursively apply it to all maps).
|
||||||
|
|
||||||
|
Sort is particularly useful for diffing two different yaml documents:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yq eval -i 'sortKeys(..)' file1.yml
|
||||||
|
yq eval -i 'sortKeys(..)' file2.yml
|
||||||
|
diff file1.yml file2.yml
|
||||||
|
```
|
||||||
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())
|
||||||
|
|
||||||
|
}
|
||||||
50
pkg/yqlib/file_utils.go
Normal file
50
pkg/yqlib/file_utils.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func safelyRenameFile(from string, to string) {
|
||||||
|
if renameError := os.Rename(from, to); renameError != nil {
|
||||||
|
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
|
||||||
|
log.Debug(renameError.Error())
|
||||||
|
// can't do this rename when running in docker to a file targeted in a mounted volume,
|
||||||
|
// so gracefully degrade to copying the entire contents.
|
||||||
|
if copyError := copyFileContents(from, to); copyError != nil {
|
||||||
|
log.Errorf("Failed copying from %v to %v", from, to)
|
||||||
|
log.Error(copyError.Error())
|
||||||
|
} else {
|
||||||
|
removeErr := os.Remove(from)
|
||||||
|
if removeErr != nil {
|
||||||
|
log.Errorf("failed removing original file: %s", from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
|
||||||
|
func copyFileContents(src, dst string) (err error) {
|
||||||
|
in, err := os.Open(src) // nolint gosec
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer safelyCloseFile(in)
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer safelyCloseFile(out)
|
||||||
|
if _, err = io.Copy(out, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return out.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func safelyCloseFile(file *os.File) {
|
||||||
|
err := file.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error closing file!")
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,12 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gopkg.in/op/go-logging.v1"
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("yq-lib")
|
||||||
|
|
||||||
type OperationType struct {
|
type OperationType struct {
|
||||||
Type string
|
Type string
|
||||||
NumArgs uint // number of arguments to the op
|
NumArgs uint // number of arguments to the op
|
||||||
@@ -17,19 +19,10 @@ type OperationType struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// operators TODO:
|
// operators TODO:
|
||||||
// - generator doc from operator tests
|
// - cookbook doc for common things
|
||||||
// - 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?)
|
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||||
// - updateTag - not recursive
|
|
||||||
// - get tag (tag)
|
|
||||||
// - compare ??
|
// - compare ??
|
||||||
// - validate ??
|
// - validate ??
|
||||||
// - exists
|
|
||||||
|
|
||||||
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
||||||
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
||||||
@@ -37,11 +30,15 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp
|
|||||||
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
||||||
|
|
||||||
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
|
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
|
||||||
|
var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator}
|
||||||
|
|
||||||
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
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 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 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 Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||||
@@ -50,10 +47,15 @@ var Pipe = &OperationType{Type: "PIPE", NumArgs: 2, Precedence: 45, Handler: Pip
|
|||||||
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
|
var Length = &OperationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Handler: LengthOperator}
|
||||||
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
|
var Collect = &OperationType{Type: "COLLECT", NumArgs: 0, Precedence: 50, Handler: CollectOperator}
|
||||||
var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Handler: GetStyleOperator}
|
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 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 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}
|
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
||||||
|
var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator}
|
||||||
|
|
||||||
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
||||||
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||||
@@ -66,14 +68,9 @@ var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
|
|||||||
|
|
||||||
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
|
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 Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
|
||||||
|
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
||||||
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 2, Precedence: 40, Handler: DeleteChildOperator}
|
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
||||||
|
|
||||||
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}
|
|
||||||
// filters matches if they have the existing path
|
|
||||||
|
|
||||||
type Operation struct {
|
type Operation struct {
|
||||||
OperationType *OperationType
|
OperationType *OperationType
|
||||||
|
|||||||
83
pkg/yqlib/operator_add.go
Normal file
83
pkg/yqlib/operator_add.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"container/list"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
|
||||||
|
return &PathTreeNode{Operation: &Operation{OperationType: Add},
|
||||||
|
Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}},
|
||||||
|
Rhs: rhs}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
|
assignmentOp := &Operation{OperationType: Assign}
|
||||||
|
assignmentOp.Preferences = &AssignOpPreferences{true}
|
||||||
|
|
||||||
|
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
|
||||||
|
return d.GetMatchingNodes(matchingNodes, assignmentOpNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
target := &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:
|
||||||
|
target.Node.Kind = yaml.SequenceNode
|
||||||
|
target.Node.Style = lhsNode.Style
|
||||||
|
target.Node.Tag = "!!seq"
|
||||||
|
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||||
|
results.PushBack(target)
|
||||||
|
case yaml.ScalarNode:
|
||||||
|
return nil, fmt.Errorf("Scalars not yet supported for addition")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
63
pkg/yqlib/operator_add_test.go
Normal file
63
pkg/yqlib/operator_add_test.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var addOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Concatenate and assign arrays",
|
||||||
|
document: `{a: {val: thing, b: [cat,dog]}}`,
|
||||||
|
expression: ".a.b += [\"cow\"]",
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: {val: thing, b: [cat, dog, cow]}}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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"
|
import "container/list"
|
||||||
|
|
||||||
|
type AssignOpPreferences struct {
|
||||||
|
UpdateAssign bool
|
||||||
|
}
|
||||||
|
|
||||||
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -23,6 +36,11 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
|
|||||||
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// // if there was nothing given, perhaps we are creating a new yaml doc
|
||||||
|
// if matchingNodes.Len() == 0 {
|
||||||
|
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
|
||||||
|
// return lhs, nil
|
||||||
|
// }
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
129
pkg/yqlib/operator_assign_test.go
Normal file
129
pkg/yqlib/operator_assign_test.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var assignOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Create yaml file",
|
||||||
|
expression: `(.a.b = "cat") | (.x = "frog")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], ()::a:\n b: cat\nx: frog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isTruthy(c *CandidateNode) (bool, error) {
|
func isTruthy(c *CandidateNode) (bool, error) {
|
||||||
@@ -25,9 +25,42 @@ func isTruthy(c *CandidateNode) (bool, error) {
|
|||||||
|
|
||||||
type boolOp func(bool, bool) bool
|
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) {
|
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
|
||||||
var results = list.New()
|
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() {
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
|
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
|
||||||
@@ -39,23 +72,9 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTre
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
err = performBoolOp(results, lhs, rhs, op)
|
||||||
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
if err != nil {
|
||||||
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
return nil, err
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -75,3 +94,20 @@ func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
|
|||||||
return b1 && b2
|
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{
|
var booleanOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
document: `{}`,
|
description: "OR example",
|
||||||
expression: `true or false`,
|
expression: `true or false`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::true\n",
|
"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`,
|
expression: `false or false`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::false\n",
|
"D0, P[], (!!bool)::false\n",
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `{a: true, b: false}`,
|
document: `{a: true, b: false}`,
|
||||||
expression: `.[] or (false, true)`,
|
expression: `.[] or (false, true)`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -27,10 +45,61 @@ var booleanOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[b], (!!bool)::true\n",
|
"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) {
|
func TestBooleanOperatorScenarios(t *testing.T) {
|
||||||
for _, tt := range booleanOperatorScenarios {
|
for _, tt := range booleanOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
|
documentScenarios(t, "Boolean Operators", booleanOperatorScenarios)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debugf("-- collectOperation")
|
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()
|
var results = list.New()
|
||||||
|
|
||||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package yqlib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -19,7 +21,9 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
|
|||||||
log.Debugf("-- collectObjectOperation")
|
log.Debugf("-- collectObjectOperation")
|
||||||
|
|
||||||
if matchMap.Len() == 0 {
|
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)
|
first := matchMap.Front().Value.(*CandidateNode)
|
||||||
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
||||||
@@ -90,7 +94,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
|
|||||||
|
|
||||||
newCandidate.Path = nil
|
newCandidate.Path = nil
|
||||||
|
|
||||||
newCandidate, err = multiply(d, newCandidate, splatCandidate)
|
newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,23 @@ import (
|
|||||||
|
|
||||||
var collectObjectOperatorScenarios = []expressionScenario{
|
var collectObjectOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
document: ``,
|
description: `Collect empty object`,
|
||||||
expression: `{}`,
|
document: ``,
|
||||||
expected: []string{},
|
expression: `{}`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{}\n",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: "{name: Mike}\n",
|
description: `Wrap (prefix) existing object`,
|
||||||
expression: `{"wrap": .}`,
|
document: "{name: Mike}\n",
|
||||||
|
expression: `{"wrap": .}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::wrap: {name: Mike}\n",
|
"D0, P[], (!!map)::wrap: {name: Mike}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: "{name: Mike}\n---\n{name: Bob}",
|
document: "{name: Mike}\n---\n{name: Bob}",
|
||||||
expression: `{"wrap": .}`,
|
expression: `{"wrap": .}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -26,6 +31,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `{name: Mike, age: 32}`,
|
document: `{name: Mike, age: 32}`,
|
||||||
expression: `{.name: .age}`,
|
expression: `{.name: .age}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -33,16 +39,19 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: `{name: Mike, pets: [cat, dog]}`,
|
description: `Using splat to create multiple objects`,
|
||||||
expression: `{.name: .pets[]}`,
|
document: `{name: Mike, pets: [cat, dog]}`,
|
||||||
|
expression: `{.name: .pets.[]}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::Mike: cat\n",
|
"D0, P[], (!!map)::Mike: cat\n",
|
||||||
"D0, P[], (!!map)::Mike: dog\n",
|
"D0, P[], (!!map)::Mike: dog\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}",
|
description: `Working with multiple documents`,
|
||||||
expression: `{.name: .pets[]}`,
|
dontFormatInputForDoc: false,
|
||||||
|
document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}",
|
||||||
|
expression: `{.name: .pets.[]}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::Mike: cat\n",
|
"D0, P[], (!!map)::Mike: cat\n",
|
||||||
"D0, P[], (!!map)::Mike: dog\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]}`,
|
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
||||||
expression: `{.name: .pets[], "f":.food[]}`,
|
expression: `{.name: .pets.[], "f":.food.[]}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::Mike: cat\nf: hotdog\n",
|
"D0, P[], (!!map)::Mike: cat\nf: hotdog\n",
|
||||||
"D0, P[], (!!map)::Mike: cat\nf: burger\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]}}`,
|
document: `{name: Mike, pets: {cows: [apl, bba]}}`,
|
||||||
expression: `{"a":.name, "b":.pets}`,
|
expression: `{"a":.name, "b":.pets}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -70,13 +81,15 @@ b: {cows: [apl, bba]}
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: ``,
|
description: "Creating yaml from scratch",
|
||||||
expression: `{"wrap": "frog"}`,
|
document: ``,
|
||||||
|
expression: `{"wrap": "frog"}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::wrap: frog\n",
|
"D0, P[], (!!map)::wrap: frog\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `{name: Mike}`,
|
document: `{name: Mike}`,
|
||||||
expression: `{"wrap": .}`,
|
expression: `{"wrap": .}`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -84,6 +97,7 @@ b: {cows: [apl, bba]}
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `{name: Mike}`,
|
document: `{name: Mike}`,
|
||||||
expression: `{"wrap": {"further": .}} | (.. style= "flow")`,
|
expression: `{"wrap": {"further": .}} | (.. style= "flow")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
|
|||||||
@@ -6,43 +6,64 @@ import (
|
|||||||
|
|
||||||
var collectOperatorScenarios = []expressionScenario{
|
var collectOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
document: ``,
|
description: "Collect empty",
|
||||||
expression: `[]`,
|
document: ``,
|
||||||
expected: []string{},
|
expression: `[]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: ``,
|
document: ``,
|
||||||
expression: `["cat"]`,
|
expression: `[3]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- 3\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Collect single",
|
||||||
|
document: ``,
|
||||||
|
expression: `["cat"]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- cat\n",
|
"D0, P[], (!!seq)::- cat\n",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
document: ``,
|
document: ``,
|
||||||
|
skipDoc: true,
|
||||||
expression: `[true]`,
|
expression: `[true]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- true\n",
|
"D0, P[], (!!seq)::- true\n",
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
document: ``,
|
{
|
||||||
expression: `["cat", "dog"]`,
|
description: "Collect many",
|
||||||
|
document: `{a: cat, b: dog}`,
|
||||||
|
expression: `[.a, .b]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- cat\n- dog\n",
|
"D0, P[], (!!seq)::- cat\n- dog\n",
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
document: ``,
|
document: ``,
|
||||||
|
skipDoc: true,
|
||||||
expression: `1 | collect`,
|
expression: `1 | collect`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- 1\n",
|
"D0, P[], (!!seq)::- 1\n",
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
document: `[1,2,3]`,
|
document: `[1,2,3]`,
|
||||||
|
skipDoc: true,
|
||||||
expression: `[.[]]`,
|
expression: `[.[]]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- 1\n- 2\n- 3\n",
|
"D0, P[], (!!seq)::- 1\n- 2\n- 3\n",
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
document: `a: {b: [1,2,3]}`,
|
document: `a: {b: [1,2,3]}`,
|
||||||
expression: `[.a.b[]]`,
|
expression: `[.a.b.[]]`,
|
||||||
|
skipDoc: true,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n",
|
"D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -75,5 +75,5 @@ func TestCommentOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range commentOperatorScenarios {
|
for _, tt := range commentOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
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]}`,
|
document: `{name: Mike, pets: [cat, dog]}`,
|
||||||
expression: `.name: .pets[]`,
|
expression: `.name: .pets.[]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
||||||
expression: `.name: .pets[], "f":.food[]`,
|
expression: `.name: .pets.[], "f":.food.[]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
||||||
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\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]}",
|
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{
|
expected: []string{
|
||||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n",
|
"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",
|
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n",
|
||||||
|
|||||||
@@ -3,19 +3,15 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
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,
|
// for each lhs, splat the node,
|
||||||
// the intersect it against the rhs expression
|
// the intersect it against the rhs expression
|
||||||
// recreate the contents using only the intersection result.
|
// 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)
|
candidate := el.Value.(*CandidateNode)
|
||||||
elMap := list.New()
|
elMap := list.New()
|
||||||
elMap.PushBack(candidate)
|
elMap.PushBack(candidate)
|
||||||
@@ -25,20 +21,22 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if candidate.Node.Kind == yaml.SequenceNode {
|
realNode := UnwrapDoc(candidate.Node)
|
||||||
|
|
||||||
|
if realNode.Kind == yaml.SequenceNode {
|
||||||
deleteFromArray(candidate, nodesToDelete)
|
deleteFromArray(candidate, nodesToDelete)
|
||||||
} else if candidate.Node.Kind == yaml.MappingNode {
|
} else if realNode.Kind == yaml.MappingNode {
|
||||||
deleteFromMap(candidate, nodesToDelete)
|
deleteFromMap(candidate, nodesToDelete)
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Cannot delete from node that's not a map or array %v", NodeToString(candidate))
|
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) {
|
func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||||
log.Debug("deleteFromMap")
|
log.Debug("deleteFromMap")
|
||||||
node := candidate.Node
|
node := UnwrapDoc(candidate.Node)
|
||||||
contents := node.Content
|
contents := node.Content
|
||||||
newContents := make([]*yaml.Node, 0)
|
newContents := make([]*yaml.Node, 0)
|
||||||
|
|
||||||
@@ -51,8 +49,13 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *list.List) {
|
|||||||
Document: candidate.Document,
|
Document: candidate.Document,
|
||||||
Path: append(candidate.Path, key.Value),
|
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)
|
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) {
|
func deleteFromArray(candidate *CandidateNode, nodesToDelete *list.List) {
|
||||||
log.Debug("deleteFromArray")
|
log.Debug("deleteFromArray")
|
||||||
node := candidate.Node
|
node := UnwrapDoc(candidate.Node)
|
||||||
contents := node.Content
|
contents := node.Content
|
||||||
newContents := make([]*yaml.Node, 0)
|
newContents := make([]*yaml.Node, 0)
|
||||||
|
|
||||||
for index := 0; index < len(contents); index = index + 1 {
|
for index := 0; index < len(contents); index = index + 1 {
|
||||||
value := contents[index]
|
value := contents[index]
|
||||||
|
|
||||||
// childCandidate := &CandidateNode{
|
childCandidate := &CandidateNode{
|
||||||
// Node: value,
|
Node: value,
|
||||||
// Document: candidate.Document,
|
Document: candidate.Document,
|
||||||
// Path: append(candidate.Path, index),
|
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 {
|
if !shouldDelete {
|
||||||
newContents = append(newContents, value)
|
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 {
|
for _, tt := range documentIndexScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
documentScenarios(t, "Document Index Operator", documentIndexScenarios)
|
documentScenarios(t, "Document Index", documentIndexScenarios)
|
||||||
}
|
}
|
||||||
@@ -6,22 +6,25 @@ import (
|
|||||||
|
|
||||||
var equalsOperatorScenarios = []expressionScenario{
|
var equalsOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
document: `[cat,goat,dog]`,
|
description: "Match string",
|
||||||
expression: `.[] | (. == "*at")`,
|
document: `[cat,goat,dog]`,
|
||||||
|
expression: `.[] | (. == "*at")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[0], (!!bool)::true\n",
|
"D0, P[0], (!!bool)::true\n",
|
||||||
"D0, P[1], (!!bool)::true\n",
|
"D0, P[1], (!!bool)::true\n",
|
||||||
"D0, P[2], (!!bool)::false\n",
|
"D0, P[2], (!!bool)::false\n",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
document: `[3, 4, 5]`,
|
description: "Match number",
|
||||||
expression: `.[] | (. == 4)`,
|
document: `[3, 4, 5]`,
|
||||||
|
expression: `.[] | (. == 4)`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[0], (!!bool)::false\n",
|
"D0, P[0], (!!bool)::false\n",
|
||||||
"D0, P[1], (!!bool)::true\n",
|
"D0, P[1], (!!bool)::true\n",
|
||||||
"D0, P[2], (!!bool)::false\n",
|
"D0, P[2], (!!bool)::false\n",
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
skipDoc: true,
|
||||||
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
|
document: `a: { cat: {b: apple, c: whatever}, pat: {b: banana} }`,
|
||||||
expression: `.a | (.[].b == "apple")`,
|
expression: `.a | (.[].b == "apple")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -30,6 +33,7 @@ var equalsOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: ``,
|
document: ``,
|
||||||
expression: `null == null`,
|
expression: `null == null`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -37,8 +41,9 @@ var equalsOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: ``,
|
description: "Match nulls",
|
||||||
expression: `null == ~`,
|
document: ``,
|
||||||
|
expression: `null == ~`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!bool)::true\n",
|
"D0, P[], (!!bool)::true\n",
|
||||||
},
|
},
|
||||||
@@ -49,5 +54,5 @@ func TestEqualOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range equalsOperatorScenarios {
|
for _, tt := range equalsOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
documentScenarios(t, "Equals Operator", equalsOperatorScenarios)
|
documentScenarios(t, "Equals", equalsOperatorScenarios)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,27 +6,54 @@ import (
|
|||||||
|
|
||||||
var explodeTest = []expressionScenario{
|
var explodeTest = []expressionScenario{
|
||||||
{
|
{
|
||||||
document: `{a: mike}`,
|
description: "Explode alias and anchor",
|
||||||
expression: `explode(.a)`,
|
document: `{f : {a: &a cat, b: *a}}`,
|
||||||
expected: []string{
|
expression: `explode(.f)`,
|
||||||
"D0, P[], (doc)::{a: mike}\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
document: `{f : {a: &a cat, b: *a}}`,
|
|
||||||
expression: `explode(.f)`,
|
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (doc)::{f: {a: cat, b: cat}}\n",
|
"D0, P[], (doc)::{f: {a: cat, b: cat}}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: `{f : {a: &a cat, *a: b}}`,
|
description: "Explode with no aliases or anchors",
|
||||||
expression: `explode(.f)`,
|
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{
|
expected: []string{
|
||||||
"D0, P[], (doc)::{f: {a: cat, cat: b}}\n",
|
"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,
|
document: mergeDocSample,
|
||||||
expression: `.foo* | explode(.) | (. style="flow")`,
|
expression: `.foo* | explode(.) | (. style="flow")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -36,6 +63,7 @@ var explodeTest = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: mergeDocSample,
|
document: mergeDocSample,
|
||||||
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
|
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -45,6 +73,7 @@ var explodeTest = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`,
|
document: `{f : {a: &a cat, b: &b {f: *a}, *a: *b}}`,
|
||||||
expression: `explode(.f)`,
|
expression: `explode(.f)`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -57,4 +86,5 @@ func TestExplodeOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range explodeTest {
|
for _, tt := range explodeTest {
|
||||||
testScenario(t, &tt)
|
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"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debugf("crossFunction LHS len: %v", lhs.Len())
|
||||||
|
|
||||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debugf("crossFunction RHS len: %v", rhs.Len())
|
||||||
|
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
|||||||
lhsCandidate := el.Value.(*CandidateNode)
|
lhsCandidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
|
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
|
||||||
|
log.Debugf("Applying calc")
|
||||||
rhsCandidate := rightEl.Value.(*CandidateNode)
|
rhsCandidate := rightEl.Value.(*CandidateNode)
|
||||||
resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
|
resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,39 +43,49 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MultiplyPreferences struct {
|
||||||
|
AppendArrays bool
|
||||||
|
}
|
||||||
|
|
||||||
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debugf("-- MultiplyOperator")
|
log.Debugf("-- MultiplyOperator")
|
||||||
return crossFunction(d, matchingNodes, pathNode, multiply)
|
return crossFunction(d, matchingNodes, pathNode, multiply(pathNode.Operation.Preferences.(*MultiplyPreferences)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhs.Node = UnwrapDoc(lhs.Node)
|
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
rhs.Node = UnwrapDoc(rhs.Node)
|
lhs.Node = UnwrapDoc(lhs.Node)
|
||||||
log.Debugf("Multipling LHS: %v", NodeToString(lhs))
|
rhs.Node = UnwrapDoc(rhs.Node)
|
||||||
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 ||
|
shouldAppendArrays := preferences.AppendArrays
|
||||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
|
||||||
|
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||||
|
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||||
|
|
||||||
|
var newBlank = &CandidateNode{
|
||||||
|
Path: lhs.Path,
|
||||||
|
Document: lhs.Document,
|
||||||
|
Filename: lhs.Filename,
|
||||||
|
Node: &yaml.Node{},
|
||||||
|
}
|
||||||
|
var newThing, err = mergeObjects(d, newBlank, lhs, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mergeObjects(d, newThing, rhs, shouldAppendArrays)
|
||||||
|
|
||||||
var newBlank = &CandidateNode{
|
|
||||||
Path: lhs.Path,
|
|
||||||
Document: lhs.Document,
|
|
||||||
Filename: lhs.Filename,
|
|
||||||
Node: &yaml.Node{},
|
|
||||||
}
|
}
|
||||||
var newThing, err = mergeObjects(d, newBlank, lhs)
|
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return mergeObjects(d, newThing, rhs)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Cannot multiply %v with %v", NodeToString(lhs), NodeToString(rhs))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
err := recursiveDecent(d, results, nodeToMap(rhs))
|
|
||||||
|
// shouldn't recurse arrays if appending
|
||||||
|
err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -83,7 +96,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for el := results.Front(); el != nil; el = el.Next() {
|
for el := results.Front(); el != nil; el = el.Next() {
|
||||||
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode))
|
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -104,7 +117,8 @@ func createTraversalTree(path []interface{}) *PathTreeNode {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode) error {
|
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
|
||||||
|
|
||||||
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
||||||
|
|
||||||
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
||||||
@@ -112,6 +126,9 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
|
|||||||
assignmentOp := &Operation{OperationType: AssignAttributes}
|
assignmentOp := &Operation{OperationType: AssignAttributes}
|
||||||
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
||||||
assignmentOp.OperationType = Assign
|
assignmentOp.OperationType = Assign
|
||||||
|
assignmentOp.Preferences = &AssignOpPreferences{false}
|
||||||
|
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
|
||||||
|
assignmentOp.OperationType = AddAssign
|
||||||
}
|
}
|
||||||
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
|
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
|
||||||
|
|
||||||
@@ -13,6 +13,13 @@ var multiplyOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
expression: `{} * {"cat":"dog"}`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::cat: dog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: `{a: {also: me}, b: {also: [1]}}`,
|
document: `{a: {also: me}, b: {also: [1]}}`,
|
||||||
@@ -22,11 +29,19 @@ var multiplyOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Merge objects together",
|
description: "Merge objects together, returning merged result only",
|
||||||
document: `{a: {also: me}, b: {also: {g: wizz}}}`,
|
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}`,
|
expression: `. * {"a":.b}`,
|
||||||
expected: []string{
|
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 +77,8 @@ var multiplyOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Merge keeps style of LHS",
|
description: "Merge keeps style of LHS",
|
||||||
|
dontFormatInputForDoc: true,
|
||||||
document: `a: {things: great}
|
document: `a: {things: great}
|
||||||
b:
|
b:
|
||||||
also: "me"
|
also: "me"
|
||||||
@@ -83,6 +99,22 @@ b:
|
|||||||
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
|
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: [1], b: [2]}`,
|
||||||
|
expression: `.a *+ .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!seq)::[1, 2]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Merge, appending arrays",
|
||||||
|
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
|
||||||
|
expression: `.a *+ .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Merge to prefix an element",
|
description: "Merge to prefix an element",
|
||||||
document: `{a: cat, b: dog}`,
|
document: `{a: cat, b: dog}`,
|
||||||
@@ -121,5 +153,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range multiplyOperatorScenarios {
|
for _, tt := range multiplyOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
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
|
||||||
|
}
|
||||||
61
pkg/yqlib/operator_path_test.go
Normal file
61
pkg/yqlib/operator_path_test.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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: "Get map key",
|
||||||
|
document: `{a: {b: cat}}`,
|
||||||
|
expression: `.a.b | path | .[-1]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a b -1], (!!str)::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: "Get array index",
|
||||||
|
document: `{a: [cat, dog]}`,
|
||||||
|
expression: `.a.[] | select(. == "dog") | path | .[-1]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a 1 -1], (!!int)::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)
|
||||||
|
}
|
||||||
@@ -3,13 +3,13 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
err := recursiveDecent(d, results, matchMap)
|
err := recursiveDecent(d, results, matchMap, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error {
|
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, recurseArray bool) error {
|
||||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
@@ -26,14 +26,15 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li
|
|||||||
log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
|
log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
|
||||||
results.PushBack(candidate)
|
results.PushBack(candidate)
|
||||||
|
|
||||||
if candidate.Node.Kind != yaml.AliasNode {
|
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
|
||||||
|
(recurseArray || candidate.Node.Kind != yaml.SequenceNode) {
|
||||||
|
|
||||||
children, err := Splat(d, nodeToMap(candidate))
|
children, err := Splat(d, nodeToMap(candidate))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = recursiveDecent(d, results, children)
|
err = recursiveDecent(d, results, children, recurseArray)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,23 @@ import (
|
|||||||
|
|
||||||
var recursiveDescentOperatorScenarios = []expressionScenario{
|
var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{}`,
|
||||||
|
expression: `..`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `[]`,
|
||||||
|
expression: `..`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `cat`,
|
document: `cat`,
|
||||||
expression: `..`,
|
expression: `..`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -13,6 +30,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `{a: frog}`,
|
document: `{a: frog}`,
|
||||||
expression: `..`,
|
expression: `..`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -21,6 +39,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `{a: {b: apple}}`,
|
document: `{a: {b: apple}}`,
|
||||||
expression: `..`,
|
expression: `..`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -30,6 +49,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `[1,2,3]`,
|
document: `[1,2,3]`,
|
||||||
expression: `..`,
|
expression: `..`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -40,6 +60,7 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `[{a: cat},2,true]`,
|
document: `[{a: cat},2,true]`,
|
||||||
expression: `..`,
|
expression: `..`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -51,26 +72,23 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
description: "Aliases are not traversed",
|
||||||
expression: `..`,
|
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||||
|
expression: `[..]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
|
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
|
||||||
"D0, P[a], (!!map)::&cat {c: frog}\n",
|
|
||||||
"D0, P[a c], (!!str)::frog\n",
|
|
||||||
"D0, P[b], (alias)::*cat\n",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
document: mergeDocSample,
|
description: "Merge docs are not traversed",
|
||||||
expression: `.foobar | ..`,
|
document: mergeDocSample,
|
||||||
|
expression: `.foobar | [..]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: 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",
|
||||||
"D0, P[foobar c], (!!str)::foobar_c\n",
|
|
||||||
"D0, P[foobar <<], (alias)::*foo\n",
|
|
||||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: mergeDocSample,
|
document: mergeDocSample,
|
||||||
expression: `.foobarList | ..`,
|
expression: `.foobarList | ..`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -88,4 +106,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range recursiveDescentOperatorScenarios {
|
for _, tt := range recursiveDescentOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
|
documentScenarios(t, "Recursive Descent", recursiveDescentOperatorScenarios)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,36 +6,47 @@ import (
|
|||||||
|
|
||||||
var selectOperatorScenarios = []expressionScenario{
|
var selectOperatorScenarios = []expressionScenario{
|
||||||
{
|
{
|
||||||
document: `[cat,goat,dog]`,
|
description: "Select elements from array",
|
||||||
expression: `.[] | select(. == "*at")`,
|
document: `[cat,goat,dog]`,
|
||||||
|
expression: `.[] | select(. == "*at")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[0], (!!str)::cat\n",
|
"D0, P[0], (!!str)::cat\n",
|
||||||
"D0, P[1], (!!str)::goat\n",
|
"D0, P[1], (!!str)::goat\n",
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `[hot, fot, dog]`,
|
document: `[hot, fot, dog]`,
|
||||||
expression: `.[] | select(. == "*at")`,
|
expression: `.[] | select(. == "*at")`,
|
||||||
expected: []string{},
|
expected: []string{},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `a: [cat,goat,dog]`,
|
document: `a: [cat,goat,dog]`,
|
||||||
expression: `.a[] | select(. == "*at")`,
|
expression: `.a.[] | select(. == "*at")`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[a 0], (!!str)::cat\n",
|
"D0, P[a 0], (!!str)::cat\n",
|
||||||
"D0, P[a 1], (!!str)::goat\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{
|
expected: []string{
|
||||||
"D0, P[a things], (!!str)::cat\n",
|
"D0, P[], (doc)::a: {things: rabbit, bob: rabbit, horse: dog}\n",
|
||||||
"D0, P[a bob], (!!str)::goat\n"},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`,
|
document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`,
|
||||||
expression: `.a[] | select(.include)`,
|
expression: `.a.[] | select(.include)`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[a things], (!!map)::{include: true}\n",
|
"D0, P[a things], (!!map)::{include: true}\n",
|
||||||
"D0, P[a andMe], (!!map)::{include: fold}\n",
|
"D0, P[a andMe], (!!map)::{include: fold}\n",
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
document: `[cat,~,dog]`,
|
document: `[cat,~,dog]`,
|
||||||
expression: `.[] | select(. == ~)`,
|
expression: `.[] | select(. == ~)`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
@@ -48,4 +59,5 @@ func TestSelectOperatorScenarios(t *testing.T) {
|
|||||||
for _, tt := range selectOperatorScenarios {
|
for _, tt := range selectOperatorScenarios {
|
||||||
testScenario(t, &tt)
|
testScenario(t, &tt)
|
||||||
}
|
}
|
||||||
|
documentScenarios(t, "Select", selectOperatorScenarios)
|
||||||
}
|
}
|
||||||
|
|||||||
53
pkg/yqlib/operator_sort_keys.go
Normal file
53
pkg/yqlib/operator_sort_keys.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
yaml "gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SortKeysOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
|
|
||||||
|
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||||
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for childEl := rhs.Front(); childEl != nil; childEl = childEl.Next() {
|
||||||
|
node := UnwrapDoc(childEl.Value.(*CandidateNode).Node)
|
||||||
|
if node.Kind == yaml.MappingNode {
|
||||||
|
sortKeys(node)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return matchingNodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortKeys(node *yaml.Node) {
|
||||||
|
keys := make([]string, len(node.Content)/2)
|
||||||
|
keyBucket := map[string]*yaml.Node{}
|
||||||
|
valueBucket := map[string]*yaml.Node{}
|
||||||
|
var contents = node.Content
|
||||||
|
for index := 0; index < len(contents); index = index + 2 {
|
||||||
|
key := contents[index]
|
||||||
|
value := contents[index+1]
|
||||||
|
keys[index/2] = key.Value
|
||||||
|
keyBucket[key.Value] = key
|
||||||
|
valueBucket[key.Value] = value
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
sortedContent := make([]*yaml.Node, len(node.Content))
|
||||||
|
for index := 0; index < len(keys); index = index + 1 {
|
||||||
|
keyString := keys[index]
|
||||||
|
sortedContent[index*2] = keyBucket[keyString]
|
||||||
|
sortedContent[1+(index*2)] = valueBucket[keyString]
|
||||||
|
}
|
||||||
|
node.Content = sortedContent
|
||||||
|
}
|
||||||
32
pkg/yqlib/operator_sort_keys_test.go
Normal file
32
pkg/yqlib/operator_sort_keys_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sortKeysOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Sort keys of map",
|
||||||
|
document: `{c: frog, a: blah, b: bing}`,
|
||||||
|
expression: `sortKeys(.)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::{a: blah, b: bing, c: frog}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Sort keys recursively",
|
||||||
|
subdescription: "Note the array elements are left unsorted, but maps inside arrays are sorted",
|
||||||
|
document: `{bParent: {c: dog, array: [3,1,2]}, aParent: {z: donkey, x: [{c: yum, b: delish}, {b: ew, a: apple}]}}`,
|
||||||
|
expression: `sortKeys(..)`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{aParent: {x: [{b: delish, c: yum}, {a: apple, b: ew}], z: donkey}, bParent: {array: [3, 1, 2], c: dog}}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortKeysOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range sortKeysOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentScenarios(t, "Sort Keys", sortKeysOperatorScenarios)
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user