mirror of
https://github.com/mikefarah/yq.git
synced 2025-01-27 00:47:56 +00:00
read command
This commit is contained in:
parent
6a0a4efa7b
commit
d19e9f6917
148
cmd/compare.go
148
cmd/compare.go
@ -1,89 +1,89 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"bufio"
|
// "bufio"
|
||||||
"bytes"
|
// "bytes"
|
||||||
"os"
|
// "os"
|
||||||
"strings"
|
// "strings"
|
||||||
|
|
||||||
"github.com/kylelemons/godebug/diff"
|
// "github.com/kylelemons/godebug/diff"
|
||||||
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
// "github.com/mikefarah/yq/v3/pkg/yqlib"
|
||||||
errors "github.com/pkg/errors"
|
// errors "github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
// "github.com/spf13/cobra"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// turn off for unit tests :(
|
// // turn off for unit tests :(
|
||||||
var forceOsExit = true
|
// var forceOsExit = true
|
||||||
|
|
||||||
func createCompareCmd() *cobra.Command {
|
// func createCompareCmd() *cobra.Command {
|
||||||
var cmdCompare = &cobra.Command{
|
// var cmdCompare = &cobra.Command{
|
||||||
Use: "compare [yaml_file_a] [yaml_file_b]",
|
// Use: "compare [yaml_file_a] [yaml_file_b]",
|
||||||
Aliases: []string{"x"},
|
// Aliases: []string{"x"},
|
||||||
Short: "yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'",
|
// Short: "yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'",
|
||||||
Example: `
|
// Example: `
|
||||||
yq x - data2.yml # reads from stdin
|
// yq x - data2.yml # reads from stdin
|
||||||
yq x -pp dataA.yaml dataB.yaml '**' # compare paths
|
// yq x -pp dataA.yaml dataB.yaml '**' # compare paths
|
||||||
yq x -d1 dataA.yaml dataB.yaml 'a.b.c'
|
// yq x -d1 dataA.yaml dataB.yaml 'a.b.c'
|
||||||
`,
|
// `,
|
||||||
Long: "Deeply compares two yaml files, prints the difference. Use with prettyPrint flag to ignore formatting differences.",
|
// Long: "Deeply compares two yaml files, prints the difference. Use with prettyPrint flag to ignore formatting differences.",
|
||||||
RunE: compareDocuments,
|
// RunE: compareDocuments,
|
||||||
}
|
// }
|
||||||
cmdCompare.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
// cmdCompare.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||||
cmdCompare.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
|
// cmdCompare.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
|
||||||
cmdCompare.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results")
|
// cmdCompare.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results")
|
||||||
cmdCompare.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "strip comments out before comparing")
|
// cmdCompare.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "strip comments out before comparing")
|
||||||
cmdCompare.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors")
|
// cmdCompare.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors")
|
||||||
return cmdCompare
|
// return cmdCompare
|
||||||
}
|
// }
|
||||||
|
|
||||||
func compareDocuments(cmd *cobra.Command, args []string) error {
|
// func compareDocuments(cmd *cobra.Command, args []string) error {
|
||||||
var path = ""
|
// var path = ""
|
||||||
|
|
||||||
if len(args) < 2 {
|
// if len(args) < 2 {
|
||||||
return errors.New("Must provide at 2 yaml files")
|
// return errors.New("Must provide at 2 yaml files")
|
||||||
} else if len(args) > 2 {
|
// } else if len(args) > 2 {
|
||||||
path = args[2]
|
// path = args[2]
|
||||||
}
|
// }
|
||||||
|
|
||||||
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
|
// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
|
||||||
if errorParsingDocIndex != nil {
|
// if errorParsingDocIndex != nil {
|
||||||
return errorParsingDocIndex
|
// return errorParsingDocIndex
|
||||||
}
|
// }
|
||||||
|
|
||||||
var matchingNodesA []*yqlib.NodeContext
|
// var matchingNodesA []*yqlib.NodeContext
|
||||||
var matchingNodesB []*yqlib.NodeContext
|
// var matchingNodesB []*yqlib.NodeContext
|
||||||
var errorDoingThings error
|
// var errorDoingThings error
|
||||||
|
|
||||||
matchingNodesA, errorDoingThings = readYamlFile(args[0], path, updateAll, docIndexInt)
|
// matchingNodesA, errorDoingThings = readYamlFile(args[0], path, updateAll, docIndexInt)
|
||||||
|
|
||||||
if errorDoingThings != nil {
|
// if errorDoingThings != nil {
|
||||||
return errorDoingThings
|
// return errorDoingThings
|
||||||
}
|
// }
|
||||||
|
|
||||||
matchingNodesB, errorDoingThings = readYamlFile(args[1], path, updateAll, docIndexInt)
|
// matchingNodesB, errorDoingThings = readYamlFile(args[1], path, updateAll, docIndexInt)
|
||||||
if errorDoingThings != nil {
|
// if errorDoingThings != nil {
|
||||||
return errorDoingThings
|
// return errorDoingThings
|
||||||
}
|
// }
|
||||||
|
|
||||||
var dataBufferA bytes.Buffer
|
// var dataBufferA bytes.Buffer
|
||||||
var dataBufferB bytes.Buffer
|
// var dataBufferB bytes.Buffer
|
||||||
errorDoingThings = printResults(matchingNodesA, bufio.NewWriter(&dataBufferA))
|
// errorDoingThings = printResults(matchingNodesA, bufio.NewWriter(&dataBufferA))
|
||||||
if errorDoingThings != nil {
|
// if errorDoingThings != nil {
|
||||||
return errorDoingThings
|
// return errorDoingThings
|
||||||
}
|
// }
|
||||||
errorDoingThings = printResults(matchingNodesB, bufio.NewWriter(&dataBufferB))
|
// errorDoingThings = printResults(matchingNodesB, bufio.NewWriter(&dataBufferB))
|
||||||
if errorDoingThings != nil {
|
// if errorDoingThings != nil {
|
||||||
return errorDoingThings
|
// return errorDoingThings
|
||||||
}
|
// }
|
||||||
|
|
||||||
diffString := diff.Diff(strings.TrimSuffix(dataBufferA.String(), "\n"), strings.TrimSuffix(dataBufferB.String(), "\n"))
|
// diffString := diff.Diff(strings.TrimSuffix(dataBufferA.String(), "\n"), strings.TrimSuffix(dataBufferB.String(), "\n"))
|
||||||
|
|
||||||
if len(diffString) > 1 {
|
// if len(diffString) > 1 {
|
||||||
cmd.Print(diffString)
|
// cmd.Print(diffString)
|
||||||
cmd.Print("\n")
|
// cmd.Print("\n")
|
||||||
if forceOsExit {
|
// if forceOsExit {
|
||||||
os.Exit(1)
|
// os.Exit(1)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
@ -1,115 +1,115 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"testing"
|
// "testing"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v3/test"
|
// "github.com/mikefarah/yq/v3/test"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func TestCompareSameCmd(t *testing.T) {
|
// func TestCompareSameCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1.yaml")
|
// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1.yaml")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := ``
|
// expectedOutput := ``
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestCompareIgnoreCommentsCmd(t *testing.T) {
|
// func TestCompareIgnoreCommentsCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml")
|
// result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := ``
|
// expectedOutput := ``
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestCompareDontIgnoreCommentsCmd(t *testing.T) {
|
// func TestCompareDontIgnoreCommentsCmd(t *testing.T) {
|
||||||
forceOsExit = false
|
// forceOsExit = false
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml")
|
// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml")
|
||||||
|
|
||||||
expectedOutput := `-a: simple # just the best
|
// expectedOutput := `-a: simple # just the best
|
||||||
+a: simple
|
// +a: simple
|
||||||
b: [1, 2]
|
// b: [1, 2]
|
||||||
c:
|
// c:
|
||||||
test: 1
|
// test: 1
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) {
|
// func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
|
// result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := ``
|
// expectedOutput := ``
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestCompareDontExplodeAnchorsCmd(t *testing.T) {
|
// func TestCompareDontExplodeAnchorsCmd(t *testing.T) {
|
||||||
forceOsExit = false
|
// forceOsExit = false
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
|
// result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
|
||||||
|
|
||||||
expectedOutput := `-foo: &foo
|
// expectedOutput := `-foo: &foo
|
||||||
+foo:
|
// +foo:
|
||||||
a: 1
|
// a: 1
|
||||||
foobar:
|
// foobar:
|
||||||
- !!merge <<: *foo
|
// - !!merge <<: *foo
|
||||||
+ a: 1
|
// + a: 1
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestCompareDifferentCmd(t *testing.T) {
|
// func TestCompareDifferentCmd(t *testing.T) {
|
||||||
forceOsExit = false
|
// forceOsExit = false
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data3.yaml")
|
// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data3.yaml")
|
||||||
|
|
||||||
expectedOutput := `-a: simple # just the best
|
// expectedOutput := `-a: simple # just the best
|
||||||
-b: [1, 2]
|
// -b: [1, 2]
|
||||||
+a: "simple" # just the best
|
// +a: "simple" # just the best
|
||||||
+b: [1, 3]
|
// +b: [1, 3]
|
||||||
c:
|
// c:
|
||||||
test: 1
|
// test: 1
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestComparePrettyCmd(t *testing.T) {
|
// func TestComparePrettyCmd(t *testing.T) {
|
||||||
forceOsExit = false
|
// forceOsExit = false
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "compare -P ../examples/data1.yaml ../examples/data3.yaml")
|
// result := test.RunCmd(cmd, "compare -P ../examples/data1.yaml ../examples/data3.yaml")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := ` a: simple # just the best
|
// expectedOutput := ` a: simple # just the best
|
||||||
b:
|
// b:
|
||||||
- 1
|
// - 1
|
||||||
- - 2
|
// - - 2
|
||||||
+ - 3
|
// + - 3
|
||||||
c:
|
// c:
|
||||||
test: 1
|
// test: 1
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestComparePathsCmd(t *testing.T) {
|
// func TestComparePathsCmd(t *testing.T) {
|
||||||
forceOsExit = false
|
// forceOsExit = false
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "compare -P -ppv ../examples/data1.yaml ../examples/data3.yaml **")
|
// result := test.RunCmd(cmd, "compare -P -ppv ../examples/data1.yaml ../examples/data3.yaml **")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := ` a: simple # just the best
|
// expectedOutput := ` a: simple # just the best
|
||||||
b.[0]: 1
|
// b.[0]: 1
|
||||||
-b.[1]: 2
|
// -b.[1]: 2
|
||||||
+b.[1]: 3
|
// +b.[1]: 3
|
||||||
c.test: 1
|
// c.test: 1
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
||||||
|
"github.com/mikefarah/yq/v3/pkg/yqlib/treeops"
|
||||||
logging "gopkg.in/op/go-logging.v1"
|
logging "gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,5 +33,5 @@ var verbose = false
|
|||||||
var version = false
|
var version = false
|
||||||
var docIndex = "0"
|
var docIndex = "0"
|
||||||
var log = logging.MustGetLogger("yq")
|
var log = logging.MustGetLogger("yq")
|
||||||
var lib = yqlib.NewYqLib()
|
var lib = treeops.NewYqTreeLib()
|
||||||
var valueParser = yqlib.NewValueParser()
|
var valueParser = yqlib.NewValueParser()
|
||||||
|
@ -1,41 +1,41 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
// "github.com/mikefarah/yq/v3/pkg/yqlib"
|
||||||
errors "github.com/pkg/errors"
|
// errors "github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
// "github.com/spf13/cobra"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func createDeleteCmd() *cobra.Command {
|
// func createDeleteCmd() *cobra.Command {
|
||||||
var cmdDelete = &cobra.Command{
|
// var cmdDelete = &cobra.Command{
|
||||||
Use: "delete [yaml_file] [path_expression]",
|
// Use: "delete [yaml_file] [path_expression]",
|
||||||
Aliases: []string{"d"},
|
// Aliases: []string{"d"},
|
||||||
Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'",
|
// Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'",
|
||||||
Example: `
|
// Example: `
|
||||||
yq delete things.yaml 'a.b.c'
|
// yq delete things.yaml 'a.b.c'
|
||||||
yq delete things.yaml 'a.*.c'
|
// yq delete things.yaml 'a.*.c'
|
||||||
yq delete things.yaml 'a.(child.subchild==co*).c'
|
// yq delete things.yaml 'a.(child.subchild==co*).c'
|
||||||
yq delete things.yaml 'a.**'
|
// yq delete things.yaml 'a.**'
|
||||||
yq delete --inplace things.yaml 'a.b.c'
|
// yq delete --inplace things.yaml 'a.b.c'
|
||||||
yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags
|
// yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags
|
||||||
yq d -i things.yaml 'a.b.c'
|
// yq d -i things.yaml 'a.b.c'
|
||||||
`,
|
// `,
|
||||||
Long: `Deletes the nodes matching the given path expression from the YAML file.
|
// Long: `Deletes the nodes matching the given path expression from the YAML file.
|
||||||
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
||||||
`,
|
// `,
|
||||||
RunE: deleteProperty,
|
// RunE: deleteProperty,
|
||||||
}
|
// }
|
||||||
cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
// cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
||||||
cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
// cmdDelete.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||||
return cmdDelete
|
// return cmdDelete
|
||||||
}
|
// }
|
||||||
|
|
||||||
func deleteProperty(cmd *cobra.Command, args []string) error {
|
// func deleteProperty(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 2 {
|
// if len(args) < 2 {
|
||||||
return errors.New("Must provide <filename> <path_to_delete>")
|
// return errors.New("Must provide <filename> <path_to_delete>")
|
||||||
}
|
// }
|
||||||
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1)
|
// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1)
|
||||||
updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]}
|
// updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]}
|
||||||
|
|
||||||
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
// return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
||||||
}
|
// }
|
||||||
|
@ -1,246 +1,246 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"strings"
|
// "strings"
|
||||||
"testing"
|
// "testing"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v3/test"
|
// "github.com/mikefarah/yq/v3/test"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func TestDeleteYamlCmd(t *testing.T) {
|
// func TestDeleteYamlCmd(t *testing.T) {
|
||||||
content := `a: 2
|
// content := `a: 2
|
||||||
b:
|
// b:
|
||||||
c: things
|
// c: things
|
||||||
d: something else
|
// d: something else
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `a: 2
|
// expectedOutput := `a: 2
|
||||||
b:
|
// b:
|
||||||
d: something else
|
// d: something else
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteDeepDoesNotExistCmd(t *testing.T) {
|
// func TestDeleteDeepDoesNotExistCmd(t *testing.T) {
|
||||||
content := `a: 2`
|
// content := `a: 2`
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `a: 2
|
// expectedOutput := `a: 2
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteSplatYaml(t *testing.T) {
|
// func TestDeleteSplatYaml(t *testing.T) {
|
||||||
content := `a: other
|
// content := `a: other
|
||||||
b: [3, 4]
|
// b: [3, 4]
|
||||||
c:
|
// c:
|
||||||
toast: leave
|
// toast: leave
|
||||||
test: 1
|
// test: 1
|
||||||
tell: 1
|
// tell: 1
|
||||||
tasty.taco: cool
|
// tasty.taco: cool
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `a: other
|
// expectedOutput := `a: other
|
||||||
b: [3, 4]
|
// b: [3, 4]
|
||||||
c:
|
// c:
|
||||||
toast: leave
|
// toast: leave
|
||||||
tasty.taco: cool
|
// tasty.taco: cool
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteSplatArrayYaml(t *testing.T) {
|
// func TestDeleteSplatArrayYaml(t *testing.T) {
|
||||||
content := `a: 2
|
// content := `a: 2
|
||||||
b:
|
// b:
|
||||||
hi:
|
// hi:
|
||||||
- thing: item1
|
// - thing: item1
|
||||||
name: fred
|
// name: fred
|
||||||
- thing: item2
|
// - thing: item2
|
||||||
name: sam
|
// name: sam
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `a: 2
|
// expectedOutput := `a: 2
|
||||||
b:
|
// b:
|
||||||
hi:
|
// hi:
|
||||||
- name: fred
|
// - name: fred
|
||||||
- name: sam
|
// - name: sam
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteDeepSplatArrayYaml(t *testing.T) {
|
// func TestDeleteDeepSplatArrayYaml(t *testing.T) {
|
||||||
content := `thing: 123
|
// content := `thing: 123
|
||||||
b:
|
// b:
|
||||||
hi:
|
// hi:
|
||||||
- thing: item1
|
// - thing: item1
|
||||||
name: fred
|
// name: fred
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
hi:
|
// hi:
|
||||||
- name: fred
|
// - name: fred
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteSplatPrefixYaml(t *testing.T) {
|
// func TestDeleteSplatPrefixYaml(t *testing.T) {
|
||||||
content := `a: 2
|
// content := `a: 2
|
||||||
b:
|
// b:
|
||||||
hi:
|
// hi:
|
||||||
c: things
|
// c: things
|
||||||
d: something else
|
// d: something else
|
||||||
there:
|
// there:
|
||||||
c: more things
|
// c: more things
|
||||||
d: more something else
|
// d: more something else
|
||||||
there2:
|
// there2:
|
||||||
c: more things also
|
// c: more things also
|
||||||
d: more something else also
|
// d: more something else also
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `a: 2
|
// expectedOutput := `a: 2
|
||||||
b:
|
// b:
|
||||||
hi:
|
// hi:
|
||||||
c: things
|
// c: things
|
||||||
d: something else
|
// d: something else
|
||||||
there:
|
// there:
|
||||||
d: more something else
|
// d: more something else
|
||||||
there2:
|
// there2:
|
||||||
d: more something else also
|
// d: more something else also
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteYamlArrayCmd(t *testing.T) {
|
// func TestDeleteYamlArrayCmd(t *testing.T) {
|
||||||
content := `- 1
|
// content := `- 1
|
||||||
- 2
|
// - 2
|
||||||
- 3
|
// - 3
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s [1]", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s [1]", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `- 1
|
// expectedOutput := `- 1
|
||||||
- 3
|
// - 3
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteYamlArrayExpressionCmd(t *testing.T) {
|
// func TestDeleteYamlArrayExpressionCmd(t *testing.T) {
|
||||||
content := `- name: fred
|
// content := `- name: fred
|
||||||
- name: cat
|
// - name: cat
|
||||||
- name: thing
|
// - name: thing
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s (name==cat)", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s (name==cat)", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `- name: fred
|
// expectedOutput := `- name: fred
|
||||||
- name: thing
|
// - name: thing
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteYamlMulti(t *testing.T) {
|
// func TestDeleteYamlMulti(t *testing.T) {
|
||||||
content := `apples: great
|
// content := `apples: great
|
||||||
---
|
// ---
|
||||||
- 1
|
// - 1
|
||||||
- 2
|
// - 2
|
||||||
- 3
|
// - 3
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
|
|
||||||
expectedOutput := `apples: great
|
// expectedOutput := `apples: great
|
||||||
---
|
// ---
|
||||||
- 1
|
// - 1
|
||||||
- 3
|
// - 3
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestDeleteYamlMultiAllCmd(t *testing.T) {
|
// func TestDeleteYamlMultiAllCmd(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
apples: great
|
// apples: great
|
||||||
---
|
// ---
|
||||||
apples: great
|
// apples: great
|
||||||
something: else
|
// something: else
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
---
|
// ---
|
||||||
something: else`
|
// something: else`
|
||||||
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
||||||
}
|
// }
|
||||||
|
222
cmd/merge.go
222
cmd/merge.go
@ -1,124 +1,124 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
// "github.com/mikefarah/yq/v3/pkg/yqlib"
|
||||||
errors "github.com/pkg/errors"
|
// errors "github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
// "github.com/spf13/cobra"
|
||||||
yaml "gopkg.in/yaml.v3"
|
// yaml "gopkg.in/yaml.v3"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func createMergeCmd() *cobra.Command {
|
// func createMergeCmd() *cobra.Command {
|
||||||
var cmdMerge = &cobra.Command{
|
// var cmdMerge = &cobra.Command{
|
||||||
Use: "merge [initial_yaml_file] [additional_yaml_file]...",
|
// Use: "merge [initial_yaml_file] [additional_yaml_file]...",
|
||||||
Aliases: []string{"m"},
|
// Aliases: []string{"m"},
|
||||||
Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml",
|
// Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml",
|
||||||
Example: `
|
// Example: `
|
||||||
yq merge things.yaml other.yaml
|
// yq merge things.yaml other.yaml
|
||||||
yq merge --inplace things.yaml other.yaml
|
// yq merge --inplace things.yaml other.yaml
|
||||||
yq m -i things.yaml other.yaml
|
// yq m -i things.yaml other.yaml
|
||||||
yq m --overwrite things.yaml other.yaml
|
// yq m --overwrite things.yaml other.yaml
|
||||||
yq m -i -x things.yaml other.yaml
|
// yq m -i -x things.yaml other.yaml
|
||||||
yq m -i -a=append things.yaml other.yaml
|
// yq m -i -a=append things.yaml other.yaml
|
||||||
yq m -i --autocreate=false things.yaml other.yaml
|
// yq m -i --autocreate=false things.yaml other.yaml
|
||||||
`,
|
// `,
|
||||||
Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
|
// Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
|
||||||
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
||||||
|
|
||||||
If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
|
// If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
|
||||||
If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
|
// If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
|
||||||
`,
|
// `,
|
||||||
RunE: mergeProperties,
|
// RunE: mergeProperties,
|
||||||
}
|
// }
|
||||||
cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
// cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
||||||
cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
|
// cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
|
||||||
cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries")
|
// cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries")
|
||||||
cmdMerge.PersistentFlags().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite)
|
// cmdMerge.PersistentFlags().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite)
|
||||||
update: recursively update arrays by their index
|
// update: recursively update arrays by their index
|
||||||
append: concatenate arrays together
|
// append: concatenate arrays together
|
||||||
overwrite: replace arrays
|
// overwrite: replace arrays
|
||||||
`)
|
// `)
|
||||||
cmdMerge.PersistentFlags().StringVarP(&commentsMergeStrategyFlag, "comments", "", "setWhenBlank", `comments merge strategy (setWhenBlank/ignore/append/overwrite)
|
// cmdMerge.PersistentFlags().StringVarP(&commentsMergeStrategyFlag, "comments", "", "setWhenBlank", `comments merge strategy (setWhenBlank/ignore/append/overwrite)
|
||||||
setWhenBlank: set comment if the original document has no comment at that node
|
// setWhenBlank: set comment if the original document has no comment at that node
|
||||||
ignore: leave comments as-is in the original
|
// ignore: leave comments as-is in the original
|
||||||
append: append comments together
|
// append: append comments together
|
||||||
overwrite: overwrite comments completely
|
// overwrite: overwrite comments completely
|
||||||
`)
|
// `)
|
||||||
cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
// cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||||
return cmdMerge
|
// return cmdMerge
|
||||||
}
|
// }
|
||||||
|
|
||||||
/*
|
// /*
|
||||||
* We don't deeply traverse arrays when appending a merge, instead we want to
|
// * We don't deeply traverse arrays when appending a merge, instead we want to
|
||||||
* append the entire array element.
|
// * append the entire array element.
|
||||||
*/
|
// */
|
||||||
func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) {
|
// func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) {
|
||||||
return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) {
|
// return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) {
|
||||||
return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy)
|
// return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func mergeProperties(cmd *cobra.Command, args []string) error {
|
// func mergeProperties(cmd *cobra.Command, args []string) error {
|
||||||
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
|
// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
|
||||||
|
|
||||||
if len(args) < 1 {
|
// if len(args) < 1 {
|
||||||
return errors.New("Must provide at least 1 yaml file")
|
// return errors.New("Must provide at least 1 yaml file")
|
||||||
}
|
// }
|
||||||
var arrayMergeStrategy yqlib.ArrayMergeStrategy
|
// var arrayMergeStrategy yqlib.ArrayMergeStrategy
|
||||||
|
|
||||||
switch arrayMergeStrategyFlag {
|
// switch arrayMergeStrategyFlag {
|
||||||
case "update":
|
// case "update":
|
||||||
arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy
|
// arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy
|
||||||
case "append":
|
// case "append":
|
||||||
arrayMergeStrategy = yqlib.AppendArrayMergeStrategy
|
// arrayMergeStrategy = yqlib.AppendArrayMergeStrategy
|
||||||
case "overwrite":
|
// case "overwrite":
|
||||||
arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy
|
// arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy
|
||||||
default:
|
// default:
|
||||||
return errors.New("Array merge strategy must be one of: update/append/overwrite")
|
// return errors.New("Array merge strategy must be one of: update/append/overwrite")
|
||||||
}
|
// }
|
||||||
|
|
||||||
var commentsMergeStrategy yqlib.CommentsMergeStrategy
|
// var commentsMergeStrategy yqlib.CommentsMergeStrategy
|
||||||
|
|
||||||
switch commentsMergeStrategyFlag {
|
// switch commentsMergeStrategyFlag {
|
||||||
case "setWhenBlank":
|
// case "setWhenBlank":
|
||||||
commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy
|
// commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy
|
||||||
case "ignore":
|
// case "ignore":
|
||||||
commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy
|
// commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy
|
||||||
case "append":
|
// case "append":
|
||||||
commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy
|
// commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy
|
||||||
case "overwrite":
|
// case "overwrite":
|
||||||
commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy
|
// commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy
|
||||||
default:
|
// default:
|
||||||
return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite")
|
// return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite")
|
||||||
}
|
// }
|
||||||
|
|
||||||
if len(args) > 1 {
|
// if len(args) > 1 {
|
||||||
// first generate update commands from the file
|
// // first generate update commands from the file
|
||||||
var filesToMerge = args[1:]
|
// var filesToMerge = args[1:]
|
||||||
|
|
||||||
for _, fileToMerge := range filesToMerge {
|
// for _, fileToMerge := range filesToMerge {
|
||||||
matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0)
|
// matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0)
|
||||||
if errorProcessingFile != nil {
|
// if errorProcessingFile != nil {
|
||||||
return errorProcessingFile
|
// return errorProcessingFile
|
||||||
}
|
// }
|
||||||
log.Debugf("finished reading for merge!")
|
// log.Debugf("finished reading for merge!")
|
||||||
for _, matchingNode := range matchingNodes {
|
// for _, matchingNode := range matchingNodes {
|
||||||
log.Debugf("matched node %v", lib.PathStackToString(matchingNode.PathStack))
|
// log.Debugf("matched node %v", lib.PathStackToString(matchingNode.PathStack))
|
||||||
yqlib.DebugNode(matchingNode.Node)
|
// yqlib.DebugNode(matchingNode.Node)
|
||||||
}
|
// }
|
||||||
for _, matchingNode := range matchingNodes {
|
// for _, matchingNode := range matchingNodes {
|
||||||
mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy)
|
// mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy)
|
||||||
updateCommands = append(updateCommands, yqlib.UpdateCommand{
|
// updateCommands = append(updateCommands, yqlib.UpdateCommand{
|
||||||
Command: "merge",
|
// Command: "merge",
|
||||||
Path: mergePath,
|
// Path: mergePath,
|
||||||
Value: matchingNode.Node,
|
// Value: matchingNode.Node,
|
||||||
Overwrite: overwriteFlag,
|
// Overwrite: overwriteFlag,
|
||||||
CommentsMergeStrategy: commentsMergeStrategy,
|
// CommentsMergeStrategy: commentsMergeStrategy,
|
||||||
// dont update the content for nodes midway, only leaf nodes
|
// // dont update the content for nodes midway, only leaf nodes
|
||||||
DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode),
|
// DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
// return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
||||||
}
|
// }
|
||||||
|
File diff suppressed because it is too large
Load Diff
92
cmd/new.go
92
cmd/new.go
@ -1,55 +1,55 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
// "github.com/mikefarah/yq/v3/pkg/yqlib"
|
||||||
"github.com/spf13/cobra"
|
// "github.com/spf13/cobra"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func createNewCmd() *cobra.Command {
|
// func createNewCmd() *cobra.Command {
|
||||||
var cmdNew = &cobra.Command{
|
// var cmdNew = &cobra.Command{
|
||||||
Use: "new [path] [value]",
|
// Use: "new [path] [value]",
|
||||||
Aliases: []string{"n"},
|
// Aliases: []string{"n"},
|
||||||
Short: "yq n [--script/-s script_file] a.b.c newValue",
|
// Short: "yq n [--script/-s script_file] a.b.c newValue",
|
||||||
Example: `
|
// Example: `
|
||||||
yq new 'a.b.c' cat
|
// yq new 'a.b.c' cat
|
||||||
yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
|
// yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
|
||||||
yq n 'a.b[+]' cat
|
// yq n 'a.b[+]' cat
|
||||||
yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags
|
// yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags
|
||||||
yq n --script create_script.yaml
|
// yq n --script create_script.yaml
|
||||||
`,
|
// `,
|
||||||
Long: `Creates a new yaml w.r.t the given path and value.
|
// Long: `Creates a new yaml w.r.t the given path and value.
|
||||||
Outputs to STDOUT
|
// Outputs to STDOUT
|
||||||
|
|
||||||
Create Scripts:
|
// Create Scripts:
|
||||||
Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
|
// Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
|
||||||
`,
|
// `,
|
||||||
RunE: newProperty,
|
// RunE: newProperty,
|
||||||
}
|
// }
|
||||||
cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml")
|
// cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml")
|
||||||
cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
|
// cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
|
||||||
cmdNew.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
|
// cmdNew.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
|
||||||
cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
// cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
||||||
cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
// cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
||||||
return cmdNew
|
// return cmdNew
|
||||||
}
|
// }
|
||||||
|
|
||||||
func newProperty(cmd *cobra.Command, args []string) error {
|
// func newProperty(cmd *cobra.Command, args []string) error {
|
||||||
var badArgsMessage = "Must provide <path_to_update> <value>"
|
// var badArgsMessage = "Must provide <path_to_update> <value>"
|
||||||
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, badArgsMessage, false)
|
// var updateCommands, updateCommandsError = readUpdateCommands(args, 2, badArgsMessage, false)
|
||||||
if updateCommandsError != nil {
|
// if updateCommandsError != nil {
|
||||||
return updateCommandsError
|
// return updateCommandsError
|
||||||
}
|
// }
|
||||||
newNode := lib.New(updateCommands[0].Path)
|
// newNode := lib.New(updateCommands[0].Path)
|
||||||
|
|
||||||
for _, updateCommand := range updateCommands {
|
// for _, updateCommand := range updateCommands {
|
||||||
|
|
||||||
errorUpdating := lib.Update(&newNode, updateCommand, true)
|
// errorUpdating := lib.Update(&newNode, updateCommand, true)
|
||||||
|
|
||||||
if errorUpdating != nil {
|
// if errorUpdating != nil {
|
||||||
return errorUpdating
|
// return errorUpdating
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
var encoder = yqlib.NewYamlEncoder(cmd.OutOrStdout(), indent, colorsEnabled)
|
// var encoder = yqlib.NewYamlEncoder(cmd.OutOrStdout(), indent, colorsEnabled)
|
||||||
return encoder.Encode(&newNode)
|
// return encoder.Encode(&newNode)
|
||||||
}
|
// }
|
||||||
|
214
cmd/new_test.go
214
cmd/new_test.go
@ -1,120 +1,120 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"testing"
|
// "testing"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v3/test"
|
// "github.com/mikefarah/yq/v3/test"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func TestNewCmd(t *testing.T) {
|
// func TestNewCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "new b.c 3")
|
// result := test.RunCmd(cmd, "new b.c 3")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestNewCmdScript(t *testing.T) {
|
// func TestNewCmdScript(t *testing.T) {
|
||||||
updateScript := `- command: update
|
// updateScript := `- command: update
|
||||||
path: b.c
|
// path: b.c
|
||||||
value: 7`
|
// value: 7`
|
||||||
scriptFilename := test.WriteTempYamlFile(updateScript)
|
// scriptFilename := test.WriteTempYamlFile(updateScript)
|
||||||
defer test.RemoveTempYamlFile(scriptFilename)
|
// defer test.RemoveTempYamlFile(scriptFilename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("new --script %s", scriptFilename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("new --script %s", scriptFilename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: 7
|
// c: 7
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestNewAnchorCmd(t *testing.T) {
|
// func TestNewAnchorCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred")
|
// result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: &fred 3
|
// c: &fred 3
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestNewAliasCmd(t *testing.T) {
|
// func TestNewAliasCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "new b.c foo --makeAlias")
|
// result := test.RunCmd(cmd, "new b.c foo --makeAlias")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: *foo
|
// c: *foo
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestNewArrayCmd(t *testing.T) {
|
// func TestNewArrayCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "new b[0] 3")
|
// result := test.RunCmd(cmd, "new b[0] 3")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
- 3
|
// - 3
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestNewCmd_Error(t *testing.T) {
|
// func TestNewCmd_Error(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "new b.c")
|
// result := test.RunCmd(cmd, "new b.c")
|
||||||
if result.Error == nil {
|
// if result.Error == nil {
|
||||||
t.Error("Expected command to fail due to missing arg")
|
// t.Error("Expected command to fail due to missing arg")
|
||||||
}
|
// }
|
||||||
expectedOutput := `Must provide <path_to_update> <value>`
|
// expectedOutput := `Must provide <path_to_update> <value>`
|
||||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestNewWithTaggedStyleCmd(t *testing.T) {
|
// func TestNewWithTaggedStyleCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "new b.c cat --tag=!!str --style=tagged")
|
// result := test.RunCmd(cmd, "new b.c cat --tag=!!str --style=tagged")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: !!str cat
|
// c: !!str cat
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestNewWithDoubleQuotedStyleCmd(t *testing.T) {
|
// func TestNewWithDoubleQuotedStyleCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "new b.c cat --style=double")
|
// result := test.RunCmd(cmd, "new b.c cat --style=double")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: "cat"
|
// c: "cat"
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestNewWithSingleQuotedStyleCmd(t *testing.T) {
|
// func TestNewWithSingleQuotedStyleCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "new b.c cat --style=single")
|
// result := test.RunCmd(cmd, "new b.c cat --style=single")
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: 'cat'
|
// c: 'cat'
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
@ -1,50 +1,50 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
// "github.com/mikefarah/yq/v3/pkg/yqlib"
|
||||||
errors "github.com/pkg/errors"
|
// errors "github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
// "github.com/spf13/cobra"
|
||||||
yaml "gopkg.in/yaml.v3"
|
// yaml "gopkg.in/yaml.v3"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func createPrefixCmd() *cobra.Command {
|
// func createPrefixCmd() *cobra.Command {
|
||||||
var cmdPrefix = &cobra.Command{
|
// var cmdPrefix = &cobra.Command{
|
||||||
Use: "prefix [yaml_file] [path]",
|
// Use: "prefix [yaml_file] [path]",
|
||||||
Aliases: []string{"p"},
|
// Aliases: []string{"p"},
|
||||||
Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
|
// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
|
||||||
Example: `
|
// Example: `
|
||||||
yq prefix things.yaml 'a.b.c'
|
// yq prefix things.yaml 'a.b.c'
|
||||||
yq prefix --inplace things.yaml 'a.b.c'
|
// yq prefix --inplace things.yaml 'a.b.c'
|
||||||
yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags
|
// yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags
|
||||||
yq p -i things.yaml 'a.b.c'
|
// yq p -i things.yaml 'a.b.c'
|
||||||
yq p --doc 2 things.yaml 'a.b.d'
|
// yq p --doc 2 things.yaml 'a.b.d'
|
||||||
yq p -d2 things.yaml 'a.b.d'
|
// yq p -d2 things.yaml 'a.b.d'
|
||||||
`,
|
// `,
|
||||||
Long: `Prefixes w.r.t to the yaml file at the given path.
|
// Long: `Prefixes w.r.t to the yaml file at the given path.
|
||||||
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
||||||
`,
|
// `,
|
||||||
RunE: prefixProperty,
|
// RunE: prefixProperty,
|
||||||
}
|
// }
|
||||||
cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
// cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
||||||
cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
// cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||||
return cmdPrefix
|
// return cmdPrefix
|
||||||
}
|
// }
|
||||||
|
|
||||||
func prefixProperty(cmd *cobra.Command, args []string) error {
|
// func prefixProperty(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
if len(args) < 2 {
|
// if len(args) < 2 {
|
||||||
return errors.New("Must provide <filename> <prefixed_path>")
|
// return errors.New("Must provide <filename> <prefixed_path>")
|
||||||
}
|
// }
|
||||||
updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]}
|
// updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]}
|
||||||
log.Debugf("args %v", args)
|
// log.Debugf("args %v", args)
|
||||||
|
|
||||||
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
|
// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
|
||||||
if errorParsingDocIndex != nil {
|
// if errorParsingDocIndex != nil {
|
||||||
return errorParsingDocIndex
|
// return errorParsingDocIndex
|
||||||
}
|
// }
|
||||||
|
|
||||||
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
|
// var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
|
||||||
return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand)
|
// return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand)
|
||||||
}
|
// }
|
||||||
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
|
// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
|
||||||
}
|
// }
|
||||||
|
@ -1,189 +1,189 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"runtime"
|
// "runtime"
|
||||||
"strings"
|
// "strings"
|
||||||
"testing"
|
// "testing"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v3/test"
|
// "github.com/mikefarah/yq/v3/test"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func TestPrefixCmd(t *testing.T) {
|
// func TestPrefixCmd(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `d:
|
// expectedOutput := `d:
|
||||||
b:
|
// b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestPrefixCmdArray(t *testing.T) {
|
// func TestPrefixCmdArray(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s [+].d.[+]", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s [+].d.[+]", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `- d:
|
// expectedOutput := `- d:
|
||||||
- b:
|
// - b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestPrefixCmd_MultiLayer(t *testing.T) {
|
// func TestPrefixCmd_MultiLayer(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `d:
|
// expectedOutput := `d:
|
||||||
e:
|
// e:
|
||||||
f:
|
// f:
|
||||||
b:
|
// b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestPrefixMultiCmd(t *testing.T) {
|
// func TestPrefixMultiCmd(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
---
|
// ---
|
||||||
apples: great
|
// apples: great
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `b:
|
// expectedOutput := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
---
|
// ---
|
||||||
d:
|
// d:
|
||||||
apples: great
|
// apples: great
|
||||||
`
|
// `
|
||||||
test.AssertResult(t, expectedOutput, result.Output)
|
// test.AssertResult(t, expectedOutput, result.Output)
|
||||||
}
|
// }
|
||||||
func TestPrefixInvalidDocumentIndexCmd(t *testing.T) {
|
// func TestPrefixInvalidDocumentIndexCmd(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -df d", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -df d", filename))
|
||||||
if result.Error == nil {
|
// if result.Error == nil {
|
||||||
t.Error("Expected command to fail due to invalid path")
|
// 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`
|
// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
|
||||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestPrefixBadDocumentIndexCmd(t *testing.T) {
|
// func TestPrefixBadDocumentIndexCmd(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
|
||||||
if result.Error == nil {
|
// if result.Error == nil {
|
||||||
t.Error("Expected command to fail due to invalid path")
|
// t.Error("Expected command to fail due to invalid path")
|
||||||
}
|
// }
|
||||||
expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
|
// expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
|
||||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||||
}
|
// }
|
||||||
func TestPrefixMultiAllCmd(t *testing.T) {
|
// func TestPrefixMultiAllCmd(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
---
|
// ---
|
||||||
apples: great
|
// apples: great
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
expectedOutput := `d:
|
// expectedOutput := `d:
|
||||||
b:
|
// b:
|
||||||
c: 3
|
// c: 3
|
||||||
---
|
// ---
|
||||||
d:
|
// d:
|
||||||
apples: great`
|
// apples: great`
|
||||||
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestPrefixCmd_Error(t *testing.T) {
|
// func TestPrefixCmd_Error(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "prefix")
|
// result := test.RunCmd(cmd, "prefix")
|
||||||
if result.Error == nil {
|
// if result.Error == nil {
|
||||||
t.Error("Expected command to fail due to missing arg")
|
// t.Error("Expected command to fail due to missing arg")
|
||||||
}
|
// }
|
||||||
expectedOutput := `Must provide <filename> <prefixed_path>`
|
// expectedOutput := `Must provide <filename> <prefixed_path>`
|
||||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) {
|
// func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "prefix fake-unknown a.b")
|
// result := test.RunCmd(cmd, "prefix fake-unknown a.b")
|
||||||
if result.Error == nil {
|
// if result.Error == nil {
|
||||||
t.Error("Expected command to fail due to unknown file")
|
// t.Error("Expected command to fail due to unknown file")
|
||||||
}
|
// }
|
||||||
var expectedOutput string
|
// var expectedOutput string
|
||||||
if runtime.GOOS == "windows" {
|
// if runtime.GOOS == "windows" {
|
||||||
expectedOutput = `open fake-unknown: The system cannot find the file specified.`
|
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
|
||||||
} else {
|
// } else {
|
||||||
expectedOutput = `open fake-unknown: no such file or directory`
|
// expectedOutput = `open fake-unknown: no such file or directory`
|
||||||
}
|
// }
|
||||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
// test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||||
}
|
// }
|
||||||
|
|
||||||
func TestPrefixCmd_Inplace(t *testing.T) {
|
// func TestPrefixCmd_Inplace(t *testing.T) {
|
||||||
content := `b:
|
// content := `b:
|
||||||
c: 3
|
// c: 3
|
||||||
`
|
// `
|
||||||
filename := test.WriteTempYamlFile(content)
|
// filename := test.WriteTempYamlFile(content)
|
||||||
defer test.RemoveTempYamlFile(filename)
|
// defer test.RemoveTempYamlFile(filename)
|
||||||
|
|
||||||
cmd := getRootCommand()
|
// cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix -i %s d", filename))
|
// result := test.RunCmd(cmd, fmt.Sprintf("prefix -i %s d", filename))
|
||||||
if result.Error != nil {
|
// if result.Error != nil {
|
||||||
t.Error(result.Error)
|
// t.Error(result.Error)
|
||||||
}
|
// }
|
||||||
gotOutput := test.ReadTempYamlFile(filename)
|
// gotOutput := test.ReadTempYamlFile(filename)
|
||||||
expectedOutput := `d:
|
// expectedOutput := `d:
|
||||||
b:
|
// b:
|
||||||
c: 3`
|
// c: 3`
|
||||||
test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
|
// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
|
||||||
}
|
// }
|
||||||
|
@ -26,7 +26,6 @@ yq r -- things.yaml '--key-starting-with-dashes.blah'
|
|||||||
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||||
cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
|
cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
|
||||||
cmdRead.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results")
|
cmdRead.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results")
|
||||||
cmdRead.PersistentFlags().BoolVarP(&printLength, "length", "l", false, "print length of results")
|
|
||||||
cmdRead.PersistentFlags().BoolVarP(&collectIntoArray, "collect", "c", false, "collect results into array")
|
cmdRead.PersistentFlags().BoolVarP(&collectIntoArray, "collect", "c", false, "collect results into array")
|
||||||
cmdRead.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
|
cmdRead.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
|
||||||
cmdRead.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "print yaml without any comments")
|
cmdRead.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "print yaml without any comments")
|
||||||
|
@ -127,7 +127,7 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) {
|
|||||||
|
|
||||||
func TestReadWithAdvancedFilterMapCmd(t *testing.T) {
|
func TestReadWithAdvancedFilterMapCmd(t *testing.T) {
|
||||||
cmd := getRootCommand()
|
cmd := getRootCommand()
|
||||||
result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e[name`==`fr*]")
|
result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e(name==fr*)")
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
t.Error(result.Error)
|
t.Error(result.Error)
|
||||||
}
|
}
|
||||||
|
12
cmd/root.go
12
cmd/root.go
@ -48,13 +48,13 @@ func New() *cobra.Command {
|
|||||||
|
|
||||||
rootCmd.AddCommand(
|
rootCmd.AddCommand(
|
||||||
createReadCmd(),
|
createReadCmd(),
|
||||||
createCompareCmd(),
|
// createCompareCmd(),
|
||||||
createValidateCmd(),
|
createValidateCmd(),
|
||||||
createWriteCmd(),
|
// createWriteCmd(),
|
||||||
createPrefixCmd(),
|
// createPrefixCmd(),
|
||||||
createDeleteCmd(),
|
// createDeleteCmd(),
|
||||||
createNewCmd(),
|
// createNewCmd(),
|
||||||
createMergeCmd(),
|
// createMergeCmd(),
|
||||||
createBashCompletionCmd(rootCmd),
|
createBashCompletionCmd(rootCmd),
|
||||||
)
|
)
|
||||||
return rootCmd
|
return rootCmd
|
||||||
|
296
cmd/utils.go
296
cmd/utils.go
@ -4,29 +4,29 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
"github.com/mikefarah/yq/v3/pkg/yqlib"
|
||||||
|
"github.com/mikefarah/yq/v3/pkg/yqlib/treeops"
|
||||||
errors "github.com/pkg/errors"
|
errors "github.com/pkg/errors"
|
||||||
yaml "gopkg.in/yaml.v3"
|
yaml "gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type readDataFn func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error)
|
type readDataFn func(document int, dataBucket *yaml.Node) ([]*treeops.CandidateNode, error)
|
||||||
|
|
||||||
func createReadFunction(path string) func(*yaml.Node) ([]*yqlib.NodeContext, error) {
|
func createReadFunction(path string) func(int, *yaml.Node) ([]*treeops.CandidateNode, error) {
|
||||||
return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) {
|
return func(document int, dataBucket *yaml.Node) ([]*treeops.CandidateNode, error) {
|
||||||
return lib.Get(dataBucket, path)
|
return lib.Get(document, dataBucket, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) {
|
func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*treeops.CandidateNode, error) {
|
||||||
return doReadYamlFile(filename, createReadFunction(path), updateAll, docIndexInt)
|
return doReadYamlFile(filename, createReadFunction(path), updateAll, docIndexInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doReadYamlFile(filename string, readFn readDataFn, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) {
|
func doReadYamlFile(filename string, readFn readDataFn, updateAll bool, docIndexInt int) ([]*treeops.CandidateNode, error) {
|
||||||
var matchingNodes []*yqlib.NodeContext
|
var matchingNodes []*treeops.CandidateNode
|
||||||
|
|
||||||
var currentIndex = 0
|
var currentIndex = 0
|
||||||
var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error {
|
var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error {
|
||||||
@ -63,14 +63,14 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, readFn readDataFn, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) {
|
func appendDocument(originalMatchingNodes []*treeops.CandidateNode, dataBucket yaml.Node, readFn readDataFn, updateAll bool, docIndexInt int, currentIndex int) ([]*treeops.CandidateNode, error) {
|
||||||
log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
|
log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
|
||||||
yqlib.DebugNode(&dataBucket)
|
// yqlib.DebugNode(&dataBucket)
|
||||||
if !updateAll && currentIndex != docIndexInt {
|
if !updateAll && currentIndex != docIndexInt {
|
||||||
return originalMatchingNodes, nil
|
return originalMatchingNodes, nil
|
||||||
}
|
}
|
||||||
log.Debugf("reading in document %v", currentIndex)
|
log.Debugf("reading in document %v", currentIndex)
|
||||||
matchingNodes, errorParsing := readFn(&dataBucket)
|
matchingNodes, errorParsing := readFn(currentIndex, &dataBucket)
|
||||||
if errorParsing != nil {
|
if errorParsing != nil {
|
||||||
return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
|
return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func printNode(node *yaml.Node, writer io.Writer) error {
|
|||||||
return encoder.Encode(node)
|
return encoder.Encode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeComments(matchingNodes []*yqlib.NodeContext) {
|
func removeComments(matchingNodes []*treeops.CandidateNode) {
|
||||||
for _, nodeContext := range matchingNodes {
|
for _, nodeContext := range matchingNodes {
|
||||||
removeCommentOfNode(nodeContext.Node)
|
removeCommentOfNode(nodeContext.Node)
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ func removeCommentOfNode(node *yaml.Node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setStyle(matchingNodes []*yqlib.NodeContext, style yaml.Style) {
|
func setStyle(matchingNodes []*treeops.CandidateNode, style yaml.Style) {
|
||||||
for _, nodeContext := range matchingNodes {
|
for _, nodeContext := range matchingNodes {
|
||||||
updateStyleOfNode(nodeContext.Node, style)
|
updateStyleOfNode(nodeContext.Node, style)
|
||||||
}
|
}
|
||||||
@ -234,10 +234,10 @@ func explodeNode(node *yaml.Node) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func explode(matchingNodes []*yqlib.NodeContext) error {
|
func explode(matchingNodes []*treeops.CandidateNode) error {
|
||||||
log.Debug("exploding nodes")
|
log.Debug("exploding nodes")
|
||||||
for _, nodeContext := range matchingNodes {
|
for _, nodeContext := range matchingNodes {
|
||||||
log.Debugf("exploding %v", nodeContext.Head)
|
log.Debugf("exploding %v", nodeContext.GetKey())
|
||||||
errorExplodingNode := explodeNode(nodeContext.Node)
|
errorExplodingNode := explodeNode(nodeContext.Node)
|
||||||
if errorExplodingNode != nil {
|
if errorExplodingNode != nil {
|
||||||
return errorExplodingNode
|
return errorExplodingNode
|
||||||
@ -246,7 +246,7 @@ func explode(matchingNodes []*yqlib.NodeContext) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error {
|
func printResults(matchingNodes []*treeops.CandidateNode, writer io.Writer) error {
|
||||||
if prettyPrint {
|
if prettyPrint {
|
||||||
setStyle(matchingNodes, 0)
|
setStyle(matchingNodes, 0)
|
||||||
}
|
}
|
||||||
@ -280,7 +280,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error {
|
|||||||
for _, mappedDoc := range matchingNodes {
|
for _, mappedDoc := range matchingNodes {
|
||||||
switch printMode {
|
switch printMode {
|
||||||
case "p":
|
case "p":
|
||||||
errorWriting = writeString(bufferedWriter, lib.PathStackToString(mappedDoc.PathStack)+"\n")
|
errorWriting = writeString(bufferedWriter, mappedDoc.PathStackToString()+"\n")
|
||||||
if errorWriting != nil {
|
if errorWriting != nil {
|
||||||
return errorWriting
|
return errorWriting
|
||||||
}
|
}
|
||||||
@ -288,7 +288,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error {
|
|||||||
// put it into a node and print that.
|
// put it into a node and print that.
|
||||||
var parentNode = yaml.Node{Kind: yaml.MappingNode}
|
var parentNode = yaml.Node{Kind: yaml.MappingNode}
|
||||||
parentNode.Content = make([]*yaml.Node, 2)
|
parentNode.Content = make([]*yaml.Node, 2)
|
||||||
parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)}
|
parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: mappedDoc.PathStackToString()}
|
||||||
parentNode.Content[1] = transformNode(mappedDoc.Node)
|
parentNode.Content[1] = transformNode(mappedDoc.Node)
|
||||||
if collectIntoArray {
|
if collectIntoArray {
|
||||||
arrayCollection.Content = append(arrayCollection.Content, &parentNode)
|
arrayCollection.Content = append(arrayCollection.Content, &parentNode)
|
||||||
@ -383,102 +383,102 @@ func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error {
|
// func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error {
|
||||||
if updateAll || currentIndex == docIndexInt {
|
// if updateAll || currentIndex == docIndexInt {
|
||||||
log.Debugf("Prefixing document %v", currentIndex)
|
// log.Debugf("Prefixing document %v", currentIndex)
|
||||||
yqlib.DebugNode(dataBucket)
|
// // yqlib.DebugNode(dataBucket)
|
||||||
updateCommand.Value = dataBucket.Content[0]
|
// updateCommand.Value = dataBucket.Content[0]
|
||||||
dataBucket.Content = make([]*yaml.Node, 1)
|
// dataBucket.Content = make([]*yaml.Node, 1)
|
||||||
|
|
||||||
newNode := lib.New(updateCommand.Path)
|
// newNode := lib.New(updateCommand.Path)
|
||||||
dataBucket.Content[0] = &newNode
|
// dataBucket.Content[0] = &newNode
|
||||||
|
|
||||||
errorUpdating := lib.Update(dataBucket, updateCommand, true)
|
// errorUpdating := lib.Update(dataBucket, updateCommand, true)
|
||||||
if errorUpdating != nil {
|
// if errorUpdating != nil {
|
||||||
return errorUpdating
|
// return errorUpdating
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error {
|
// func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error {
|
||||||
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
|
// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
|
||||||
if errorParsingDocIndex != nil {
|
// if errorParsingDocIndex != nil {
|
||||||
return errorParsingDocIndex
|
// return errorParsingDocIndex
|
||||||
}
|
// }
|
||||||
|
|
||||||
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
|
// var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
|
||||||
if updateAll || currentIndex == docIndexInt {
|
// if updateAll || currentIndex == docIndexInt {
|
||||||
log.Debugf("Updating doc %v", currentIndex)
|
// log.Debugf("Updating doc %v", currentIndex)
|
||||||
for _, updateCommand := range updateCommands {
|
// for _, updateCommand := range updateCommands {
|
||||||
log.Debugf("Processing update to Path %v", updateCommand.Path)
|
// log.Debugf("Processing update to Path %v", updateCommand.Path)
|
||||||
errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag)
|
// errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag)
|
||||||
if errorUpdating != nil {
|
// if errorUpdating != nil {
|
||||||
return errorUpdating
|
// return errorUpdating
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return readAndUpdate(writer, inputFile, updateData)
|
// return readAndUpdate(writer, inputFile, updateData)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
|
// func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
|
||||||
var destination io.Writer
|
// var destination io.Writer
|
||||||
var destinationName string
|
// var destinationName string
|
||||||
var completedSuccessfully = false
|
// var completedSuccessfully = false
|
||||||
if writeInplace {
|
// if writeInplace {
|
||||||
info, err := os.Stat(inputFile)
|
// info, err := os.Stat(inputFile)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
// mkdir temp dir as some docker images does not have temp dir
|
// // mkdir temp dir as some docker images does not have temp dir
|
||||||
_, err = os.Stat(os.TempDir())
|
// _, err = os.Stat(os.TempDir())
|
||||||
if os.IsNotExist(err) {
|
// if os.IsNotExist(err) {
|
||||||
err = os.Mkdir(os.TempDir(), 0700)
|
// err = os.Mkdir(os.TempDir(), 0700)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
} else if err != nil {
|
// } else if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
tempFile, err := ioutil.TempFile("", "temp")
|
// tempFile, err := ioutil.TempFile("", "temp")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
destinationName = tempFile.Name()
|
// destinationName = tempFile.Name()
|
||||||
err = os.Chmod(destinationName, info.Mode())
|
// err = os.Chmod(destinationName, info.Mode())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
destination = tempFile
|
// destination = tempFile
|
||||||
defer func() {
|
// defer func() {
|
||||||
safelyCloseFile(tempFile)
|
// safelyCloseFile(tempFile)
|
||||||
if completedSuccessfully {
|
// if completedSuccessfully {
|
||||||
safelyRenameFile(tempFile.Name(), inputFile)
|
// safelyRenameFile(tempFile.Name(), inputFile)
|
||||||
}
|
// }
|
||||||
}()
|
// }()
|
||||||
} else {
|
// } else {
|
||||||
destination = stdOut
|
// destination = stdOut
|
||||||
destinationName = "Stdout"
|
// destinationName = "Stdout"
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Debugf("Writing to %v from %v", destinationName, inputFile)
|
// log.Debugf("Writing to %v from %v", destinationName, inputFile)
|
||||||
|
|
||||||
bufferedWriter := bufio.NewWriter(destination)
|
// bufferedWriter := bufio.NewWriter(destination)
|
||||||
defer safelyFlush(bufferedWriter)
|
// defer safelyFlush(bufferedWriter)
|
||||||
|
|
||||||
var encoder yqlib.Encoder
|
// var encoder yqlib.Encoder
|
||||||
if outputToJSON {
|
// if outputToJSON {
|
||||||
encoder = yqlib.NewJsonEncoder(bufferedWriter, prettyPrint, indent)
|
// encoder = yqlib.NewJsonEncoder(bufferedWriter, prettyPrint, indent)
|
||||||
} else {
|
// } else {
|
||||||
encoder = yqlib.NewYamlEncoder(bufferedWriter, indent, colorsEnabled)
|
// encoder = yqlib.NewYamlEncoder(bufferedWriter, indent, colorsEnabled)
|
||||||
}
|
// }
|
||||||
|
|
||||||
var errorProcessing = readStream(inputFile, mapYamlDecoder(updateData, encoder))
|
// var errorProcessing = readStream(inputFile, mapYamlDecoder(updateData, encoder))
|
||||||
completedSuccessfully = errorProcessing == nil
|
// completedSuccessfully = errorProcessing == nil
|
||||||
return errorProcessing
|
// return errorProcessing
|
||||||
}
|
// }
|
||||||
|
|
||||||
type updateCommandParsed struct {
|
type updateCommandParsed struct {
|
||||||
Command string
|
Command string
|
||||||
@ -486,53 +486,53 @@ type updateCommandParsed struct {
|
|||||||
Value yaml.Node
|
Value yaml.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string, allowNoValue bool) ([]yqlib.UpdateCommand, error) {
|
// func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string, allowNoValue bool) ([]yqlib.UpdateCommand, error) {
|
||||||
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
|
// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
|
||||||
if writeScript != "" {
|
// if writeScript != "" {
|
||||||
var parsedCommands = make([]updateCommandParsed, 0)
|
// var parsedCommands = make([]updateCommandParsed, 0)
|
||||||
|
|
||||||
err := readData(writeScript, 0, &parsedCommands)
|
// err := readData(writeScript, 0, &parsedCommands)
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
// if err != nil && err != io.EOF {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Debugf("Read write commands file '%v'", parsedCommands)
|
// log.Debugf("Read write commands file '%v'", parsedCommands)
|
||||||
for index := range parsedCommands {
|
// for index := range parsedCommands {
|
||||||
parsedCommand := parsedCommands[index]
|
// parsedCommand := parsedCommands[index]
|
||||||
updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true}
|
// updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true}
|
||||||
updateCommands = append(updateCommands, updateCommand)
|
// updateCommands = append(updateCommands, updateCommand)
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Debugf("Read write commands file '%v'", updateCommands)
|
// log.Debugf("Read write commands file '%v'", updateCommands)
|
||||||
} else if sourceYamlFile != "" && len(args) == expectedArgs-1 {
|
// } else if sourceYamlFile != "" && len(args) == expectedArgs-1 {
|
||||||
log.Debugf("Reading value from %v", sourceYamlFile)
|
// log.Debugf("Reading value from %v", sourceYamlFile)
|
||||||
var value yaml.Node
|
// var value yaml.Node
|
||||||
err := readData(sourceYamlFile, 0, &value)
|
// err := readData(sourceYamlFile, 0, &value)
|
||||||
if err != nil && err != io.EOF {
|
// if err != nil && err != io.EOF {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
log.Debug("args %v", args[expectedArgs-2])
|
// log.Debug("args %v", args[expectedArgs-2])
|
||||||
updateCommands = make([]yqlib.UpdateCommand, 1)
|
// updateCommands = make([]yqlib.UpdateCommand, 1)
|
||||||
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value.Content[0], Overwrite: true}
|
// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value.Content[0], Overwrite: true}
|
||||||
} else if len(args) == expectedArgs {
|
// } else if len(args) == expectedArgs {
|
||||||
updateCommands = make([]yqlib.UpdateCommand, 1)
|
// updateCommands = make([]yqlib.UpdateCommand, 1)
|
||||||
log.Debug("args %v", args)
|
// log.Debug("args %v", args)
|
||||||
log.Debug("path %v", args[expectedArgs-2])
|
// log.Debug("path %v", args[expectedArgs-2])
|
||||||
log.Debug("Value %v", args[expectedArgs-1])
|
// log.Debug("Value %v", args[expectedArgs-1])
|
||||||
value := valueParser.Parse(args[expectedArgs-1], customTag, customStyle, anchorName, makeAlias)
|
// value := valueParser.Parse(args[expectedArgs-1], customTag, customStyle, anchorName, makeAlias)
|
||||||
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value, Overwrite: true, CommentsMergeStrategy: yqlib.IgnoreCommentsMergeStrategy}
|
// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value, Overwrite: true, CommentsMergeStrategy: yqlib.IgnoreCommentsMergeStrategy}
|
||||||
} else if len(args) == expectedArgs-1 && allowNoValue {
|
// } else if len(args) == expectedArgs-1 && allowNoValue {
|
||||||
// don't update the value
|
// // don't update the value
|
||||||
updateCommands = make([]yqlib.UpdateCommand, 1)
|
// updateCommands = make([]yqlib.UpdateCommand, 1)
|
||||||
log.Debug("args %v", args)
|
// log.Debug("args %v", args)
|
||||||
log.Debug("path %v", args[expectedArgs-2])
|
// log.Debug("path %v", args[expectedArgs-2])
|
||||||
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse("", customTag, customStyle, anchorName, makeAlias), Overwrite: true, DontUpdateNodeValue: true}
|
// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse("", customTag, customStyle, anchorName, makeAlias), Overwrite: true, DontUpdateNodeValue: true}
|
||||||
} else {
|
// } else {
|
||||||
return nil, errors.New(badArgsMessage)
|
// return nil, errors.New(badArgsMessage)
|
||||||
}
|
// }
|
||||||
return updateCommands, nil
|
// return updateCommands, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func safelyRenameFile(from string, to string) {
|
func safelyRenameFile(from string, to string) {
|
||||||
if renameError := os.Rename(from, to); renameError != nil {
|
if renameError := os.Rename(from, to); renameError != nil {
|
||||||
|
110
cmd/write.go
110
cmd/write.go
@ -1,61 +1,61 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"github.com/spf13/cobra"
|
// "github.com/spf13/cobra"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func createWriteCmd() *cobra.Command {
|
// func createWriteCmd() *cobra.Command {
|
||||||
var cmdWrite = &cobra.Command{
|
// var cmdWrite = &cobra.Command{
|
||||||
Use: "write [yaml_file] [path_expression] [value]",
|
// Use: "write [yaml_file] [path_expression] [value]",
|
||||||
Aliases: []string{"w"},
|
// Aliases: []string{"w"},
|
||||||
Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue",
|
// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue",
|
||||||
Example: `
|
// Example: `
|
||||||
yq write things.yaml 'a.b.c' true
|
// yq write things.yaml 'a.b.c' true
|
||||||
yq write things.yaml 'a.*.c' true
|
// yq write things.yaml 'a.*.c' true
|
||||||
yq write things.yaml 'a.**' true
|
// yq write things.yaml 'a.**' true
|
||||||
yq write things.yaml 'a.(child.subchild==co*).c' 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 '!!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 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 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 things.yaml 'a.b.c' cat
|
||||||
yq w -i -s update_script.yaml things.yaml
|
// 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 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
|
// 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.
|
// 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.
|
// 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.
|
// Append value to array adds the value to the end of array.
|
||||||
|
|
||||||
Update Scripts:
|
// Update Scripts:
|
||||||
Note that you can give an update script to perform more sophisticated update. Update script
|
// 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:
|
// format is list of update commands (update or delete) like so:
|
||||||
---
|
// ---
|
||||||
- command: update
|
// - command: update
|
||||||
path: b.c
|
// path: b.c
|
||||||
value:
|
// value:
|
||||||
#great
|
// #great
|
||||||
things: frog # wow!
|
// things: frog # wow!
|
||||||
- command: delete
|
// - command: delete
|
||||||
path: b.d
|
// path: b.d
|
||||||
`,
|
// `,
|
||||||
RunE: writeProperty,
|
// RunE: writeProperty,
|
||||||
}
|
// }
|
||||||
cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
// 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(&writeScript, "script", "s", "", "yaml script for updating yaml")
|
||||||
cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)")
|
// 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(&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(&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(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
|
||||||
cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
||||||
cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
||||||
return cmdWrite
|
// return cmdWrite
|
||||||
}
|
// }
|
||||||
|
|
||||||
func writeProperty(cmd *cobra.Command, args []string) error {
|
// func writeProperty(cmd *cobra.Command, args []string) error {
|
||||||
var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
|
// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
|
||||||
if updateCommandsError != nil {
|
// if updateCommandsError != nil {
|
||||||
return updateCommandsError
|
// return updateCommandsError
|
||||||
}
|
// }
|
||||||
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
// return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
|
||||||
}
|
// }
|
||||||
|
1217
cmd/write_test.go
1217
cmd/write_test.go
File diff suppressed because it is too large
Load Diff
5
pkg/yqlib/constants.go
Normal file
5
pkg/yqlib/constants.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import "gopkg.in/op/go-logging.v1"
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("yq-lib")
|
@ -1,295 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DataNavigator interface {
|
|
||||||
Traverse(value *yaml.Node, path []interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type navigator struct {
|
|
||||||
navigationStrategy NavigationStrategy
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
|
|
||||||
return &navigator{
|
|
||||||
navigationStrategy: NavigationStrategy,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) Traverse(value *yaml.Node, path []interface{}) error {
|
|
||||||
emptyArray := make([]interface{}, 0)
|
|
||||||
log.Debugf("Traversing path %v", pathStackToString(path))
|
|
||||||
return n.doTraverse(value, "", path, emptyArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) doTraverse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
|
|
||||||
|
|
||||||
log.Debug("head %v", head)
|
|
||||||
DebugNode(value)
|
|
||||||
var nodeContext = NewNodeContext(value, head, tail, pathStack)
|
|
||||||
|
|
||||||
var errorDeepSplatting error
|
|
||||||
// no need to deeply traverse the DocumentNode, as it's already covered by its first child.
|
|
||||||
if head == "**" && value.Kind != yaml.DocumentNode && value.Kind != yaml.ScalarNode && n.navigationStrategy.ShouldDeeplyTraverse(nodeContext) {
|
|
||||||
if len(pathStack) == 0 || pathStack[len(pathStack)-1] != "<<" {
|
|
||||||
errorDeepSplatting = n.recurse(value, head, tail, pathStack)
|
|
||||||
}
|
|
||||||
// ignore errors here, we are deep splatting so we may accidently give a string key
|
|
||||||
// to an array sequence
|
|
||||||
if len(tail) > 0 {
|
|
||||||
_ = n.recurse(value, tail[0], tail[1:], pathStack)
|
|
||||||
}
|
|
||||||
return errorDeepSplatting
|
|
||||||
}
|
|
||||||
|
|
||||||
if value.Kind == yaml.DocumentNode {
|
|
||||||
log.Debugf("its a document, diving into %v", head)
|
|
||||||
DebugNode(value)
|
|
||||||
return n.recurse(value, head, tail, pathStack)
|
|
||||||
} else if len(tail) > 0 && value.Kind != yaml.ScalarNode {
|
|
||||||
log.Debugf("diving into %v", tail[0])
|
|
||||||
DebugNode(value)
|
|
||||||
return n.recurse(value, tail[0], tail[1:], pathStack)
|
|
||||||
}
|
|
||||||
return n.navigationStrategy.Visit(nodeContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
|
|
||||||
if original.Kind != expectedKind {
|
|
||||||
log.Debug("wanted %v but it was %v, overriding", KindString(expectedKind), KindString(original.Kind))
|
|
||||||
return &yaml.Node{Kind: expectedKind}
|
|
||||||
}
|
|
||||||
return original
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
|
|
||||||
log.Debug("recursing, processing %v, pathStack %v", head, pathStackToString(pathStack))
|
|
||||||
|
|
||||||
nodeContext := NewNodeContext(value, head, tail, pathStack)
|
|
||||||
|
|
||||||
if head == "**" && !n.navigationStrategy.ShouldOnlyDeeplyVisitLeaves(nodeContext) {
|
|
||||||
nodeContext.IsMiddleNode = true
|
|
||||||
errorVisitingDeeply := n.navigationStrategy.Visit(nodeContext)
|
|
||||||
if errorVisitingDeeply != nil {
|
|
||||||
return errorVisitingDeeply
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch value.Kind {
|
|
||||||
case yaml.MappingNode:
|
|
||||||
log.Debug("its a map with %v entries", len(value.Content)/2)
|
|
||||||
headString := fmt.Sprintf("%v", head)
|
|
||||||
return n.recurseMap(value, headString, tail, pathStack)
|
|
||||||
|
|
||||||
case yaml.SequenceNode:
|
|
||||||
log.Debug("its a sequence of %v things!", len(value.Content))
|
|
||||||
|
|
||||||
switch head := head.(type) {
|
|
||||||
case int64:
|
|
||||||
return n.recurseArray(value, head, head, tail, pathStack)
|
|
||||||
default:
|
|
||||||
|
|
||||||
if head == "+" {
|
|
||||||
return n.appendArray(value, head, tail, pathStack)
|
|
||||||
} else if len(value.Content) == 0 && head == "**" {
|
|
||||||
return n.navigationStrategy.Visit(nodeContext)
|
|
||||||
}
|
|
||||||
return n.splatArray(value, head, tail, pathStack)
|
|
||||||
}
|
|
||||||
case yaml.AliasNode:
|
|
||||||
log.Debug("its an alias!")
|
|
||||||
DebugNode(value.Alias)
|
|
||||||
if n.navigationStrategy.FollowAlias(nodeContext) {
|
|
||||||
log.Debug("following the alias")
|
|
||||||
return n.recurse(value.Alias, head, tail, pathStack)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case yaml.DocumentNode:
|
|
||||||
return n.doTraverse(value.Content[0], head, tail, pathStack)
|
|
||||||
default:
|
|
||||||
return n.navigationStrategy.Visit(nodeContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []interface{}, pathStack []interface{}) error {
|
|
||||||
traversedEntry := false
|
|
||||||
errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error {
|
|
||||||
log.Debug("recurseMap: visitMatchingEntries for %v", contents[indexInMap].Value)
|
|
||||||
n.navigationStrategy.DebugVisitedNodes()
|
|
||||||
newPathStack := append(pathStack, contents[indexInMap].Value)
|
|
||||||
log.Debug("should I traverse? head: %v, path: %v", head, pathStackToString(newPathStack))
|
|
||||||
DebugNode(value)
|
|
||||||
if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) {
|
|
||||||
log.Debug("recurseMap: Going to traverse")
|
|
||||||
traversedEntry = true
|
|
||||||
contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(head, tail, contents[indexInMap+1].Kind))
|
|
||||||
errorTraversing := n.doTraverse(contents[indexInMap+1], head, tail, newPathStack)
|
|
||||||
log.Debug("recurseMap: Finished traversing")
|
|
||||||
n.navigationStrategy.DebugVisitedNodes()
|
|
||||||
return errorTraversing
|
|
||||||
} else {
|
|
||||||
log.Debug("nope not traversing")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if errorVisiting != nil {
|
|
||||||
return errorVisiting
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(value.Content) == 0 && head == "**" {
|
|
||||||
return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack))
|
|
||||||
} else if traversedEntry || n.navigationStrategy.GetPathParser().IsPathExpression(head) || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, errorParsingInt := strconv.ParseInt(head, 10, 64)
|
|
||||||
|
|
||||||
mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
|
|
||||||
|
|
||||||
if errorParsingInt == nil {
|
|
||||||
// fixes a json encoding problem where keys that look like numbers
|
|
||||||
// get treated as numbers and cannot be used in a json map
|
|
||||||
mapEntryKey.Style = yaml.LiteralStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
value.Content = append(value.Content, &mapEntryKey)
|
|
||||||
mapEntryValue := yaml.Node{Kind: guessKind(head, tail, 0)}
|
|
||||||
value.Content = append(value.Content, &mapEntryValue)
|
|
||||||
log.Debug("adding a new node %v - def a string", head)
|
|
||||||
return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head))
|
|
||||||
}
|
|
||||||
|
|
||||||
// need to pass the node in, as it may be aliased
|
|
||||||
type mapVisitorFn func(contents []*yaml.Node, index int) error
|
|
||||||
|
|
||||||
func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error {
|
|
||||||
var contents = node.Content
|
|
||||||
for index := 0; index < len(contents); index = index + 2 {
|
|
||||||
content := contents[index]
|
|
||||||
|
|
||||||
log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
|
|
||||||
n.navigationStrategy.DebugVisitedNodes()
|
|
||||||
errorVisiting := visit(contents, index)
|
|
||||||
if errorVisiting != nil {
|
|
||||||
return errorVisiting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error {
|
|
||||||
var contents = node.Content
|
|
||||||
log.Debug("visitMatchingEntries %v", head)
|
|
||||||
DebugNode(node)
|
|
||||||
// value.Content is a concatenated array of key, value,
|
|
||||||
// so keys are in the even indexes, values in odd.
|
|
||||||
// merge aliases are defined first, but we only want to traverse them
|
|
||||||
// if we don't find a match directly on this node first.
|
|
||||||
errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
|
|
||||||
|
|
||||||
if errorVisitedDirectEntries != nil || !n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) {
|
|
||||||
return errorVisitedDirectEntries
|
|
||||||
}
|
|
||||||
return n.visitAliases(contents, head, tail, pathStack, visit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error {
|
|
||||||
// merge aliases are defined first, but we only want to traverse them
|
|
||||||
// if we don't find a match on this node first.
|
|
||||||
// traverse them backwards so that the last alias overrides the preceding.
|
|
||||||
// a node can either be
|
|
||||||
// an alias to one other node (e.g. <<: *blah)
|
|
||||||
// or a sequence of aliases (e.g. <<: [*blah, *foo])
|
|
||||||
log.Debug("checking for aliases, head: %v, pathstack: %v", head, pathStackToString(pathStack))
|
|
||||||
for index := len(contents) - 2; index >= 0; index = index - 2 {
|
|
||||||
|
|
||||||
if contents[index+1].Kind == yaml.AliasNode && contents[index].Value == "<<" {
|
|
||||||
valueNode := contents[index+1]
|
|
||||||
log.Debug("found an alias")
|
|
||||||
DebugNode(contents[index])
|
|
||||||
DebugNode(valueNode)
|
|
||||||
|
|
||||||
errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit)
|
|
||||||
if errorInAlias != nil {
|
|
||||||
return errorInAlias
|
|
||||||
}
|
|
||||||
} else if contents[index+1].Kind == yaml.SequenceNode {
|
|
||||||
// could be an array of aliases...
|
|
||||||
errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit)
|
|
||||||
if errorVisitingAliasSeq != nil {
|
|
||||||
return errorVisitingAliasSeq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []interface{}, pathStack []interface{}, visit mapVisitorFn) error {
|
|
||||||
// need to search this backwards too, so that aliases defined last override the preceding.
|
|
||||||
for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
|
|
||||||
child := possibleAliasArray[aliasIndex]
|
|
||||||
if child.Kind == yaml.AliasNode {
|
|
||||||
log.Debug("found an alias")
|
|
||||||
DebugNode(child)
|
|
||||||
errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit)
|
|
||||||
if errorInAlias != nil {
|
|
||||||
return errorInAlias
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) splatArray(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
|
|
||||||
for index, childValue := range value.Content {
|
|
||||||
log.Debug("processing")
|
|
||||||
DebugNode(childValue)
|
|
||||||
childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind))
|
|
||||||
|
|
||||||
newPathStack := append(pathStack, index)
|
|
||||||
if n.navigationStrategy.ShouldTraverse(NewNodeContext(childValue, head, tail, newPathStack), childValue.Value) {
|
|
||||||
// here we should not deeply traverse the array if we are appending..not sure how to do that.
|
|
||||||
// need to visit instead...
|
|
||||||
// easiest way is to pop off the head and pass the rest of the tail in.
|
|
||||||
var err = n.doTraverse(childValue, head, tail, newPathStack)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) appendArray(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
|
|
||||||
var newNode = yaml.Node{Kind: guessKind(head, tail, 0)}
|
|
||||||
value.Content = append(value.Content, &newNode)
|
|
||||||
log.Debug("appending a new node, %v", value.Content)
|
|
||||||
return n.doTraverse(&newNode, head, tail, append(pathStack, len(value.Content)-1))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *navigator) recurseArray(value *yaml.Node, index int64, head interface{}, tail []interface{}, pathStack []interface{}) error {
|
|
||||||
var contentLength = int64(len(value.Content))
|
|
||||||
for contentLength <= index {
|
|
||||||
value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)})
|
|
||||||
contentLength = int64(len(value.Content))
|
|
||||||
}
|
|
||||||
var indexToUse = index
|
|
||||||
|
|
||||||
if indexToUse < 0 {
|
|
||||||
indexToUse = contentLength + indexToUse
|
|
||||||
}
|
|
||||||
|
|
||||||
if indexToUse < 0 {
|
|
||||||
return fmt.Errorf("Index [%v] out of range, array size is %v", index, contentLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
value.Content[indexToUse] = n.getOrReplace(value.Content[indexToUse], guessKind(head, tail, value.Content[indexToUse].Kind))
|
|
||||||
|
|
||||||
return n.doTraverse(value.Content[indexToUse], head, tail, append(pathStack, index))
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package yqlib
|
|
@ -1,73 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DeleteNavigationStrategy(pathElementToDelete interface{}) NavigationStrategy {
|
|
||||||
parser := NewPathParser()
|
|
||||||
return &NavigationStrategyImpl{
|
|
||||||
visitedNodes: []*NodeContext{},
|
|
||||||
pathParser: parser,
|
|
||||||
followAlias: func(nodeContext NodeContext) bool {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
visit: func(nodeContext NodeContext) error {
|
|
||||||
node := nodeContext.Node
|
|
||||||
log.Debug("need to find and delete %v in here (%v)", pathElementToDelete, pathStackToString(nodeContext.PathStack))
|
|
||||||
DebugNode(node)
|
|
||||||
if node.Kind == yaml.SequenceNode {
|
|
||||||
newContent := deleteFromArray(parser, node.Content, nodeContext.PathStack, pathElementToDelete)
|
|
||||||
node.Content = newContent
|
|
||||||
} else if node.Kind == yaml.MappingNode {
|
|
||||||
node.Content = deleteFromMap(parser, node.Content, nodeContext.PathStack, pathElementToDelete)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, pathElementToDelete interface{}) []*yaml.Node {
|
|
||||||
newContents := make([]*yaml.Node, 0)
|
|
||||||
for index := 0; index < len(contents); index = index + 2 {
|
|
||||||
keyNode := contents[index]
|
|
||||||
valueNode := contents[index+1]
|
|
||||||
if !pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, make([]interface{}, 0), pathStack), keyNode.Value) {
|
|
||||||
log.Debug("adding node %v", keyNode.Value)
|
|
||||||
newContents = append(newContents, keyNode, valueNode)
|
|
||||||
} else {
|
|
||||||
log.Debug("skipping node %v", keyNode.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newContents
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteFromArray(pathParser PathParser, content []*yaml.Node, pathStack []interface{}, pathElementToDelete interface{}) []*yaml.Node {
|
|
||||||
|
|
||||||
switch pathElementToDelete := pathElementToDelete.(type) {
|
|
||||||
case int64:
|
|
||||||
return deleteIndexInArray(content, pathElementToDelete)
|
|
||||||
default:
|
|
||||||
log.Debug("%v is not a numeric index, finding matching patterns", pathElementToDelete)
|
|
||||||
var newArray = make([]*yaml.Node, 0)
|
|
||||||
|
|
||||||
for _, childValue := range content {
|
|
||||||
if !pathParser.MatchesNextPathElement(NewNodeContext(childValue, pathElementToDelete, make([]interface{}, 0), pathStack), childValue.Value) {
|
|
||||||
newArray = append(newArray, childValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newArray
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteIndexInArray(content []*yaml.Node, index int64) []*yaml.Node {
|
|
||||||
log.Debug("deleting index %v in array", index)
|
|
||||||
if index >= int64(len(content)) {
|
|
||||||
log.Debug("index %v is greater than content length %v", index, len(content))
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
return append(content[:index], content[index+1:]...)
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy {
|
|
||||||
return &NavigationStrategyImpl{
|
|
||||||
visitedNodes: []*NodeContext{},
|
|
||||||
pathParser: NewPathParser(),
|
|
||||||
visit: func(nodeContext NodeContext) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
shouldVisitExtraFn: func(nodeContext NodeContext) bool {
|
|
||||||
log.Debug("does %v match %v ? %v", nodeContext.Node.Value, value, nodeContext.Node.Value == value)
|
|
||||||
return matchesString(value, nodeContext.Node.Value)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
208
pkg/yqlib/lib.go
208
pkg/yqlib/lib.go
@ -1,208 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
logging "gopkg.in/op/go-logging.v1"
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logging.MustGetLogger("yq")
|
|
||||||
|
|
||||||
type UpdateCommand struct {
|
|
||||||
Command string
|
|
||||||
Path string
|
|
||||||
Value *yaml.Node
|
|
||||||
Overwrite bool
|
|
||||||
DontUpdateNodeValue bool
|
|
||||||
DontUpdateNodeContent bool
|
|
||||||
CommentsMergeStrategy CommentsMergeStrategy
|
|
||||||
}
|
|
||||||
|
|
||||||
func KindString(kind yaml.Kind) string {
|
|
||||||
switch kind {
|
|
||||||
case yaml.ScalarNode:
|
|
||||||
return "ScalarNode"
|
|
||||||
case yaml.SequenceNode:
|
|
||||||
return "SequenceNode"
|
|
||||||
case yaml.MappingNode:
|
|
||||||
return "MappingNode"
|
|
||||||
case yaml.DocumentNode:
|
|
||||||
return "DocumentNode"
|
|
||||||
case yaml.AliasNode:
|
|
||||||
return "AliasNode"
|
|
||||||
default:
|
|
||||||
return "unknown!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DebugNode(value *yaml.Node) {
|
|
||||||
if value == nil {
|
|
||||||
log.Debug("-- node is nil --")
|
|
||||||
} else if log.IsEnabledFor(logging.DEBUG) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
encoder := yaml.NewEncoder(buf)
|
|
||||||
errorEncoding := encoder.Encode(value)
|
|
||||||
if errorEncoding != nil {
|
|
||||||
log.Error("Error debugging node, %v", errorEncoding.Error())
|
|
||||||
}
|
|
||||||
encoder.Close()
|
|
||||||
log.Debug("Tag: %v, Kind: %v, Anchor: %v", value.Tag, KindString(value.Kind), value.Anchor)
|
|
||||||
log.Debug("Head Comment: %v", value.HeadComment)
|
|
||||||
log.Debug("Line Comment: %v", value.LineComment)
|
|
||||||
log.Debug("FootComment Comment: %v", value.FootComment)
|
|
||||||
log.Debug("\n%v", buf.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathStackToString(pathStack []interface{}) string {
|
|
||||||
return mergePathStackToString(pathStack, UpdateArrayMergeStrategy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
for index, path := range pathStack {
|
|
||||||
switch path.(type) {
|
|
||||||
case int, int64:
|
|
||||||
if arrayMergeStrategy == AppendArrayMergeStrategy {
|
|
||||||
sb.WriteString("[+]")
|
|
||||||
} else {
|
|
||||||
sb.WriteString(fmt.Sprintf("[%v]", path))
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
s := fmt.Sprintf("%v", path)
|
|
||||||
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
|
|
||||||
|
|
||||||
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
|
|
||||||
hasDoubleQuotes := strings.Contains(s, "\"")
|
|
||||||
wrappingCharacterStart := "\""
|
|
||||||
wrappingCharacterEnd := "\""
|
|
||||||
if hasDoubleQuotes {
|
|
||||||
wrappingCharacterStart = "("
|
|
||||||
wrappingCharacterEnd = ")"
|
|
||||||
}
|
|
||||||
if hasSpecial || errParsingInt == nil {
|
|
||||||
sb.WriteString(wrappingCharacterStart)
|
|
||||||
}
|
|
||||||
sb.WriteString(s)
|
|
||||||
if hasSpecial || errParsingInt == nil {
|
|
||||||
sb.WriteString(wrappingCharacterEnd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if index < len(pathStack)-1 {
|
|
||||||
sb.WriteString(".")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func guessKind(head interface{}, tail []interface{}, guess yaml.Kind) yaml.Kind {
|
|
||||||
log.Debug("guessKind: tail %v", tail)
|
|
||||||
if len(tail) == 0 && guess == 0 {
|
|
||||||
log.Debug("end of path, must be a scalar")
|
|
||||||
return yaml.ScalarNode
|
|
||||||
} else if len(tail) == 0 {
|
|
||||||
return guess
|
|
||||||
}
|
|
||||||
var next = tail[0]
|
|
||||||
switch next.(type) {
|
|
||||||
case int64:
|
|
||||||
return yaml.SequenceNode
|
|
||||||
default:
|
|
||||||
var nextString = fmt.Sprintf("%v", next)
|
|
||||||
if nextString == "+" {
|
|
||||||
return yaml.SequenceNode
|
|
||||||
}
|
|
||||||
pathParser := NewPathParser()
|
|
||||||
if pathParser.IsPathExpression(nextString) && (guess == yaml.SequenceNode || guess == yaml.MappingNode) {
|
|
||||||
return guess
|
|
||||||
} else if guess == yaml.AliasNode {
|
|
||||||
log.Debug("guess was an alias, okey doke.")
|
|
||||||
return guess
|
|
||||||
} else if head == "**" {
|
|
||||||
log.Debug("deep wildcard, go with the guess")
|
|
||||||
return guess
|
|
||||||
}
|
|
||||||
log.Debug("forcing a mapping node")
|
|
||||||
log.Debug("yaml.SequenceNode %v", guess == yaml.SequenceNode)
|
|
||||||
log.Debug("yaml.ScalarNode %v", guess == yaml.ScalarNode)
|
|
||||||
return yaml.MappingNode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type YqLib interface {
|
|
||||||
Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
|
|
||||||
GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error)
|
|
||||||
Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
|
|
||||||
New(path string) yaml.Node
|
|
||||||
|
|
||||||
PathStackToString(pathStack []interface{}) string
|
|
||||||
MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
|
|
||||||
}
|
|
||||||
|
|
||||||
type lib struct {
|
|
||||||
parser PathParser
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewYqLib() YqLib {
|
|
||||||
return &lib{
|
|
||||||
parser: NewPathParser(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
|
|
||||||
var paths = l.parser.ParsePath(path)
|
|
||||||
navigationStrategy := ReadNavigationStrategy()
|
|
||||||
navigator := NewDataNavigator(navigationStrategy)
|
|
||||||
error := navigator.Traverse(rootNode, paths)
|
|
||||||
return navigationStrategy.GetVisitedNodes(), error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lib) GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) {
|
|
||||||
var paths = l.parser.ParsePath(path)
|
|
||||||
navigationStrategy := ReadForMergeNavigationStrategy(arrayMergeStrategy)
|
|
||||||
navigator := NewDataNavigator(navigationStrategy)
|
|
||||||
error := navigator.Traverse(rootNode, paths)
|
|
||||||
return navigationStrategy.GetVisitedNodes(), error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lib) PathStackToString(pathStack []interface{}) string {
|
|
||||||
return pathStackToString(pathStack)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lib) MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string {
|
|
||||||
return mergePathStackToString(pathStack, arrayMergeStrategy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lib) New(path string) yaml.Node {
|
|
||||||
var paths = l.parser.ParsePath(path)
|
|
||||||
newNode := yaml.Node{Kind: guessKind("", paths, 0)}
|
|
||||||
return newNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error {
|
|
||||||
log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
|
|
||||||
switch updateCommand.Command {
|
|
||||||
case "update":
|
|
||||||
var paths = l.parser.ParsePath(updateCommand.Path)
|
|
||||||
navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand, autoCreate))
|
|
||||||
return navigator.Traverse(rootNode, paths)
|
|
||||||
case "merge":
|
|
||||||
var paths = l.parser.ParsePath(updateCommand.Path)
|
|
||||||
navigator := NewDataNavigator(MergeNavigationStrategy(updateCommand, autoCreate))
|
|
||||||
return navigator.Traverse(rootNode, paths)
|
|
||||||
case "delete":
|
|
||||||
var paths = l.parser.ParsePath(updateCommand.Path)
|
|
||||||
lastBit, newTail := paths[len(paths)-1], paths[:len(paths)-1]
|
|
||||||
navigator := NewDataNavigator(DeleteNavigationStrategy(lastBit))
|
|
||||||
return navigator.Traverse(rootNode, newTail)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unknown command %v", updateCommand.Command)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,176 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v3/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLib(t *testing.T) {
|
|
||||||
|
|
||||||
subject := NewYqLib()
|
|
||||||
|
|
||||||
t.Run("PathStackToString_Empty", func(t *testing.T) {
|
|
||||||
emptyArray := make([]interface{}, 0)
|
|
||||||
got := subject.PathStackToString(emptyArray)
|
|
||||||
test.AssertResult(t, ``, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("PathStackToString", func(t *testing.T) {
|
|
||||||
array := make([]interface{}, 3)
|
|
||||||
array[0] = "a"
|
|
||||||
array[1] = 0
|
|
||||||
array[2] = "b"
|
|
||||||
got := subject.PathStackToString(array)
|
|
||||||
test.AssertResult(t, `a.[0].b`, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("MergePathStackToString", func(t *testing.T) {
|
|
||||||
array := make([]interface{}, 3)
|
|
||||||
array[0] = "a"
|
|
||||||
array[1] = 0
|
|
||||||
array[2] = "b"
|
|
||||||
got := subject.MergePathStackToString(array, AppendArrayMergeStrategy)
|
|
||||||
test.AssertResult(t, `a.[+].b`, got)
|
|
||||||
})
|
|
||||||
|
|
||||||
// t.Run("TestReadPath_WithError", func(t *testing.T) {
|
|
||||||
// var data = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// b:
|
|
||||||
// - c
|
|
||||||
// `)
|
|
||||||
|
|
||||||
// _, err := subject.ReadPath(data, "b.[a]")
|
|
||||||
// if err == nil {
|
|
||||||
// t.Fatal("Expected error due to invalid path")
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("TestWritePath", func(t *testing.T) {
|
|
||||||
// var data = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// b:
|
|
||||||
// 2: c
|
|
||||||
// `)
|
|
||||||
|
|
||||||
// got := subject.WritePath(data, "b.3", "a")
|
|
||||||
// test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("TestPrefixPath", func(t *testing.T) {
|
|
||||||
// var data = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// b:
|
|
||||||
// 2: c
|
|
||||||
// `)
|
|
||||||
|
|
||||||
// got := subject.PrefixPath(data, "a.d")
|
|
||||||
// test.AssertResult(t, `[{a [{d [{b [{2 c}]}]}]}]`, fmt.Sprintf("%v", got))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("TestDeletePath", func(t *testing.T) {
|
|
||||||
// var data = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// b:
|
|
||||||
// 2: c
|
|
||||||
// 3: a
|
|
||||||
// `)
|
|
||||||
|
|
||||||
// got, _ := subject.DeletePath(data, "b.2")
|
|
||||||
// test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("TestDeletePath_WithError", func(t *testing.T) {
|
|
||||||
// var data = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// b:
|
|
||||||
// - c
|
|
||||||
// `)
|
|
||||||
|
|
||||||
// _, err := subject.DeletePath(data, "b.[a]")
|
|
||||||
// if err == nil {
|
|
||||||
// t.Fatal("Expected error due to invalid path")
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("TestMerge", func(t *testing.T) {
|
|
||||||
// var dst = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// a: b
|
|
||||||
// c: d
|
|
||||||
// `)
|
|
||||||
// var src = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// a: 1
|
|
||||||
// b: 2
|
|
||||||
// `)
|
|
||||||
|
|
||||||
// var mergedData = make(map[interface{}]interface{})
|
|
||||||
// mergedData["root"] = dst
|
|
||||||
// var mapDataBucket = make(map[interface{}]interface{})
|
|
||||||
// mapDataBucket["root"] = src
|
|
||||||
|
|
||||||
// err := subject.Merge(&mergedData, mapDataBucket, false, false)
|
|
||||||
// if err != nil {
|
|
||||||
// t.Fatal("Unexpected error")
|
|
||||||
// }
|
|
||||||
// test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"]))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("TestMerge_WithOverwrite", func(t *testing.T) {
|
|
||||||
// var dst = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// a: b
|
|
||||||
// c: d
|
|
||||||
// `)
|
|
||||||
// var src = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// a: 1
|
|
||||||
// b: 2
|
|
||||||
// `)
|
|
||||||
|
|
||||||
// var mergedData = make(map[interface{}]interface{})
|
|
||||||
// mergedData["root"] = dst
|
|
||||||
// var mapDataBucket = make(map[interface{}]interface{})
|
|
||||||
// mapDataBucket["root"] = src
|
|
||||||
|
|
||||||
// err := subject.Merge(&mergedData, mapDataBucket, true, false)
|
|
||||||
// if err != nil {
|
|
||||||
// t.Fatal("Unexpected error")
|
|
||||||
// }
|
|
||||||
// test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("TestMerge_WithAppend", func(t *testing.T) {
|
|
||||||
// var dst = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// a: b
|
|
||||||
// c: d
|
|
||||||
// `)
|
|
||||||
// var src = test.ParseData(`
|
|
||||||
// ---
|
|
||||||
// a: 1
|
|
||||||
// b: 2
|
|
||||||
// `)
|
|
||||||
|
|
||||||
// var mergedData = make(map[interface{}]interface{})
|
|
||||||
// mergedData["root"] = dst
|
|
||||||
// var mapDataBucket = make(map[interface{}]interface{})
|
|
||||||
// mapDataBucket["root"] = src
|
|
||||||
|
|
||||||
// err := subject.Merge(&mergedData, mapDataBucket, false, true)
|
|
||||||
// if err != nil {
|
|
||||||
// t.Fatal("Unexpected error")
|
|
||||||
// }
|
|
||||||
// test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
|
|
||||||
// })
|
|
||||||
|
|
||||||
// t.Run("TestMerge_WithError", func(t *testing.T) {
|
|
||||||
// err := subject.Merge(nil, nil, false, false)
|
|
||||||
// if err == nil {
|
|
||||||
// t.Fatal("Expected error due to nil")
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import "gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
type ArrayMergeStrategy uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
UpdateArrayMergeStrategy ArrayMergeStrategy = 1 << iota
|
|
||||||
OverwriteArrayMergeStrategy
|
|
||||||
AppendArrayMergeStrategy
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommentsMergeStrategy uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
SetWhenBlankCommentsMergeStrategy CommentsMergeStrategy = 1 << iota
|
|
||||||
IgnoreCommentsMergeStrategy
|
|
||||||
OverwriteCommentsMergeStrategy
|
|
||||||
AppendCommentsMergeStrategy
|
|
||||||
)
|
|
||||||
|
|
||||||
func MergeNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
|
|
||||||
return &NavigationStrategyImpl{
|
|
||||||
visitedNodes: []*NodeContext{},
|
|
||||||
pathParser: NewPathParser(),
|
|
||||||
followAlias: func(nodeContext NodeContext) bool {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
autoCreateMap: func(nodeContext NodeContext) bool {
|
|
||||||
return autoCreate
|
|
||||||
},
|
|
||||||
visit: func(nodeContext NodeContext) error {
|
|
||||||
node := nodeContext.Node
|
|
||||||
changesToApply := updateCommand.Value
|
|
||||||
|
|
||||||
if node.Kind == yaml.DocumentNode && changesToApply.Kind != yaml.DocumentNode {
|
|
||||||
// when the path is empty, it matches both the top level pseudo document node
|
|
||||||
// and the actual top level node (e.g. map/sequence/whatever)
|
|
||||||
// so when we are updating with no path, make sure we update the right node.
|
|
||||||
node = node.Content[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("going to update")
|
|
||||||
DebugNode(node)
|
|
||||||
log.Debug("with")
|
|
||||||
DebugNode(changesToApply)
|
|
||||||
|
|
||||||
if updateCommand.Overwrite || node.Value == "" {
|
|
||||||
node.Value = changesToApply.Value
|
|
||||||
node.Tag = changesToApply.Tag
|
|
||||||
node.Kind = changesToApply.Kind
|
|
||||||
node.Style = changesToApply.Style
|
|
||||||
node.Anchor = changesToApply.Anchor
|
|
||||||
node.Alias = changesToApply.Alias
|
|
||||||
|
|
||||||
if !updateCommand.DontUpdateNodeContent {
|
|
||||||
node.Content = changesToApply.Content
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch updateCommand.CommentsMergeStrategy {
|
|
||||||
case OverwriteCommentsMergeStrategy:
|
|
||||||
node.HeadComment = changesToApply.HeadComment
|
|
||||||
node.LineComment = changesToApply.LineComment
|
|
||||||
node.FootComment = changesToApply.FootComment
|
|
||||||
case SetWhenBlankCommentsMergeStrategy:
|
|
||||||
if node.HeadComment == "" {
|
|
||||||
node.HeadComment = changesToApply.HeadComment
|
|
||||||
}
|
|
||||||
if node.LineComment == "" {
|
|
||||||
node.LineComment = changesToApply.LineComment
|
|
||||||
}
|
|
||||||
if node.FootComment == "" {
|
|
||||||
node.FootComment = changesToApply.FootComment
|
|
||||||
}
|
|
||||||
case AppendCommentsMergeStrategy:
|
|
||||||
if node.HeadComment == "" {
|
|
||||||
node.HeadComment = changesToApply.HeadComment
|
|
||||||
} else {
|
|
||||||
node.HeadComment = node.HeadComment + "\n" + changesToApply.HeadComment
|
|
||||||
}
|
|
||||||
if node.LineComment == "" {
|
|
||||||
node.LineComment = changesToApply.LineComment
|
|
||||||
} else {
|
|
||||||
node.LineComment = node.LineComment + " " + changesToApply.LineComment
|
|
||||||
}
|
|
||||||
if node.FootComment == "" {
|
|
||||||
node.FootComment = changesToApply.FootComment
|
|
||||||
} else {
|
|
||||||
node.FootComment = node.FootComment + "\n" + changesToApply.FootComment
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("result")
|
|
||||||
DebugNode(node)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NodeContext struct {
|
|
||||||
Node *yaml.Node
|
|
||||||
Head interface{}
|
|
||||||
Tail []interface{}
|
|
||||||
PathStack []interface{}
|
|
||||||
// middle nodes are nodes that match along the original path, but not a
|
|
||||||
// target match of the path. This is only relevant when ShouldOnlyDeeplyVisitLeaves is false.
|
|
||||||
IsMiddleNode bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNodeContext(node *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) NodeContext {
|
|
||||||
newTail := make([]interface{}, len(tail))
|
|
||||||
copy(newTail, tail)
|
|
||||||
|
|
||||||
newPathStack := make([]interface{}, len(pathStack))
|
|
||||||
copy(newPathStack, pathStack)
|
|
||||||
return NodeContext{
|
|
||||||
Node: node,
|
|
||||||
Head: head,
|
|
||||||
Tail: newTail,
|
|
||||||
PathStack: newPathStack,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NavigationStrategy interface {
|
|
||||||
FollowAlias(nodeContext NodeContext) bool
|
|
||||||
AutoCreateMap(nodeContext NodeContext) bool
|
|
||||||
Visit(nodeContext NodeContext) error
|
|
||||||
// node key is the string value of the last element in the path stack
|
|
||||||
// we use it to match against the pathExpression in head.
|
|
||||||
ShouldTraverse(nodeContext NodeContext, nodeKey string) bool
|
|
||||||
ShouldDeeplyTraverse(nodeContext NodeContext) bool
|
|
||||||
// when deeply traversing, should we visit all matching nodes, or just leaves?
|
|
||||||
ShouldOnlyDeeplyVisitLeaves(NodeContext) bool
|
|
||||||
GetVisitedNodes() []*NodeContext
|
|
||||||
DebugVisitedNodes()
|
|
||||||
GetPathParser() PathParser
|
|
||||||
}
|
|
||||||
|
|
||||||
type NavigationStrategyImpl struct {
|
|
||||||
followAlias func(nodeContext NodeContext) bool
|
|
||||||
autoCreateMap func(nodeContext NodeContext) bool
|
|
||||||
visit func(nodeContext NodeContext) error
|
|
||||||
shouldVisitExtraFn func(nodeContext NodeContext) bool
|
|
||||||
shouldDeeplyTraverse func(nodeContext NodeContext) bool
|
|
||||||
shouldOnlyDeeplyVisitLeaves func(nodeContext NodeContext) bool
|
|
||||||
visitedNodes []*NodeContext
|
|
||||||
pathParser PathParser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) GetPathParser() PathParser {
|
|
||||||
return ns.pathParser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext {
|
|
||||||
return ns.visitedNodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) FollowAlias(nodeContext NodeContext) bool {
|
|
||||||
if ns.followAlias != nil {
|
|
||||||
return ns.followAlias(nodeContext)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) AutoCreateMap(nodeContext NodeContext) bool {
|
|
||||||
if ns.autoCreateMap != nil {
|
|
||||||
return ns.autoCreateMap(nodeContext)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) ShouldDeeplyTraverse(nodeContext NodeContext) bool {
|
|
||||||
if ns.shouldDeeplyTraverse != nil {
|
|
||||||
return ns.shouldDeeplyTraverse(nodeContext)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) ShouldOnlyDeeplyVisitLeaves(nodeContext NodeContext) bool {
|
|
||||||
if ns.shouldOnlyDeeplyVisitLeaves != nil {
|
|
||||||
return ns.shouldOnlyDeeplyVisitLeaves(nodeContext)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool {
|
|
||||||
// we should traverse aliases (if enabled), but not visit them :/
|
|
||||||
if len(nodeContext.PathStack) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if ns.alreadyVisited(nodeContext.PathStack) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" &&
|
|
||||||
ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
|
|
||||||
pathStack := nodeContext.PathStack
|
|
||||||
if len(pathStack) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
log.Debug("tail len %v", len(nodeContext.Tail))
|
|
||||||
|
|
||||||
if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
|
|
||||||
log.Debug("nodeKey: %v, nodeContext.Head: %v", nodeKey, nodeContext.Head)
|
|
||||||
|
|
||||||
// only visit aliases if its an exact match
|
|
||||||
return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
|
|
||||||
ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
|
|
||||||
log.Debug("Visit?, %v, %v", nodeContext.Head, pathStackToString(nodeContext.PathStack))
|
|
||||||
DebugNode(nodeContext.Node)
|
|
||||||
if ns.shouldVisit(nodeContext) {
|
|
||||||
log.Debug("yep, visiting")
|
|
||||||
// pathStack array must be
|
|
||||||
// copied, as append() may sometimes reuse and modify the array
|
|
||||||
ns.visitedNodes = append(ns.visitedNodes, &nodeContext)
|
|
||||||
ns.DebugVisitedNodes()
|
|
||||||
return ns.visit(nodeContext)
|
|
||||||
}
|
|
||||||
log.Debug("nope, skip it")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) DebugVisitedNodes() {
|
|
||||||
log.Debug("Visited Nodes:")
|
|
||||||
for _, candidate := range ns.visitedNodes {
|
|
||||||
log.Debug(" - %v", pathStackToString(candidate.PathStack))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
|
|
||||||
log.Debug("checking already visited pathStack: %v", pathStackToString(pathStack))
|
|
||||||
for _, candidate := range ns.visitedNodes {
|
|
||||||
candidatePathStack := candidate.PathStack
|
|
||||||
if patchStacksMatch(candidatePathStack, pathStack) {
|
|
||||||
log.Debug("paths match, already seen it")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
log.Debug("never seen it before!")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool {
|
|
||||||
log.Debug("checking against path: %v", pathStackToString(path1))
|
|
||||||
|
|
||||||
if len(path1) != len(path2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for index, p1Value := range path1 {
|
|
||||||
|
|
||||||
p2Value := path2[index]
|
|
||||||
if p1Value != p2Value {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PathParser interface {
|
|
||||||
ParsePath(path string) []interface{}
|
|
||||||
MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
|
|
||||||
IsPathExpression(pathElement string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathParser struct{}
|
|
||||||
|
|
||||||
func NewPathParser() PathParser {
|
|
||||||
return &pathParser{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchesString(expression string, value string) bool {
|
|
||||||
var prefixMatch = strings.TrimSuffix(expression, "*")
|
|
||||||
if prefixMatch != expression {
|
|
||||||
log.Debug("prefix match, %v", strings.HasPrefix(value, prefixMatch))
|
|
||||||
return strings.HasPrefix(value, prefixMatch)
|
|
||||||
}
|
|
||||||
return value == expression
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathParser) IsPathExpression(pathElement string) bool {
|
|
||||||
return pathElement == "*" || pathElement == "**" || strings.Contains(pathElement, "==")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* node: node that we may traverse/visit
|
|
||||||
* head: path element expression to match against
|
|
||||||
* tail: remaining path element expressions
|
|
||||||
* pathStack: stack of actual paths we've matched to get to node
|
|
||||||
* nodeKey: actual value of this nodes 'key' or index.
|
|
||||||
*/
|
|
||||||
func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool {
|
|
||||||
head := nodeContext.Head
|
|
||||||
if head == "**" || head == "*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
var headString = fmt.Sprintf("%v", head)
|
|
||||||
|
|
||||||
if strings.Contains(headString, "==") && nodeContext.Node.Kind != yaml.ScalarNode {
|
|
||||||
log.Debug("ooh deep recursion time")
|
|
||||||
result := strings.SplitN(headString, "==", 2)
|
|
||||||
path := strings.TrimSpace(result[0])
|
|
||||||
value := strings.TrimSpace(result[1])
|
|
||||||
log.Debug("path %v", path)
|
|
||||||
log.Debug("value %v", value)
|
|
||||||
DebugNode(nodeContext.Node)
|
|
||||||
navigationStrategy := FilterMatchingNodesNavigationStrategy(value)
|
|
||||||
|
|
||||||
navigator := NewDataNavigator(navigationStrategy)
|
|
||||||
err := navigator.Traverse(nodeContext.Node, p.ParsePath(path))
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error deep recursing - ignoring")
|
|
||||||
log.Error(err.Error())
|
|
||||||
}
|
|
||||||
log.Debug("done deep recursing, found %v matches", len(navigationStrategy.GetVisitedNodes()))
|
|
||||||
return len(navigationStrategy.GetVisitedNodes()) > 0
|
|
||||||
} else if strings.Contains(headString, "==") && nodeContext.Node.Kind == yaml.ScalarNode {
|
|
||||||
result := strings.SplitN(headString, "==", 2)
|
|
||||||
path := strings.TrimSpace(result[0])
|
|
||||||
value := strings.TrimSpace(result[1])
|
|
||||||
if path == "." {
|
|
||||||
log.Debug("need to match scalar")
|
|
||||||
return matchesString(value, nodeContext.Node.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if head == "+" {
|
|
||||||
log.Debug("head is +, nodeKey is %v", nodeKey)
|
|
||||||
var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint
|
|
||||||
if err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchesString(headString, nodeKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathParser) ParsePath(path string) []interface{} {
|
|
||||||
var paths = make([]interface{}, 0)
|
|
||||||
if path == "" {
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
return p.parsePathAccum(paths, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathParser) parsePathAccum(paths []interface{}, remaining string) []interface{} {
|
|
||||||
head, tail := p.nextYamlPath(remaining)
|
|
||||||
if tail == "" {
|
|
||||||
return append(paths, head)
|
|
||||||
}
|
|
||||||
return p.parsePathAccum(append(paths, head), tail)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathParser) nextYamlPath(path string) (pathElement interface{}, remaining string) {
|
|
||||||
switch path[0] {
|
|
||||||
case '[':
|
|
||||||
// e.g [0].blah.cat -> we need to return "0" and "blah.cat"
|
|
||||||
var value, remainingBit = p.search(path[1:], []uint8{']'}, true)
|
|
||||||
var number, errParsingInt = strconv.ParseInt(value, 10, 64) // nolint
|
|
||||||
if errParsingInt == nil {
|
|
||||||
return number, remainingBit
|
|
||||||
}
|
|
||||||
return value, remainingBit
|
|
||||||
case '"':
|
|
||||||
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
|
|
||||||
return p.search(path[1:], []uint8{'"'}, true)
|
|
||||||
case '(':
|
|
||||||
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
|
|
||||||
return p.search(path[1:], []uint8{')'}, true)
|
|
||||||
default:
|
|
||||||
// e.g "a.blah.cat" -> return "a" and "blah.cat"
|
|
||||||
return p.search(path[0:], []uint8{'.', '[', '"', '('}, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathParser) search(path string, matchingChars []uint8, skipNext bool) (pathElement string, remaining string) {
|
|
||||||
for i := 0; i < len(path); i++ {
|
|
||||||
var char = path[i]
|
|
||||||
if p.contains(matchingChars, char) {
|
|
||||||
var remainingStart = i + 1
|
|
||||||
if skipNext {
|
|
||||||
remainingStart = remainingStart + 1
|
|
||||||
} else if !skipNext && char != '.' {
|
|
||||||
remainingStart = i
|
|
||||||
}
|
|
||||||
if remainingStart > len(path) {
|
|
||||||
remainingStart = len(path)
|
|
||||||
}
|
|
||||||
return path[0:i], path[remainingStart:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pathParser) contains(matchingChars []uint8, candidate uint8) bool {
|
|
||||||
for _, a := range matchingChars {
|
|
||||||
if a == candidate {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mikefarah/yq/v3/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
var parser = NewPathParser()
|
|
||||||
|
|
||||||
var parsePathsTests = []struct {
|
|
||||||
path string
|
|
||||||
expectedPaths []interface{}
|
|
||||||
}{
|
|
||||||
{"a.b", append(make([]interface{}, 0), "a", "b")},
|
|
||||||
{"a.b.**", append(make([]interface{}, 0), "a", "b", "**")},
|
|
||||||
{"a.b.*", append(make([]interface{}, 0), "a", "b", "*")},
|
|
||||||
{"a.b[0]", append(make([]interface{}, 0), "a", "b", int64(0))},
|
|
||||||
{"a.b.0", append(make([]interface{}, 0), "a", "b", "0")},
|
|
||||||
{"a.b.d[+]", append(make([]interface{}, 0), "a", "b", "d", "+")},
|
|
||||||
{"a", append(make([]interface{}, 0), "a")},
|
|
||||||
{"a.b.c", append(make([]interface{}, 0), "a", "b", "c")},
|
|
||||||
{"\"a.b\".c", append(make([]interface{}, 0), "a.b", "c")},
|
|
||||||
{"a.\"b.c\".d", append(make([]interface{}, 0), "a", "b.c", "d")},
|
|
||||||
{"[1].a.d", append(make([]interface{}, 0), int64(1), "a", "d")},
|
|
||||||
{"a[0].c", append(make([]interface{}, 0), "a", int64(0), "c")},
|
|
||||||
{"[0]", append(make([]interface{}, 0), int64(0))},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserParsePath(t *testing.T) {
|
|
||||||
for _, tt := range parsePathsTests {
|
|
||||||
test.AssertResultComplex(t, tt.expectedPaths, parser.ParsePath(tt.path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementSplat(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "*"}
|
|
||||||
test.AssertResult(t, true, parser.MatchesNextPathElement(node, ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementDeepSplat(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "**"}
|
|
||||||
test.AssertResult(t, true, parser.MatchesNextPathElement(node, ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementAppendArrayValid(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "+"}
|
|
||||||
test.AssertResult(t, true, parser.MatchesNextPathElement(node, "3"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementAppendArrayInvalid(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "+"}
|
|
||||||
test.AssertResult(t, false, parser.MatchesNextPathElement(node, "cat"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementPrefixMatchesWhole(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "cat*"}
|
|
||||||
test.AssertResult(t, true, parser.MatchesNextPathElement(node, "cat"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementPrefixMatchesStart(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "cat*"}
|
|
||||||
test.AssertResult(t, true, parser.MatchesNextPathElement(node, "caterpillar"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementPrefixMismatch(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "cat*"}
|
|
||||||
test.AssertResult(t, false, parser.MatchesNextPathElement(node, "dog"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementExactMatch(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "farahtek"}
|
|
||||||
test.AssertResult(t, true, parser.MatchesNextPathElement(node, "farahtek"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathParserMatchesNextPathElementExactMismatch(t *testing.T) {
|
|
||||||
var node = NodeContext{Head: "farahtek"}
|
|
||||||
test.AssertResult(t, false, parser.MatchesNextPathElement(node, "othertek"))
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
import "gopkg.in/yaml.v3"
|
|
||||||
|
|
||||||
func ReadForMergeNavigationStrategy(arrayMergeStrategy ArrayMergeStrategy) NavigationStrategy {
|
|
||||||
return &NavigationStrategyImpl{
|
|
||||||
visitedNodes: []*NodeContext{},
|
|
||||||
pathParser: NewPathParser(),
|
|
||||||
followAlias: func(nodeContext NodeContext) bool {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
visit: func(nodeContext NodeContext) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
|
|
||||||
if nodeContext.Node.Kind == yaml.SequenceNode && arrayMergeStrategy == OverwriteArrayMergeStrategy {
|
|
||||||
nodeContext.IsMiddleNode = false
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var isInArray = false
|
|
||||||
if len(nodeContext.PathStack) > 0 {
|
|
||||||
var lastElement = nodeContext.PathStack[len(nodeContext.PathStack)-1]
|
|
||||||
switch lastElement.(type) {
|
|
||||||
case int:
|
|
||||||
isInArray = true
|
|
||||||
default:
|
|
||||||
isInArray = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arrayMergeStrategy == UpdateArrayMergeStrategy || !isInArray
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
func ReadNavigationStrategy() NavigationStrategy {
|
|
||||||
return &NavigationStrategyImpl{
|
|
||||||
visitedNodes: []*NodeContext{},
|
|
||||||
pathParser: NewPathParser(),
|
|
||||||
visit: func(nodeContext NodeContext) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
62
pkg/yqlib/treeops/candidate_node.go
Normal file
62
pkg/yqlib/treeops/candidate_node.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package treeops
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CandidateNode struct {
|
||||||
|
Node *yaml.Node // the actual node
|
||||||
|
Path []interface{} /// the path we took to get to this node
|
||||||
|
Document uint // the document index of this node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *CandidateNode) GetKey() string {
|
||||||
|
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *CandidateNode) PathStackToString() string {
|
||||||
|
return mergePathStackToString(n.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergePathStackToString(pathStack []interface{}) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
for index, path := range pathStack {
|
||||||
|
switch path.(type) {
|
||||||
|
case int, int64:
|
||||||
|
// if arrayMergeStrategy == AppendArrayMergeStrategy {
|
||||||
|
// sb.WriteString("[+]")
|
||||||
|
// } else {
|
||||||
|
sb.WriteString(fmt.Sprintf("[%v]", path))
|
||||||
|
// }
|
||||||
|
|
||||||
|
default:
|
||||||
|
s := fmt.Sprintf("%v", path)
|
||||||
|
var _, errParsingInt = strconv.ParseInt(s, 10, 64) // nolint
|
||||||
|
|
||||||
|
hasSpecial := strings.Contains(s, ".") || strings.Contains(s, "[") || strings.Contains(s, "]") || strings.Contains(s, "\"")
|
||||||
|
hasDoubleQuotes := strings.Contains(s, "\"")
|
||||||
|
wrappingCharacterStart := "\""
|
||||||
|
wrappingCharacterEnd := "\""
|
||||||
|
if hasDoubleQuotes {
|
||||||
|
wrappingCharacterStart = "("
|
||||||
|
wrappingCharacterEnd = ")"
|
||||||
|
}
|
||||||
|
if hasSpecial || errParsingInt == nil {
|
||||||
|
sb.WriteString(wrappingCharacterStart)
|
||||||
|
}
|
||||||
|
sb.WriteString(s)
|
||||||
|
if hasSpecial || errParsingInt == nil {
|
||||||
|
sb.WriteString(wrappingCharacterEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if index < len(pathStack)-1 {
|
||||||
|
sb.WriteString(".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
@ -35,7 +35,7 @@ func (d *dataTreeNavigator) traverse(matchMap *orderedmap.OrderedMap, pathNode *
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, n := range newNodes {
|
for _, n := range newNodes {
|
||||||
matchingNodeMap.Set(n.getKey(), n)
|
matchingNodeMap.Set(n.GetKey(), n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat
|
|||||||
var matchingNodeMap = orderedmap.NewOrderedMap()
|
var matchingNodeMap = orderedmap.NewOrderedMap()
|
||||||
|
|
||||||
for _, n := range matchingNodes {
|
for _, n := range matchingNodes {
|
||||||
matchingNodeMap.Set(n.getKey(), n)
|
matchingNodeMap.Set(n.GetKey(), n)
|
||||||
}
|
}
|
||||||
|
|
||||||
matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode)
|
matchedNodes, err := d.getMatchingNodes(matchingNodeMap, pathNode)
|
||||||
@ -63,6 +63,10 @@ func (d *dataTreeNavigator) GetMatchingNodes(matchingNodes []*CandidateNode, pat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
|
func (d *dataTreeNavigator) getMatchingNodes(matchingNodes *orderedmap.OrderedMap, pathNode *PathTreeNode) (*orderedmap.OrderedMap, error) {
|
||||||
|
if pathNode == nil {
|
||||||
|
log.Debugf("getMatchingNodes - nothing to do")
|
||||||
|
return matchingNodes, nil
|
||||||
|
}
|
||||||
log.Debugf("Processing Path: %v", pathNode.PathElement.toString())
|
log.Debugf("Processing Path: %v", pathNode.PathElement.toString())
|
||||||
if pathNode.PathElement.PathElementType == SelfReference {
|
if pathNode.PathElement.PathElementType == SelfReference {
|
||||||
return matchingNodes, nil
|
return matchingNodes, nil
|
||||||
|
@ -17,7 +17,7 @@ func DeleteChildOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordered
|
|||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
candidate := el.Value.(*CandidateNode)
|
candidate := el.Value.(*CandidateNode)
|
||||||
elMap := orderedmap.NewOrderedMap()
|
elMap := orderedmap.NewOrderedMap()
|
||||||
elMap.Set(candidate.getKey(), candidate)
|
elMap.Set(candidate.GetKey(), candidate)
|
||||||
nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs)
|
nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs)
|
||||||
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
|
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -50,9 +50,9 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa
|
|||||||
Document: candidate.Document,
|
Document: candidate.Document,
|
||||||
Path: append(candidate.Path, key.Value),
|
Path: append(candidate.Path, key.Value),
|
||||||
}
|
}
|
||||||
_, shouldDelete := nodesToDelete.Get(childCandidate.getKey())
|
_, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
|
||||||
|
|
||||||
log.Debugf("shouldDelete %v ? %v", childCandidate.getKey(), shouldDelete)
|
log.Debugf("shouldDelete %v ? %v", childCandidate.GetKey(), shouldDelete)
|
||||||
|
|
||||||
if !shouldDelete {
|
if !shouldDelete {
|
||||||
newContents = append(newContents, key, value)
|
newContents = append(newContents, key, value)
|
||||||
@ -76,7 +76,7 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.Ordered
|
|||||||
Path: append(candidate.Path, index),
|
Path: append(candidate.Path, index),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, shouldDelete := nodesToDelete.Get(childCandidate.getKey())
|
_, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
|
||||||
if !shouldDelete {
|
if !shouldDelete {
|
||||||
newContents = append(newContents, value)
|
newContents = append(newContents, value)
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,6 @@ import (
|
|||||||
|
|
||||||
var log = logging.MustGetLogger("yq-treeops")
|
var log = logging.MustGetLogger("yq-treeops")
|
||||||
|
|
||||||
type CandidateNode struct {
|
|
||||||
Node *yaml.Node // the actual node
|
|
||||||
Path []interface{} /// the path we took to get to this node
|
|
||||||
Document uint // the document index of this node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *CandidateNode) getKey() string {
|
|
||||||
return fmt.Sprintf("%v - %v", n.Document, n.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PathElementType uint32
|
type PathElementType uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -76,7 +66,7 @@ func (p *PathElement) toString() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type YqTreeLib interface {
|
type YqTreeLib interface {
|
||||||
Get(rootNode *yaml.Node, path string) ([]*CandidateNode, error)
|
Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error)
|
||||||
// GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error)
|
// GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error)
|
||||||
// Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
|
// Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
|
||||||
// New(path string) yaml.Node
|
// New(path string) yaml.Node
|
||||||
@ -85,10 +75,24 @@ type YqTreeLib interface {
|
|||||||
// MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
|
// MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewYqTreeLib() YqTreeLib {
|
||||||
|
return &lib{treeCreator: NewPathTreeCreator()}
|
||||||
|
}
|
||||||
|
|
||||||
type lib struct {
|
type lib struct {
|
||||||
treeCreator PathTreeCreator
|
treeCreator PathTreeCreator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *lib) Get(document int, documentNode *yaml.Node, path string) ([]*CandidateNode, error) {
|
||||||
|
nodes := []*CandidateNode{&CandidateNode{Node: documentNode.Content[0], Document: 0}}
|
||||||
|
navigator := NewDataTreeNavigator(NavigationPrefs{})
|
||||||
|
pathNode, errPath := l.treeCreator.ParsePath(path)
|
||||||
|
if errPath != nil {
|
||||||
|
return nil, errPath
|
||||||
|
}
|
||||||
|
return navigator.GetMatchingNodes(nodes, pathNode)
|
||||||
|
}
|
||||||
|
|
||||||
//use for debugging only
|
//use for debugging only
|
||||||
func NodesToString(collection *orderedmap.OrderedMap) string {
|
func NodesToString(collection *orderedmap.OrderedMap) string {
|
||||||
if !log.IsEnabledFor(logging.DEBUG) {
|
if !log.IsEnabledFor(logging.DEBUG) {
|
||||||
|
@ -24,7 +24,7 @@ func AssignOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap,
|
|||||||
}
|
}
|
||||||
for el := lhs.Front(); el != nil; el = el.Next() {
|
for el := lhs.Front(); el != nil; el = el.Next() {
|
||||||
node := el.Value.(*CandidateNode)
|
node := el.Value.(*CandidateNode)
|
||||||
log.Debugf("Assiging %v to %v", node.getKey(), pathNode.Rhs.PathElement.StringValue)
|
log.Debugf("Assiging %v to %v", node.GetKey(), pathNode.Rhs.PathElement.StringValue)
|
||||||
node.Node.Value = pathNode.Rhs.PathElement.StringValue
|
node.Node.Value = pathNode.Rhs.PathElement.StringValue
|
||||||
}
|
}
|
||||||
return lhs, nil
|
return lhs, nil
|
||||||
@ -41,7 +41,7 @@ func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p
|
|||||||
}
|
}
|
||||||
for el := rhs.Front(); el != nil; el = el.Next() {
|
for el := rhs.Front(); el != nil; el = el.Next() {
|
||||||
node := el.Value.(*CandidateNode)
|
node := el.Value.(*CandidateNode)
|
||||||
lhs.Set(node.getKey(), node)
|
lhs.Set(node.GetKey(), node)
|
||||||
}
|
}
|
||||||
return lhs, nil
|
return lhs, nil
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordere
|
|||||||
|
|
||||||
func splatNode(d *dataTreeNavigator, candidate *CandidateNode) (*orderedmap.OrderedMap, error) {
|
func splatNode(d *dataTreeNavigator, candidate *CandidateNode) (*orderedmap.OrderedMap, error) {
|
||||||
elMap := orderedmap.NewOrderedMap()
|
elMap := orderedmap.NewOrderedMap()
|
||||||
elMap.Set(candidate.getKey(), candidate)
|
elMap.Set(candidate.GetKey(), candidate)
|
||||||
//need to splat matching nodes, then search through them
|
//need to splat matching nodes, then search through them
|
||||||
splatter := &PathTreeNode{PathElement: &PathElement{
|
splatter := &PathTreeNode{PathElement: &PathElement{
|
||||||
PathElementType: PathKey,
|
PathElementType: PathKey,
|
||||||
@ -112,7 +112,7 @@ func CountOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNo
|
|||||||
length := childMatches.Len()
|
length := childMatches.Len()
|
||||||
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
|
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
|
||||||
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
|
||||||
results.Set(candidate.getKey(), lengthCand)
|
results.Set(candidate.GetKey(), lengthCand)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ func findMatchingChildren(d *dataTreeNavigator, results *orderedmap.OrderedMap,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
children = orderedmap.NewOrderedMap()
|
children = orderedmap.NewOrderedMap()
|
||||||
children.Set(candidate.getKey(), candidate)
|
children.Set(candidate.GetKey(), candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
for childEl := children.Front(); childEl != nil; childEl = childEl.Next() {
|
for childEl := children.Front(); childEl != nil; childEl = childEl.Next() {
|
||||||
|
@ -39,6 +39,10 @@ func (p *pathTreeCreator) ParsePath(path string) (*PathTreeNode, error) {
|
|||||||
func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) {
|
func (p *pathTreeCreator) CreatePathTree(postFixPath []*PathElement) (*PathTreeNode, error) {
|
||||||
var stack = make([]*PathTreeNode, 0)
|
var stack = make([]*PathTreeNode, 0)
|
||||||
|
|
||||||
|
if len(postFixPath) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, pathElement := range postFixPath {
|
for _, pathElement := range postFixPath {
|
||||||
var newNode = PathTreeNode{PathElement: pathElement}
|
var newNode = PathTreeNode{PathElement: pathElement}
|
||||||
if pathElement.PathElementType == Operation {
|
if pathElement.PathElementType == Operation {
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
package yqlib
|
|
||||||
|
|
||||||
func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
|
|
||||||
return &NavigationStrategyImpl{
|
|
||||||
visitedNodes: []*NodeContext{},
|
|
||||||
pathParser: NewPathParser(),
|
|
||||||
followAlias: func(nodeContext NodeContext) bool {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
autoCreateMap: func(nodeContext NodeContext) bool {
|
|
||||||
return autoCreate
|
|
||||||
},
|
|
||||||
visit: func(nodeContext NodeContext) error {
|
|
||||||
node := nodeContext.Node
|
|
||||||
changesToApply := updateCommand.Value
|
|
||||||
if updateCommand.Overwrite || node.Value == "" {
|
|
||||||
log.Debug("going to update")
|
|
||||||
DebugNode(node)
|
|
||||||
log.Debug("with")
|
|
||||||
DebugNode(changesToApply)
|
|
||||||
if !updateCommand.DontUpdateNodeValue {
|
|
||||||
node.Value = changesToApply.Value
|
|
||||||
}
|
|
||||||
node.Tag = changesToApply.Tag
|
|
||||||
node.Kind = changesToApply.Kind
|
|
||||||
node.Style = changesToApply.Style
|
|
||||||
if !updateCommand.DontUpdateNodeContent {
|
|
||||||
node.Content = changesToApply.Content
|
|
||||||
}
|
|
||||||
node.Anchor = changesToApply.Anchor
|
|
||||||
node.Alias = changesToApply.Alias
|
|
||||||
if updateCommand.CommentsMergeStrategy != IgnoreCommentsMergeStrategy {
|
|
||||||
node.HeadComment = changesToApply.HeadComment
|
|
||||||
node.LineComment = changesToApply.LineComment
|
|
||||||
node.FootComment = changesToApply.FootComment
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user