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
import (
"bufio"
"bytes"
"os"
"strings"
// import (
// "bufio"
// "bytes"
// "os"
// "strings"
"github.com/kylelemons/godebug/diff"
"github.com/mikefarah/yq/v3/pkg/yqlib"
errors "github.com/pkg/errors"
"github.com/spf13/cobra"
)
// "github.com/kylelemons/godebug/diff"
// "github.com/mikefarah/yq/v3/pkg/yqlib"
// errors "github.com/pkg/errors"
// "github.com/spf13/cobra"
// )
// turn off for unit tests :(
var forceOsExit = true
// // turn off for unit tests :(
// var forceOsExit = true
func createCompareCmd() *cobra.Command {
var cmdCompare = &cobra.Command{
Use: "compare [yaml_file_a] [yaml_file_b]",
Aliases: []string{"x"},
Short: "yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'",
Example: `
yq x - data2.yml # reads from stdin
yq x -pp dataA.yaml dataB.yaml '**' # compare paths
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.",
RunE: compareDocuments,
}
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(&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(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors")
return cmdCompare
}
// func createCompareCmd() *cobra.Command {
// var cmdCompare = &cobra.Command{
// Use: "compare [yaml_file_a] [yaml_file_b]",
// Aliases: []string{"x"},
// Short: "yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'",
// Example: `
// yq x - data2.yml # reads from stdin
// yq x -pp dataA.yaml dataB.yaml '**' # compare paths
// 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.",
// RunE: compareDocuments,
// }
// 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(&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(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors")
// return cmdCompare
// }
func compareDocuments(cmd *cobra.Command, args []string) error {
var path = ""
// func compareDocuments(cmd *cobra.Command, args []string) error {
// var path = ""
if len(args) < 2 {
return errors.New("Must provide at 2 yaml files")
} else if len(args) > 2 {
path = args[2]
}
// if len(args) < 2 {
// return errors.New("Must provide at 2 yaml files")
// } else if len(args) > 2 {
// path = args[2]
// }
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
// if errorParsingDocIndex != nil {
// return errorParsingDocIndex
// }
var matchingNodesA []*yqlib.NodeContext
var matchingNodesB []*yqlib.NodeContext
var errorDoingThings error
// var matchingNodesA []*yqlib.NodeContext
// var matchingNodesB []*yqlib.NodeContext
// var errorDoingThings error
matchingNodesA, errorDoingThings = readYamlFile(args[0], path, updateAll, docIndexInt)
// matchingNodesA, errorDoingThings = readYamlFile(args[0], path, updateAll, docIndexInt)
if errorDoingThings != nil {
return errorDoingThings
}
// if errorDoingThings != nil {
// return errorDoingThings
// }
matchingNodesB, errorDoingThings = readYamlFile(args[1], path, updateAll, docIndexInt)
if errorDoingThings != nil {
return errorDoingThings
}
// matchingNodesB, errorDoingThings = readYamlFile(args[1], path, updateAll, docIndexInt)
// if errorDoingThings != nil {
// return errorDoingThings
// }
var dataBufferA bytes.Buffer
var dataBufferB bytes.Buffer
errorDoingThings = printResults(matchingNodesA, bufio.NewWriter(&dataBufferA))
if errorDoingThings != nil {
return errorDoingThings
}
errorDoingThings = printResults(matchingNodesB, bufio.NewWriter(&dataBufferB))
if errorDoingThings != nil {
return errorDoingThings
}
// var dataBufferA bytes.Buffer
// var dataBufferB bytes.Buffer
// errorDoingThings = printResults(matchingNodesA, bufio.NewWriter(&dataBufferA))
// if errorDoingThings != nil {
// return errorDoingThings
// }
// errorDoingThings = printResults(matchingNodesB, bufio.NewWriter(&dataBufferB))
// if errorDoingThings != nil {
// 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 {
cmd.Print(diffString)
cmd.Print("\n")
if forceOsExit {
os.Exit(1)
}
}
return nil
}
// if len(diffString) > 1 {
// cmd.Print(diffString)
// cmd.Print("\n")
// if forceOsExit {
// os.Exit(1)
// }
// }
// return nil
// }

View File

@ -1,115 +1,115 @@
package cmd
import (
"testing"
// import (
// "testing"
"github.com/mikefarah/yq/v3/test"
)
// "github.com/mikefarah/yq/v3/test"
// )
func TestCompareSameCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := ``
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestCompareSameCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1.yaml")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := ``
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestCompareIgnoreCommentsCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := ``
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestCompareIgnoreCommentsCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := ``
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestCompareDontIgnoreCommentsCmd(t *testing.T) {
forceOsExit = false
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml")
// func TestCompareDontIgnoreCommentsCmd(t *testing.T) {
// forceOsExit = false
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml")
expectedOutput := `-a: simple # just the best
+a: simple
b: [1, 2]
c:
test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `-a: simple # just the best
// +a: simple
// b: [1, 2]
// c:
// test: 1
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := ``
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := ``
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestCompareDontExplodeAnchorsCmd(t *testing.T) {
forceOsExit = false
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
// func TestCompareDontExplodeAnchorsCmd(t *testing.T) {
// forceOsExit = false
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
expectedOutput := `-foo: &foo
+foo:
a: 1
foobar:
- !!merge <<: *foo
+ a: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `-foo: &foo
// +foo:
// a: 1
// foobar:
// - !!merge <<: *foo
// + a: 1
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestCompareDifferentCmd(t *testing.T) {
forceOsExit = false
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data3.yaml")
// func TestCompareDifferentCmd(t *testing.T) {
// forceOsExit = false
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data3.yaml")
expectedOutput := `-a: simple # just the best
-b: [1, 2]
+a: "simple" # just the best
+b: [1, 3]
c:
test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `-a: simple # just the best
// -b: [1, 2]
// +a: "simple" # just the best
// +b: [1, 3]
// c:
// test: 1
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestComparePrettyCmd(t *testing.T) {
forceOsExit = false
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare -P ../examples/data1.yaml ../examples/data3.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := ` a: simple # just the best
b:
- 1
- - 2
+ - 3
c:
test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestComparePrettyCmd(t *testing.T) {
// forceOsExit = false
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "compare -P ../examples/data1.yaml ../examples/data3.yaml")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := ` a: simple # just the best
// b:
// - 1
// - - 2
// + - 3
// c:
// test: 1
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestComparePathsCmd(t *testing.T) {
forceOsExit = false
cmd := getRootCommand()
result := test.RunCmd(cmd, "compare -P -ppv ../examples/data1.yaml ../examples/data3.yaml **")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := ` a: simple # just the best
b.[0]: 1
-b.[1]: 2
+b.[1]: 3
c.test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestComparePathsCmd(t *testing.T) {
// forceOsExit = false
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "compare -P -ppv ../examples/data1.yaml ../examples/data3.yaml **")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := ` a: simple # just the best
// b.[0]: 1
// -b.[1]: 2
// +b.[1]: 3
// c.test: 1
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }

View File

@ -2,6 +2,7 @@ package cmd
import (
"github.com/mikefarah/yq/v3/pkg/yqlib"
"github.com/mikefarah/yq/v3/pkg/yqlib/treeops"
logging "gopkg.in/op/go-logging.v1"
)
@ -32,5 +33,5 @@ var verbose = false
var version = false
var docIndex = "0"
var log = logging.MustGetLogger("yq")
var lib = yqlib.NewYqLib()
var lib = treeops.NewYqTreeLib()
var valueParser = yqlib.NewValueParser()

View File

@ -1,41 +1,41 @@
package cmd
import (
"github.com/mikefarah/yq/v3/pkg/yqlib"
errors "github.com/pkg/errors"
"github.com/spf13/cobra"
)
// import (
// "github.com/mikefarah/yq/v3/pkg/yqlib"
// errors "github.com/pkg/errors"
// "github.com/spf13/cobra"
// )
func createDeleteCmd() *cobra.Command {
var cmdDelete = &cobra.Command{
Use: "delete [yaml_file] [path_expression]",
Aliases: []string{"d"},
Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'",
Example: `
yq delete things.yaml 'a.b.c'
yq delete things.yaml 'a.*.c'
yq delete things.yaml 'a.(child.subchild==co*).c'
yq delete things.yaml 'a.**'
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 d -i things.yaml 'a.b.c'
`,
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.
`,
RunE: deleteProperty,
}
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)")
return cmdDelete
}
// func createDeleteCmd() *cobra.Command {
// var cmdDelete = &cobra.Command{
// Use: "delete [yaml_file] [path_expression]",
// Aliases: []string{"d"},
// Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'",
// Example: `
// yq delete things.yaml 'a.b.c'
// yq delete things.yaml 'a.*.c'
// yq delete things.yaml 'a.(child.subchild==co*).c'
// yq delete things.yaml 'a.**'
// 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 d -i things.yaml 'a.b.c'
// `,
// 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.
// `,
// RunE: deleteProperty,
// }
// 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)")
// return cmdDelete
// }
func deleteProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide <filename> <path_to_delete>")
}
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1)
updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]}
// func deleteProperty(cmd *cobra.Command, args []string) error {
// if len(args) < 2 {
// return errors.New("Must provide <filename> <path_to_delete>")
// }
// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 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
import (
"fmt"
"strings"
"testing"
// import (
// "fmt"
// "strings"
// "testing"
"github.com/mikefarah/yq/v3/test"
)
// "github.com/mikefarah/yq/v3/test"
// )
func TestDeleteYamlCmd(t *testing.T) {
content := `a: 2
b:
c: things
d: something else
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteYamlCmd(t *testing.T) {
// content := `a: 2
// b:
// c: things
// d: something else
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `a: 2
b:
d: something else
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `a: 2
// b:
// d: something else
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteDeepDoesNotExistCmd(t *testing.T) {
content := `a: 2`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteDeepDoesNotExistCmd(t *testing.T) {
// content := `a: 2`
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `a: 2
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `a: 2
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteSplatYaml(t *testing.T) {
content := `a: other
b: [3, 4]
c:
toast: leave
test: 1
tell: 1
tasty.taco: cool
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteSplatYaml(t *testing.T) {
// content := `a: other
// b: [3, 4]
// c:
// toast: leave
// test: 1
// tell: 1
// tasty.taco: cool
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `a: other
b: [3, 4]
c:
toast: leave
tasty.taco: cool
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `a: other
// b: [3, 4]
// c:
// toast: leave
// tasty.taco: cool
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteSplatArrayYaml(t *testing.T) {
content := `a: 2
b:
hi:
- thing: item1
name: fred
- thing: item2
name: sam
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteSplatArrayYaml(t *testing.T) {
// content := `a: 2
// b:
// hi:
// - thing: item1
// name: fred
// - thing: item2
// name: sam
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `a: 2
b:
hi:
- name: fred
- name: sam
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `a: 2
// b:
// hi:
// - name: fred
// - name: sam
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteDeepSplatArrayYaml(t *testing.T) {
content := `thing: 123
b:
hi:
- thing: item1
name: fred
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteDeepSplatArrayYaml(t *testing.T) {
// content := `thing: 123
// b:
// hi:
// - thing: item1
// name: fred
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `b:
hi:
- name: fred
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `b:
// hi:
// - name: fred
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteSplatPrefixYaml(t *testing.T) {
content := `a: 2
b:
hi:
c: things
d: something else
there:
c: more things
d: more something else
there2:
c: more things also
d: more something else also
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteSplatPrefixYaml(t *testing.T) {
// content := `a: 2
// b:
// hi:
// c: things
// d: something else
// there:
// c: more things
// d: more something else
// there2:
// c: more things also
// d: more something else also
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `a: 2
b:
hi:
c: things
d: something else
there:
d: more something else
there2:
d: more something else also
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `a: 2
// b:
// hi:
// c: things
// d: something else
// there:
// d: more something else
// there2:
// d: more something else also
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteYamlArrayCmd(t *testing.T) {
content := `- 1
- 2
- 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteYamlArrayCmd(t *testing.T) {
// content := `- 1
// - 2
// - 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s [1]", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s [1]", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `- 1
- 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `- 1
// - 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteYamlArrayExpressionCmd(t *testing.T) {
content := `- name: fred
- name: cat
- name: thing
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteYamlArrayExpressionCmd(t *testing.T) {
// content := `- name: fred
// - name: cat
// - name: thing
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s (name==cat)", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s (name==cat)", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `- name: fred
- name: thing
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `- name: fred
// - name: thing
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteYamlMulti(t *testing.T) {
content := `apples: great
---
- 1
- 2
- 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteYamlMulti(t *testing.T) {
// content := `apples: great
// ---
// - 1
// - 2
// - 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename))
if result.Error != nil {
t.Error(result.Error)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
expectedOutput := `apples: great
---
- 1
- 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
// expectedOutput := `apples: great
// ---
// - 1
// - 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestDeleteYamlMultiAllCmd(t *testing.T) {
content := `b:
c: 3
apples: great
---
apples: great
something: else
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestDeleteYamlMultiAllCmd(t *testing.T) {
// content := `b:
// c: 3
// apples: great
// ---
// apples: great
// something: else
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
---
something: else`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 3
// ---
// something: else`
// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
// }

View File

@ -1,124 +1,124 @@
package cmd
import (
"github.com/mikefarah/yq/v3/pkg/yqlib"
errors "github.com/pkg/errors"
"github.com/spf13/cobra"
yaml "gopkg.in/yaml.v3"
)
// import (
// "github.com/mikefarah/yq/v3/pkg/yqlib"
// errors "github.com/pkg/errors"
// "github.com/spf13/cobra"
// yaml "gopkg.in/yaml.v3"
// )
func createMergeCmd() *cobra.Command {
var cmdMerge = &cobra.Command{
Use: "merge [initial_yaml_file] [additional_yaml_file]...",
Aliases: []string{"m"},
Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml",
Example: `
yq merge things.yaml other.yaml
yq merge --inplace things.yaml other.yaml
yq m -i things.yaml other.yaml
yq m --overwrite things.yaml other.yaml
yq m -i -x things.yaml other.yaml
yq m -i -a=append 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).
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
// func createMergeCmd() *cobra.Command {
// var cmdMerge = &cobra.Command{
// Use: "merge [initial_yaml_file] [additional_yaml_file]...",
// Aliases: []string{"m"},
// Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml",
// Example: `
// yq merge things.yaml other.yaml
// yq merge --inplace things.yaml other.yaml
// yq m -i things.yaml other.yaml
// yq m --overwrite things.yaml other.yaml
// yq m -i -x things.yaml other.yaml
// yq m -i -a=append 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).
// 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 append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
`,
RunE: mergeProperties,
}
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(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries")
cmdMerge.PersistentFlags().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite)
update: recursively update arrays by their index
append: concatenate arrays together
overwrite: replace arrays
`)
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
ignore: leave comments as-is in the original
append: append comments together
overwrite: overwrite comments completely
`)
cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdMerge
}
// 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.
// `,
// RunE: mergeProperties,
// }
// 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(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries")
// cmdMerge.PersistentFlags().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite)
// update: recursively update arrays by their index
// append: concatenate arrays together
// overwrite: replace arrays
// `)
// 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
// ignore: leave comments as-is in the original
// append: append comments together
// overwrite: overwrite comments completely
// `)
// cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
// return cmdMerge
// }
/*
* We don't deeply traverse arrays when appending a merge, instead we want to
* append the entire array element.
*/
func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) {
return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) {
return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy)
}
}
// /*
// * We don't deeply traverse arrays when appending a merge, instead we want to
// * append the entire array element.
// */
// func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) {
// return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) {
// return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy)
// }
// }
func mergeProperties(cmd *cobra.Command, args []string) error {
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
// func mergeProperties(cmd *cobra.Command, args []string) error {
// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
if len(args) < 1 {
return errors.New("Must provide at least 1 yaml file")
}
var arrayMergeStrategy yqlib.ArrayMergeStrategy
// if len(args) < 1 {
// return errors.New("Must provide at least 1 yaml file")
// }
// var arrayMergeStrategy yqlib.ArrayMergeStrategy
switch arrayMergeStrategyFlag {
case "update":
arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy
case "append":
arrayMergeStrategy = yqlib.AppendArrayMergeStrategy
case "overwrite":
arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy
default:
return errors.New("Array merge strategy must be one of: update/append/overwrite")
}
// switch arrayMergeStrategyFlag {
// case "update":
// arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy
// case "append":
// arrayMergeStrategy = yqlib.AppendArrayMergeStrategy
// case "overwrite":
// arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy
// default:
// return errors.New("Array merge strategy must be one of: update/append/overwrite")
// }
var commentsMergeStrategy yqlib.CommentsMergeStrategy
// var commentsMergeStrategy yqlib.CommentsMergeStrategy
switch commentsMergeStrategyFlag {
case "setWhenBlank":
commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy
case "ignore":
commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy
case "append":
commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy
case "overwrite":
commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy
default:
return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite")
}
// switch commentsMergeStrategyFlag {
// case "setWhenBlank":
// commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy
// case "ignore":
// commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy
// case "append":
// commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy
// case "overwrite":
// commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy
// default:
// return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite")
// }
if len(args) > 1 {
// first generate update commands from the file
var filesToMerge = args[1:]
// if len(args) > 1 {
// // first generate update commands from the file
// var filesToMerge = args[1:]
for _, fileToMerge := range filesToMerge {
matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0)
if errorProcessingFile != nil {
return errorProcessingFile
}
log.Debugf("finished reading for merge!")
for _, matchingNode := range matchingNodes {
log.Debugf("matched node %v", lib.PathStackToString(matchingNode.PathStack))
yqlib.DebugNode(matchingNode.Node)
}
for _, matchingNode := range matchingNodes {
mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy)
updateCommands = append(updateCommands, yqlib.UpdateCommand{
Command: "merge",
Path: mergePath,
Value: matchingNode.Node,
Overwrite: overwriteFlag,
CommentsMergeStrategy: commentsMergeStrategy,
// dont update the content for nodes midway, only leaf nodes
DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode),
})
}
}
}
// for _, fileToMerge := range filesToMerge {
// matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0)
// if errorProcessingFile != nil {
// return errorProcessingFile
// }
// log.Debugf("finished reading for merge!")
// for _, matchingNode := range matchingNodes {
// log.Debugf("matched node %v", lib.PathStackToString(matchingNode.PathStack))
// yqlib.DebugNode(matchingNode.Node)
// }
// for _, matchingNode := range matchingNodes {
// mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy)
// updateCommands = append(updateCommands, yqlib.UpdateCommand{
// Command: "merge",
// Path: mergePath,
// Value: matchingNode.Node,
// Overwrite: overwriteFlag,
// CommentsMergeStrategy: commentsMergeStrategy,
// // dont update the content for nodes midway, only leaf nodes
// 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
import (
"github.com/mikefarah/yq/v3/pkg/yqlib"
"github.com/spf13/cobra"
)
// import (
// "github.com/mikefarah/yq/v3/pkg/yqlib"
// "github.com/spf13/cobra"
// )
func createNewCmd() *cobra.Command {
var cmdNew = &cobra.Command{
Use: "new [path] [value]",
Aliases: []string{"n"},
Short: "yq n [--script/-s script_file] a.b.c newValue",
Example: `
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[+]' cat
yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags
yq n --script create_script.yaml
`,
Long: `Creates a new yaml w.r.t the given path and value.
Outputs to STDOUT
// func createNewCmd() *cobra.Command {
// var cmdNew = &cobra.Command{
// Use: "new [path] [value]",
// Aliases: []string{"n"},
// Short: "yq n [--script/-s script_file] a.b.c newValue",
// Example: `
// 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[+]' cat
// yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags
// yq n --script create_script.yaml
// `,
// Long: `Creates a new yaml w.r.t the given path and value.
// Outputs to STDOUT
Create Scripts:
Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
`,
RunE: newProperty,
}
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(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
return cmdNew
}
// Create Scripts:
// Note that you can give a create script to perform more sophisticated yaml. This follows the same format as the update script.
// `,
// RunE: newProperty,
// }
// 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(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
// cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
// cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
// return cmdNew
// }
func newProperty(cmd *cobra.Command, args []string) error {
var badArgsMessage = "Must provide <path_to_update> <value>"
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, badArgsMessage, false)
if updateCommandsError != nil {
return updateCommandsError
}
newNode := lib.New(updateCommands[0].Path)
// func newProperty(cmd *cobra.Command, args []string) error {
// var badArgsMessage = "Must provide <path_to_update> <value>"
// var updateCommands, updateCommandsError = readUpdateCommands(args, 2, badArgsMessage, false)
// if updateCommandsError != nil {
// return updateCommandsError
// }
// 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 {
return errorUpdating
}
}
// if errorUpdating != nil {
// return errorUpdating
// }
// }
var encoder = yqlib.NewYamlEncoder(cmd.OutOrStdout(), indent, colorsEnabled)
return encoder.Encode(&newNode)
}
// var encoder = yqlib.NewYamlEncoder(cmd.OutOrStdout(), indent, colorsEnabled)
// return encoder.Encode(&newNode)
// }

View File

@ -1,120 +1,120 @@
package cmd
import (
"fmt"
"testing"
// import (
// "fmt"
// "testing"
"github.com/mikefarah/yq/v3/test"
)
// "github.com/mikefarah/yq/v3/test"
// )
func TestNewCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c 3")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestNewCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "new b.c 3")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestNewCmdScript(t *testing.T) {
updateScript := `- command: update
path: b.c
value: 7`
scriptFilename := test.WriteTempYamlFile(updateScript)
defer test.RemoveTempYamlFile(scriptFilename)
// func TestNewCmdScript(t *testing.T) {
// updateScript := `- command: update
// path: b.c
// value: 7`
// scriptFilename := test.WriteTempYamlFile(updateScript)
// defer test.RemoveTempYamlFile(scriptFilename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("new --script %s", scriptFilename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 7
`
test.AssertResult(t, expectedOutput, result.Output)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("new --script %s", scriptFilename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 7
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestNewAnchorCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: &fred 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestNewAnchorCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: &fred 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestNewAliasCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c foo --makeAlias")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: *foo
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestNewAliasCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "new b.c foo --makeAlias")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: *foo
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestNewArrayCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b[0] 3")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
- 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestNewArrayCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "new b[0] 3")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// - 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestNewCmd_Error(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c")
if result.Error == nil {
t.Error("Expected command to fail due to missing arg")
}
expectedOutput := `Must provide <path_to_update> <value>`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
// func TestNewCmd_Error(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "new b.c")
// if result.Error == nil {
// t.Error("Expected command to fail due to missing arg")
// }
// expectedOutput := `Must provide <path_to_update> <value>`
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
func TestNewWithTaggedStyleCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c cat --tag=!!str --style=tagged")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: !!str cat
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestNewWithTaggedStyleCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "new b.c cat --tag=!!str --style=tagged")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: !!str cat
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestNewWithDoubleQuotedStyleCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c cat --style=double")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: "cat"
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestNewWithDoubleQuotedStyleCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "new b.c cat --style=double")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: "cat"
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestNewWithSingleQuotedStyleCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c cat --style=single")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 'cat'
`
test.AssertResult(t, expectedOutput, result.Output)
}
// func TestNewWithSingleQuotedStyleCmd(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "new b.c cat --style=single")
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 'cat'
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }

View File

@ -1,50 +1,50 @@
package cmd
import (
"github.com/mikefarah/yq/v3/pkg/yqlib"
errors "github.com/pkg/errors"
"github.com/spf13/cobra"
yaml "gopkg.in/yaml.v3"
)
// import (
// "github.com/mikefarah/yq/v3/pkg/yqlib"
// errors "github.com/pkg/errors"
// "github.com/spf13/cobra"
// yaml "gopkg.in/yaml.v3"
// )
func createPrefixCmd() *cobra.Command {
var cmdPrefix = &cobra.Command{
Use: "prefix [yaml_file] [path]",
Aliases: []string{"p"},
Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
Example: `
yq prefix 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 p -i things.yaml 'a.b.c'
yq p --doc 2 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.
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
`,
RunE: prefixProperty,
}
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)")
return cmdPrefix
}
// func createPrefixCmd() *cobra.Command {
// var cmdPrefix = &cobra.Command{
// Use: "prefix [yaml_file] [path]",
// Aliases: []string{"p"},
// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
// Example: `
// yq prefix 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 p -i things.yaml 'a.b.c'
// yq p --doc 2 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.
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
// `,
// RunE: prefixProperty,
// }
// 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)")
// return cmdPrefix
// }
func prefixProperty(cmd *cobra.Command, args []string) error {
// func prefixProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide <filename> <prefixed_path>")
}
updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]}
log.Debugf("args %v", args)
// if len(args) < 2 {
// return errors.New("Must provide <filename> <prefixed_path>")
// }
// updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]}
// log.Debugf("args %v", args)
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
// if errorParsingDocIndex != nil {
// return errorParsingDocIndex
// }
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand)
}
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
}
// var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
// return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand)
// }
// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
// }

View File

@ -1,189 +1,189 @@
package cmd
import (
"fmt"
"runtime"
"strings"
"testing"
// import (
// "fmt"
// "runtime"
// "strings"
// "testing"
"github.com/mikefarah/yq/v3/test"
)
// "github.com/mikefarah/yq/v3/test"
// )
func TestPrefixCmd(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestPrefixCmd(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `d:
b:
c: 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `d:
// b:
// c: 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestPrefixCmdArray(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestPrefixCmdArray(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s [+].d.[+]", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `- d:
- b:
c: 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s [+].d.[+]", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `- d:
// - b:
// c: 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestPrefixCmd_MultiLayer(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestPrefixCmd_MultiLayer(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `d:
e:
f:
b:
c: 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `d:
// e:
// f:
// b:
// c: 3
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
func TestPrefixMultiCmd(t *testing.T) {
content := `b:
c: 3
---
apples: great
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestPrefixMultiCmd(t *testing.T) {
// content := `b:
// c: 3
// ---
// apples: great
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
---
d:
apples: great
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestPrefixInvalidDocumentIndexCmd(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `b:
// c: 3
// ---
// d:
// apples: great
// `
// test.AssertResult(t, expectedOutput, result.Output)
// }
// func TestPrefixInvalidDocumentIndexCmd(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -df d", filename))
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -df d", filename))
// if result.Error == nil {
// t.Error("Expected command to fail due to invalid path")
// }
// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
func TestPrefixBadDocumentIndexCmd(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestPrefixBadDocumentIndexCmd(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
func TestPrefixMultiAllCmd(t *testing.T) {
content := `b:
c: 3
---
apples: great
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
// if result.Error == nil {
// t.Error("Expected command to fail due to invalid path")
// }
// expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
// func TestPrefixMultiAllCmd(t *testing.T) {
// content := `b:
// c: 3
// ---
// apples: great
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `d:
b:
c: 3
---
d:
apples: great`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// expectedOutput := `d:
// b:
// c: 3
// ---
// d:
// apples: great`
// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
// }
func TestPrefixCmd_Error(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "prefix")
if result.Error == nil {
t.Error("Expected command to fail due to missing arg")
}
expectedOutput := `Must provide <filename> <prefixed_path>`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
// func TestPrefixCmd_Error(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "prefix")
// if result.Error == nil {
// t.Error("Expected command to fail due to missing arg")
// }
// expectedOutput := `Must provide <filename> <prefixed_path>`
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "prefix fake-unknown a.b")
if result.Error == nil {
t.Error("Expected command to fail due to unknown file")
}
var expectedOutput string
if runtime.GOOS == "windows" {
expectedOutput = `open fake-unknown: The system cannot find the file specified.`
} else {
expectedOutput = `open fake-unknown: no such file or directory`
}
test.AssertResult(t, expectedOutput, result.Error.Error())
}
// func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "prefix fake-unknown a.b")
// if result.Error == nil {
// t.Error("Expected command to fail due to unknown file")
// }
// var expectedOutput string
// if runtime.GOOS == "windows" {
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
// } else {
// expectedOutput = `open fake-unknown: no such file or directory`
// }
// test.AssertResult(t, expectedOutput, result.Error.Error())
// }
func TestPrefixCmd_Inplace(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
// func TestPrefixCmd_Inplace(t *testing.T) {
// content := `b:
// c: 3
// `
// filename := test.WriteTempYamlFile(content)
// defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix -i %s d", filename))
if result.Error != nil {
t.Error(result.Error)
}
gotOutput := test.ReadTempYamlFile(filename)
expectedOutput := `d:
b:
c: 3`
test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
}
// cmd := getRootCommand()
// result := test.RunCmd(cmd, fmt.Sprintf("prefix -i %s d", filename))
// if result.Error != nil {
// t.Error(result.Error)
// }
// gotOutput := test.ReadTempYamlFile(filename)
// expectedOutput := `d:
// b:
// c: 3`
// 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(&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().BoolVarP(&printLength, "length", "l", false, "print length of results")
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(&stripComments, "stripComments", "", false, "print yaml without any comments")

View File

@ -127,7 +127,7 @@ func TestReadWithAdvancedFilterCmd(t *testing.T) {
func TestReadWithAdvancedFilterMapCmd(t *testing.T) {
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 {
t.Error(result.Error)
}

View File

@ -48,13 +48,13 @@ func New() *cobra.Command {
rootCmd.AddCommand(
createReadCmd(),
createCompareCmd(),
// createCompareCmd(),
createValidateCmd(),
createWriteCmd(),
createPrefixCmd(),
createDeleteCmd(),
createNewCmd(),
createMergeCmd(),
// createWriteCmd(),
// createPrefixCmd(),
// createDeleteCmd(),
// createNewCmd(),
// createMergeCmd(),
createBashCompletionCmd(rootCmd),
)
return rootCmd

View File

@ -4,29 +4,29 @@ import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"github.com/mikefarah/yq/v3/pkg/yqlib"
"github.com/mikefarah/yq/v3/pkg/yqlib/treeops"
errors "github.com/pkg/errors"
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) {
return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) {
return lib.Get(dataBucket, path)
func createReadFunction(path string) func(int, *yaml.Node) ([]*treeops.CandidateNode, error) {
return func(document int, dataBucket *yaml.Node) ([]*treeops.CandidateNode, error) {
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)
}
func doReadYamlFile(filename string, readFn readDataFn, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) {
var matchingNodes []*yqlib.NodeContext
func doReadYamlFile(filename string, readFn readDataFn, updateAll bool, docIndexInt int) ([]*treeops.CandidateNode, error) {
var matchingNodes []*treeops.CandidateNode
var currentIndex = 0
var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error {
@ -63,14 +63,14 @@ func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
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)
yqlib.DebugNode(&dataBucket)
// yqlib.DebugNode(&dataBucket)
if !updateAll && currentIndex != docIndexInt {
return originalMatchingNodes, nil
}
log.Debugf("reading in document %v", currentIndex)
matchingNodes, errorParsing := readFn(&dataBucket)
matchingNodes, errorParsing := readFn(currentIndex, &dataBucket)
if errorParsing != nil {
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)
}
func removeComments(matchingNodes []*yqlib.NodeContext) {
func removeComments(matchingNodes []*treeops.CandidateNode) {
for _, nodeContext := range matchingNodes {
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 {
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")
for _, nodeContext := range matchingNodes {
log.Debugf("exploding %v", nodeContext.Head)
log.Debugf("exploding %v", nodeContext.GetKey())
errorExplodingNode := explodeNode(nodeContext.Node)
if errorExplodingNode != nil {
return errorExplodingNode
@ -246,7 +246,7 @@ func explode(matchingNodes []*yqlib.NodeContext) error {
return nil
}
func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error {
func printResults(matchingNodes []*treeops.CandidateNode, writer io.Writer) error {
if prettyPrint {
setStyle(matchingNodes, 0)
}
@ -280,7 +280,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error {
for _, mappedDoc := range matchingNodes {
switch printMode {
case "p":
errorWriting = writeString(bufferedWriter, lib.PathStackToString(mappedDoc.PathStack)+"\n")
errorWriting = writeString(bufferedWriter, mappedDoc.PathStackToString()+"\n")
if errorWriting != nil {
return errorWriting
}
@ -288,7 +288,7 @@ func printResults(matchingNodes []*yqlib.NodeContext, writer io.Writer) error {
// put it into a node and print that.
var parentNode = yaml.Node{Kind: yaml.MappingNode}
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)
if collectIntoArray {
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 {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Prefixing document %v", currentIndex)
yqlib.DebugNode(dataBucket)
updateCommand.Value = dataBucket.Content[0]
dataBucket.Content = make([]*yaml.Node, 1)
// func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error {
// if updateAll || currentIndex == docIndexInt {
// log.Debugf("Prefixing document %v", currentIndex)
// // yqlib.DebugNode(dataBucket)
// updateCommand.Value = dataBucket.Content[0]
// dataBucket.Content = make([]*yaml.Node, 1)
newNode := lib.New(updateCommand.Path)
dataBucket.Content[0] = &newNode
// newNode := lib.New(updateCommand.Path)
// dataBucket.Content[0] = &newNode
errorUpdating := lib.Update(dataBucket, updateCommand, true)
if errorUpdating != nil {
return errorUpdating
}
}
return nil
}
// errorUpdating := lib.Update(dataBucket, updateCommand, true)
// if errorUpdating != nil {
// return errorUpdating
// }
// }
// return nil
// }
func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error {
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
// func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error {
// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
// if errorParsingDocIndex != nil {
// return errorParsingDocIndex
// }
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Updating doc %v", currentIndex)
for _, updateCommand := range updateCommands {
log.Debugf("Processing update to Path %v", updateCommand.Path)
errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag)
if errorUpdating != nil {
return errorUpdating
}
}
}
return nil
}
return readAndUpdate(writer, inputFile, updateData)
}
// var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
// if updateAll || currentIndex == docIndexInt {
// log.Debugf("Updating doc %v", currentIndex)
// for _, updateCommand := range updateCommands {
// log.Debugf("Processing update to Path %v", updateCommand.Path)
// errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag)
// if errorUpdating != nil {
// return errorUpdating
// }
// }
// }
// return nil
// }
// return readAndUpdate(writer, inputFile, updateData)
// }
func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
var destination io.Writer
var destinationName string
var completedSuccessfully = false
if writeInplace {
info, err := os.Stat(inputFile)
if err != nil {
return err
}
// mkdir temp dir as some docker images does not have temp dir
_, err = os.Stat(os.TempDir())
if os.IsNotExist(err) {
err = os.Mkdir(os.TempDir(), 0700)
if err != nil {
return err
}
} else if err != nil {
return err
}
tempFile, err := ioutil.TempFile("", "temp")
if err != nil {
return err
}
destinationName = tempFile.Name()
err = os.Chmod(destinationName, info.Mode())
if err != nil {
return err
}
destination = tempFile
defer func() {
safelyCloseFile(tempFile)
if completedSuccessfully {
safelyRenameFile(tempFile.Name(), inputFile)
}
}()
} else {
destination = stdOut
destinationName = "Stdout"
}
// func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
// var destination io.Writer
// var destinationName string
// var completedSuccessfully = false
// if writeInplace {
// info, err := os.Stat(inputFile)
// if err != nil {
// return err
// }
// // mkdir temp dir as some docker images does not have temp dir
// _, err = os.Stat(os.TempDir())
// if os.IsNotExist(err) {
// err = os.Mkdir(os.TempDir(), 0700)
// if err != nil {
// return err
// }
// } else if err != nil {
// return err
// }
// tempFile, err := ioutil.TempFile("", "temp")
// if err != nil {
// return err
// }
// destinationName = tempFile.Name()
// err = os.Chmod(destinationName, info.Mode())
// if err != nil {
// return err
// }
// destination = tempFile
// defer func() {
// safelyCloseFile(tempFile)
// if completedSuccessfully {
// safelyRenameFile(tempFile.Name(), inputFile)
// }
// }()
// } else {
// destination = 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)
defer safelyFlush(bufferedWriter)
// bufferedWriter := bufio.NewWriter(destination)
// defer safelyFlush(bufferedWriter)
var encoder yqlib.Encoder
if outputToJSON {
encoder = yqlib.NewJsonEncoder(bufferedWriter, prettyPrint, indent)
} else {
encoder = yqlib.NewYamlEncoder(bufferedWriter, indent, colorsEnabled)
}
// var encoder yqlib.Encoder
// if outputToJSON {
// encoder = yqlib.NewJsonEncoder(bufferedWriter, prettyPrint, indent)
// } else {
// encoder = yqlib.NewYamlEncoder(bufferedWriter, indent, colorsEnabled)
// }
var errorProcessing = readStream(inputFile, mapYamlDecoder(updateData, encoder))
completedSuccessfully = errorProcessing == nil
return errorProcessing
}
// var errorProcessing = readStream(inputFile, mapYamlDecoder(updateData, encoder))
// completedSuccessfully = errorProcessing == nil
// return errorProcessing
// }
type updateCommandParsed struct {
Command string
@ -486,53 +486,53 @@ type updateCommandParsed struct {
Value yaml.Node
}
func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string, allowNoValue bool) ([]yqlib.UpdateCommand, error) {
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
if writeScript != "" {
var parsedCommands = make([]updateCommandParsed, 0)
// func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string, allowNoValue bool) ([]yqlib.UpdateCommand, error) {
// var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
// if writeScript != "" {
// var parsedCommands = make([]updateCommandParsed, 0)
err := readData(writeScript, 0, &parsedCommands)
// err := readData(writeScript, 0, &parsedCommands)
if err != nil && err != io.EOF {
return nil, err
}
// if err != nil && err != io.EOF {
// return nil, err
// }
log.Debugf("Read write commands file '%v'", parsedCommands)
for index := range parsedCommands {
parsedCommand := parsedCommands[index]
updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true}
updateCommands = append(updateCommands, updateCommand)
}
// log.Debugf("Read write commands file '%v'", parsedCommands)
// for index := range parsedCommands {
// parsedCommand := parsedCommands[index]
// updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true}
// updateCommands = append(updateCommands, updateCommand)
// }
log.Debugf("Read write commands file '%v'", updateCommands)
} else if sourceYamlFile != "" && len(args) == expectedArgs-1 {
log.Debugf("Reading value from %v", sourceYamlFile)
var value yaml.Node
err := readData(sourceYamlFile, 0, &value)
if err != nil && err != io.EOF {
return nil, err
}
log.Debug("args %v", args[expectedArgs-2])
updateCommands = make([]yqlib.UpdateCommand, 1)
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value.Content[0], Overwrite: true}
} else if len(args) == expectedArgs {
updateCommands = make([]yqlib.UpdateCommand, 1)
log.Debug("args %v", args)
log.Debug("path %v", args[expectedArgs-2])
log.Debug("Value %v", args[expectedArgs-1])
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}
} else if len(args) == expectedArgs-1 && allowNoValue {
// don't update the value
updateCommands = make([]yqlib.UpdateCommand, 1)
log.Debug("args %v", args)
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}
} else {
return nil, errors.New(badArgsMessage)
}
return updateCommands, nil
}
// log.Debugf("Read write commands file '%v'", updateCommands)
// } else if sourceYamlFile != "" && len(args) == expectedArgs-1 {
// log.Debugf("Reading value from %v", sourceYamlFile)
// var value yaml.Node
// err := readData(sourceYamlFile, 0, &value)
// if err != nil && err != io.EOF {
// return nil, err
// }
// log.Debug("args %v", args[expectedArgs-2])
// updateCommands = make([]yqlib.UpdateCommand, 1)
// updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value.Content[0], Overwrite: true}
// } else if len(args) == expectedArgs {
// updateCommands = make([]yqlib.UpdateCommand, 1)
// log.Debug("args %v", args)
// log.Debug("path %v", args[expectedArgs-2])
// log.Debug("Value %v", args[expectedArgs-1])
// 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}
// } else if len(args) == expectedArgs-1 && allowNoValue {
// // don't update the value
// updateCommands = make([]yqlib.UpdateCommand, 1)
// log.Debug("args %v", args)
// 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}
// } else {
// return nil, errors.New(badArgsMessage)
// }
// return updateCommands, nil
// }
func safelyRenameFile(from string, to string) {
if renameError := os.Rename(from, to); renameError != nil {

View File

@ -1,61 +1,61 @@
package cmd
import (
"github.com/spf13/cobra"
)
// import (
// "github.com/spf13/cobra"
// )
func createWriteCmd() *cobra.Command {
var cmdWrite = &cobra.Command{
Use: "write [yaml_file] [path_expression] [value]",
Aliases: []string{"w"},
Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue",
Example: `
yq write things.yaml 'a.b.c' true
yq write things.yaml 'a.*.c' true
yq write things.yaml 'a.**' true
yq write things.yaml 'a.(child.subchild==co*).c' true
yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
yq write things.yaml 'a.b.c' --tag '!!float' 3
yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags
yq w -i things.yaml 'a.b.c' cat
yq w -i -s update_script.yaml things.yaml
yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array
yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file
`,
Long: `Updates the yaml file w.r.t the given path and value.
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
// func createWriteCmd() *cobra.Command {
// var cmdWrite = &cobra.Command{
// Use: "write [yaml_file] [path_expression] [value]",
// Aliases: []string{"w"},
// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue",
// Example: `
// yq write things.yaml 'a.b.c' true
// yq write things.yaml 'a.*.c' true
// yq write things.yaml 'a.**' true
// yq write things.yaml 'a.(child.subchild==co*).c' true
// yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
// yq write things.yaml 'a.b.c' --tag '!!float' 3
// yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags
// yq w -i things.yaml 'a.b.c' cat
// yq w -i -s update_script.yaml things.yaml
// yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array
// yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file
// `,
// Long: `Updates the yaml file w.r.t the given path and value.
// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
Append value to array adds the value to the end of array.
// Append value to array adds the value to the end of array.
Update Scripts:
Note that you can give an update script to perform more sophisticated update. Update script
format is list of update commands (update or delete) like so:
---
- command: update
path: b.c
value:
#great
things: frog # wow!
- command: delete
path: b.d
`,
RunE: writeProperty,
}
cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)")
cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
return cmdWrite
}
// Update Scripts:
// Note that you can give an update script to perform more sophisticated update. Update script
// format is list of update commands (update or delete) like so:
// ---
// - command: update
// path: b.c
// value:
// #great
// things: frog # wow!
// - command: delete
// path: b.d
// `,
// RunE: writeProperty,
// }
// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
// cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
// cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)")
// cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
// cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
// return cmdWrite
// }
func writeProperty(cmd *cobra.Command, args []string) error {
var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
if updateCommandsError != nil {
return updateCommandsError
}
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
}
// func writeProperty(cmd *cobra.Command, args []string) error {
// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
// if updateCommandsError != nil {
// return updateCommandsError
// }
// return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
// }

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
}
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()
for _, n := range matchingNodes {
matchingNodeMap.Set(n.getKey(), n)
matchingNodeMap.Set(n.GetKey(), n)
}
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) {
if pathNode == nil {
log.Debugf("getMatchingNodes - nothing to do")
return matchingNodes, nil
}
log.Debugf("Processing Path: %v", pathNode.PathElement.toString())
if pathNode.PathElement.PathElementType == SelfReference {
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() {
candidate := el.Value.(*CandidateNode)
elMap := orderedmap.NewOrderedMap()
elMap.Set(candidate.getKey(), candidate)
elMap.Set(candidate.GetKey(), candidate)
nodesToDelete, err := d.getMatchingNodes(elMap, pathNode.Rhs)
log.Debug("nodesToDelete:\n%v", NodesToString(nodesToDelete))
if err != nil {
@ -50,9 +50,9 @@ func deleteFromMap(candidate *CandidateNode, nodesToDelete *orderedmap.OrderedMa
Document: candidate.Document,
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 {
newContents = append(newContents, key, value)
@ -76,7 +76,7 @@ func deleteFromArray(candidate *CandidateNode, nodesToDelete *orderedmap.Ordered
Path: append(candidate.Path, index),
}
_, shouldDelete := nodesToDelete.Get(childCandidate.getKey())
_, shouldDelete := nodesToDelete.Get(childCandidate.GetKey())
if !shouldDelete {
newContents = append(newContents, value)
}

View File

@ -11,16 +11,6 @@ import (
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
const (
@ -76,7 +66,7 @@ func (p *PathElement) toString() string {
}
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)
// Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
// New(path string) yaml.Node
@ -85,10 +75,24 @@ type YqTreeLib interface {
// MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
}
func NewYqTreeLib() YqTreeLib {
return &lib{treeCreator: NewPathTreeCreator()}
}
type lib struct {
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
func NodesToString(collection *orderedmap.OrderedMap) string {
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() {
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
}
return lhs, nil
@ -41,7 +41,7 @@ func UnionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.OrderedMap, p
}
for el := rhs.Front(); el != nil; el = el.Next() {
node := el.Value.(*CandidateNode)
lhs.Set(node.getKey(), node)
lhs.Set(node.GetKey(), node)
}
return lhs, nil
}
@ -67,7 +67,7 @@ func IntersectionOperator(d *dataTreeNavigator, matchingNodes *orderedmap.Ordere
func splatNode(d *dataTreeNavigator, candidate *CandidateNode) (*orderedmap.OrderedMap, error) {
elMap := orderedmap.NewOrderedMap()
elMap.Set(candidate.getKey(), candidate)
elMap.Set(candidate.GetKey(), candidate)
//need to splat matching nodes, then search through them
splatter := &PathTreeNode{PathElement: &PathElement{
PathElementType: PathKey,
@ -112,7 +112,7 @@ func CountOperator(d *dataTreeNavigator, matchMap *orderedmap.OrderedMap, pathNo
length := childMatches.Len()
node := &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", length), Tag: "!!int"}
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 {
children = orderedmap.NewOrderedMap()
children.Set(candidate.getKey(), candidate)
children.Set(candidate.GetKey(), candidate)
}
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) {
var stack = make([]*PathTreeNode, 0)
if len(postFixPath) == 0 {
return nil, nil
}
for _, pathElement := range postFixPath {
var newNode = PathTreeNode{PathElement: pathElement}
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
},
}
}