diff options
Diffstat (limited to 'input')
-rw-r--r-- | input/prompt.go | 182 |
1 files changed, 179 insertions, 3 deletions
diff --git a/input/prompt.go b/input/prompt.go index 960ecd62..2093695f 100644 --- a/input/prompt.go +++ b/input/prompt.go @@ -2,14 +2,20 @@ package input import ( "bufio" + "errors" "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" ) @@ -26,11 +32,27 @@ func Required(name string, value string) (string, error) { 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 +} + func Prompt(prompt, name string, validators ...PromptValidator) (string, error) { return PromptDefault(prompt, name, "", validators...) } func PromptDefault(prompt, name, preValue string, validators ...PromptValidator) (string, error) { +loop: for { if preValue != "" { _, _ = fmt.Fprintf(os.Stderr, "%s [%s]: ", prompt, preValue) @@ -56,7 +78,7 @@ func PromptDefault(prompt, name, preValue string, validators ...PromptValidator) } if complaint != "" { _, _ = fmt.Fprintln(os.Stderr, complaint) - continue + continue loop } } @@ -75,6 +97,7 @@ func PromptPassword(prompt, name string, validators ...PromptValidator) (string, }) defer cancel() +loop: for { _, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt) @@ -96,7 +119,7 @@ func PromptPassword(prompt, name string, validators ...PromptValidator) (string, } if complaint != "" { _, _ = fmt.Fprintln(os.Stderr, complaint) - continue + continue loop } } @@ -121,10 +144,163 @@ func PromptChoice(prompt string, choices []string) (int, error) { index, err := strconv.Atoi(line) if err != nil || index < 1 || index > len(choices) { - fmt.Println("invalid input") + _, _ = fmt.Fprintf(os.Stderr, "invalid input") continue } return index, 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.Fprintf(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...) +} + +var ErrDirectPrompt = errors.New("direct prompt selected") +var ErrInteractiveCreation = errors.New("interactive creation selected") + +func PromptCredential(target, name string, credentials []auth.Credential) (auth.Credential, error) { + if len(credentials) == 0 { + return nil, nil + } + + sort.Sort(auth.ById(credentials)) + + for { + _, _ = fmt.Fprintf(os.Stderr, "[1]: enter my %s\n", name) + + _, _ = fmt.Fprintln(os.Stderr) + _, _ = fmt.Fprintf(os.Stderr, "Existing %s for %s:", 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+2, + 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, err + } + + line = strings.TrimSpace(line) + index, err := strconv.Atoi(line) + if err != nil || index < 1 || index > len(credentials)+1 { + _, _ = fmt.Fprintln(os.Stderr, "invalid input") + continue + } + + switch index { + case 1: + return nil, ErrDirectPrompt + default: + return credentials[index-2], nil + } + } +} + +func PromptCredentialWithInteractive(target, name string, credentials []auth.Credential) (auth.Credential, error) { + sort.Sort(auth.ById(credentials)) + + for { + _, _ = fmt.Fprintf(os.Stderr, "[1]: enter my %s\n", name) + _, _ = fmt.Fprintf(os.Stderr, "[2]: interactive %s creation\n", name) + + if len(credentials) > 0 { + _, _ = fmt.Fprintln(os.Stderr) + _, _ = fmt.Fprintf(os.Stderr, "Existing %s for %s:", 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+2, + 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, err + } + + line = strings.TrimSpace(line) + index, err := strconv.Atoi(line) + if err != nil || index < 1 || index > len(credentials)+1 { + _, _ = fmt.Fprintln(os.Stderr, "invalid input") + continue + } + + switch index { + case 1: + return nil, ErrDirectPrompt + case 2: + return nil, ErrInteractiveCreation + default: + return credentials[index-3], nil + } + } +} |