mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
31 Commits
4.0.0-alph
...
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 |
@@ -22,4 +22,4 @@ LABEL version=${VERSION}
|
||||
|
||||
WORKDIR /workdir
|
||||
|
||||
ENTRYPOINT [/usr/bin/yq]
|
||||
ENTRYPOINT ["/usr/bin/yq"]
|
||||
|
||||
@@ -2,10 +2,10 @@ package cmd
|
||||
|
||||
var unwrapScalar = true
|
||||
|
||||
// var writeInplace = false
|
||||
var writeInplace = false
|
||||
var outputToJSON = false
|
||||
|
||||
// var exitStatus = false
|
||||
var exitStatus = false
|
||||
var forceColor = false
|
||||
var forceNoColor = false
|
||||
var colorsEnabled = false
|
||||
@@ -15,4 +15,4 @@ var nullInput = false
|
||||
var verbose = false
|
||||
var version = false
|
||||
|
||||
// var log = logging.MustGetLogger("yq")
|
||||
var completedSuccessfully = false
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||
@@ -23,6 +25,7 @@ yq es -n '{"a": "b"}'
|
||||
return cmdEvalAll
|
||||
}
|
||||
func evaluateAll(cmd *cobra.Command, args []string) error {
|
||||
cmd.SilenceUsage = true
|
||||
// 0 args, read std in
|
||||
// 1 arg, null input, process expression
|
||||
// 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) {
|
||||
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)
|
||||
|
||||
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = yqlib.EvaluateAllFileStreams("", []string{"-"}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
|
||||
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
|
||||
} else {
|
||||
err = yqlib.EvaluateAllFileStreams("", []string{args[0]}, printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], args[1:], printer)
|
||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
}
|
||||
|
||||
completedSuccessfully = err == nil
|
||||
|
||||
if err == nil && exitStatus && !printer.PrintedAnything() {
|
||||
return errors.New("no matches found")
|
||||
}
|
||||
|
||||
cmd.SilenceUsage = true
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||
@@ -23,6 +25,7 @@ yq es -n '{"a": "b"}'
|
||||
return cmdEvalSequence
|
||||
}
|
||||
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||
cmd.SilenceUsage = true
|
||||
// 0 args, read std in
|
||||
// 1 arg, null input, process expression
|
||||
// 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) {
|
||||
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)
|
||||
|
||||
streamEvaluator := yqlib.NewStreamEvaluator()
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
if pipingStdIn {
|
||||
err = yqlib.EvaluateFileStreamsSequence("", []string{"-"}, printer)
|
||||
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer)
|
||||
} else {
|
||||
cmd.Println(cmd.UsageString())
|
||||
return nil
|
||||
}
|
||||
case 1:
|
||||
if nullInput {
|
||||
err = yqlib.EvaluateAllFileStreams(args[0], []string{}, printer)
|
||||
err = streamEvaluator.EvaluateNew(args[0], printer)
|
||||
} else {
|
||||
err = yqlib.EvaluateFileStreamsSequence("", []string{args[0]}, printer)
|
||||
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||
}
|
||||
default:
|
||||
err = yqlib.EvaluateFileStreamsSequence(args[0], args[1:], printer)
|
||||
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||
}
|
||||
completedSuccessfully = err == nil
|
||||
|
||||
if err == nil && exitStatus && !printer.PrintedAnything() {
|
||||
return errors.New("no matches found")
|
||||
}
|
||||
|
||||
cmd.SilenceUsage = true
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ func New() *cobra.Command {
|
||||
|
||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
|
||||
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(&forceNoColor, "no-colors", "M", false, "force print with no colors")
|
||||
|
||||
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "4.0.0-alpha1"
|
||||
Version = "4.0.0-beta1"
|
||||
|
||||
// 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
|
||||
|
||||
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)
|
||||
// }
|
||||
45
pkg/yqlib/all_at_once_evaluator.go
Normal file
45
pkg/yqlib/all_at_once_evaluator.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
/**
|
||||
Loads all yaml documents of all files given into memory, then runs the given expression once.
|
||||
**/
|
||||
type Evaluator interface {
|
||||
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
||||
}
|
||||
|
||||
type allAtOnceEvaluator struct {
|
||||
treeNavigator DataTreeNavigator
|
||||
treeCreator PathTreeCreator
|
||||
}
|
||||
|
||||
func NewAllAtOnceEvaluator() Evaluator {
|
||||
return &allAtOnceEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
||||
}
|
||||
|
||||
func (e *allAtOnceEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||
fileIndex := 0
|
||||
node, err := treeCreator.ParsePath(expression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var allDocuments *list.List = list.New()
|
||||
for _, filename := range filenames {
|
||||
reader, err := readStream(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileDocuments, err := readDocuments(reader, filename, fileIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allDocuments.PushBackList(fileDocuments)
|
||||
fileIndex = fileIndex + 1
|
||||
}
|
||||
matches, err := treeNavigator.GetMatchingNodes(allDocuments, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintResults(matches)
|
||||
}
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CandidateNode struct {
|
||||
Node *yaml.Node // the actual node
|
||||
Path []interface{} /// the path we took to get to this node
|
||||
Document uint // the document index of this node
|
||||
Filename string
|
||||
Node *yaml.Node // the actual node
|
||||
Path []interface{} /// the path we took to get to this node
|
||||
Document uint // the document index of this node
|
||||
Filename string
|
||||
FileIndex int
|
||||
}
|
||||
|
||||
func (n *CandidateNode) GetKey() string {
|
||||
|
||||
@@ -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"
|
||||
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
type dataTreeNavigator struct {
|
||||
navigationPrefs NavigationPrefs
|
||||
}
|
||||
|
||||
type NavigationPrefs struct {
|
||||
FollowAlias bool
|
||||
}
|
||||
|
||||
type DataTreeNavigator interface {
|
||||
// given a list of CandidateEntities and a pathNode,
|
||||
// this will process the list against the given pathNode and return
|
||||
@@ -23,8 +15,11 @@ type DataTreeNavigator interface {
|
||||
GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error)
|
||||
}
|
||||
|
||||
func NewDataTreeNavigator(navigationPrefs NavigationPrefs) DataTreeNavigator {
|
||||
return &dataTreeNavigator{navigationPrefs}
|
||||
type dataTreeNavigator struct {
|
||||
}
|
||||
|
||||
func NewDataTreeNavigator() DataTreeNavigator {
|
||||
return &dataTreeNavigator{}
|
||||
}
|
||||
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
1
pkg/yqlib/doc/.gitignore
vendored
Normal file
1
pkg/yqlib/doc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.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
|
||||
```
|
||||
|
||||
@@ -5,8 +5,19 @@ Which will assign the LHS node values to the RHS node values. The RHS expression
|
||||
|
||||
### relative form: `|=`
|
||||
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
|
||||
## Examples
|
||||
### Update parent to be the child value
|
||||
## 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:
|
||||
@@ -23,7 +34,7 @@ a:
|
||||
g: foof
|
||||
```
|
||||
|
||||
### Update to be the sibling value
|
||||
## Update node to be the sibling value
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -40,7 +51,7 @@ a: sibling
|
||||
b: sibling
|
||||
```
|
||||
|
||||
### Updated multiple paths
|
||||
## Updated multiple paths
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: fieldA
|
||||
@@ -58,7 +69,7 @@ b: fieldB
|
||||
c: potatoe
|
||||
```
|
||||
|
||||
### Update string value
|
||||
## Update string value
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -74,7 +85,7 @@ a:
|
||||
b: frog
|
||||
```
|
||||
|
||||
### Update string value via |=
|
||||
## Update string value via |=
|
||||
Note there is no difference between `=` and `|=` when the RHS is a scalar
|
||||
|
||||
Given a sample.yml file of:
|
||||
@@ -92,7 +103,7 @@ a:
|
||||
b: frog
|
||||
```
|
||||
|
||||
### Update selected results
|
||||
## Update selected results
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -101,7 +112,7 @@ a:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a[] | select(. == "apple") |= "frog"' sample.yml
|
||||
yq eval '.a.[] | select(. == "apple") |= "frog"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -110,7 +121,7 @@ a:
|
||||
c: cactus
|
||||
```
|
||||
|
||||
### Update array values
|
||||
## Update array values
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- candy
|
||||
@@ -128,10 +139,10 @@ will output
|
||||
- bogs
|
||||
```
|
||||
|
||||
### Update empty object
|
||||
## Update empty object
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
'': null
|
||||
{}
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@@ -139,25 +150,20 @@ yq eval '.a.b |= "bogs"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
'': null
|
||||
a:
|
||||
b: bogs
|
||||
{a: {b: bogs}}
|
||||
```
|
||||
|
||||
### Update empty object and array
|
||||
## Update empty object and array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
'': null
|
||||
{}
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.a.b[0] |= "bogs"' sample.yml
|
||||
yq eval '.a.b.[0] |= "bogs"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
'': null
|
||||
a:
|
||||
b:
|
||||
- bogs
|
||||
{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
|
||||
```
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
This creates an array using the expression between the square brackets.
|
||||
|
||||
|
||||
## Examples
|
||||
### Collect empty
|
||||
## Collect empty
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '[]'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
[]
|
||||
```
|
||||
|
||||
### Collect single
|
||||
## Collect single
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '["cat"]'
|
||||
@@ -23,7 +23,7 @@ will output
|
||||
- cat
|
||||
```
|
||||
|
||||
### Collect many
|
||||
## Collect many
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
This is used to construct objects (or maps). This can be used against existing yaml, or to create fresh yaml documents.
|
||||
## Examples
|
||||
### Collect empty object
|
||||
## Collect empty object
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '{}'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
{}
|
||||
```
|
||||
|
||||
### Wrap (prefix) existing object
|
||||
## Wrap (prefix) existing object
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
name: Mike
|
||||
@@ -24,7 +24,7 @@ wrap:
|
||||
name: Mike
|
||||
```
|
||||
|
||||
### Using splat to create multiple objects
|
||||
## Using splat to create multiple objects
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
name: Mike
|
||||
@@ -34,7 +34,7 @@ pets:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '{.name: .pets[]}' sample.yml
|
||||
yq eval '{.name: .pets.[]}' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -42,7 +42,7 @@ Mike: cat
|
||||
Mike: dog
|
||||
```
|
||||
|
||||
### Working with multiple documents
|
||||
## Working with multiple documents
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
name: Mike
|
||||
@@ -57,7 +57,7 @@ pets:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '{.name: .pets[]}' sample.yml
|
||||
yq eval '{.name: .pets.[]}' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
@@ -67,7 +67,7 @@ Rosey: monkey
|
||||
Rosey: sheep
|
||||
```
|
||||
|
||||
### Creating yaml from scratch
|
||||
## Creating yaml from scratch
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '{"wrap": "frog"}'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
Use these comment operators to set or retrieve comments.
|
||||
## Examples
|
||||
### Set line comment
|
||||
## Set line comment
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -14,7 +13,7 @@ will output
|
||||
a: cat # single
|
||||
```
|
||||
|
||||
### Set head comment
|
||||
## Set head comment
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -30,7 +29,7 @@ will output
|
||||
a: cat
|
||||
```
|
||||
|
||||
### Set foot comment, using an expression
|
||||
## Set foot comment, using an expression
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -46,7 +45,7 @@ a: cat
|
||||
# cat
|
||||
```
|
||||
|
||||
### Remove comment
|
||||
## Remove comment
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat # comment
|
||||
@@ -62,7 +61,7 @@ a: cat
|
||||
b: dog # leave this
|
||||
```
|
||||
|
||||
### Remove all comments
|
||||
## Remove all comments
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat # comment
|
||||
@@ -76,7 +75,7 @@ will output
|
||||
a: cat
|
||||
```
|
||||
|
||||
### Get line comment
|
||||
## Get line comment
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat # meow
|
||||
@@ -90,7 +89,7 @@ will output
|
||||
meow
|
||||
```
|
||||
|
||||
### Get head comment
|
||||
## Get head comment
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat # meow
|
||||
@@ -104,7 +103,7 @@ will output
|
||||
|
||||
```
|
||||
|
||||
### Get foot comment
|
||||
## Get foot comment
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat # meow
|
||||
@@ -1,6 +1,5 @@
|
||||
Deletes matching entries in maps or arrays.
|
||||
## Examples
|
||||
### Delete entry in map
|
||||
## Delete entry in map
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -15,7 +14,7 @@ will output
|
||||
a: cat
|
||||
```
|
||||
|
||||
### Delete entry in array
|
||||
## Delete entry in array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 1
|
||||
@@ -32,7 +31,7 @@ will output
|
||||
- 3
|
||||
```
|
||||
|
||||
### Delete no matches
|
||||
## Delete no matches
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -48,7 +47,7 @@ a: cat
|
||||
b: dog
|
||||
```
|
||||
|
||||
### Delete matching entries
|
||||
## Delete matching entries
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
## Examples
|
||||
### Retrieve a document index
|
||||
Use the `documentIndex` operator to select nodes of a particular document.
|
||||
## Retrieve a document index
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -18,7 +17,7 @@ will output
|
||||
1
|
||||
```
|
||||
|
||||
### Filter by document index
|
||||
## Filter by document index
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -34,7 +33,7 @@ will output
|
||||
a: frog
|
||||
```
|
||||
|
||||
### Print Document Index with matches
|
||||
## Print Document Index with matches
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -1,5 +1,3 @@
|
||||
## Equals Operator
|
||||
|
||||
This is a boolean operator that will return ```true``` if the LHS is equal to the RHS and ``false`` otherwise.
|
||||
|
||||
```
|
||||
@@ -13,8 +11,7 @@ select(.a == .b)
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
### Match string
|
||||
## Match string
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- cat
|
||||
@@ -32,7 +29,7 @@ true
|
||||
false
|
||||
```
|
||||
|
||||
### Match number
|
||||
## Match number
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 3
|
||||
@@ -50,7 +47,7 @@ true
|
||||
false
|
||||
```
|
||||
|
||||
### Match nulls
|
||||
## Match nulls
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'null == ~'
|
||||
@@ -1,6 +1,5 @@
|
||||
Explodes (or dereferences) aliases and anchors.
|
||||
## Examples
|
||||
### Explode alias and anchor
|
||||
## Explode alias and anchor
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
@@ -18,7 +17,7 @@ f:
|
||||
b: cat
|
||||
```
|
||||
|
||||
### Explode with no aliases or anchors
|
||||
## Explode with no aliases or anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: mike
|
||||
@@ -32,7 +31,7 @@ will output
|
||||
a: mike
|
||||
```
|
||||
|
||||
### Explode with alias keys
|
||||
## Explode with alias keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
f:
|
||||
@@ -50,7 +49,7 @@ f:
|
||||
cat: b
|
||||
```
|
||||
|
||||
### Explode with merge anchors
|
||||
## Explode with merge anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
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
|
||||
```
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# Mulitply Operator
|
||||
## Examples
|
||||
### Merge objects together
|
||||
sample.yml:
|
||||
```yaml
|
||||
{a: {also: me}, b: {also: {g: wizz}}}
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '. * {"a":.b}' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
{a: {also: {g: wizz}}, b: {also: {g: wizz}}}
|
||||
```
|
||||
### Merge keeps style of LHS
|
||||
sample.yml:
|
||||
```yaml
|
||||
a: {things: great}
|
||||
b:
|
||||
also: "me"
|
||||
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '. * {"a":.b}' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
a: {things: great, also: "me"}
|
||||
b:
|
||||
also: "me"
|
||||
```
|
||||
### Merge arrays
|
||||
sample.yml:
|
||||
```yaml
|
||||
{a: [1,2,3], b: [3,4,5]}
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '. * {"a":.b}' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
{a: [3, 4, 5], b: [3, 4, 5]}
|
||||
```
|
||||
### Merge to prefix an element
|
||||
sample.yml:
|
||||
```yaml
|
||||
{a: cat, b: dog}
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '. * {"a": {"c": .a}}' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
{a: {c: cat}, b: dog}
|
||||
```
|
||||
### Merge with simple aliases
|
||||
sample.yml:
|
||||
```yaml
|
||||
{a: &cat {c: frog}, b: {f: *cat}, c: {g: thongs}}
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '.c * .b' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
{g: thongs, f: *cat}
|
||||
```
|
||||
### Merge does not copy anchor names
|
||||
sample.yml:
|
||||
```yaml
|
||||
{a: {c: &cat frog}, b: {f: *cat}, c: {g: thongs}}
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '.c * .a' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
{g: thongs, c: frog}
|
||||
```
|
||||
### Merge with merge anchors
|
||||
sample.yml:
|
||||
```yaml
|
||||
|
||||
foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
|
||||
bar: &bar
|
||||
b: bar_b
|
||||
thing: bar_thing
|
||||
c: bar_c
|
||||
|
||||
foobarList:
|
||||
b: foobarList_b
|
||||
<<: [*foo,*bar]
|
||||
c: foobarList_c
|
||||
|
||||
foobar:
|
||||
c: foobar_c
|
||||
<<: *foo
|
||||
thing: foobar_thing
|
||||
|
||||
```
|
||||
Expression
|
||||
```bash
|
||||
yq '.foobar * .foobarList' < sample.yml
|
||||
```
|
||||
Result
|
||||
```yaml
|
||||
c: foobarList_c
|
||||
<<: [*foo, *bar]
|
||||
thing: foobar_thing
|
||||
b: foobarList_b
|
||||
```
|
||||
@@ -2,9 +2,18 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope
|
||||
|
||||
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
|
||||
|
||||
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.
|
||||
## Examples
|
||||
### Merge objects together, returning merged result only
|
||||
|
||||
## Merging files
|
||||
Note the use of eval-all to ensure all documents are loaded into memory.
|
||||
|
||||
```bash
|
||||
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml
|
||||
```
|
||||
|
||||
## Merge objects together, returning merged result only
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -27,7 +36,7 @@ fieldA: cat
|
||||
fieldB: dog
|
||||
```
|
||||
|
||||
### Merge objects together, returning parent object
|
||||
## Merge objects together, returning parent object
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -55,12 +64,13 @@ b:
|
||||
fieldB: dog
|
||||
```
|
||||
|
||||
### Merge keeps style of LHS
|
||||
## Merge keeps style of LHS
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: {things: great}
|
||||
b:
|
||||
also: "me"
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@@ -73,7 +83,7 @@ b:
|
||||
also: "me"
|
||||
```
|
||||
|
||||
### Merge arrays
|
||||
## Merge arrays
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -101,7 +111,39 @@ b:
|
||||
- 5
|
||||
```
|
||||
|
||||
### Merge to prefix an element
|
||||
## 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
|
||||
@@ -118,7 +160,7 @@ a:
|
||||
b: dog
|
||||
```
|
||||
|
||||
### Merge with simple aliases
|
||||
## Merge with simple aliases
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat
|
||||
@@ -138,7 +180,7 @@ g: thongs
|
||||
f: *cat
|
||||
```
|
||||
|
||||
### Merge does not copy anchor names
|
||||
## Merge does not copy anchor names
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -158,7 +200,7 @@ g: thongs
|
||||
c: frog
|
||||
```
|
||||
|
||||
### Merge with merge anchors
|
||||
## Merge with merge anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -1,72 +0,0 @@
|
||||
This is a boolean operator and will return `true` when given a `false` value (including null), and `false` otherwise.
|
||||
## Examples
|
||||
### Not true is false
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'true | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
### Not false is true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input 'false | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
true
|
||||
```
|
||||
|
||||
### String values considered to be true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '"cat" | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
### Empty string value considered to be true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '"" | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
### Numbers are considered to be true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '1 | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
### Zero is considered to be true
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '0 | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
### Null is considered to be false
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '~ | not'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
true
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
@@ -3,69 +3,7 @@ This operator recursively matches all children nodes given of a particular eleme
|
||||
```bash
|
||||
yq eval '.. style= "flow"' file.yaml
|
||||
```
|
||||
## Examples
|
||||
### Map
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
b: apple
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '..' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
b: apple
|
||||
b: apple
|
||||
apple
|
||||
```
|
||||
|
||||
### Array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '..' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
1
|
||||
2
|
||||
3
|
||||
```
|
||||
|
||||
### Array of maps
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- a: cat
|
||||
- 2
|
||||
- true
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '..' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a: cat
|
||||
- 2
|
||||
- true
|
||||
a: cat
|
||||
cat
|
||||
2
|
||||
true
|
||||
```
|
||||
|
||||
### Aliases are not traversed
|
||||
## Aliases are not traversed
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat
|
||||
@@ -74,20 +12,20 @@ b: *cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '..' sample.yml
|
||||
yq eval '[..]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: &cat
|
||||
- a: &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
- &cat
|
||||
c: frog
|
||||
b: *cat
|
||||
&cat
|
||||
c: frog
|
||||
frog
|
||||
*cat
|
||||
- frog
|
||||
- *cat
|
||||
```
|
||||
|
||||
### Merge docs are not traversed
|
||||
## Merge docs are not traversed
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -111,15 +49,15 @@ foobar:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '.foobar | ..' sample.yml
|
||||
yq eval '.foobar | [..]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
foobar_c
|
||||
*foo
|
||||
foobar_thing
|
||||
- c: foobar_c
|
||||
!!merge <<: *foo
|
||||
thing: foobar_thing
|
||||
- foobar_c
|
||||
- *foo
|
||||
- foobar_thing
|
||||
```
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
Select is used to filter arrays and maps by a boolean expression.
|
||||
## Examples
|
||||
### Select elements from array
|
||||
## Select elements from array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- cat
|
||||
@@ -17,7 +16,7 @@ cat
|
||||
goat
|
||||
```
|
||||
|
||||
### Select and update matching values in map
|
||||
## Select and update matching values in map
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -27,7 +26,7 @@ a:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '(.a[] | select(. == "*at")) |= "rabbit"' sample.yml
|
||||
yq eval '(.a.[] | select(. == "*at")) |= "rabbit"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
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
|
||||
```
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
The style operator can be used to get or set the style of nodes (e.g. string style, yaml style)
|
||||
## Examples
|
||||
### Set tagged style
|
||||
## Set tagged style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -21,7 +20,7 @@ c: !!float 3.2
|
||||
e: !!bool true
|
||||
```
|
||||
|
||||
### Set double quote style
|
||||
## Set double quote style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -41,7 +40,7 @@ c: "3.2"
|
||||
e: "true"
|
||||
```
|
||||
|
||||
### Set single quote style
|
||||
## Set single quote style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -61,7 +60,7 @@ c: '3.2'
|
||||
e: 'true'
|
||||
```
|
||||
|
||||
### Set literal quote style
|
||||
## Set literal quote style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -85,7 +84,7 @@ e: |-
|
||||
true
|
||||
```
|
||||
|
||||
### Set folded quote style
|
||||
## Set folded quote style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -109,7 +108,7 @@ e: >-
|
||||
true
|
||||
```
|
||||
|
||||
### Set flow quote style
|
||||
## Set flow quote style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -126,7 +125,9 @@ will output
|
||||
{a: cat, b: 5, c: 3.2, e: true}
|
||||
```
|
||||
|
||||
### Set empty (default) quote style
|
||||
## Pretty print
|
||||
Set empty (default) quote style
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -146,11 +147,10 @@ c: 3.2
|
||||
e: true
|
||||
```
|
||||
|
||||
### Read style
|
||||
## Read style
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
b: thing
|
||||
{a: "cat", b: 'thing'}
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@@ -158,8 +158,8 @@ yq eval '.. | style' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
|
||||
|
||||
flow
|
||||
double
|
||||
single
|
||||
```
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`).
|
||||
## Examples
|
||||
### Get tag
|
||||
The tag operator can be used to get or set the tag of nodes (e.g. `!!str`, `!!int`, `!!bool`).
|
||||
## Get tag
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -23,7 +22,7 @@ will output
|
||||
!!seq
|
||||
```
|
||||
|
||||
### Convert numbers to strings
|
||||
## Convert numbers to strings
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat
|
||||
@@ -1,6 +1,5 @@
|
||||
This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
|
||||
## Examples
|
||||
### Simple map navigation
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
|
||||
## Simple map navigation
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -15,7 +14,7 @@ will output
|
||||
b: apple
|
||||
```
|
||||
|
||||
### Splat
|
||||
## Splat
|
||||
Often used to pipe children into other operators
|
||||
|
||||
Given a sample.yml file of:
|
||||
@@ -33,7 +32,23 @@ b: apple
|
||||
c: banana
|
||||
```
|
||||
|
||||
### Children don't exist
|
||||
## Special characters
|
||||
Use quotes around path elements with special characters
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
"{}": frog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '."{}"' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
frog
|
||||
```
|
||||
|
||||
## Children don't exist
|
||||
Nodes are added dynamically while traversing
|
||||
|
||||
Given a sample.yml file of:
|
||||
@@ -49,7 +64,7 @@ will output
|
||||
null
|
||||
```
|
||||
|
||||
### Wildcard matching
|
||||
## Wildcard matching
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
@@ -66,7 +81,7 @@ apple
|
||||
things
|
||||
```
|
||||
|
||||
### Aliases
|
||||
## Aliases
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat
|
||||
@@ -82,7 +97,7 @@ will output
|
||||
*cat
|
||||
```
|
||||
|
||||
### Traversing aliases with splat
|
||||
## Traversing aliases with splat
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat
|
||||
@@ -98,7 +113,7 @@ will output
|
||||
frog
|
||||
```
|
||||
|
||||
### Traversing aliases explicitly
|
||||
## Traversing aliases explicitly
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: &cat
|
||||
@@ -114,7 +129,7 @@ will output
|
||||
frog
|
||||
```
|
||||
|
||||
### Traversing arrays by index
|
||||
## Traversing arrays by index
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 1
|
||||
@@ -123,42 +138,42 @@ Given a sample.yml file of:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[0]' sample.yml
|
||||
yq eval '.[0]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
1
|
||||
```
|
||||
|
||||
### Maps with numeric keys
|
||||
## Maps with numeric keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
2: cat
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[2]' sample.yml
|
||||
yq eval '.[2]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cat
|
||||
```
|
||||
|
||||
### Maps with non existing numeric keys
|
||||
## Maps with non existing numeric keys
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: b
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq eval '[0]' sample.yml
|
||||
yq eval '.[0]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
null
|
||||
```
|
||||
|
||||
### Traversing merge anchors
|
||||
## Traversing merge anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -189,7 +204,7 @@ will output
|
||||
foo_a
|
||||
```
|
||||
|
||||
### Traversing merge anchors with override
|
||||
## Traversing merge anchors with override
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -220,7 +235,7 @@ will output
|
||||
foo_c
|
||||
```
|
||||
|
||||
### Traversing merge anchors with local override
|
||||
## Traversing merge anchors with local override
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -251,7 +266,7 @@ will output
|
||||
foobar_thing
|
||||
```
|
||||
|
||||
### Splatting merge anchors
|
||||
## Splatting merge anchors
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -284,7 +299,7 @@ foo_a
|
||||
foobar_thing
|
||||
```
|
||||
|
||||
### Traversing merge anchor lists
|
||||
## Traversing merge anchor lists
|
||||
Note that the later merge anchors override previous
|
||||
|
||||
Given a sample.yml file of:
|
||||
@@ -317,7 +332,7 @@ will output
|
||||
bar_thing
|
||||
```
|
||||
|
||||
### Splatting merge anchor lists
|
||||
## Splatting merge anchor lists
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
foo: &foo
|
||||
@@ -1,6 +1,5 @@
|
||||
This operator is used to combine different results together.
|
||||
## Examples
|
||||
### Combine scalars
|
||||
## Combine scalars
|
||||
Running
|
||||
```bash
|
||||
yq eval --null-input '1, true, "cat"'
|
||||
@@ -12,7 +11,7 @@ true
|
||||
cat
|
||||
```
|
||||
|
||||
### Combine selected paths
|
||||
## Combine selected paths
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: fieldA
|
||||
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`.
|
||||
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.
|
||||
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.
|
||||
@@ -1,5 +1,3 @@
|
||||
## Equals Operator
|
||||
|
||||
This is a boolean operator that will return ```true``` if the LHS is equal to the RHS and ``false`` otherwise.
|
||||
|
||||
```
|
||||
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.
|
||||
@@ -2,4 +2,13 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope
|
||||
|
||||
Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings).
|
||||
|
||||
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||
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
|
||||
```
|
||||
@@ -1 +0,0 @@
|
||||
This is a boolean operator and will return `true` when given a `false` value (including null), and `false` otherwise.
|
||||
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.
|
||||
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 +0,0 @@
|
||||
The tag operator can be used to get or set the tag of ndoes (e.g. `!!str`, `!!int`, `!!bool`).
|
||||
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 +0,0 @@
|
||||
This is the simples (and perhaps most used) operator, it is used to navigate deeply into yaml structurse.
|
||||
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.
|
||||
@@ -32,7 +32,7 @@ func TestJsonEncoderPreservesObjectOrder(t *testing.T) {
|
||||
writer := bufio.NewWriter(&output)
|
||||
|
||||
var jsonEncoder = NewJsonEncoder(writer, 2)
|
||||
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml")
|
||||
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var log = logging.MustGetLogger("yq-lib")
|
||||
|
||||
type OperationType struct {
|
||||
Type string
|
||||
NumArgs uint // number of arguments to the op
|
||||
@@ -17,15 +19,10 @@ type OperationType struct {
|
||||
}
|
||||
|
||||
// operators TODO:
|
||||
// - get path operator (like doc index)
|
||||
// - get file index op (like doc index)
|
||||
// - get file name op (like doc index)
|
||||
// - write in place
|
||||
// - mergeAppend (merges and appends arrays)
|
||||
// - cookbook doc for common things
|
||||
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||
// - compare ??
|
||||
// - validate ??
|
||||
// - exists
|
||||
|
||||
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
||||
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
||||
@@ -33,13 +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 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 AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
|
||||
var AssignTag = &OperationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: AssignTagOperator}
|
||||
var AssignComment = &OperationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: AssignCommentsOperator}
|
||||
|
||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 40, Handler: MultiplyOperator}
|
||||
var Multiply = &OperationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 45, Handler: MultiplyOperator}
|
||||
var Add = &OperationType{Type: "ADD", NumArgs: 2, Precedence: 45, Handler: AddOperator}
|
||||
|
||||
var Equals = &OperationType{Type: "EQUALS", NumArgs: 2, Precedence: 40, Handler: EqualsOperator}
|
||||
var CreateMap = &OperationType{Type: "CREATE_MAP", NumArgs: 2, Precedence: 40, Handler: CreateMapOperator}
|
||||
@@ -51,8 +50,12 @@ var GetStyle = &OperationType{Type: "GET_STYLE", NumArgs: 0, Precedence: 50, Han
|
||||
var GetTag = &OperationType{Type: "GET_TAG", NumArgs: 0, Precedence: 50, Handler: GetTagOperator}
|
||||
var GetComment = &OperationType{Type: "GET_COMMENT", NumArgs: 0, Precedence: 50, Handler: GetCommentsOperator}
|
||||
var GetDocumentIndex = &OperationType{Type: "GET_DOCUMENT_INDEX", NumArgs: 0, Precedence: 50, Handler: GetDocumentIndexOperator}
|
||||
var GetFilename = &OperationType{Type: "GET_FILENAME", NumArgs: 0, Precedence: 50, Handler: GetFilenameOperator}
|
||||
var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence: 50, Handler: GetFileIndexOperator}
|
||||
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
|
||||
|
||||
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
||||
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 TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||
@@ -66,11 +69,9 @@ var Empty = &OperationType{Type: "EMPTY", NumArgs: 50, Handler: EmptyOperator}
|
||||
var RecursiveDescent = &OperationType{Type: "RECURSIVE_DESCENT", NumArgs: 0, Precedence: 50, Handler: RecursiveDescentOperator}
|
||||
|
||||
var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler: SelectOperator}
|
||||
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
||||
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
||||
|
||||
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}
|
||||
// filters matches if they have the existing path
|
||||
|
||||
type Operation struct {
|
||||
OperationType *OperationType
|
||||
Value interface{}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -36,6 +36,11 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,14 @@ import (
|
||||
|
||||
var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Update parent to be the child value",
|
||||
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{
|
||||
@@ -14,7 +21,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update to be the sibling value",
|
||||
description: "Update node to be the sibling value",
|
||||
document: `{a: {b: child}, b: sibling}`,
|
||||
expression: `.a = .b`,
|
||||
expected: []string{
|
||||
@@ -73,7 +80,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Update selected results",
|
||||
document: `{a: {b: apple, c: cactus}}`,
|
||||
expression: `.a[] | select(. == "apple") |= "frog"`,
|
||||
expression: `.a.[] | select(. == "apple") |= "frog"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: frog, c: cactus}}\n",
|
||||
},
|
||||
@@ -87,17 +94,19 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update empty object",
|
||||
document: `{}`,
|
||||
expression: `.a.b |= "bogs"`,
|
||||
description: "Update empty object",
|
||||
dontFormatInputForDoc: true,
|
||||
document: `{}`,
|
||||
expression: `.a.b |= "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: bogs}}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Update empty object and array",
|
||||
document: `{}`,
|
||||
expression: `.a.b[0] |= "bogs"`,
|
||||
description: "Update empty object and array",
|
||||
dontFormatInputForDoc: true,
|
||||
document: `{}`,
|
||||
expression: `.a.b.[0] |= "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: [bogs]}}\n",
|
||||
},
|
||||
@@ -105,7 +114,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: `.a.b[1].c |= "bogs"`,
|
||||
expression: `.a.b.[1].c |= "bogs"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n",
|
||||
},
|
||||
@@ -116,5 +125,5 @@ func TestAssignOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range assignOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Assign Operator", assignOperatorScenarios)
|
||||
documentScenarios(t, "Assign", assignOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package yqlib
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func isTruthy(c *CandidateNode) (bool, error) {
|
||||
@@ -25,9 +25,42 @@ func isTruthy(c *CandidateNode) (bool, error) {
|
||||
|
||||
type boolOp func(bool, bool) bool
|
||||
|
||||
func performBoolOp(results *list.List, lhs *list.List, rhs *list.List, op boolOp) error {
|
||||
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
||||
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
||||
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return errDecoding
|
||||
}
|
||||
|
||||
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
|
||||
rhsCandidate := rhsChild.Value.(*CandidateNode)
|
||||
rhsTrue, errDecoding := isTruthy(rhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return errDecoding
|
||||
}
|
||||
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
|
||||
results.PushBack(boolResult)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode, op boolOp) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
if matchingNodes.Len() == 0 {
|
||||
lhs, err := d.GetMatchingNodes(list.New(), pathNode.Lhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rhs, err := d.GetMatchingNodes(list.New(), pathNode.Rhs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return results, performBoolOp(results, lhs, rhs, op)
|
||||
}
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
lhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Lhs)
|
||||
@@ -39,23 +72,9 @@ func booleanOp(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTre
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for lhsChild := lhs.Front(); lhsChild != nil; lhsChild = lhsChild.Next() {
|
||||
lhsCandidate := lhsChild.Value.(*CandidateNode)
|
||||
lhsTrue, errDecoding := isTruthy(lhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
|
||||
for rhsChild := rhs.Front(); rhsChild != nil; rhsChild = rhsChild.Next() {
|
||||
rhsCandidate := rhsChild.Value.(*CandidateNode)
|
||||
rhsTrue, errDecoding := isTruthy(rhsCandidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
boolResult := createBooleanCandidate(lhsCandidate, op(lhsTrue, rhsTrue))
|
||||
|
||||
results.PushBack(boolResult)
|
||||
}
|
||||
err = performBoolOp(results, lhs, rhs, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,3 +94,20 @@ func AndOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
|
||||
return b1 && b2
|
||||
})
|
||||
}
|
||||
|
||||
func NotOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- notOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
log.Debug("notOperation checking %v", candidate)
|
||||
truthy, errDecoding := isTruthy(candidate)
|
||||
if errDecoding != nil {
|
||||
return nil, errDecoding
|
||||
}
|
||||
result := createBooleanCandidate(candidate, !truthy)
|
||||
results.PushBack(result)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
@@ -6,18 +6,36 @@ import (
|
||||
|
||||
var booleanOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: `{}`,
|
||||
expression: `true or false`,
|
||||
description: "OR example",
|
||||
expression: `true or false`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
}, {
|
||||
document: `{}`,
|
||||
},
|
||||
{
|
||||
description: "AND example",
|
||||
expression: `true and false`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: "[{a: bird, b: dog}, {a: frog, b: bird}, {a: cat, b: fly}]",
|
||||
description: "Matching nodes with select, equals and or",
|
||||
expression: `[.[] | select(.a == "cat" or .b == "dog")]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- {a: bird, b: dog}\n- {a: cat, b: fly}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `false or false`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{a: true, b: false}`,
|
||||
expression: `.[] or (false, true)`,
|
||||
expected: []string{
|
||||
@@ -27,10 +45,61 @@ var booleanOperatorScenarios = []expressionScenario{
|
||||
"D0, P[b], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Not true is false",
|
||||
expression: `true | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Not false is true",
|
||||
expression: `false | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "String values considered to be true",
|
||||
expression: `"cat" | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Empty string value considered to be true",
|
||||
expression: `"" | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Numbers are considered to be true",
|
||||
expression: `1 | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Zero is considered to be true",
|
||||
expression: `0 | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
description: "Null is considered to be false",
|
||||
expression: `~ | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestBooleanOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range booleanOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Boolean Operators", booleanOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,18 @@ package yqlib
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func CollectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("-- collectOperation")
|
||||
|
||||
if matchMap.Len() == 0 {
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return nodeToMap(candidate), nil
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
|
||||
@@ -2,6 +2,8 @@ package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -19,7 +21,9 @@ func CollectObjectOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *
|
||||
log.Debugf("-- collectObjectOperation")
|
||||
|
||||
if matchMap.Len() == 0 {
|
||||
return list.New(), nil
|
||||
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map", Value: "{}"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return nodeToMap(candidate), nil
|
||||
}
|
||||
first := matchMap.Front().Value.(*CandidateNode)
|
||||
var rotated []*list.List = make([]*list.List, len(first.Node.Content))
|
||||
@@ -90,7 +94,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
|
||||
|
||||
newCandidate.Path = nil
|
||||
|
||||
newCandidate, err = multiply(d, newCandidate, splatCandidate)
|
||||
newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
||||
description: `Collect empty object`,
|
||||
document: ``,
|
||||
expression: `{}`,
|
||||
expected: []string{},
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: `Wrap (prefix) existing object`,
|
||||
@@ -39,7 +41,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: `Using splat to create multiple objects`,
|
||||
document: `{name: Mike, pets: [cat, dog]}`,
|
||||
expression: `{.name: .pets[]}`,
|
||||
expression: `{.name: .pets.[]}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Mike: cat\n",
|
||||
"D0, P[], (!!map)::Mike: dog\n",
|
||||
@@ -49,7 +51,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
||||
description: `Working with multiple documents`,
|
||||
dontFormatInputForDoc: false,
|
||||
document: "{name: Mike, pets: [cat, dog]}\n---\n{name: Rosey, pets: [monkey, sheep]}",
|
||||
expression: `{.name: .pets[]}`,
|
||||
expression: `{.name: .pets.[]}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Mike: cat\n",
|
||||
"D0, P[], (!!map)::Mike: dog\n",
|
||||
@@ -60,7 +62,7 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
||||
expression: `{.name: .pets[], "f":.food[]}`,
|
||||
expression: `{.name: .pets.[], "f":.food.[]}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::Mike: cat\nf: hotdog\n",
|
||||
"D0, P[], (!!map)::Mike: cat\nf: burger\n",
|
||||
|
||||
@@ -9,7 +9,17 @@ var collectOperatorScenarios = []expressionScenario{
|
||||
description: "Collect empty",
|
||||
document: ``,
|
||||
expression: `[]`,
|
||||
expected: []string{},
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: ``,
|
||||
expression: `[3]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- 3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Collect single",
|
||||
@@ -52,7 +62,7 @@ var collectOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
document: `a: {b: [1,2,3]}`,
|
||||
expression: `[.a.b[]]`,
|
||||
expression: `[.a.b.[]]`,
|
||||
skipDoc: true,
|
||||
expected: []string{
|
||||
"D0, P[a b], (!!seq)::- 1\n- 2\n- 3\n",
|
||||
|
||||
@@ -75,5 +75,5 @@ func TestCommentOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range commentOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Comments Operator", commentOperatorScenarios)
|
||||
documentScenarios(t, "Comment Operators", commentOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@ var createMapOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: [cat, dog]}`,
|
||||
expression: `.name: .pets[]`,
|
||||
expression: `.name: .pets.[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `{name: Mike, pets: [cat, dog], food: [hotdog, burger]}`,
|
||||
expression: `.name: .pets[], "f":.food[]`,
|
||||
expression: `.name: .pets.[], "f":.food.[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n",
|
||||
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n",
|
||||
@@ -36,7 +36,7 @@ var createMapOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
document: "{name: Mike, pets: [cat, dog], food: [hotdog, burger]}\n---\n{name: Fred, pets: [mouse], food: [pizza, onion, apple]}",
|
||||
expression: `.name: .pets[], "f":.food[]`,
|
||||
expression: `.name: .pets.[], "f":.food.[]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- [{Mike: cat}, {Mike: dog}]\n- [{Fred: mouse}]\n",
|
||||
"D0, P[], (!!seq)::- [{f: hotdog}, {f: burger}]\n- [{f: pizza}, {f: onion}, {f: apple}]\n",
|
||||
|
||||
@@ -43,5 +43,5 @@ func TestDeleteOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range deleteOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Delete Operator", deleteOperatorScenarios)
|
||||
documentScenarios(t, "Delete", deleteOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@ func TestDocumentIndexScenarios(t *testing.T) {
|
||||
for _, tt := range documentIndexScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Document Index Operator", documentIndexScenarios)
|
||||
documentScenarios(t, "Document Index", documentIndexScenarios)
|
||||
}
|
||||
@@ -54,5 +54,5 @@ func TestEqualOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range equalsOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Equals Operator", equalsOperatorScenarios)
|
||||
documentScenarios(t, "Equals", equalsOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -86,5 +86,5 @@ func TestExplodeOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range explodeTest {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Explode Operator", explodeTest)
|
||||
documentScenarios(t, "Explode", explodeTest)
|
||||
}
|
||||
|
||||
38
pkg/yqlib/operator_file.go
Normal file
38
pkg/yqlib/operator_file.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func GetFilenameOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetFilename")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Filename, Tag: "!!str"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func GetFileIndexOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
log.Debugf("GetFileIndex")
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", candidate.FileIndex), Tag: "!!int"}
|
||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||
results.PushBack(lengthCand)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
31
pkg/yqlib/operator_file_test.go
Normal file
31
pkg/yqlib/operator_file_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var fileOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Get filename",
|
||||
document: `{a: cat}`,
|
||||
expression: `filename`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::sample.yml\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Get file index",
|
||||
document: `{a: cat}`,
|
||||
expression: `fileIndex`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::0\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestFileOperatorsScenarios(t *testing.T) {
|
||||
for _, tt := range fileOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "File Operators", fileOperatorScenarios)
|
||||
}
|
||||
53
pkg/yqlib/operator_has.go
Normal file
53
pkg/yqlib/operator_has.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"strconv"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func HasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
|
||||
log.Debugf("-- hasOperation")
|
||||
var results = list.New()
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
wanted := rhs.Front().Value.(*CandidateNode).Node
|
||||
wantedKey := wanted.Value
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for el := matchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
// grab the first value
|
||||
var contents = candidate.Node.Content
|
||||
switch candidate.Node.Kind {
|
||||
case yaml.MappingNode:
|
||||
candidateHasKey := false
|
||||
for index := 0; index < len(contents) && !candidateHasKey; index = index + 2 {
|
||||
key := contents[index]
|
||||
if key.Value == wantedKey {
|
||||
candidateHasKey = true
|
||||
}
|
||||
}
|
||||
results.PushBack(createBooleanCandidate(candidate, candidateHasKey))
|
||||
case yaml.SequenceNode:
|
||||
candidateHasKey := false
|
||||
if wanted.Tag == "!!int" {
|
||||
var number, errParsingInt = strconv.ParseInt(wantedKey, 10, 64) // nolint
|
||||
if errParsingInt != nil {
|
||||
return nil, errParsingInt
|
||||
}
|
||||
candidateHasKey = int64(len(contents)) > number
|
||||
}
|
||||
results.PushBack(createBooleanCandidate(candidate, candidateHasKey))
|
||||
default:
|
||||
results.PushBack(createBooleanCandidate(candidate, false))
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
48
pkg/yqlib/operator_has_test.go
Normal file
48
pkg/yqlib/operator_has_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var hasOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Has map key",
|
||||
document: `- a: "yes"
|
||||
- a: ~
|
||||
- a:
|
||||
- b: nope
|
||||
`,
|
||||
expression: `.[] | has("a")`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::true\n",
|
||||
"D0, P[1], (!!bool)::true\n",
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
"D0, P[3], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
dontFormatInputForDoc: true,
|
||||
description: "Has array index",
|
||||
document: `- []
|
||||
- [1]
|
||||
- [1, 2]
|
||||
- [1, null]
|
||||
- [1, 2, 3]
|
||||
`,
|
||||
expression: `.[] | has(1)`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!bool)::false\n",
|
||||
"D0, P[1], (!!bool)::false\n",
|
||||
"D0, P[2], (!!bool)::true\n",
|
||||
"D0, P[3], (!!bool)::true\n",
|
||||
"D0, P[4], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestHasOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range hasOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Has", hasOperatorScenarios)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type CrossFunctionCalculation func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error)
|
||||
@@ -15,12 +15,14 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("crossFunction LHS len: %v", lhs.Len())
|
||||
|
||||
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("crossFunction RHS len: %v", rhs.Len())
|
||||
|
||||
var results = list.New()
|
||||
|
||||
@@ -28,6 +30,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
||||
lhsCandidate := el.Value.(*CandidateNode)
|
||||
|
||||
for rightEl := rhs.Front(); rightEl != nil; rightEl = rightEl.Next() {
|
||||
log.Debugf("Applying calc")
|
||||
rhsCandidate := rightEl.Value.(*CandidateNode)
|
||||
resultCandidate, err := calculation(d, lhsCandidate, rhsCandidate)
|
||||
if err != nil {
|
||||
@@ -40,39 +43,49 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type MultiplyPreferences struct {
|
||||
AppendArrays bool
|
||||
}
|
||||
|
||||
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
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) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
log.Debugf("Multipling LHS: %v", NodeToString(lhs))
|
||||
log.Debugf("- RHS: %v", NodeToString(rhs))
|
||||
func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = UnwrapDoc(lhs.Node)
|
||||
rhs.Node = UnwrapDoc(rhs.Node)
|
||||
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||
|
||||
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||
shouldAppendArrays := preferences.AppendArrays
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mergeObjects(d, newThing, rhs)
|
||||
|
||||
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
||||
}
|
||||
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()
|
||||
err := recursiveDecent(d, results, nodeToMap(rhs))
|
||||
|
||||
// shouldn't recurse arrays if appending
|
||||
err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -83,7 +96,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode)
|
||||
}
|
||||
|
||||
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 {
|
||||
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))
|
||||
|
||||
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
||||
@@ -113,6 +127,8 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
|
||||
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
||||
assignmentOp.OperationType = Assign
|
||||
assignmentOp.Preferences = &AssignOpPreferences{false}
|
||||
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
|
||||
assignmentOp.OperationType = AddAssign
|
||||
}
|
||||
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
|
||||
|
||||
@@ -13,6 +13,13 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
"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,
|
||||
document: `{a: {also: me}, b: {also: [1]}}`,
|
||||
@@ -92,6 +99,22 @@ b:
|
||||
"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",
|
||||
document: `{a: cat, b: dog}`,
|
||||
@@ -130,5 +153,5 @@ func TestMultiplyOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range multiplyOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Multiply 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,65 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var notOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Not true is false",
|
||||
expression: `true | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Not false is true",
|
||||
expression: `false | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "String values considered to be true",
|
||||
expression: `"cat" | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Empty string value considered to be true",
|
||||
expression: `"" | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Numbers are considered to be true",
|
||||
expression: `1 | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Zero is considered to be true",
|
||||
expression: `0 | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
description: "Null is considered to be false",
|
||||
expression: `~ | not`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestNotOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range notOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Not Operator", notOperatorScenarios)
|
||||
}
|
||||
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 (
|
||||
"container/list"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||
var results = list.New()
|
||||
|
||||
err := recursiveDecent(d, results, matchMap)
|
||||
err := recursiveDecent(d, results, matchMap, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -17,7 +17,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
|
||||
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() {
|
||||
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))
|
||||
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))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = recursiveDecent(d, results, children)
|
||||
err = recursiveDecent(d, results, children, recurseArray)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,22 @@ import (
|
||||
)
|
||||
|
||||
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`,
|
||||
@@ -23,9 +39,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Map",
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `..`,
|
||||
skipDoc: true,
|
||||
document: `{a: {b: apple}}`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: {b: apple}}\n",
|
||||
"D0, P[a], (!!map)::{b: apple}\n",
|
||||
@@ -33,9 +49,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Array",
|
||||
document: `[1,2,3]`,
|
||||
expression: `..`,
|
||||
skipDoc: true,
|
||||
document: `[1,2,3]`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[1, 2, 3]\n",
|
||||
"D0, P[0], (!!int)::1\n",
|
||||
@@ -44,9 +60,9 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Array of maps",
|
||||
document: `[{a: cat},2,true]`,
|
||||
expression: `..`,
|
||||
skipDoc: true,
|
||||
document: `[{a: cat},2,true]`,
|
||||
expression: `..`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[{a: cat}, 2, true]\n",
|
||||
"D0, P[0], (!!map)::{a: cat}\n",
|
||||
@@ -58,23 +74,17 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Aliases are not traversed",
|
||||
document: `{a: &cat {c: frog}, b: *cat}`,
|
||||
expression: `..`,
|
||||
expression: `[..]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
|
||||
"D0, P[a], (!!map)::&cat {c: frog}\n",
|
||||
"D0, P[a c], (!!str)::frog\n",
|
||||
"D0, P[b], (alias)::*cat\n",
|
||||
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merge docs are not traversed",
|
||||
document: mergeDocSample,
|
||||
expression: `.foobar | ..`,
|
||||
expression: `.foobar | [..]`,
|
||||
expected: []string{
|
||||
"D0, P[foobar], (!!map)::c: foobar_c\n!!merge <<: *foo\nthing: foobar_thing\n",
|
||||
"D0, P[foobar c], (!!str)::foobar_c\n",
|
||||
"D0, P[foobar <<], (alias)::*foo\n",
|
||||
"D0, P[foobar thing], (!!str)::foobar_thing\n",
|
||||
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -96,5 +106,5 @@ func TestRecursiveDescentOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range recursiveDescentOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Recursive Descent Operator", recursiveDescentOperatorScenarios)
|
||||
documentScenarios(t, "Recursive Descent", recursiveDescentOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ var selectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: [cat,goat,dog]`,
|
||||
expression: `.a[] | select(. == "*at")`,
|
||||
expression: `.a.[] | select(. == "*at")`,
|
||||
expected: []string{
|
||||
"D0, P[a 0], (!!str)::cat\n",
|
||||
"D0, P[a 1], (!!str)::goat\n"},
|
||||
@@ -31,7 +31,7 @@ var selectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Select and update matching values in map",
|
||||
document: `a: { things: cat, bob: goat, horse: dog }`,
|
||||
expression: `(.a[] | select(. == "*at")) |= "rabbit"`,
|
||||
expression: `(.a.[] | select(. == "*at")) |= "rabbit"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: {things: rabbit, bob: rabbit, horse: dog}\n",
|
||||
},
|
||||
@@ -39,7 +39,7 @@ var selectOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: { things: {include: true}, notMe: {include: false}, andMe: {include: fold} }`,
|
||||
expression: `.a[] | select(.include)`,
|
||||
expression: `.a.[] | select(.include)`,
|
||||
expected: []string{
|
||||
"D0, P[a things], (!!map)::{include: true}\n",
|
||||
"D0, P[a andMe], (!!map)::{include: fold}\n",
|
||||
@@ -59,5 +59,5 @@ func TestSelectOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range selectOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Select Operator", selectOperatorScenarios)
|
||||
documentScenarios(t, "Select", selectOperatorScenarios)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -70,9 +70,10 @@ e: >-
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Set empty (default) quote style",
|
||||
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
||||
expression: `.. style=""`,
|
||||
description: "Pretty print",
|
||||
subdescription: "Set empty (default) quote style",
|
||||
document: `{a: cat, b: 5, c: 3.2, e: true}`,
|
||||
expression: `.. style=""`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
|
||||
},
|
||||
@@ -86,9 +87,10 @@ e: >-
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Read style",
|
||||
document: `{a: "cat", b: 'thing'}`,
|
||||
expression: `.. | style`,
|
||||
description: "Read style",
|
||||
document: `{a: "cat", b: 'thing'}`,
|
||||
dontFormatInputForDoc: true,
|
||||
expression: `.. | style`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::flow\n",
|
||||
"D0, P[a], (!!str)::double\n",
|
||||
@@ -110,5 +112,5 @@ func TestStyleOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range styleOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Style Operator", styleOperatorScenarios)
|
||||
documentScenarios(t, "Style", styleOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@ func TestTagOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range tagOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Tag Operator", tagOperatorScenarios)
|
||||
documentScenarios(t, "Tag", tagOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -45,6 +45,15 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
"D0, P[1], (!!map)::{c: banana}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Special characters",
|
||||
subdescription: "Use quotes around path elements with special characters",
|
||||
document: `{"{}": frog}`,
|
||||
expression: `."{}"`,
|
||||
expected: []string{
|
||||
"D0, P[{}], (!!str)::frog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Children don't exist",
|
||||
subdescription: "Nodes are added dynamically while traversing",
|
||||
@@ -150,7 +159,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Traversing arrays by index",
|
||||
document: `[1,2,3]`,
|
||||
expression: `[0]`,
|
||||
expression: `.[0]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!int)::1\n",
|
||||
},
|
||||
@@ -158,7 +167,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Maps with numeric keys",
|
||||
document: `{2: cat}`,
|
||||
expression: `[2]`,
|
||||
expression: `.[2]`,
|
||||
expected: []string{
|
||||
"D0, P[2], (!!str)::cat\n",
|
||||
},
|
||||
@@ -166,7 +175,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Maps with non existing numeric keys",
|
||||
document: `{a: b}`,
|
||||
expression: `[0]`,
|
||||
expression: `.[0]`,
|
||||
expected: []string{
|
||||
"D0, P[0], (!!null)::null\n",
|
||||
},
|
||||
@@ -271,5 +280,5 @@ func TestTraversePathOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range traversePathOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Traverse Operator", traversePathOperatorScenarios)
|
||||
documentScenarios(t, "Traverse", traversePathOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -29,5 +29,5 @@ func TestUnionOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range unionOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentScenarios(t, "Union Operator", unionOperatorScenarios)
|
||||
documentScenarios(t, "Union", unionOperatorScenarios)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type expressionScenario struct {
|
||||
@@ -29,17 +30,26 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
||||
|
||||
node, err := treeCreator.ParsePath(s.expression)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
|
||||
return
|
||||
}
|
||||
inputs := list.New()
|
||||
|
||||
if s.document != "" {
|
||||
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml")
|
||||
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Error(err, s.document)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
candidateNode := &CandidateNode{
|
||||
Document: 0,
|
||||
Filename: "",
|
||||
Node: &yaml.Node{Tag: "!!null"},
|
||||
FileIndex: 0,
|
||||
}
|
||||
inputs.PushBack(candidateNode)
|
||||
|
||||
}
|
||||
|
||||
results, err = treeNavigator.GetMatchingNodes(inputs, node)
|
||||
@@ -90,7 +100,8 @@ func formatYaml(yaml string) string {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = EvaluateStream("sample.yaml", strings.NewReader(yaml), node, printer)
|
||||
streamEvaluator := NewStreamEvaluator()
|
||||
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(yaml), node, printer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -113,17 +124,13 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
|
||||
}
|
||||
|
||||
w := bufio.NewWriter(f)
|
||||
writeOrPanic(w, "\n")
|
||||
|
||||
writeOrPanic(w, "\n## Examples\n")
|
||||
|
||||
for index, s := range scenarios {
|
||||
for _, s := range scenarios {
|
||||
if !s.skipDoc {
|
||||
|
||||
if s.description != "" {
|
||||
writeOrPanic(w, fmt.Sprintf("### %v\n", s.description))
|
||||
} else {
|
||||
writeOrPanic(w, fmt.Sprintf("### Example %v\n", index))
|
||||
}
|
||||
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
|
||||
|
||||
if s.subdescription != "" {
|
||||
writeOrPanic(w, s.subdescription)
|
||||
writeOrPanic(w, "\n\n")
|
||||
@@ -131,7 +138,7 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
|
||||
formattedDoc := ""
|
||||
if s.document != "" {
|
||||
if s.dontFormatInputForDoc {
|
||||
formattedDoc = s.document
|
||||
formattedDoc = s.document + "\n"
|
||||
} else {
|
||||
formattedDoc = formatYaml(s.document)
|
||||
}
|
||||
@@ -155,18 +162,20 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
|
||||
var output bytes.Buffer
|
||||
var err error
|
||||
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
|
||||
streamEvaluator := NewStreamEvaluator()
|
||||
|
||||
if s.document != "" {
|
||||
node, err := treeCreator.ParsePath(s.expression)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = EvaluateStream("sample.yaml", strings.NewReader(formattedDoc), node, printer)
|
||||
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
} else {
|
||||
err = EvaluateAllFileStreams(s.expression, []string{}, printer)
|
||||
err = streamEvaluator.EvaluateNew(s.expression, printer)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -37,8 +37,23 @@ var pathTests = []struct {
|
||||
// {`.a, .b`, append(make([]interface{}, 0), "a", "OR", "b")},
|
||||
// {`[.a, .b]`, append(make([]interface{}, 0), "[", "a", "OR", "b", "]")},
|
||||
// {`."[a", ."b]"`, append(make([]interface{}, 0), "[a", "OR", "b]")},
|
||||
// {`.a[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")},
|
||||
// {`.a.[]`, append(make([]interface{}, 0), "a", "PIPE", "[]")},
|
||||
// {`.[].a`, append(make([]interface{}, 0), "[]", "PIPE", "a")},
|
||||
// {
|
||||
// `["cat"]`,
|
||||
// append(make([]interface{}, 0), "[", "cat (string)", "]"),
|
||||
// append(make([]interface{}, 0), "cat (string)", "COLLECT", "PIPE"),
|
||||
// },
|
||||
{
|
||||
`[]`,
|
||||
append(make([]interface{}, 0), "[", "]"),
|
||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"),
|
||||
},
|
||||
{
|
||||
`[3]`,
|
||||
append(make([]interface{}, 0), "[", "3 (int64)", "]"),
|
||||
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "PIPE"),
|
||||
},
|
||||
{
|
||||
`d0.a`,
|
||||
append(make([]interface{}, 0), "d0", "PIPE", "a"),
|
||||
@@ -85,7 +100,7 @@ var pathTests = []struct {
|
||||
append(make([]interface{}, 0), "a", "mike (string)", "CREATE_MAP", "COLLECT_OBJECT", "PIPE"),
|
||||
},
|
||||
{
|
||||
`{.a: .c, .b[]: .f.g[]}`,
|
||||
`{.a: .c, .b.[]: .f.g.[]}`,
|
||||
append(make([]interface{}, 0), "{", "a", "CREATE_MAP", "c", "UNION", "b", "PIPE", "[]", "CREATE_MAP", "f", "PIPE", "g", "PIPE", "[]", "}"),
|
||||
append(make([]interface{}, 0), "a", "c", "CREATE_MAP", "b", "[]", "PIPE", "f", "g", "PIPE", "[]", "PIPE", "CREATE_MAP", "UNION", "COLLECT_OBJECT", "PIPE"),
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user