aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2020-01-07 22:06:42 +0100
committerMichael Muré <batolettre@gmail.com>2020-02-08 17:18:27 +0100
commitdb893494bb1492a3d9e32787a5ada1dd8f639ef3 (patch)
treeca956e6f2024d82a7600faca6d584cc3406593f0
parent97bc5ccd229b7b438262a84e3c42783b4d4a82af (diff)
downloadgit-bug-db893494bb1492a3d9e32787a5ada1dd8f639ef3.tar.gz
input: better reusable prompt functions
-rw-r--r--bridge/github/config.go139
-rw-r--r--commands/user_create.go6
-rw-r--r--input/prompt.go106
3 files changed, 110 insertions, 141 deletions
diff --git a/bridge/github/config.go b/bridge/github/config.go
index ea4b622f..8c4bf7c5 100644
--- a/bridge/github/config.go
+++ b/bridge/github/config.go
@@ -14,21 +14,19 @@ import (
"sort"
"strconv"
"strings"
- "syscall"
"time"
text "github.com/MichaelMure/go-term-text"
"github.com/pkg/errors"
- "golang.org/x/crypto/ssh/terminal"
"github.com/MichaelMure/git-bug/bridge/core"
"github.com/MichaelMure/git-bug/bridge/core/auth"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entity"
"github.com/MichaelMure/git-bug/identity"
+ "github.com/MichaelMure/git-bug/input"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/colors"
- "github.com/MichaelMure/git-bug/util/interrupt"
)
const (
@@ -320,21 +318,14 @@ func promptToken() (string, error) {
panic("regexp compile:" + err.Error())
}
- for {
- fmt.Print("Enter token: ")
-
- line, err := bufio.NewReader(os.Stdin).ReadString('\n')
- if err != nil {
- return "", err
+ validator := func(name string, value string) (complaint string, err error) {
+ if re.MatchString(value) {
+ return "", nil
}
-
- token := strings.TrimSpace(line)
- if re.MatchString(token) {
- return token, nil
- }
-
- fmt.Println("token has incorrect format")
+ return "token has incorrect format", nil
}
+
+ return input.Prompt("Enter token", "token", "", input.Required, validator)
}
func loginAndRequestToken(owner, project string) (string, error) {
@@ -348,17 +339,18 @@ func loginAndRequestToken(owner, project string) (string, error) {
fmt.Println()
// prompt project visibility to know the token scope needed for the repository
- isPublic, err := promptProjectVisibility()
+ i, err := input.PromptChoice("repository visibility", []string{"public", "private"})
if err != nil {
return "", err
}
+ isPublic := i == 0
username, err := promptUsername()
if err != nil {
return "", err
}
- password, err := promptPassword()
+ password, err := input.PromptPassword("Password", "password", input.Required)
if err != nil {
return "", err
}
@@ -387,12 +379,12 @@ func loginAndRequestToken(owner, project string) (string, error) {
// Handle 2FA is needed
OTPHeader := resp.Header.Get("X-GitHub-OTP")
if resp.StatusCode == http.StatusUnauthorized && OTPHeader != "" {
- otpCode, err := prompt2FA()
+ otpCode, err := input.PromptPassword("Two-factor authentication code", "code", input.Required)
if err != nil {
return "", err
}
- resp, err = requestTokenWith2FA(note, username, password, otpCode, scope)
+ resp, err = requestTokenWith2FA(note, login, password, otpCode, scope)
if err != nil {
return "", err
}
@@ -408,29 +400,6 @@ func loginAndRequestToken(owner, project string) (string, error) {
return "", fmt.Errorf("error creating token %v: %v", resp.StatusCode, string(b))
}
-func promptUsername() (string, error) {
- for {
- fmt.Print("username: ")
-
- line, err := bufio.NewReader(os.Stdin).ReadString('\n')
- if err != nil {
- return "", err
- }
-
- line = strings.TrimSpace(line)
-
- ok, err := validateUsername(line)
- if err != nil {
- return "", err
- }
- if ok {
- return line, nil
- }
-
- fmt.Println("invalid username")
- }
-}
-
func promptURL(repo repository.RepoCommon) (string, string, error) {
// remote suggestions
remotes, err := repo.GetRemotes()
@@ -585,87 +554,3 @@ func validateProject(owner, project string, token *auth.Token) (bool, error) {
return resp.StatusCode == http.StatusOK, nil
}
-
-func promptPassword() (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()
-
- for {
- fmt.Print("password: ")
-
- 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
- }
-
- if len(bytePassword) > 0 {
- return string(bytePassword), nil
- }
-
- fmt.Println("password is empty")
- }
-}
-
-func prompt2FA() (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()
-
- for {
- fmt.Print("two-factor authentication code: ")
-
- byte2fa, err := terminal.ReadPassword(int(syscall.Stdin))
- fmt.Println()
- if err != nil {
- return "", err
- }
-
- if len(byte2fa) > 0 {
- return string(byte2fa), nil
- }
-
- fmt.Println("code is empty")
- }
-}
-
-func promptProjectVisibility() (bool, error) {
- for {
- fmt.Println("[1]: public")
- fmt.Println("[2]: private")
- fmt.Print("repository visibility: ")
-
- line, err := bufio.NewReader(os.Stdin).ReadString('\n')
- fmt.Println()
- if err != nil {
- return false, err
- }
-
- line = strings.TrimSpace(line)
-
- index, err := strconv.Atoi(line)
- if err != nil || (index != 1 && index != 2) {
- fmt.Println("invalid input")
- continue
- }
-
- // return true for public repositories, false for private
- return index == 1, nil
- }
-}
diff --git a/commands/user_create.go b/commands/user_create.go
index 15b9767e..dab8a879 100644
--- a/commands/user_create.go
+++ b/commands/user_create.go
@@ -23,7 +23,7 @@ func runUserCreate(cmd *cobra.Command, args []string) error {
return err
}
- name, err := input.PromptValueRequired("Name", preName)
+ name, err := input.Prompt("Name", "name", preName, input.Required)
if err != nil {
return err
}
@@ -33,12 +33,12 @@ func runUserCreate(cmd *cobra.Command, args []string) error {
return err
}
- email, err := input.PromptValueRequired("Email", preEmail)
+ email, err := input.Prompt("Email", "email", preEmail, input.Required)
if err != nil {
return err
}
- login, err := input.PromptValue("Avatar URL", "")
+ login, err := input.Prompt("Avatar URL", "avatar", "")
if err != nil {
return err
}
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
+ }
+}