This repository has been archived by the owner on Apr 29, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
edit.go
135 lines (104 loc) · 3.37 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
// Inspired by https://samrapdev.com/capturing-sensitive-input-with-editor-in-golang-from-the-cli/
package main
import (
"bytes"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/util/term"
"github.com/caos/orbos/internal/git"
)
func EditCommand(rv RootValues) *cobra.Command {
return &cobra.Command{
Use: "edit [file]",
Short: "Edit a file and push changes to the remote orb repository",
Args: cobra.ExactArgs(1),
Example: `orbctl edit desired.yml`,
RunE: func(cmd *cobra.Command, args []string) error {
_, _, orbConfig, gitClient, errFunc := rv()
if errFunc != nil {
return errFunc(cmd)
}
if err := orbConfig.IsConnectable(); err != nil {
return err
}
if err := gitClient.Configure(orbConfig.URL, []byte(orbConfig.Repokey)); err != nil {
return err
}
if err := gitClient.Clone(); err != nil {
return err
}
edited, err := CaptureInputFromEditor(GetPreferredEditorFromEnvironment, bytes.NewReader(gitClient.Read(args[0])))
if err != nil {
panic(err)
}
return gitClient.UpdateRemote("File written by orbctl", git.File{
Path: args[0],
Content: edited,
})
},
}
}
// DefaultEditor is vim because we're adults ;)
const DefaultEditor = "vim"
// PreferredEditorResolver is a function that returns an editor that the user
// prefers to use, such as the configured `$EDITOR` environment variable.
type PreferredEditorResolver func() string
// GetPreferredEditorFromEnvironment returns the user's editor as defined by the
// `$EDITOR` environment variable, or the `DefaultEditor` if it is not set.
func GetPreferredEditorFromEnvironment() string {
editor := os.Getenv("EDITOR")
if editor == "" {
return DefaultEditor
}
return editor
}
func resolveEditorArguments(executable string, filename string) []string {
args := []string{filename}
if strings.Contains(executable, "Visual Studio Code.app") {
args = append([]string{"--wait"}, args...)
}
if strings.Contains(executable, "vim") {
args = append([]string{"--not-a-term", "-c", "set nowrap"}, args...)
}
// Other common editors
return args
}
// OpenFileInEditor opens filename in a text editor.
func OpenFileInEditor(filename string, resolveEditor PreferredEditorResolver) error {
// Get the full executable path for the editor.
executable, err := exec.LookPath(resolveEditor())
if err != nil {
return err
}
cmd := exec.Command(executable, resolveEditorArguments(executable, filename)...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return (term.TTY{In: os.Stdin, TryDev: true}).Safe(cmd.Run)
}
// CaptureInputFromEditor opens a temporary file in a text editor and returns
// the written bytes on success or an error on failure. It handles deletion
// of the temporary file behind the scenes.
func CaptureInputFromEditor(resolveEditor PreferredEditorResolver, content io.Reader) ([]byte, error) {
file, err := ioutil.TempFile(os.TempDir(), "*")
if err != nil {
return []byte{}, err
}
filename := file.Name()
// Defer removal of the temporary file in case any of the next steps fail.
defer os.Remove(filename)
if _, err := io.Copy(file, content); err != nil {
return []byte{}, err
}
if err = file.Close(); err != nil {
return []byte{}, err
}
if err = OpenFileInEditor(filename, resolveEditor); err != nil {
return []byte{}, err
}
return ioutil.ReadFile(filename)
}