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