get.porter.sh/porter@v1.3.0/pkg/editor/editor.go (about) 1 package editor 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 10 "get.porter.sh/porter/pkg" 11 "get.porter.sh/porter/pkg/portercontext" 12 ) 13 14 // Editor displays content to a user using an external text editor, like vi or notepad. 15 // The content is captured and returned. 16 // 17 // The `EDITOR` environment variable is checked to find an editor. 18 // Failing that, use some sensible default depending on the operating system. 19 // 20 // This is useful for editing things like configuration files, especially those 21 // that might be stored on a remote server. For example: the content could be retrieved 22 // from the remote store, edited locally, then saved back. 23 type Editor struct { 24 *portercontext.Context 25 contents []byte 26 tempFilename string 27 } 28 29 // New returns a new Editor with the temp filename and contents provided. 30 func New(context *portercontext.Context, tempFilename string, contents []byte) *Editor { 31 return &Editor{ 32 Context: context, 33 tempFilename: tempFilename, 34 contents: contents, 35 } 36 } 37 38 func (e *Editor) editorArgs(filename string) []string { 39 shell := e.Getenv("SHELL") 40 if shell == "" { 41 shell = defaultShell 42 } 43 editor := e.Getenv("EDITOR") 44 if editor == "" { 45 editor = defaultEditor 46 } 47 48 // Example of what will be run: 49 // on *nix: sh -c "vi /tmp/test.txt" 50 // on windows: cmd /C "C:\Program Files\Visual Studio Code\Code.exe --wait C:\somefile.txt" 51 // 52 // Pass the editor command to the shell so we don't have to parse the command ourselves. 53 // Passing the editor command that could possibly have an argument (e.g. --wait for VSCode) to the 54 // shell means we don't have to parse this ourselves, like splitting on spaces. 55 return []string{shell, shellCommandFlag, fmt.Sprintf("%s %s", editor, filename)} 56 } 57 58 // Run opens the editor, displaying the contents through a temporary file. 59 // The content is returned once the editor closes. 60 func (e *Editor) Run(ctx context.Context) ([]byte, error) { 61 tempFile, err := e.FileSystem.OpenFile(filepath.Join(os.TempDir(), e.tempFilename), os.O_RDWR|os.O_CREATE|os.O_EXCL, pkg.FileModeWritable) 62 if err != nil { 63 return nil, err 64 } 65 defer func() { 66 err = errors.Join(err, e.FileSystem.Remove(tempFile.Name())) 67 }() 68 _, err = tempFile.Write(e.contents) 69 if err != nil { 70 return nil, err 71 } 72 73 // close here without defer so cmd can grab the file 74 tempFile.Close() 75 76 args := e.editorArgs(tempFile.Name()) 77 cmd := e.NewCommand(ctx, args[0], args[1:]...) 78 cmd.Stdout = e.Out 79 cmd.Stderr = e.Err 80 cmd.Stdin = e.In 81 err = cmd.Run() 82 if err != nil { 83 return nil, err 84 } 85 86 contents, err := e.FileSystem.ReadFile(tempFile.Name()) 87 if err != nil { 88 return nil, err 89 } 90 91 return contents, err 92 }