prompt.go

  1package input
  2
  3import (
  4	"bufio"
  5	"fmt"
  6	"os"
  7	"strconv"
  8	"strings"
  9	"syscall"
 10
 11	"golang.org/x/crypto/ssh/terminal"
 12
 13	"github.com/MichaelMure/git-bug/util/interrupt"
 14)
 15
 16// PromptValidator is a validator for a user entry
 17// If complaint is "", value is considered valid, otherwise it's the error reported to the user
 18// If err != nil, a terminal error happened
 19type PromptValidator func(name string, value string) (complaint string, err error)
 20
 21// Required is a validator preventing a "" value
 22func Required(name string, value string) (string, error) {
 23	if value == "" {
 24		return fmt.Sprintf("%s is empty", name), nil
 25	}
 26	return "", nil
 27}
 28
 29func Prompt(prompt, name string, validators ...PromptValidator) (string, error) {
 30	return PromptDefault(prompt, name, "", validators...)
 31}
 32
 33func PromptDefault(prompt, name, preValue string, validators ...PromptValidator) (string, error) {
 34	for {
 35		if preValue != "" {
 36			_, _ = fmt.Fprintf(os.Stderr, "%s [%s]: ", prompt, preValue)
 37		} else {
 38			_, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt)
 39		}
 40
 41		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
 42		if err != nil {
 43			return "", err
 44		}
 45
 46		line = strings.TrimSpace(line)
 47
 48		if preValue != "" && line == "" {
 49			line = preValue
 50		}
 51
 52		for _, validator := range validators {
 53			complaint, err := validator(name, line)
 54			if err != nil {
 55				return "", err
 56			}
 57			if complaint != "" {
 58				_, _ = fmt.Fprintln(os.Stderr, complaint)
 59				continue
 60			}
 61		}
 62
 63		return line, nil
 64	}
 65}
 66
 67func PromptPassword(prompt, name string, validators ...PromptValidator) (string, error) {
 68	termState, err := terminal.GetState(syscall.Stdin)
 69	if err != nil {
 70		return "", err
 71	}
 72
 73	cancel := interrupt.RegisterCleaner(func() error {
 74		return terminal.Restore(syscall.Stdin, termState)
 75	})
 76	defer cancel()
 77
 78	for {
 79		_, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt)
 80
 81		bytePassword, err := terminal.ReadPassword(syscall.Stdin)
 82		// new line for coherent formatting, ReadPassword clip the normal new line
 83		// entered by the user
 84		fmt.Println()
 85
 86		if err != nil {
 87			return "", err
 88		}
 89
 90		pass := string(bytePassword)
 91
 92		for _, validator := range validators {
 93			complaint, err := validator(name, pass)
 94			if err != nil {
 95				return "", err
 96			}
 97			if complaint != "" {
 98				_, _ = fmt.Fprintln(os.Stderr, complaint)
 99				continue
100			}
101		}
102
103		return pass, nil
104	}
105}
106
107func PromptChoice(prompt string, choices []string) (int, error) {
108	for {
109		for i, choice := range choices {
110			_, _ = fmt.Fprintf(os.Stderr, "[%d]: %s\n", i+1, choice)
111		}
112		_, _ = fmt.Fprintf(os.Stderr, "%s: ", prompt)
113
114		line, err := bufio.NewReader(os.Stdin).ReadString('\n')
115		fmt.Println()
116		if err != nil {
117			return 0, err
118		}
119
120		line = strings.TrimSpace(line)
121
122		index, err := strconv.Atoi(line)
123		if err != nil || index < 1 || index > len(choices) {
124			fmt.Println("invalid input")
125			continue
126		}
127
128		return index, nil
129	}
130}