From 8de10e550d9a9792036d56aa866e5ddd8090fe88 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Sun, 29 Nov 2020 20:25:47 +1100 Subject: [PATCH] wip - write in place --- cmd/constant.go | 4 +- cmd/evaluate_all_command.go | 20 + cmd/root.go | 1 + cmd/write.go | 61 --- cmd/write_test.go | 610 ---------------------------- pkg/yqlib/file_utils.go | 50 +++ pkg/yqlib/lib.go | 1 + pkg/yqlib/printer.go | 11 +- pkg/yqlib/utils.go | 53 --- pkg/yqlib/write_in_place_handler.go | 55 +++ 10 files changed, 137 insertions(+), 729 deletions(-) delete mode 100644 cmd/write.go delete mode 100644 cmd/write_test.go create mode 100644 pkg/yqlib/file_utils.go create mode 100644 pkg/yqlib/write_in_place_handler.go diff --git a/cmd/constant.go b/cmd/constant.go index dc4d01ff..efc0d1bd 100644 --- a/cmd/constant.go +++ b/cmd/constant.go @@ -2,7 +2,7 @@ package cmd var unwrapScalar = true -// var writeInplace = false +var writeInplace = false var outputToJSON = false // var exitStatus = false @@ -14,5 +14,3 @@ var noDocSeparators = false var nullInput = false var verbose = false var version = false - -// var log = logging.MustGetLogger("yq") diff --git a/cmd/evaluate_all_command.go b/cmd/evaluate_all_command.go index 460f0cb7..00daffda 100644 --- a/cmd/evaluate_all_command.go +++ b/cmd/evaluate_all_command.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "os" "github.com/mikefarah/yq/v4/pkg/yqlib" @@ -39,7 +40,24 @@ func evaluateAll(cmd *cobra.Command, args []string) error { if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) { colorsEnabled = true } + + if writeInplace && len(args) < 2 { + return fmt.Errorf("Write inplace flag only applicable when giving an expression and at least one file") + } + + completedSuccessfully := false + + if writeInplace { + writeInPlaceHandler := yqlib.NewWriteInPlaceHandler(args[1]) + out, err = writeInPlaceHandler.CreateTempFile() + if err != nil { + return err + } + defer writeInPlaceHandler.FinishWriteInPlace(completedSuccessfully) + } + printer := yqlib.NewPrinter(out, outputToJSON, unwrapScalar, colorsEnabled, indent, !noDocSeparators) + allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator() switch len(args) { case 0: @@ -59,6 +77,8 @@ func evaluateAll(cmd *cobra.Command, args []string) error { err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer) } + completedSuccessfully = err == nil + cmd.SilenceUsage = true return err } diff --git a/cmd/root.go b/cmd/root.go index 6836a864..05878555 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -46,6 +46,7 @@ func New() *cobra.Command { rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") + rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.") rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") rootCmd.PersistentFlags().BoolVarP(&forceNoColor, "no-colors", "M", false, "force print with no colors") diff --git a/cmd/write.go b/cmd/write.go deleted file mode 100644 index a1c30607..00000000 --- a/cmd/write.go +++ /dev/null @@ -1,61 +0,0 @@ -package cmd - -// import ( -// "github.com/spf13/cobra" -// ) - -// func createWriteCmd() *cobra.Command { -// var cmdWrite = &cobra.Command{ -// Use: "write [yaml_file] [path_expression] [value]", -// Aliases: []string{"w"}, -// Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue", -// Example: ` -// yq write things.yaml 'a.b.c' true -// yq write things.yaml 'a.*.c' true -// yq write things.yaml 'a.**' true -// yq write things.yaml 'a.(child.subchild==co*).c' true -// yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool -// yq write things.yaml 'a.b.c' --tag '!!float' 3 -// yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags -// yq w -i things.yaml 'a.b.c' cat -// yq w -i -s update_script.yaml things.yaml -// yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array -// yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file -// `, -// Long: `Updates the yaml file w.r.t the given path and value. -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. - -// Append value to array adds the value to the end of array. - -// Update Scripts: -// Note that you can give an update script to perform more sophisticated update. Update script -// format is list of update commands (update or delete) like so: -// --- -// - command: update -// path: b.c -// value: -// #great -// things: frog # wow! -// - command: delete -// path: b.d -// `, -// RunE: writeProperty, -// } -// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") -// cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") -// cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)") -// cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)") -// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") -// cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged") -// cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name") -// cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name") -// return cmdWrite -// } - -// func writeProperty(cmd *cobra.Command, args []string) error { -// var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide ", true) -// if updateCommandsError != nil { -// return updateCommandsError -// } -// return updateDoc(args[0], updateCommands, cmd.OutOrStdout()) -// } diff --git a/cmd/write_test.go b/cmd/write_test.go deleted file mode 100644 index bb1ab671..00000000 --- a/cmd/write_test.go +++ /dev/null @@ -1,610 +0,0 @@ -package cmd - -// import ( -// "fmt" -// "runtime" -// "strings" -// "testing" - -// "github.com/mikefarah/yq/v3/test" -// ) - -// func TestWriteCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteKeepCommentsCmd(t *testing.T) { -// content := `b: -// c: 3 # comment -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 # comment -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithTaggedStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: !!str cat -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: "cat" -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteUpdateStyleOnlyCmd(t *testing.T) { -// content := `b: -// c: dog -// d: things -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 'dog' -// d: 'things' -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteUpdateTagOnlyCmd(t *testing.T) { -// content := `b: -// c: true -// d: false -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: "true" -// d: "false" -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithSingleQuotedStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 'cat' -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithLiteralStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: |- -// cat -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteWithFoldedStyleCmd(t *testing.T) { -// content := `b: -// c: dog -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: >- -// cat -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteEmptyMultiDocCmd(t *testing.T) { -// content := `# this is empty -// --- -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `c: 7 - -// # this is empty -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) { -// content := `--- -// # empty -// --- -// cat: frog -// --- - -// # empty -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := ` - -// # empty -// --- -// cat: frog -// c: 7 -// --- - -// # empty -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteFromFileCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// source := `kittens: are cute # sure are!` -// fromFilename := test.WriteTempYamlFile(source) -// defer test.RemoveTempYamlFile(fromFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: -// kittens: are cute # sure are! -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteEmptyCmd(t *testing.T) { -// content := `` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteAutoCreateCmd(t *testing.T) { -// content := `applications: -// - name: app -// env:` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `applications: -// - name: app -// env: -// hello: world -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmdScript(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// updateScript := `- command: update -// path: b.c -// value: 7` -// scriptFilename := test.WriteTempYamlFile(updateScript) -// defer test.RemoveTempYamlFile(scriptFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmdEmptyScript(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// updateScript := `` -// scriptFilename := test.WriteTempYamlFile(updateScript) -// defer test.RemoveTempYamlFile(scriptFilename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteMultiCmd(t *testing.T) { -// content := `b: -// c: 3 -// --- -// apples: great -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// --- -// apples: ok -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } -// func TestWriteInvalidDocumentIndexCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename)) -// if result.Error == nil { -// t.Error("Expected command to fail due to invalid path") -// } -// expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestWriteBadDocumentIndexCmd(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename)) -// if result.Error == nil { -// t.Error("Expected command to fail due to invalid path") -// } -// expectedOutput := `asked to process document index 1 but there are only 1 document(s)` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } -// func TestWriteMultiAllCmd(t *testing.T) { -// content := `b: -// c: 3 -// --- -// apples: great -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: 3 -// apples: ok -// --- -// apples: ok` -// test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) -// } - -// func TestWriteCmd_EmptyArray(t *testing.T) { -// content := `b: 3` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: 3 -// a: [] -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_Error(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "write") -// if result.Error == nil { -// t.Error("Expected command to fail due to missing arg") -// } -// expectedOutput := `Must provide ` -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestWriteCmd_ErrorUnreadableFile(t *testing.T) { -// cmd := getRootCommand() -// result := test.RunCmd(cmd, "write fake-unknown a.b 3") -// if result.Error == nil { -// t.Error("Expected command to fail due to unknown file") -// } -// var expectedOutput string -// if runtime.GOOS == "windows" { -// expectedOutput = `open fake-unknown: The system cannot find the file specified.` -// } else { -// expectedOutput = `open fake-unknown: no such file or directory` -// } -// test.AssertResult(t, expectedOutput, result.Error.Error()) -// } - -// func TestWriteCmd_Inplace(t *testing.T) { -// content := `b: -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// gotOutput := test.ReadTempYamlFile(filename) -// expectedOutput := `b: -// c: 7` -// test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) -// } - -// func TestWriteCmd_InplaceError(t *testing.T) { -// content := `b: cat -// c: 3 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename)) -// if result.Error == nil { -// t.Error("Expected Error to occur!") -// } -// gotOutput := test.ReadTempYamlFile(filename) -// test.AssertResult(t, content, gotOutput) -// } - -// func TestWriteCmd_Append(t *testing.T) { -// content := `b: -// - foo -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// - foo -// - 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_AppendInline(t *testing.T) { -// content := `b: [foo]` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: [foo, 7] -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_AppendInlinePretty(t *testing.T) { -// content := `b: [foo]` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// - foo -// - 7 -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_AppendEmptyArray(t *testing.T) { -// content := `a: 2 -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `a: 2 -// b: -// - v -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_SplatArray(t *testing.T) { -// content := `b: -// - c: thing -// - c: another thing -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// - c: new -// - c: new -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_SplatMap(t *testing.T) { -// content := `b: -// c: thing -// d: another thing -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: new -// d: new -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } - -// func TestWriteCmd_SplatMapEmpty(t *testing.T) { -// content := `b: -// c: thing -// d: another thing -// ` -// filename := test.WriteTempYamlFile(content) -// defer test.RemoveTempYamlFile(filename) - -// cmd := getRootCommand() -// result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename)) -// if result.Error != nil { -// t.Error(result.Error) -// } -// expectedOutput := `b: -// c: {} -// d: another thing -// ` -// test.AssertResult(t, expectedOutput, result.Output) -// } diff --git a/pkg/yqlib/file_utils.go b/pkg/yqlib/file_utils.go new file mode 100644 index 00000000..cfe8d2c4 --- /dev/null +++ b/pkg/yqlib/file_utils.go @@ -0,0 +1,50 @@ +package yqlib + +import ( + "io" + "os" +) + +func safelyRenameFile(from string, to string) { + if renameError := os.Rename(from, to); renameError != nil { + log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) + log.Debug(renameError.Error()) + // can't do this rename when running in docker to a file targeted in a mounted volume, + // so gracefully degrade to copying the entire contents. + if copyError := copyFileContents(from, to); copyError != nil { + log.Errorf("Failed copying from %v to %v", from, to) + log.Error(copyError.Error()) + } else { + removeErr := os.Remove(from) + if removeErr != nil { + log.Errorf("failed removing original file: %s", from) + } + } + } +} + +// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang +func copyFileContents(src, dst string) (err error) { + in, err := os.Open(src) // nolint gosec + if err != nil { + return err + } + defer safelyCloseFile(in) + out, err := os.Create(dst) + if err != nil { + return err + } + defer safelyCloseFile(out) + if _, err = io.Copy(out, in); err != nil { + return err + } + return out.Sync() +} + +func safelyCloseFile(file *os.File) { + err := file.Close() + if err != nil { + log.Error("Error closing file!") + log.Error(err.Error()) + } +} diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index a36e0557..d6782658 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -18,6 +18,7 @@ type OperationType struct { // operators TODO: // - cookbook doc for common things +// - existStatus // - write in place // - mergeEmpty (sets only if the document is empty, do I do that now?) // - compare ?? diff --git a/pkg/yqlib/printer.go b/pkg/yqlib/printer.go index 2e81c3b8..6c7f41e7 100644 --- a/pkg/yqlib/printer.go +++ b/pkg/yqlib/printer.go @@ -5,7 +5,7 @@ import ( "container/list" "io" - "gopkg.in/yaml.v3" + yaml "gopkg.in/yaml.v3" ) type Printer interface { @@ -53,6 +53,13 @@ func (p *resultsPrinter) writeString(writer io.Writer, txt string) error { return errorWriting } +func (p *resultsPrinter) safelyFlush(writer *bufio.Writer) { + if err := writer.Flush(); err != nil { + log.Error("Error flushing writer!") + log.Error(err.Error()) + } +} + func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { log.Debug("PrintResults for %v matches", matchingNodes.Len()) var err error @@ -66,7 +73,7 @@ func (p *resultsPrinter) PrintResults(matchingNodes *list.List) error { } bufferedWriter := bufio.NewWriter(p.writer) - defer safelyFlush(bufferedWriter) + defer p.safelyFlush(bufferedWriter) if matchingNodes.Len() == 0 { log.Debug("no matching results, nothing to print") diff --git a/pkg/yqlib/utils.go b/pkg/yqlib/utils.go index d46cdd77..092df9d2 100644 --- a/pkg/yqlib/utils.go +++ b/pkg/yqlib/utils.go @@ -9,8 +9,6 @@ import ( yaml "gopkg.in/yaml.v3" ) -//TODO: convert to interface + struct - var treeNavigator = NewDataTreeNavigator() var treeCreator = NewPathTreeCreator() @@ -52,54 +50,3 @@ func readDocuments(reader io.Reader, filename string, fileIndex int) (*list.List currentIndex = currentIndex + 1 } } - -// func safelyRenameFile(from string, to string) { -// if renameError := os.Rename(from, to); renameError != nil { -// log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to) -// log.Debug(renameError.Error()) -// // can't do this rename when running in docker to a file targeted in a mounted volume, -// // so gracefully degrade to copying the entire contents. -// if copyError := copyFileContents(from, to); copyError != nil { -// log.Errorf("Failed copying from %v to %v", from, to) -// log.Error(copyError.Error()) -// } else { -// removeErr := os.Remove(from) -// if removeErr != nil { -// log.Errorf("failed removing original file: %s", from) -// } -// } -// } -// } - -// thanks https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang -// func copyFileContents(src, dst string) (err error) { -// in, err := os.Open(src) // nolint gosec -// if err != nil { -// return err -// } -// defer safelyCloseFile(in) -// out, err := os.Create(dst) -// if err != nil { -// return err -// } -// defer safelyCloseFile(out) -// if _, err = io.Copy(out, in); err != nil { -// return err -// } -// return out.Sync() -// } - -func safelyFlush(writer *bufio.Writer) { - if err := writer.Flush(); err != nil { - log.Error("Error flushing writer!") - log.Error(err.Error()) - } - -} -func safelyCloseFile(file *os.File) { - err := file.Close() - if err != nil { - log.Error("Error closing file!") - log.Error(err.Error()) - } -} diff --git a/pkg/yqlib/write_in_place_handler.go b/pkg/yqlib/write_in_place_handler.go new file mode 100644 index 00000000..b282c044 --- /dev/null +++ b/pkg/yqlib/write_in_place_handler.go @@ -0,0 +1,55 @@ +package yqlib + +import ( + "io/ioutil" + "os" +) + +type WriteInPlaceHandler interface { + CreateTempFile() (*os.File, error) + FinishWriteInPlace(evaluatedSuccessfully bool) +} + +type writeInPlaceHandler struct { + inputFilename string + tempFile *os.File +} + +func NewWriteInPlaceHandler(inputFile string) WriteInPlaceHandler { + + return &writeInPlaceHandler{inputFile, nil} +} + +func (w *writeInPlaceHandler) CreateTempFile() (*os.File, error) { + info, err := os.Stat(w.inputFilename) + if err != nil { + return nil, err + } + _, err = os.Stat(os.TempDir()) + if os.IsNotExist(err) { + err = os.Mkdir(os.TempDir(), 0700) + if err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + + file, err := ioutil.TempFile("", "temp") + if err != nil { + return nil, err + } + + err = os.Chmod(file.Name(), info.Mode()) + w.tempFile = file + return file, err +} + +func (w *writeInPlaceHandler) FinishWriteInPlace(evaluatedSuccessfully bool) { + safelyCloseFile(w.tempFile) + if evaluatedSuccessfully { + safelyRenameFile(w.tempFile.Name(), w.inputFilename) + } else { + os.Remove(w.tempFile.Name()) + } +}