feat(cli): interactive numbered picker (stdin/stderr separated)
This commit is contained in:
parent
09c3b2b175
commit
e8a4be2698
2 changed files with 118 additions and 0 deletions
66
cli/picker.go
Normal file
66
cli/picker.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseChoice reads a 1-based integer from r, validated in [1, max].
|
||||
func parseChoice(r io.Reader, max int) (int, error) {
|
||||
scanner := bufio.NewScanner(r)
|
||||
if !scanner.Scan() {
|
||||
return 0, fmt.Errorf("no input")
|
||||
}
|
||||
text := strings.TrimSpace(scanner.Text())
|
||||
if text == "" {
|
||||
return 0, fmt.Errorf("enter a number between 1 and %d", max)
|
||||
}
|
||||
n, err := strconv.Atoi(text)
|
||||
if err != nil || n < 1 || n > max {
|
||||
return 0, fmt.Errorf("enter a number between 1 and %d", max)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func pickProduct(products []Product) (Product, error) {
|
||||
fmt.Fprintln(os.Stderr, "\nSelect a product:")
|
||||
for i, p := range products {
|
||||
fmt.Fprintf(os.Stderr, " %2d. %s\n", i+1, p.Name)
|
||||
}
|
||||
fmt.Fprint(os.Stderr, "\nChoice: ")
|
||||
n, err := parseChoice(os.Stdin, len(products))
|
||||
if err != nil {
|
||||
return Product{}, err
|
||||
}
|
||||
return products[n-1], nil
|
||||
}
|
||||
|
||||
func pickLanguage(langs []Language) (Language, error) {
|
||||
fmt.Fprintln(os.Stderr, "\nSelect a language:")
|
||||
for i, l := range langs {
|
||||
fmt.Fprintf(os.Stderr, " %2d. %s\n", i+1, l.Language)
|
||||
}
|
||||
fmt.Fprint(os.Stderr, "\nChoice: ")
|
||||
n, err := parseChoice(os.Stdin, len(langs))
|
||||
if err != nil {
|
||||
return Language{}, err
|
||||
}
|
||||
return langs[n-1], nil
|
||||
}
|
||||
|
||||
func pickEvalProduct(products []EvalProduct) (EvalProduct, error) {
|
||||
fmt.Fprintln(os.Stderr, "\nSelect an evaluation product:")
|
||||
for i, p := range products {
|
||||
fmt.Fprintf(os.Stderr, " %2d. %s\n", i+1, p.Name)
|
||||
}
|
||||
fmt.Fprint(os.Stderr, "\nChoice: ")
|
||||
n, err := parseChoice(os.Stdin, len(products))
|
||||
if err != nil {
|
||||
return EvalProduct{}, err
|
||||
}
|
||||
return products[n-1], nil
|
||||
}
|
||||
52
cli/picker_test.go
Normal file
52
cli/picker_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseChoice_valid(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
max int
|
||||
want int
|
||||
}{
|
||||
{"1\n", 3, 1},
|
||||
{"3\n", 3, 3},
|
||||
{" 2 \n", 5, 2},
|
||||
}
|
||||
for _, c := range cases {
|
||||
got, err := parseChoice(strings.NewReader(c.input), c.max)
|
||||
if err != nil {
|
||||
t.Errorf("input %q: unexpected error: %v", c.input, err)
|
||||
continue
|
||||
}
|
||||
if got != c.want {
|
||||
t.Errorf("input %q: got %d, want %d", c.input, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseChoice_outOfRange(t *testing.T) {
|
||||
cases := []string{"0\n", "4\n"}
|
||||
for _, input := range cases {
|
||||
_, err := parseChoice(strings.NewReader(input), 3)
|
||||
if err == nil {
|
||||
t.Errorf("input %q: expected error for out-of-range, got nil", input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseChoice_nonNumeric(t *testing.T) {
|
||||
_, err := parseChoice(strings.NewReader("abc\n"), 3)
|
||||
if err == nil {
|
||||
t.Error("expected error for non-numeric input, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseChoice_emptyInput(t *testing.T) {
|
||||
_, err := parseChoice(strings.NewReader("\n"), 3)
|
||||
if err == nil {
|
||||
t.Error("expected error for empty input, got nil")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue