From db893494bb1492a3d9e32787a5ada1dd8f639ef3 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Tue, 7 Jan 2020 22:06:42 +0100 Subject: input: better reusable prompt functions --- input/prompt.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 11 deletions(-) (limited to 'input') diff --git a/input/prompt.go b/input/prompt.go index 6036c062..c7887abb 100644 --- a/input/prompt.go +++ b/input/prompt.go @@ -4,23 +4,36 @@ import ( "bufio" "fmt" "os" + "strconv" "strings" + "syscall" + + "golang.org/x/crypto/ssh/terminal" + + "github.com/MichaelMure/git-bug/util/interrupt" ) -func PromptValue(name string, preValue string) (string, error) { - return promptValue(name, preValue, false) +// PromptValidator is a validator for a user entry +type PromptValidator func(name string, value string) (complaint string, err error) + +// Required is a validator preventing a "" value +func Required(name string, value string) (string, error) { + if value == "" { + return fmt.Sprintf("%s is empty", name), nil + } + return "", nil } -func PromptValueRequired(name string, preValue string) (string, error) { - return promptValue(name, preValue, true) +func Prompt(prompt, name string, validators ...PromptValidator) (string, error) { + return PromptDefault(prompt, name, "", validators...) } -func promptValue(name string, preValue string, required bool) (string, error) { +func PromptDefault(prompt, name, preValue string, validators ...PromptValidator) (string, error) { for { if preValue != "" { - _, _ = fmt.Fprintf(os.Stderr, "%s [%s]: ", name, preValue) + _, _ = fmt.Fprintf(os.Stderr, "%s [%s]: ", prompt, preValue) } else { - _, _ = fmt.Fprintf(os.Stderr, "%s: ", name) + _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt) } line, err := bufio.NewReader(os.Stdin).ReadString('\n') @@ -31,14 +44,85 @@ func promptValue(name string, preValue string, required bool) (string, error) { line = strings.TrimSpace(line) if preValue != "" && line == "" { - return preValue, nil + line = preValue } - if required && line == "" { - _, _ = fmt.Fprintf(os.Stderr, "%s is empty\n", name) - continue + for _, validator := range validators { + complaint, err := validator(name, line) + if err != nil { + return "", err + } + if complaint != "" { + _, _ = fmt.Fprintln(os.Stderr, complaint) + continue + } } return line, nil } } + +func PromptPassword(prompt, name string, validators ...PromptValidator) (string, error) { + termState, err := terminal.GetState(syscall.Stdin) + if err != nil { + return "", err + } + + cancel := interrupt.RegisterCleaner(func() error { + return terminal.Restore(syscall.Stdin, termState) + }) + defer cancel() + + for { + _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt) + + bytePassword, err := terminal.ReadPassword(syscall.Stdin) + // new line for coherent formatting, ReadPassword clip the normal new line + // entered by the user + fmt.Println() + + if err != nil { + return "", err + } + + pass := string(bytePassword) + + for _, validator := range validators { + complaint, err := validator(name, pass) + if err != nil { + return "", err + } + if complaint != "" { + _, _ = fmt.Fprintln(os.Stderr, complaint) + continue + } + } + + return pass, nil + } +} + +func PromptChoice(prompt string, choices []string) (int, error) { + for { + for i, choice := range choices { + _, _ = fmt.Fprintf(os.Stderr, "[%d]: %s\n", i+1, choice) + } + _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt) + + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + fmt.Println() + if err != nil { + return 0, err + } + + line = strings.TrimSpace(line) + + index, err := strconv.Atoi(line) + if err != nil || index < 1 || index > len(choices) { + fmt.Println("invalid input") + continue + } + + return index, nil + } +} -- cgit