mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-15 10:12:06 +03:00
266 lines
6.2 KiB
Go
266 lines
6.2 KiB
Go
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
|
|
}
|
|
}
|
|
}
|