From 04520390a73c4a3e0bb148b0efd1b981d1f9d55b Mon Sep 17 00:00:00 2001 From: Yonah Date: Wed, 18 Mar 2026 18:42:53 +0900 Subject: [PATCH] cmd/streamdata: prompt helpers These read user input from the terminal. They are quite sound, but only manually tested for now. Signed-off-by: Yonah --- cmd/streamdata/prompt.go | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 cmd/streamdata/prompt.go diff --git a/cmd/streamdata/prompt.go b/cmd/streamdata/prompt.go new file mode 100644 index 0000000..40cf31b --- /dev/null +++ b/cmd/streamdata/prompt.go @@ -0,0 +1,83 @@ +package main + +import ( + "bufio" + "errors" + "io" + "math" + "os" + "strconv" + "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 +} + +// 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 +} + +// 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 +}