aboutsummaryrefslogtreecommitdiffstats
path: root/commands/input/prompt.go
diff options
context:
space:
mode:
Diffstat (limited to 'commands/input/prompt.go')
-rw-r--r--commands/input/prompt.go265
1 files changed, 265 insertions, 0 deletions
diff --git a/commands/input/prompt.go b/commands/input/prompt.go
new file mode 100644
index 00000000..576d45bb
--- /dev/null
+++ b/commands/input/prompt.go
@@ -0,0 +1,265 @@
+package input
+
+import (
+ "bufio"
+ "fmt"
+ "net/url"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+
+ "golang.org/x/crypto/ssh/terminal"
+
+ "github.com/MichaelMure/git-bug/bridge/core/auth"
+ "github.com/MichaelMure/git-bug/util/colors"
+ "github.com/MichaelMure/git-bug/util/interrupt"
+)
+
+// PromptValidator is a validator for a user entry
+// If complaint is "", value is considered valid, otherwise it's the error reported to the user
+// If err != nil, a terminal error happened
+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
+}
+
+// IsURL is a validator checking that the value is a fully formed URL
+func IsURL(name string, value string) (string, error) {
+ u, err := url.Parse(value)
+ if err != nil {
+ return fmt.Sprintf("%s is invalid: %v", name, err), nil
+ }
+ if u.Scheme == "" {
+ return fmt.Sprintf("%s is missing a scheme", name), nil
+ }
+ if u.Host == "" {
+ return fmt.Sprintf("%s is missing a host", name), nil
+ }
+ return "", nil
+}
+
+// Prompts
+
+// Prompt is a simple text input.
+func Prompt(prompt, name string, validators ...PromptValidator) (string, error) {
+ return PromptDefault(prompt, name, "", validators...)
+}
+
+// PromptDefault is a simple text input with a default value.
+func PromptDefault(prompt, name, preValue string, validators ...PromptValidator) (string, error) {
+loop:
+ for {
+ if preValue != "" {
+ _, _ = fmt.Fprintf(os.Stderr, "%s [%s]: ", prompt, preValue)
+ } else {
+ _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt)
+ }
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ if err != nil {
+ return "", err
+ }
+
+ line = strings.TrimSpace(line)
+
+ if preValue != "" && line == "" {
+ line = preValue
+ }
+
+ for _, validator := range validators {
+ complaint, err := validator(name, line)
+ if err != nil {
+ return "", err
+ }
+ if complaint != "" {
+ _, _ = fmt.Fprintln(os.Stderr, complaint)
+ continue loop
+ }
+ }
+
+ return line, nil
+ }
+}
+
+// PromptPassword is a specialized text input that doesn't display the characters entered.
+func PromptPassword(prompt, name string, validators ...PromptValidator) (string, error) {
+ termState, err := terminal.GetState(int(syscall.Stdin))
+ if err != nil {
+ return "", err
+ }
+
+ cancel := interrupt.RegisterCleaner(func() error {
+ return terminal.Restore(int(syscall.Stdin), termState)
+ })
+ defer cancel()
+
+loop:
+ for {
+ _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt)
+
+ bytePassword, err := terminal.ReadPassword(int(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 loop
+ }
+ }
+
+ return pass, nil
+ }
+}
+
+// PromptChoice is a prompt giving possible choices
+// Return the index starting at zero of the choice selected.
+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.Fprintln(os.Stderr, "invalid input")
+ continue
+ }
+
+ return index - 1, nil
+ }
+}
+
+func PromptURLWithRemote(prompt, name string, validRemotes []string, validators ...PromptValidator) (string, error) {
+ if len(validRemotes) == 0 {
+ return Prompt(prompt, name, validators...)
+ }
+
+ sort.Strings(validRemotes)
+
+ for {
+ _, _ = fmt.Fprintln(os.Stderr, "\nDetected projects:")
+
+ for i, remote := range validRemotes {
+ _, _ = fmt.Fprintf(os.Stderr, "[%d]: %v\n", i+1, remote)
+ }
+
+ _, _ = fmt.Fprintf(os.Stderr, "\n[0]: Another project\n\n")
+ _, _ = fmt.Fprintf(os.Stderr, "Select option: ")
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ if err != nil {
+ return "", err
+ }
+
+ line = strings.TrimSpace(line)
+
+ index, err := strconv.Atoi(line)
+ if err != nil || index < 0 || index > len(validRemotes) {
+ _, _ = fmt.Fprintln(os.Stderr, "invalid input")
+ continue
+ }
+
+ // if user want to enter another project url break this loop
+ if index == 0 {
+ break
+ }
+
+ return validRemotes[index-1], nil
+ }
+
+ return Prompt(prompt, name, validators...)
+}
+
+func PromptCredential(target, name string, credentials []auth.Credential, choices []string) (auth.Credential, int, error) {
+ if len(credentials) == 0 && len(choices) == 0 {
+ return nil, 0, fmt.Errorf("no possible choice")
+ }
+ if len(credentials) == 0 && len(choices) == 1 {
+ return nil, 0, nil
+ }
+
+ sort.Sort(auth.ById(credentials))
+
+ for {
+ _, _ = fmt.Fprintln(os.Stderr)
+
+ offset := 0
+ for i, choice := range choices {
+ _, _ = fmt.Fprintf(os.Stderr, "[%d]: %s\n", i+1, choice)
+ offset++
+ }
+
+ if len(credentials) > 0 {
+ _, _ = fmt.Fprintln(os.Stderr)
+ _, _ = fmt.Fprintf(os.Stderr, "Existing %s for %s:\n", name, target)
+
+ for i, cred := range credentials {
+ meta := make([]string, 0, len(cred.Metadata()))
+ for k, v := range cred.Metadata() {
+ meta = append(meta, k+":"+v)
+ }
+ sort.Strings(meta)
+ metaFmt := strings.Join(meta, ",")
+
+ fmt.Printf("[%d]: %s => (%s) (%s)\n",
+ i+1+offset,
+ colors.Cyan(cred.ID().Human()),
+ metaFmt,
+ cred.CreateTime().Format(time.RFC822),
+ )
+ }
+ }
+
+ _, _ = fmt.Fprintln(os.Stderr)
+ _, _ = fmt.Fprintf(os.Stderr, "Select option: ")
+
+ line, err := bufio.NewReader(os.Stdin).ReadString('\n')
+ _, _ = fmt.Fprintln(os.Stderr)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ line = strings.TrimSpace(line)
+ index, err := strconv.Atoi(line)
+ if err != nil || index < 1 || index > len(choices)+len(credentials) {
+ _, _ = fmt.Fprintln(os.Stderr, "invalid input")
+ continue
+ }
+
+ switch {
+ case index <= len(choices):
+ return nil, index - 1, nil
+ default:
+ return credentials[index-len(choices)-1], 0, nil
+ }
+ }
+}