read command

This commit is contained in:
Mike Farah 2020-10-13 12:51:37 +11:00
parent 6a0a4efa7b
commit d19e9f6917
37 changed files with 2308 additions and 3604 deletions

View File

@ -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
} // }

View File

@ -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)
} // }

View File

@ -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()

View File

@ -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())
} // }

View File

@ -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 "))
} // }

View File

@ -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

View File

@ -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)
} // }

View File

@ -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)
} // }

View File

@ -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)
} // }

View File

@ -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 "))
} // }

View File

@ -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")

View File

@ -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)
} }

View File

@ -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

View File

@ -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 {

View File

@ -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())
} // }

File diff suppressed because it is too large Load Diff

5
pkg/yqlib/constants.go Normal file
View File

@ -0,0 +1,5 @@
package yqlib
import "gopkg.in/op/go-logging.v1"
var log = logging.MustGetLogger("yq-lib")

View File

@ -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))
}

View File

@ -1 +0,0 @@
package yqlib

View File

@ -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:]...)
}

View File

@ -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)
},
}
}

View File

@ -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)
}
}

View File

@ -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")
// }
// })
}

View File

@ -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
},
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"))
}

View File

@ -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
},
}
}

View File

@ -1,11 +0,0 @@
package yqlib
func ReadNavigationStrategy() NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
visit: func(nodeContext NodeContext) error {
return nil
},
}
}

View 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()
}

View File

@ -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

View File

@ -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)
} }

View File

@ -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) {

View File

@ -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() {

View File

@ -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 {

View File

@ -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
},
}
}