mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
14 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 |
@@ -2,10 +2,10 @@ package cmd
|
|||||||
|
|
||||||
var unwrapScalar = true
|
var unwrapScalar = true
|
||||||
|
|
||||||
// var writeInplace = false
|
var writeInplace = false
|
||||||
var outputToJSON = false
|
var outputToJSON = false
|
||||||
|
|
||||||
// var exitStatus = false
|
var exitStatus = false
|
||||||
var forceColor = false
|
var forceColor = false
|
||||||
var forceNoColor = false
|
var forceNoColor = false
|
||||||
var colorsEnabled = false
|
var colorsEnabled = false
|
||||||
@@ -15,4 +15,4 @@ var nullInput = false
|
|||||||
var verbose = false
|
var verbose = false
|
||||||
var version = false
|
var version = false
|
||||||
|
|
||||||
// var log = logging.MustGetLogger("yq")
|
var completedSuccessfully = false
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||||
@@ -23,6 +25,7 @@ yq es -n '{"a": "b"}'
|
|||||||
return cmdEvalAll
|
return cmdEvalAll
|
||||||
}
|
}
|
||||||
func evaluateAll(cmd *cobra.Command, args []string) error {
|
func evaluateAll(cmd *cobra.Command, args []string) error {
|
||||||
|
cmd.SilenceUsage = true
|
||||||
// 0 args, read std in
|
// 0 args, read std in
|
||||||
// 1 arg, null input, process expression
|
// 1 arg, null input, process expression
|
||||||
// 1 arg, read file in sequence
|
// 1 arg, read file in sequence
|
||||||
@@ -39,7 +42,26 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
|||||||
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
||||||
colorsEnabled = true
|
colorsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if writeInplace && len(args) < 2 {
|
||||||
|
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeInplace {
|
||||||
|
// only use colors if its forced
|
||||||
|
colorsEnabled = forceColor
|
||||||
|
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
|
||||||
|
out, err = writeInPlaceHandler.CreateTempFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// need to indirectly call the function so that completedSuccessfully is
|
||||||
|
// passed when we finish execution as opposed to now
|
||||||
|
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
|
||||||
|
}
|
||||||
|
|
||||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||||
|
|
||||||
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -51,7 +73,7 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
if nullInput {
|
if nullInput {
|
||||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer)
|
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer)
|
||||||
} else {
|
} else {
|
||||||
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||||
}
|
}
|
||||||
@@ -59,6 +81,11 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
|
|||||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.SilenceUsage = true
|
completedSuccessfully = err == nil
|
||||||
|
|
||||||
|
if err == nil && exitStatus && !printer.PrintedAnything() {
|
||||||
|
return errors.New("no matches found")
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
||||||
@@ -23,6 +25,7 @@ yq es -n '{"a": "b"}'
|
|||||||
return cmdEvalSequence
|
return cmdEvalSequence
|
||||||
}
|
}
|
||||||
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
func evaluateSequence(cmd *cobra.Command, args []string) error {
|
||||||
|
cmd.SilenceUsage = true
|
||||||
// 0 args, read std in
|
// 0 args, read std in
|
||||||
// 1 arg, null input, process expression
|
// 1 arg, null input, process expression
|
||||||
// 1 arg, read file in sequence
|
// 1 arg, read file in sequence
|
||||||
@@ -39,10 +42,27 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
|||||||
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
|
||||||
colorsEnabled = true
|
colorsEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if writeInplace && len(args) < 2 {
|
||||||
|
return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file")
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeInplace {
|
||||||
|
// only use colors if its forced
|
||||||
|
colorsEnabled = forceColor
|
||||||
|
writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1])
|
||||||
|
out, err = writeInPlaceHandler.CreateTempFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// need to indirectly call the function so that completedSuccessfully is
|
||||||
|
// passed when we finish execution as opposed to now
|
||||||
|
defer func() { writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) }()
|
||||||
|
}
|
||||||
|
|
||||||
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators)
|
||||||
|
|
||||||
streamEvaluator := yqlib.NewStreamEvaluator()
|
streamEvaluator := yqlib.NewStreamEvaluator()
|
||||||
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
|
||||||
|
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -54,14 +74,18 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
case 1:
|
case 1:
|
||||||
if nullInput {
|
if nullInput {
|
||||||
err = allAtOnceEvaluator.EvaluateFiles(args[0], []string{}, printer)
|
err = streamEvaluator.EvaluateNew(args[0], printer)
|
||||||
} else {
|
} else {
|
||||||
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)
|
||||||
}
|
}
|
||||||
|
completedSuccessfully = err == nil
|
||||||
|
|
||||||
|
if err == nil && exitStatus && !printer.PrintedAnything() {
|
||||||
|
return errors.New("no matches found")
|
||||||
|
}
|
||||||
|
|
||||||
cmd.SilenceUsage = true
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ func New() *cobra.Command {
|
|||||||
|
|
||||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
||||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
|
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")
|
||||||
rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors")
|
rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ var (
|
|||||||
GitDescribe string
|
GitDescribe string
|
||||||
|
|
||||||
// Version is main version number that is being run at the moment.
|
// Version is main version number that is being run at the moment.
|
||||||
Version = "4.0.0-alpha2"
|
Version = "4.0.0-beta1"
|
||||||
|
|
||||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
||||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||||
|
|||||||
61
cmd/write.go
61
cmd/write.go
@@ -1,61 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "github.com/spf13/cobra"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// func createWriteCmd() *cobra.Command {
|
|
||||||
// var cmdWrite = &cobra.Command{
|
|
||||||
// Use: "write [yaml_file] [path_expression] [value]",
|
|
||||||
// Aliases: []string{"w"},
|
|
||||||
// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue",
|
|
||||||
// Example: `
|
|
||||||
// yq write things.yaml 'a.b.c' true
|
|
||||||
// yq write things.yaml 'a.*.c' true
|
|
||||||
// yq write things.yaml 'a.**' true
|
|
||||||
// yq write things.yaml 'a.(child.subchild==co*).c' true
|
|
||||||
// yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
|
|
||||||
// yq write things.yaml 'a.b.c' --tag '!!float' 3
|
|
||||||
// yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags
|
|
||||||
// yq w -i things.yaml 'a.b.c' cat
|
|
||||||
// yq w -i -s update_script.yaml things.yaml
|
|
||||||
// yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array
|
|
||||||
// yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file
|
|
||||||
// `,
|
|
||||||
// Long: `Updates the yaml file w.r.t the given path and value.
|
|
||||||
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
|
||||||
|
|
||||||
// Append value to array adds the value to the end of array.
|
|
||||||
|
|
||||||
// Update Scripts:
|
|
||||||
// Note that you can give an update script to perform more sophisticated update. Update script
|
|
||||||
// format is list of update commands (update or delete) like so:
|
|
||||||
// ---
|
|
||||||
// - command: update
|
|
||||||
// path: b.c
|
|
||||||
// value:
|
|
||||||
// #great
|
|
||||||
// things: frog # wow!
|
|
||||||
// - command: delete
|
|
||||||
// path: b.d
|
|
||||||
// `,
|
|
||||||
// RunE: writeProperty,
|
|
||||||
// }
|
|
||||||
// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
|
|
||||||
// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
|
||||||
// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
|
||||||
// return cmdWrite
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func writeProperty(cmd *cobra.Command, args []string) error {
|
|
||||||
// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
|
|
||||||
// if updateCommandsError != nil {
|
|
||||||
// return updateCommandsError
|
|
||||||
// }
|
|
||||||
// return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
|
||||||
// }
|
|
||||||
@@ -1,610 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
// import (
|
|
||||||
// "fmt"
|
|
||||||
// "runtime"
|
|
||||||
// "strings"
|
|
||||||
// "testing"
|
|
||||||
|
|
||||||
// "github.com/mikefarah/yq/v3/test"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// func TestWriteCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteKeepCommentsCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3 # comment
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7 # comment
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithTaggedStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: !!str cat
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: "cat"
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteUpdateStyleOnlyCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// d: things
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 'dog'
|
|
||||||
// d: 'things'
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteUpdateTagOnlyCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: true
|
|
||||||
// d: false
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: "true"
|
|
||||||
// d: "false"
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithSingleQuotedStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 'cat'
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithLiteralStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: |-
|
|
||||||
// cat
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteWithFoldedStyleCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: dog
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: >-
|
|
||||||
// cat
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteEmptyMultiDocCmd(t *testing.T) {
|
|
||||||
// content := `# this is empty
|
|
||||||
// ---
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `c: 7
|
|
||||||
|
|
||||||
// # this is empty
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) {
|
|
||||||
// content := `---
|
|
||||||
// # empty
|
|
||||||
// ---
|
|
||||||
// cat: frog
|
|
||||||
// ---
|
|
||||||
|
|
||||||
// # empty
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `
|
|
||||||
|
|
||||||
// # empty
|
|
||||||
// ---
|
|
||||||
// cat: frog
|
|
||||||
// c: 7
|
|
||||||
// ---
|
|
||||||
|
|
||||||
// # empty
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteFromFileCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// source := `kittens: are cute # sure are!`
|
|
||||||
// fromFilename := test.WriteTempYamlFile(source)
|
|
||||||
// defer test.RemoveTempYamlFile(fromFilename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c:
|
|
||||||
// kittens: are cute # sure are!
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteEmptyCmd(t *testing.T) {
|
|
||||||
// content := ``
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteAutoCreateCmd(t *testing.T) {
|
|
||||||
// content := `applications:
|
|
||||||
// - name: app
|
|
||||||
// env:`
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `applications:
|
|
||||||
// - name: app
|
|
||||||
// env:
|
|
||||||
// hello: world
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmdScript(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// updateScript := `- command: update
|
|
||||||
// path: b.c
|
|
||||||
// value: 7`
|
|
||||||
// scriptFilename := test.WriteTempYamlFile(updateScript)
|
|
||||||
// defer test.RemoveTempYamlFile(scriptFilename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmdEmptyScript(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// updateScript := ``
|
|
||||||
// scriptFilename := test.WriteTempYamlFile(updateScript)
|
|
||||||
// defer test.RemoveTempYamlFile(scriptFilename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteMultiCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// ---
|
|
||||||
// apples: great
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 3
|
|
||||||
// ---
|
|
||||||
// apples: ok
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
// func TestWriteInvalidDocumentIndexCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename))
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected command to fail due to invalid path")
|
|
||||||
// }
|
|
||||||
// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteBadDocumentIndexCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected command to fail due to invalid path")
|
|
||||||
// }
|
|
||||||
// expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
|
||||||
// }
|
|
||||||
// func TestWriteMultiAllCmd(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// ---
|
|
||||||
// apples: great
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 3
|
|
||||||
// apples: ok
|
|
||||||
// ---
|
|
||||||
// apples: ok`
|
|
||||||
// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_EmptyArray(t *testing.T) {
|
|
||||||
// content := `b: 3`
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b: 3
|
|
||||||
// a: []
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_Error(t *testing.T) {
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, "write")
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected command to fail due to missing arg")
|
|
||||||
// }
|
|
||||||
// expectedOutput := `Must provide <filename> <path_to_update> <value>`
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, "write fake-unknown a.b 3")
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected command to fail due to unknown file")
|
|
||||||
// }
|
|
||||||
// var expectedOutput string
|
|
||||||
// if runtime.GOOS == "windows" {
|
|
||||||
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
|
|
||||||
// } else {
|
|
||||||
// expectedOutput = `open fake-unknown: no such file or directory`
|
|
||||||
// }
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_Inplace(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// gotOutput := test.ReadTempYamlFile(filename)
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: 7`
|
|
||||||
// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_InplaceError(t *testing.T) {
|
|
||||||
// content := `b: cat
|
|
||||||
// c: 3
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
|
|
||||||
// if result.Error == nil {
|
|
||||||
// t.Error("Expected Error to occur!")
|
|
||||||
// }
|
|
||||||
// gotOutput := test.ReadTempYamlFile(filename)
|
|
||||||
// test.AssertResult(t, content, gotOutput)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_Append(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// - foo
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// - foo
|
|
||||||
// - 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_AppendInline(t *testing.T) {
|
|
||||||
// content := `b: [foo]`
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b: [foo, 7]
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_AppendInlinePretty(t *testing.T) {
|
|
||||||
// content := `b: [foo]`
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// - foo
|
|
||||||
// - 7
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_AppendEmptyArray(t *testing.T) {
|
|
||||||
// content := `a: 2
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `a: 2
|
|
||||||
// b:
|
|
||||||
// - v
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_SplatArray(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// - c: thing
|
|
||||||
// - c: another thing
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// - c: new
|
|
||||||
// - c: new
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_SplatMap(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: thing
|
|
||||||
// d: another thing
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: new
|
|
||||||
// d: new
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func TestWriteCmd_SplatMapEmpty(t *testing.T) {
|
|
||||||
// content := `b:
|
|
||||||
// c: thing
|
|
||||||
// d: another thing
|
|
||||||
// `
|
|
||||||
// filename := test.WriteTempYamlFile(content)
|
|
||||||
// defer test.RemoveTempYamlFile(filename)
|
|
||||||
|
|
||||||
// cmd := getRootCommand()
|
|
||||||
// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename))
|
|
||||||
// if result.Error != nil {
|
|
||||||
// t.Error(result.Error)
|
|
||||||
// }
|
|
||||||
// expectedOutput := `b:
|
|
||||||
// c: {}
|
|
||||||
// d: another thing
|
|
||||||
// `
|
|
||||||
// test.AssertResult(t, expectedOutput, result.Output)
|
|
||||||
// }
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import "gopkg.in/op/go-logging.v1"
|
|
||||||
|
|
||||||
var log = logging.MustGetLogger("yq-lib")
|
|
||||||
1
pkg/yqlib/doc/.gitignore
vendored
Normal file
1
pkg/yqlib/doc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.zip
|
||||||
@@ -2,6 +2,32 @@ Add behaves differently according to the type of the LHS:
|
|||||||
- arrays: concatenate
|
- arrays: concatenate
|
||||||
- number scalars: arithmetic addition (soon)
|
- number scalars: arithmetic addition (soon)
|
||||||
- string scalars: concatenate (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
|
## Concatenate arrays
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -5,6 +5,18 @@ Which will assign the LHS node values to the RHS node values. The RHS expression
|
|||||||
|
|
||||||
### relative form: `|=`
|
### 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.
|
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
|
||||||
|
## Create yaml file
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
yq eval --null-input '(.a.b = "cat") | (.x = "frog")'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
b: cat
|
||||||
|
x: frog
|
||||||
|
```
|
||||||
|
|
||||||
## Update node to be the child value
|
## Update node to be the child value
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -148,7 +160,7 @@ Given a sample.yml file of:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '.a.b[0] |= "bogs"' sample.yml
|
yq eval '.a.b.[0] |= "bogs"' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ 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).
|
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.
|
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||||
|
|
||||||
## Merging files
|
## Merging files
|
||||||
@@ -109,6 +111,38 @@ b:
|
|||||||
- 5
|
- 5
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Merge, appending arrays
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a:
|
||||||
|
array:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- animal: dog
|
||||||
|
value: coconut
|
||||||
|
b:
|
||||||
|
array:
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
- animal: cat
|
||||||
|
value: banana
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq eval '.a *+ .b' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
array:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- animal: dog
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
- animal: cat
|
||||||
|
value: banana
|
||||||
|
```
|
||||||
|
|
||||||
## Merge to prefix an element
|
## Merge to prefix an element
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
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.
|
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
|
## Map path
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -15,6 +18,21 @@ will output
|
|||||||
- b
|
- 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
|
## Array path
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```yaml
|
||||||
@@ -32,6 +50,22 @@ will output
|
|||||||
- 1
|
- 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
|
## Print path and value
|
||||||
Given a sample.yml file of:
|
Given a sample.yml file of:
|
||||||
```yaml
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ Given a sample.yml file of:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '[0]' sample.yml
|
yq eval '.[0]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -152,7 +152,7 @@ Given a sample.yml file of:
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '[2]' sample.yml
|
yq eval '.[2]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
@@ -166,7 +166,7 @@ a: b
|
|||||||
```
|
```
|
||||||
then
|
then
|
||||||
```bash
|
```bash
|
||||||
yq eval '[0]' sample.yml
|
yq eval '.[0]' sample.yml
|
||||||
```
|
```
|
||||||
will output
|
will output
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
0
pkg/yqlib/doc/aa.md
Normal file
0
pkg/yqlib/doc/aa.md
Normal file
@@ -1,4 +1,6 @@
|
|||||||
Add behaves differently according to the type of the LHS:
|
Add behaves differently according to the type of the LHS:
|
||||||
- arrays: concatenate
|
- arrays: concatenate
|
||||||
- number scalars: arithmetic addition (soon)
|
- number scalars: arithmetic addition (soon)
|
||||||
- string scalars: concatenate (soon)
|
- string scalars: concatenate (soon)
|
||||||
|
|
||||||
|
Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`.
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ 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).
|
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.
|
Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below.
|
||||||
|
|
||||||
## Merging files
|
## Merging files
|
||||||
|
|||||||
@@ -1 +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.
|
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
|
||||||
|
```
|
||||||
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"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("yq-lib")
|
||||||
|
|
||||||
type OperationType struct {
|
type OperationType struct {
|
||||||
Type string
|
Type string
|
||||||
NumArgs uint // number of arguments to the op
|
NumArgs uint // number of arguments to the op
|
||||||
@@ -17,12 +19,10 @@ type OperationType struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// operators TODO:
|
// operators TODO:
|
||||||
// - write in place
|
// - cookbook doc for common things
|
||||||
// - mergeAppend (merges and appends arrays)
|
|
||||||
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
// - mergeEmpty (sets only if the document is empty, do I do that now?)
|
||||||
// - compare ??
|
// - compare ??
|
||||||
// - validate ??
|
// - validate ??
|
||||||
// - exists
|
|
||||||
|
|
||||||
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}
|
||||||
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOperator}
|
||||||
@@ -30,6 +30,7 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp
|
|||||||
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator}
|
||||||
|
|
||||||
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
|
var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator}
|
||||||
|
var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator}
|
||||||
|
|
||||||
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator}
|
||||||
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
|
var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator}
|
||||||
@@ -54,6 +55,7 @@ var GetFileIndex = &OperationType{Type: "GET_FILE_INDEX", NumArgs: 0, Precedence
|
|||||||
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
|
var GetPath = &OperationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, Handler: GetPathOperator}
|
||||||
|
|
||||||
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
var Explode = &OperationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: ExplodeOperator}
|
||||||
|
var SortKeys = &OperationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: SortKeysOperator}
|
||||||
|
|
||||||
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
var CollectObject = &OperationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: CollectObjectOperator}
|
||||||
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
var TraversePath = &OperationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: TraversePathOperator}
|
||||||
@@ -70,9 +72,6 @@ var Select = &OperationType{Type: "SELECT", NumArgs: 1, Precedence: 50, Handler:
|
|||||||
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
var Has = &OperationType{Type: "HAS", NumArgs: 1, Precedence: 50, Handler: HasOperator}
|
||||||
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
var DeleteChild = &OperationType{Type: "DELETE", NumArgs: 1, Precedence: 40, Handler: DeleteChildOperator}
|
||||||
|
|
||||||
// var Exists = &OperationType{Type: "Length", NumArgs: 2, Precedence: 35}
|
|
||||||
// filters matches if they have the existing path
|
|
||||||
|
|
||||||
type Operation struct {
|
type Operation struct {
|
||||||
OperationType *OperationType
|
OperationType *OperationType
|
||||||
Value interface{}
|
Value interface{}
|
||||||
|
|||||||
@@ -8,6 +8,20 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
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 {
|
func toNodes(candidates *list.List) []*yaml.Node {
|
||||||
|
|
||||||
if candidates.Len() == 0 {
|
if candidates.Len() == 0 {
|
||||||
@@ -45,7 +59,7 @@ func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
|
|||||||
lhsCandidate := el.Value.(*CandidateNode)
|
lhsCandidate := el.Value.(*CandidateNode)
|
||||||
lhsNode := UnwrapDoc(lhsCandidate.Node)
|
lhsNode := UnwrapDoc(lhsCandidate.Node)
|
||||||
|
|
||||||
var newBlank = &CandidateNode{
|
target := &CandidateNode{
|
||||||
Path: lhsCandidate.Path,
|
Path: lhsCandidate.Path,
|
||||||
Document: lhsCandidate.Document,
|
Document: lhsCandidate.Document,
|
||||||
Filename: lhsCandidate.Filename,
|
Filename: lhsCandidate.Filename,
|
||||||
@@ -56,11 +70,11 @@ func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT
|
|||||||
case yaml.MappingNode:
|
case yaml.MappingNode:
|
||||||
return nil, fmt.Errorf("Maps not yet supported for addition")
|
return nil, fmt.Errorf("Maps not yet supported for addition")
|
||||||
case yaml.SequenceNode:
|
case yaml.SequenceNode:
|
||||||
newBlank.Node.Kind = yaml.SequenceNode
|
target.Node.Kind = yaml.SequenceNode
|
||||||
newBlank.Node.Style = lhsNode.Style
|
target.Node.Style = lhsNode.Style
|
||||||
newBlank.Node.Tag = "!!seq"
|
target.Node.Tag = "!!seq"
|
||||||
newBlank.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
target.Node.Content = append(lhsNode.Content, toNodes(rhs)...)
|
||||||
results.PushBack(newBlank)
|
results.PushBack(target)
|
||||||
case yaml.ScalarNode:
|
case yaml.ScalarNode:
|
||||||
return nil, fmt.Errorf("Scalars not yet supported for addition")
|
return nil, fmt.Errorf("Scalars not yet supported for addition")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var addOperatorScenarios = []expressionScenario{
|
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",
|
description: "Concatenate arrays",
|
||||||
document: `{a: [1,2], b: [3,4]}`,
|
document: `{a: [1,2], b: [3,4]}`,
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
|
|||||||
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
candidate.UpdateFrom(first.Value.(*CandidateNode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// // if there was nothing given, perhaps we are creating a new yaml doc
|
||||||
|
// if matchingNodes.Len() == 0 {
|
||||||
|
// log.Debug("started with nothing, returning LHS, %v", lhs.Len())
|
||||||
|
// return lhs, nil
|
||||||
|
// }
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var assignOperatorScenarios = []expressionScenario{
|
var assignOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Create yaml file",
|
||||||
|
expression: `(.a.b = "cat") | (.x = "frog")`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], ()::a:\n b: cat\nx: frog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Update node to be the child value",
|
description: "Update node to be the child value",
|
||||||
document: `{a: {b: {g: foof}}}`,
|
document: `{a: {b: {g: foof}}}`,
|
||||||
@@ -99,7 +106,7 @@ var assignOperatorScenarios = []expressionScenario{
|
|||||||
description: "Update empty object and array",
|
description: "Update empty object and array",
|
||||||
dontFormatInputForDoc: true,
|
dontFormatInputForDoc: true,
|
||||||
document: `{}`,
|
document: `{}`,
|
||||||
expression: `.a.b[0] |= "bogs"`,
|
expression: `.a.b.[0] |= "bogs"`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (doc)::{a: {b: [bogs]}}\n",
|
"D0, P[], (doc)::{a: {b: [bogs]}}\n",
|
||||||
},
|
},
|
||||||
@@ -107,7 +114,7 @@ var assignOperatorScenarios = []expressionScenario{
|
|||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: `{}`,
|
document: `{}`,
|
||||||
expression: `.a.b[1].c |= "bogs"`,
|
expression: `.a.b.[1].c |= "bogs"`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n",
|
"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
|
|||||||
|
|
||||||
newCandidate.Path = nil
|
newCandidate.Path = nil
|
||||||
|
|
||||||
newCandidate, err = multiply(d, newCandidate, splatCandidate)
|
newCandidate, err = multiply(&MultiplyPreferences{AppendArrays: false})(d, newCandidate, splatCandidate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ var collectOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (!!seq)::[]\n",
|
"D0, P[], (!!seq)::[]\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: ``,
|
||||||
|
expression: `[3]`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::- 3\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Collect single",
|
description: "Collect single",
|
||||||
document: ``,
|
document: ``,
|
||||||
|
|||||||
@@ -43,39 +43,49 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pat
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MultiplyPreferences struct {
|
||||||
|
AppendArrays bool
|
||||||
|
}
|
||||||
|
|
||||||
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func MultiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
log.Debugf("-- MultiplyOperator")
|
log.Debugf("-- MultiplyOperator")
|
||||||
return crossFunction(d, matchingNodes, pathNode, multiply)
|
return crossFunction(d, matchingNodes, pathNode, multiply(pathNode.Operation.Preferences.(*MultiplyPreferences)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func multiply(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func multiply(preferences *MultiplyPreferences) func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
lhs.Node = UnwrapDoc(lhs.Node)
|
return func(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||||
rhs.Node = UnwrapDoc(rhs.Node)
|
lhs.Node = UnwrapDoc(lhs.Node)
|
||||||
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
rhs.Node = UnwrapDoc(rhs.Node)
|
||||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
log.Debugf("Multipling LHS: %v", lhs.Node.Tag)
|
||||||
|
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||||
|
|
||||||
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
shouldAppendArrays := preferences.AppendArrays
|
||||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
|
||||||
|
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||||
|
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||||
|
|
||||||
|
var newBlank = &CandidateNode{
|
||||||
|
Path: lhs.Path,
|
||||||
|
Document: lhs.Document,
|
||||||
|
Filename: lhs.Filename,
|
||||||
|
Node: &yaml.Node{},
|
||||||
|
}
|
||||||
|
var newThing, err = mergeObjects(d, newBlank, lhs, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mergeObjects(d, newThing, rhs, shouldAppendArrays)
|
||||||
|
|
||||||
var newBlank = &CandidateNode{
|
|
||||||
Path: lhs.Path,
|
|
||||||
Document: lhs.Document,
|
|
||||||
Filename: lhs.Filename,
|
|
||||||
Node: &yaml.Node{},
|
|
||||||
}
|
}
|
||||||
var newThing, err = mergeObjects(d, newBlank, lhs)
|
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return mergeObjects(d, newThing, rhs)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) {
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
err := recursiveDecent(d, results, nodeToMap(rhs))
|
|
||||||
|
// shouldn't recurse arrays if appending
|
||||||
|
err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -86,7 +96,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for el := results.Front(); el != nil; el = el.Next() {
|
for el := results.Front(); el != nil; el = el.Next() {
|
||||||
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode))
|
err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -107,7 +117,8 @@ func createTraversalTree(path []interface{}) *PathTreeNode {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode) error {
|
func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error {
|
||||||
|
|
||||||
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs))
|
||||||
|
|
||||||
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
lhsPath := rhs.Path[pathIndexToStartFrom:]
|
||||||
@@ -116,6 +127,8 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
|
|||||||
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
|
||||||
assignmentOp.OperationType = Assign
|
assignmentOp.OperationType = Assign
|
||||||
assignmentOp.Preferences = &AssignOpPreferences{false}
|
assignmentOp.Preferences = &AssignOpPreferences{false}
|
||||||
|
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
|
||||||
|
assignmentOp.OperationType = AddAssign
|
||||||
}
|
}
|
||||||
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
|
rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,13 @@ var multiplyOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
"D0, P[], (!!map)::{a: {also: me}, b: {also: me}}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
expression: `{} * {"cat":"dog"}`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::cat: dog\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: `{a: {also: me}, b: {also: [1]}}`,
|
document: `{a: {also: me}, b: {also: [1]}}`,
|
||||||
@@ -92,6 +99,22 @@ b:
|
|||||||
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
|
"D0, P[], (!!map)::{a: [3, 4, 5], b: [3, 4, 5]}\n",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{a: [1], b: [2]}`,
|
||||||
|
expression: `.a *+ .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!seq)::[1, 2]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Merge, appending arrays",
|
||||||
|
document: `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`,
|
||||||
|
expression: `.a *+ .b`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "Merge to prefix an element",
|
description: "Merge to prefix an element",
|
||||||
document: `{a: cat, b: dog}`,
|
document: `{a: cat, b: dog}`,
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ var pathOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[a b], (!!seq)::- a\n- b\n",
|
"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",
|
description: "Array path",
|
||||||
document: `{a: [cat, dog]}`,
|
document: `{a: [cat, dog]}`,
|
||||||
@@ -21,6 +29,14 @@ var pathOperatorScenarios = []expressionScenario{
|
|||||||
"D0, P[a 1], (!!seq)::- a\n- 1\n",
|
"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",
|
description: "Print path and value",
|
||||||
document: `{a: [cat, dog, frog]}`,
|
document: `{a: [cat, dog, frog]}`,
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package yqlib
|
|||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
|
||||||
var results = list.New()
|
var results = list.New()
|
||||||
|
|
||||||
err := recursiveDecent(d, results, matchMap)
|
err := recursiveDecent(d, results, matchMap, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List) error {
|
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, recurseArray bool) error {
|
||||||
for el := matchMap.Front(); el != nil; el = el.Next() {
|
for el := matchMap.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
|
|
||||||
@@ -26,14 +26,15 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li
|
|||||||
log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
|
log.Debugf("Recursive Decent, added %v", NodeToString(candidate))
|
||||||
results.PushBack(candidate)
|
results.PushBack(candidate)
|
||||||
|
|
||||||
if candidate.Node.Kind != yaml.AliasNode {
|
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
|
||||||
|
(recurseArray || candidate.Node.Kind != yaml.SequenceNode) {
|
||||||
|
|
||||||
children, err := Splat(d, nodeToMap(candidate))
|
children, err := Splat(d, nodeToMap(candidate))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = recursiveDecent(d, results, children)
|
err = recursiveDecent(d, results, children, recurseArray)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var recursiveDescentOperatorScenarios = []expressionScenario{
|
var recursiveDescentOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `{}`,
|
||||||
|
expression: `..`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!map)::{}\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skipDoc: true,
|
||||||
|
document: `[]`,
|
||||||
|
expression: `..`,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (!!seq)::[]\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
skipDoc: true,
|
skipDoc: true,
|
||||||
document: `cat`,
|
document: `cat`,
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
@@ -159,7 +159,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
|||||||
{
|
{
|
||||||
description: "Traversing arrays by index",
|
description: "Traversing arrays by index",
|
||||||
document: `[1,2,3]`,
|
document: `[1,2,3]`,
|
||||||
expression: `[0]`,
|
expression: `.[0]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[0], (!!int)::1\n",
|
"D0, P[0], (!!int)::1\n",
|
||||||
},
|
},
|
||||||
@@ -167,7 +167,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
|||||||
{
|
{
|
||||||
description: "Maps with numeric keys",
|
description: "Maps with numeric keys",
|
||||||
document: `{2: cat}`,
|
document: `{2: cat}`,
|
||||||
expression: `[2]`,
|
expression: `.[2]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[2], (!!str)::cat\n",
|
"D0, P[2], (!!str)::cat\n",
|
||||||
},
|
},
|
||||||
@@ -175,7 +175,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
|||||||
{
|
{
|
||||||
description: "Maps with non existing numeric keys",
|
description: "Maps with non existing numeric keys",
|
||||||
document: `{a: b}`,
|
document: `{a: b}`,
|
||||||
expression: `[0]`,
|
expression: `.[0]`,
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"D0, P[0], (!!null)::null\n",
|
"D0, P[0], (!!null)::null\n",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v4/test"
|
"github.com/mikefarah/yq/v4/test"
|
||||||
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type expressionScenario struct {
|
type expressionScenario struct {
|
||||||
@@ -37,9 +38,18 @@ func testScenario(t *testing.T, s *expressionScenario) {
|
|||||||
if s.document != "" {
|
if s.document != "" {
|
||||||
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
|
inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err, s.document)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
candidateNode := &CandidateNode{
|
||||||
|
Document: 0,
|
||||||
|
Filename: "",
|
||||||
|
Node: &yaml.Node{Tag: "!!null"},
|
||||||
|
FileIndex: 0,
|
||||||
|
}
|
||||||
|
inputs.PushBack(candidateNode)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err = treeNavigator.GetMatchingNodes(inputs, node)
|
results, err = treeNavigator.GetMatchingNodes(inputs, node)
|
||||||
@@ -152,20 +162,20 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
|
|||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
var err error
|
var err error
|
||||||
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
|
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
|
||||||
|
streamEvaluator := NewStreamEvaluator()
|
||||||
|
|
||||||
if s.document != "" {
|
if s.document != "" {
|
||||||
node, err := treeCreator.ParsePath(s.expression)
|
node, err := treeCreator.ParsePath(s.expression)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
streamEvaluator := NewStreamEvaluator()
|
|
||||||
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
|
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
allAtOnceEvaluator := NewAllAtOnceEvaluator()
|
err = streamEvaluator.EvaluateNew(s.expression, printer)
|
||||||
err = allAtOnceEvaluator.EvaluateFiles(s.expression, []string{}, printer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ var pathTests = []struct {
|
|||||||
append(make([]interface{}, 0), "[", "]"),
|
append(make([]interface{}, 0), "[", "]"),
|
||||||
append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"),
|
append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`[3]`,
|
||||||
|
append(make([]interface{}, 0), "[", "3 (int64)", "]"),
|
||||||
|
append(make([]interface{}, 0), "3 (int64)", "COLLECT", "PIPE"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
`d0.a`,
|
`d0.a`,
|
||||||
append(make([]interface{}, 0), "d0", "PIPE", "a"),
|
append(make([]interface{}, 0), "d0", "PIPE", "a"),
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
lexer.Add([]byte(`,`), opToken(Union))
|
lexer.Add([]byte(`,`), opToken(Union))
|
||||||
lexer.Add([]byte(`:\s*`), opToken(CreateMap))
|
lexer.Add([]byte(`:\s*`), opToken(CreateMap))
|
||||||
lexer.Add([]byte(`length`), opToken(Length))
|
lexer.Add([]byte(`length`), opToken(Length))
|
||||||
|
lexer.Add([]byte(`sortKeys`), opToken(SortKeys))
|
||||||
lexer.Add([]byte(`select`), opToken(Select))
|
lexer.Add([]byte(`select`), opToken(Select))
|
||||||
lexer.Add([]byte(`has`), opToken(Has))
|
lexer.Add([]byte(`has`), opToken(Has))
|
||||||
lexer.Add([]byte(`explode`), opToken(Explode))
|
lexer.Add([]byte(`explode`), opToken(Explode))
|
||||||
@@ -223,7 +224,6 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
|
|
||||||
lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true}))
|
lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true}))
|
||||||
|
|
||||||
lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(false))
|
|
||||||
lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true))
|
lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true))
|
||||||
|
|
||||||
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
|
lexer.Add([]byte("( |\t|\n|\r)+"), skip)
|
||||||
@@ -251,8 +251,10 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
lexer.Add([]byte(`\]`), literalToken(CloseCollect, true))
|
lexer.Add([]byte(`\]`), literalToken(CloseCollect, true))
|
||||||
lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false))
|
lexer.Add([]byte(`\{`), literalToken(OpenCollectObject, false))
|
||||||
lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true))
|
lexer.Add([]byte(`\}`), literalToken(CloseCollectObject, true))
|
||||||
lexer.Add([]byte(`\*`), opToken(Multiply))
|
lexer.Add([]byte(`\*`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: false}))
|
||||||
|
lexer.Add([]byte(`\*\+`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: true}))
|
||||||
lexer.Add([]byte(`\+`), opToken(Add))
|
lexer.Add([]byte(`\+`), opToken(Add))
|
||||||
|
lexer.Add([]byte(`\+=`), opToken(AddAssign))
|
||||||
|
|
||||||
err := lexer.Compile()
|
err := lexer.Compile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import (
|
|||||||
"container/list"
|
"container/list"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Printer interface {
|
type Printer interface {
|
||||||
PrintResults(matchingNodes *list.List) error
|
PrintResults(matchingNodes *list.List) error
|
||||||
|
PrintedAnything() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type resultsPrinter struct {
|
type resultsPrinter struct {
|
||||||
@@ -21,6 +22,7 @@ type resultsPrinter struct {
|
|||||||
writer io.Writer
|
writer io.Writer
|
||||||
firstTimePrinting bool
|
firstTimePrinting bool
|
||||||
previousDocIndex uint
|
previousDocIndex uint
|
||||||
|
printedMatches bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer {
|
func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer {
|
||||||
@@ -35,7 +37,14 @@ func NewPrinter(writer io.Writer, outputToJSON bool, unwrapScalar bool, colorsEn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *resultsPrinter) PrintedAnything() bool {
|
||||||
|
return p.printedMatches
|
||||||
|
}
|
||||||
|
|
||||||
func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error {
|
func (p *resultsPrinter) printNode(node *yaml.Node, writer io.Writer) error {
|
||||||
|
p.printedMatches = p.printedMatches || (node.Tag != "!!null" &&
|
||||||
|
(node.Tag != "!!bool" || node.Value != "false"))
|
||||||
|
|
||||||
var encoder Encoder
|
var encoder Encoder
|
||||||
if node.Kind == yaml.ScalarNode && p.unwrapScalar && !p.outputToJSON {
|
if node.Kind == yaml.ScalarNode && p.unwrapScalar && !p.outputToJSON {
|
||||||
return p.writeString(writer, node.Value+"\n")
|
return p.writeString(writer, node.Value+"\n")
|
||||||
@@ -53,6 +62,13 @@ func (p *resultsPrinter) writeString(writer io.Writer, txt string) error {
|
|||||||
return errorWriting
|
return errorWriting
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *resultsPrinter) safelyFlush(writer *bufio.Writer) {
|
||||||
|
if err := writer.Flush(); err != nil {
|
||||||
|
log.Error("Error flushing writer!")
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
|
func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
|
||||||
log.Debug("PrintResults for %v matches", matchingNodes.Len())
|
log.Debug("PrintResults for %v matches", matchingNodes.Len())
|
||||||
var err error
|
var err error
|
||||||
@@ -66,7 +82,7 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bufferedWriter := bufio.NewWriter(p.writer)
|
bufferedWriter := bufio.NewWriter(p.writer)
|
||||||
defer safelyFlush(bufferedWriter)
|
defer p.safelyFlush(bufferedWriter)
|
||||||
|
|
||||||
if matchingNodes.Len() == 0 {
|
if matchingNodes.Len() == 0 {
|
||||||
log.Debug("no matching results, nothing to print")
|
log.Debug("no matching results, nothing to print")
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
type StreamEvaluator interface {
|
type StreamEvaluator interface {
|
||||||
Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error
|
Evaluate(filename string, reader io.Reader, node *PathTreeNode, printer Printer) error
|
||||||
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
EvaluateFiles(expression string, filenames []string, printer Printer) error
|
||||||
|
EvaluateNew(expression string, printer Printer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamEvaluator struct {
|
type streamEvaluator struct {
|
||||||
@@ -23,6 +24,27 @@ func NewStreamEvaluator() StreamEvaluator {
|
|||||||
return &streamEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
return &streamEvaluator{treeNavigator: NewDataTreeNavigator(), treeCreator: NewPathTreeCreator()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *streamEvaluator) EvaluateNew(expression string, printer Printer) error {
|
||||||
|
node, err := treeCreator.ParsePath(expression)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
candidateNode := &CandidateNode{
|
||||||
|
Document: 0,
|
||||||
|
Filename: "",
|
||||||
|
Node: &yaml.Node{Tag: "!!null"},
|
||||||
|
FileIndex: 0,
|
||||||
|
}
|
||||||
|
inputList := list.New()
|
||||||
|
inputList.PushBack(candidateNode)
|
||||||
|
|
||||||
|
matches, errorParsing := treeNavigator.GetMatchingNodes(inputList, node)
|
||||||
|
if errorParsing != nil {
|
||||||
|
return errorParsing
|
||||||
|
}
|
||||||
|
return printer.PrintResults(matches)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
func (s *streamEvaluator) EvaluateFiles(expression string, filenames []string, printer Printer) error {
|
||||||
|
|
||||||
node, err := treeCreator.ParsePath(expression)
|
node, err := treeCreator.ParsePath(expression)
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
//TODO: convert to interface + struct
|
|
||||||
|
|
||||||
var treeNavigator = NewDataTreeNavigator()
|
var treeNavigator = NewDataTreeNavigator()
|
||||||
var treeCreator = NewPathTreeCreator()
|
var treeCreator = NewPathTreeCreator()
|
||||||
|
|
||||||
@@ -52,54 +50,3 @@ func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List
|
|||||||
currentIndex = currentIndex + 1
|
currentIndex = currentIndex + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 safelyFlush(writer *bufio.Writer) {
|
|
||||||
if err := writer.Flush(); err != nil {
|
|
||||||
log.Error("Error flushing writer!")
|
|
||||||
log.Error(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
func safelyCloseFile(file *os.File) {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error closing file!")
|
|
||||||
log.Error(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
59
pkg/yqlib/write_in_place_handler.go
Normal file
59
pkg/yqlib/write_in_place_handler.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WriteInPlaceHandler interface {
|
||||||
|
CreateTempFile() (*os.File, error)
|
||||||
|
FinishWriteInPlace(evaluatedSuccessfully bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeInPlaceHandler struct {
|
||||||
|
inputFilename string
|
||||||
|
tempFile *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriteInPlaceHandler(inputFile string) WriteInPlaceHandler {
|
||||||
|
|
||||||
|
return &writeInPlaceHandler{inputFile, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writeInPlaceHandler) CreateTempFile() (*os.File, error) {
|
||||||
|
info, err := os.Stat(w.inputFilename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = os.Stat(os.TempDir())
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.Mkdir(os.TempDir(), 0700)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := ioutil.TempFile("", "temp")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(file.Name(), info.Mode())
|
||||||
|
log.Debug("writing to tempfile: %v", file.Name())
|
||||||
|
w.tempFile = file
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writeInPlaceHandler) FinishWriteInPlace(evaluatedSuccessfully bool) {
|
||||||
|
log.Debug("Going to write-inplace, evaluatedSuccessfully=%v, target=%v", evaluatedSuccessfully, w.inputFilename)
|
||||||
|
safelyCloseFile(w.tempFile)
|
||||||
|
if evaluatedSuccessfully {
|
||||||
|
log.Debug("moved temp file to target")
|
||||||
|
safelyRenameFile(w.tempFile.Name(), w.inputFilename)
|
||||||
|
} else {
|
||||||
|
log.Debug("removed temp file")
|
||||||
|
os.Remove(w.tempFile.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,62 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
# acceptance test
|
# acceptance test
|
||||||
X=$(./yq e '.b.c |= 3' ./examples/sample.yaml | ./yq e '.b.c' -)
|
|
||||||
|
|
||||||
if [[ $X != 3 ]]; then
|
|
||||||
echo "Failed acceptance test: expected 3 but was $X"
|
|
||||||
|
echo "test eval-sequence"
|
||||||
|
random=$((1 + $RANDOM % 10))
|
||||||
|
./yq e -n ".a = $random" > test.yml
|
||||||
|
X=$(./yq e '.a' test.yml)
|
||||||
|
|
||||||
|
if [[ $X != $random ]]; then
|
||||||
|
echo "Failed create: expected $random but was $X"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "--success"
|
||||||
|
|
||||||
|
echo "test update-in-place"
|
||||||
|
|
||||||
|
update=$(($random + 1))
|
||||||
|
./yq e -i ".a = $update" test.yml
|
||||||
|
|
||||||
|
X=$(./yq e '.a' test.yml)
|
||||||
|
if [[ $X != $update ]]; then
|
||||||
|
echo "Failed to update inplace test: expected $update but was $X"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "--success"
|
||||||
|
|
||||||
|
echo "test eval-all"
|
||||||
|
./yq ea -n ".a = $random" > test-eval-all.yml
|
||||||
|
Y=$(./yq ea '.a' test-eval-all.yml)
|
||||||
|
|
||||||
|
if [[ $Y != $random ]]; then
|
||||||
|
echo "Failed create with eval all: expected $random but was $X"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "--success"
|
||||||
|
|
||||||
|
echo "test no exit status"
|
||||||
|
./yq e '.z' test.yml
|
||||||
|
echo "--success"
|
||||||
|
|
||||||
|
echo "test exit status"
|
||||||
|
set +e
|
||||||
|
|
||||||
|
./yq e -e '.z' test.yml
|
||||||
|
|
||||||
|
if [[ $? != 1 ]]; then
|
||||||
|
echo "Expected error code 1 but was $?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "--success"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
rm test.yml
|
||||||
|
rm test-eval-all.yml
|
||||||
echo "acceptance tests passed"
|
echo "acceptance tests passed"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: yq
|
name: yq
|
||||||
version: '4.0.0-alpha2'
|
version: '4.0.0-beta1'
|
||||||
summary: A lightweight and portable command-line YAML processor
|
summary: A lightweight and portable command-line YAML processor
|
||||||
description: |
|
description: |
|
||||||
The aim of the project is to be the jq or sed of yaml files.
|
The aim of the project is to be the jq or sed of yaml files.
|
||||||
|
|||||||
Reference in New Issue
Block a user