These wrap existing helpers to implement certain retry behaviour. Signed-off-by: Yonah <contrib@gensokyo.uk>
136 lines
2.8 KiB
Go
136 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"hakurei.app/ext"
|
|
)
|
|
|
|
// toErr writes panic value satisfying error to the value pointed to by errP,
|
|
// and re-panics other non-nil values.
|
|
func toErr(errP *error) {
|
|
r := recover()
|
|
if r == nil {
|
|
return
|
|
}
|
|
|
|
if err, ok := r.(error); !ok {
|
|
panic(r)
|
|
} else if *errP == nil {
|
|
*errP = err
|
|
}
|
|
}
|
|
|
|
// isattyStd checks whether standard streams used for prompts are terminals.
|
|
func isattyStd() {
|
|
if !ext.Isatty(syscall.Stdin) {
|
|
panic(errors.New("stdin does not appear to be a terminal"))
|
|
}
|
|
if !ext.Isatty(syscall.Stderr) {
|
|
panic(errors.New("stdin does not appear to be a terminal"))
|
|
}
|
|
}
|
|
|
|
// prompt prompts the user and reads the response as a string.
|
|
func prompt(br *bufio.Reader, p string) (string, bool) {
|
|
os.Stderr.WriteString(p)
|
|
s, err := br.ReadString('\n')
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return "", false
|
|
}
|
|
panic(err)
|
|
}
|
|
return s[:len(s)-1], true
|
|
}
|
|
|
|
// promptTrimNoEmpty prompts the user until EOF or a non-empty and
|
|
// non-whitespace-only response is read. It returns read response with leading
|
|
// and trailing whitespaces trimmed.
|
|
func promptTrimNoEmpty(br *bufio.Reader, p string) (string, bool) {
|
|
invalid:
|
|
s, ok := prompt(br, p)
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
goto invalid
|
|
}
|
|
return s, true
|
|
}
|
|
|
|
// noResponseUint is returned by promptUint when the user responds with a single
|
|
// newline character.
|
|
const noResponseUint = math.MaxUint64
|
|
|
|
// promptUint prompts the user for an unsigned integer until EOF or a valid
|
|
// response is read.
|
|
func promptUint(br *bufio.Reader, p string) (uint64, bool) {
|
|
invalid:
|
|
s, ok := prompt(br, p)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
|
|
if s == "" {
|
|
return noResponseUint, false
|
|
}
|
|
|
|
v, err := strconv.ParseUint(s, 10, 64)
|
|
if err != nil {
|
|
goto invalid
|
|
}
|
|
return v, true
|
|
}
|
|
|
|
// promptUintFallback is like promptUint, but returns fallback if the user
|
|
// responds with a single newline character.
|
|
func promptUintFallback(
|
|
br *bufio.Reader,
|
|
p string,
|
|
fallback uint64,
|
|
) (uint64, bool) {
|
|
v, ok := promptUint(br, p+
|
|
"["+strconv.FormatUint(fallback, 10)+"] ")
|
|
if !ok && v == noResponseUint {
|
|
v, ok = fallback, true
|
|
}
|
|
return v, ok
|
|
}
|
|
|
|
// promptUintFallbackBounds is like promptUint, but checks input bounds and
|
|
// returns fallback if the user responds with a single newline character.
|
|
func promptUintFallbackBounds(
|
|
br *bufio.Reader,
|
|
p string,
|
|
lb, ub uint64,
|
|
fallback uint64,
|
|
) (uint64, bool) {
|
|
invalid:
|
|
v, ok := promptUintFallback(br, p, fallback)
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
if v < lb || v > ub {
|
|
goto invalid
|
|
}
|
|
return v, true
|
|
}
|
|
|
|
// require panics with an error if ok is false, and returns v otherwise.
|
|
func require[T any](v T, ok bool) T {
|
|
if !ok {
|
|
panic(errors.New("cancelled"))
|
|
}
|
|
return v
|
|
}
|