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 +}