promptNonInteractiveSelect prints a numbered list and reads a single selection. The user may enter a number (1-based index) or the option value directly.
(scanner *bufio.Scanner, title string, options []struct{ label, value string })
| 382 | // promptNonInteractiveSelect prints a numbered list and reads a single selection. |
| 383 | // The user may enter a number (1-based index) or the option value directly. |
| 384 | func promptNonInteractiveSelect(scanner *bufio.Scanner, title string, options []struct{ label, value string }) (string, error) { |
| 385 | fmt.Fprintf(os.Stderr, "\n%s\n", title) |
| 386 | for i, opt := range options { |
| 387 | fmt.Fprintf(os.Stderr, " %d) %s\n", i+1, opt.label) |
| 388 | } |
| 389 | fmt.Fprintf(os.Stderr, "Select (1-%d): ", len(options)) |
| 390 | |
| 391 | if !scanner.Scan() { |
| 392 | if err := scanner.Err(); err != nil { |
| 393 | return "", fmt.Errorf("failed to read input: %w", err) |
| 394 | } |
| 395 | return "", errors.New("no input provided") |
| 396 | } |
| 397 | input := strings.TrimSpace(scanner.Text()) |
| 398 | |
| 399 | // Accept a numeric index |
| 400 | if idx, err := strconv.Atoi(input); err == nil { |
| 401 | if idx < 1 || idx > len(options) { |
| 402 | return "", fmt.Errorf("selection out of range (must be 1-%d)", len(options)) |
| 403 | } |
| 404 | return options[idx-1].value, nil |
| 405 | } |
| 406 | |
| 407 | // Accept the value directly |
| 408 | for _, opt := range options { |
| 409 | if opt.value == input { |
| 410 | return opt.value, nil |
| 411 | } |
| 412 | } |
| 413 | return "", fmt.Errorf("invalid selection %q", input) |
| 414 | } |
| 415 | |
| 416 | // promptNonInteractiveMultiSelect prints a numbered list and reads comma-separated selections. |
| 417 | // Each token may be a 1-based index or an option value. An empty input selects nothing. |