-
Notifications
You must be signed in to change notification settings - Fork 6
/
edit.go
146 lines (132 loc) · 4.62 KB
/
edit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package vaultcli
import (
"bytes"
"fmt"
"log"
"os"
"strings"
cst "github.com/DelineaXPM/dsv-cli/constants"
"github.com/DelineaXPM/dsv-cli/errors"
"github.com/DelineaXPM/dsv-cli/format"
"github.com/DelineaXPM/dsv-cli/utils"
"github.com/spf13/viper"
"golang.org/x/sys/execabs"
)
type SaveFunc func(data []byte) (resp []byte, err *errors.ApiError)
func EditData(data []byte, saveFunc SaveFunc, startErr *errors.ApiError, retry bool) (edited []byte, runErr *errors.ApiError) {
viper.Set(cst.Output, "disable-color")
dataFormatted, errString := format.FormatResponse(data, nil, viper.GetBool(cst.Beautify))
viper.Set(cst.Output, format.OutToStdout)
if errString != "" {
return nil, errors.NewS(errString)
}
dataEdited, err := doEditData([]byte(dataFormatted), startErr)
if err != nil {
return nil, err
}
resp, postErr := saveFunc(dataEdited)
if retry && postErr != nil {
return EditData(dataEdited, saveFunc, postErr, true)
}
return resp, postErr
}
func doEditData(data []byte, startErr *errors.ApiError) (edited []byte, runErr *errors.ApiError) {
editorCmd, getErr := getEditorCmd()
if getErr != nil || editorCmd == "" {
return nil, getErr
}
tmpDir := os.TempDir()
tmpFile, err := os.CreateTemp(tmpDir, cst.CmdRoot)
if err != nil {
return nil, errors.New(err).Grow("Error while creating temp file to edit data")
}
defer func() {
if err := os.Remove(tmpFile.Name()); err != nil {
log.Printf("Warning: failed to remove temporary file: '%s'\n%v", tmpFile.Name(), err)
}
}()
if err := os.WriteFile(tmpFile.Name(), data, 0o600); err != nil {
return nil, errors.New(err).Grow("Error while copying data to temp file")
}
// This is necessary for Windows. Opening a file in a parent process and then
// trying to write it in a child process is not allowed. So we close the file
// in the parent process first. This does not affect the behavior on Unix.
if err := tmpFile.Close(); err != nil {
log.Printf("Warning: failed to close temporary file: '%s'\n%v", tmpFile.Name(), err)
}
editorPath, err := execabs.LookPath(editorCmd)
if err != nil {
return nil, errors.New(err).Grow(fmt.Sprintf("Error while looking up path to editor %q", editorCmd))
}
args := []string{tmpFile.Name()}
if startErr != nil && (strings.HasSuffix(editorPath, "vim") || strings.HasSuffix(editorPath, "vi")) {
args = append(args, "-c")
errMsg := fmt.Sprintf("Error saving to %s. Please correct and save again or exit: %s", cst.ProductName, startErr.String())
args = append(args, fmt.Sprintf(`:echoerr '%s'`, strings.Replace(errMsg, `'`, `''`, -1)))
}
cmd := execabs.Command(editorPath, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
return nil, errors.New(err).Grow(fmt.Sprintf("Command failed to start: '%s %s'", editorCmd, tmpFile.Name()))
}
err = cmd.Wait()
if err != nil {
return nil, errors.New(err).Grow(fmt.Sprintf("Command failed to return: '%s %s'", editorCmd, tmpFile.Name()))
}
edited, err = os.ReadFile(tmpFile.Name())
if err != nil {
return nil, errors.New(err).Grow(fmt.Sprintf("Failed to read edited file: %q", tmpFile.Name()))
}
if bytes.Equal(data, edited) {
return nil, errors.NewS("Data not modified")
}
if len(edited) == 0 {
return nil, errors.NewS("Cannot save empty file")
}
return edited, startErr
}
func getEditorCmd() (string, *errors.ApiError) {
if utils.NewEnvProvider().GetOs() == "windows" {
return "notepad.exe", nil
}
editor := viper.GetString(cst.Editor)
// if editor specified in cli-config
if editor != "" {
return editor, nil
}
// try to find default editor on system
out, err := execabs.Command("bash", "-c", getDefaultEditorSh).Output()
editor = strings.TrimSpace(string(out))
if err != nil || editor == "" {
return "", errors.New(err).Grow("Failed to find default text editor. Please set 'editor' in the cli-config or make sure $EDITOR, $VISUAL is set on your system.")
}
// verbose - let them know why a certain editor is being implicitly chosen
log.Printf("Using editor '%s' as it is found as default editor on the system. To override, set in cli-config (%s config update editor <EDITOR_NAME>)", editor, cst.CmdRoot)
return editor, nil
}
const getDefaultEditorSh = `
#!/bin/sh
if [ -n "$VISUAL" ]; then
echo $VISUAL
elif [ -n "$EDITOR" ]; then
echo $EDITOR
elif type sensible-editor >/dev/null 2>/dev/null; then
echo sensible-editor "$@"
elif cmd=$(xdg-mime query default ) 2>/dev/null; [ -n "$cmd" ]; then
echo "$cmd"
else
editors='nano joe vi'
if [ -n "$DISPLAY" ]; then
editors="gedit kate $editors"
fi
for x in $editors; do
if type "$x" >/dev/null 2>/dev/null; then
echo "$x"
exit 0
fi
done
fi
`