Files
streamdata/cmd/streamdata/prompt.go
Yonah 76485e7708 cmd/streamdata: additional prompt helpers
These wrap existing helpers to implement certain retry behaviour.

Signed-off-by: Yonah <contrib@gensokyo.uk>
2026-03-19 00:36:46 +09:00

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
}