-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e3ee453
commit 4c5a635
Showing
2 changed files
with
168 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package prompts | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
"time" | ||
|
||
"github.com/spectrocloud-labs/prompts-tui/prompts/mocks" | ||
) | ||
|
||
var ( | ||
editorBinary = "vi" | ||
editorPath = "" | ||
GetCmdExecutor = getEditorExecutor | ||
) | ||
|
||
func init() { | ||
visual := os.Getenv("VISUAL") | ||
editor := os.Getenv("EDITOR") | ||
if visual != "" { | ||
editorBinary = visual | ||
logger.Info(fmt.Sprintf("Detected VISUAL env var. Overrode default editor (vi) with %s.", editorBinary)) | ||
} else if editor != "" { | ||
editorBinary = editor | ||
logger.Info(fmt.Sprintf("Detected EDITOR env var. Overrode default editor (vi) with %s.", editorBinary)) | ||
} | ||
var err error | ||
editorPath, err = exec.LookPath(editorBinary) | ||
if err != nil { | ||
logger.Info(fmt.Sprintf("Error: %s not found on PATH. Either install vi or export VISUAL or EDITOR to an editor of your choosing.", editorBinary)) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func getEditorExecutor(editor, filename string) mocks.CommandExecutor { | ||
cmd := exec.Command(editor, filename) | ||
cmd.Stdin = os.Stdin | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
return cmd | ||
} | ||
|
||
func EditFile(initialContent []byte) ([]byte, error) { | ||
tmpFile, err := os.CreateTemp(os.TempDir(), "validator") | ||
if err != nil { | ||
return nil, err | ||
} | ||
filename := tmpFile.Name() | ||
if err := tmpFile.Close(); err != nil { | ||
return nil, err | ||
} | ||
|
||
if initialContent != nil { | ||
if err := os.WriteFile(filename, initialContent, 0600); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
cmd := GetCmdExecutor(editorPath, filename) | ||
if err := cmd.Start(); err != nil { | ||
return nil, err | ||
} | ||
if err := cmd.Wait(); err != nil { | ||
return nil, err | ||
} | ||
|
||
data, err := os.ReadFile(filename) //#nosec | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := os.Remove(filename); err != nil { | ||
return nil, err | ||
} | ||
return data, nil | ||
} | ||
|
||
// EditFileValidated prompts a user to edit a file with a predefined prompt, initial content, and separator. | ||
// An optional validation function can be specified to validate the content of each line. | ||
// Entries within the file must be newline-separated. Additionally, a minimum number of entries can be specified. | ||
// The values on each line are joined by the separator and returned to the caller. | ||
func EditFileValidated(prompt, content, separator string, validate func(input string) error, minEntries int) (string, error) { | ||
if separator == "" { | ||
return "", errors.New("a non-empty separator is required") | ||
} | ||
|
||
for { | ||
var partsBytes []byte | ||
if content != "" { | ||
parts := bytes.Split([]byte(content), []byte(separator)) | ||
partsBytes = bytes.Join(parts, []byte("\n")) | ||
} | ||
|
||
partsBytes, err := EditFile(append([]byte(prompt), partsBytes...)) | ||
if err != nil { | ||
return content, err | ||
} | ||
lines := strings.Split(string(partsBytes), "\n") | ||
|
||
// Parse final lines, skipping comments and optionally validating each line | ||
finalLines := make([]string, 0) | ||
for _, l := range lines { | ||
l = strings.TrimSpace(l) | ||
if l != "" && !strings.HasPrefix(l, "#") { | ||
if validate != nil { | ||
if err = validate(l); err != nil { | ||
break | ||
} | ||
} | ||
finalLines = append(finalLines, l) | ||
} | ||
} | ||
|
||
if err != nil && errors.Is(err, ValidationError) { | ||
// for integration tests, return the error | ||
if os.Getenv("IS_TEST") == "true" { | ||
return "", err | ||
} | ||
// otherwise, we assume the validation function logged | ||
// a meaningful error message and let the user try again | ||
time.Sleep(5 * time.Second) | ||
continue | ||
} | ||
if minEntries > 0 && len(finalLines) < minEntries { | ||
logger.Info(fmt.Sprintf("Error editing file: %d or more entries are required", minEntries)) | ||
time.Sleep(5 * time.Second) | ||
continue | ||
} | ||
|
||
content = strings.TrimRight(strings.Join(finalLines, separator), separator) | ||
return content, err | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package mocks | ||
|
||
import ( | ||
"os" | ||
) | ||
|
||
type CommandExecutor interface { | ||
Start() error | ||
Wait() error | ||
} | ||
|
||
type MockFileEditor struct { | ||
FileContents []string | ||
filename string | ||
} | ||
|
||
func (m *MockFileEditor) Start() error { | ||
if err := os.WriteFile(m.filename, []byte(m.FileContents[0]), 0600); err != nil { | ||
return err | ||
} | ||
m.FileContents = m.FileContents[1:] | ||
return nil | ||
} | ||
|
||
func (m *MockFileEditor) Wait() error { | ||
return nil | ||
} | ||
|
||
func (m *MockFileEditor) GetCmdExecutor(vimPath string, filename string) CommandExecutor { | ||
m.filename = filename | ||
return m | ||
} |