promptNonInteractiveMultiSelect prints a numbered list and reads comma-separated selections. Each token may be a 1-based index or an option value. An empty input selects nothing.
(scanner *bufio.Scanner, title string, options []struct{ label, value string })
| 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. |
| 418 | func promptNonInteractiveMultiSelect(scanner *bufio.Scanner, title string, options []struct{ label, value string }) ([]string, error) { |
| 419 | fmt.Fprintf(os.Stderr, "\n%s\n", title) |
| 420 | for i, opt := range options { |
| 421 | fmt.Fprintf(os.Stderr, " %d) %s\n", i+1, opt.label) |
| 422 | } |
| 423 | fmt.Fprintf(os.Stderr, "Enter comma-separated numbers or values (leave blank for none): ") |
| 424 | |
| 425 | if !scanner.Scan() { |
| 426 | if err := scanner.Err(); err != nil { |
| 427 | return nil, fmt.Errorf("failed to read input: %w", err) |
| 428 | } |
| 429 | // EOF / empty → no selections |
| 430 | return nil, nil |
| 431 | } |
| 432 | input := strings.TrimSpace(scanner.Text()) |
| 433 | if input == "" { |
| 434 | return nil, nil |
| 435 | } |
| 436 | |
| 437 | // Build a lookup map for value-based selection |
| 438 | valueSet := make(map[string]string, len(options)) |
| 439 | for _, opt := range options { |
| 440 | valueSet[opt.value] = opt.value |
| 441 | } |
| 442 | |
| 443 | tokens := strings.Split(input, ",") |
| 444 | seen := make(map[string]struct{}, len(tokens)) |
| 445 | var selected []string |
| 446 | for _, tok := range tokens { |
| 447 | tok = strings.TrimSpace(tok) |
| 448 | if tok == "" { |
| 449 | continue |
| 450 | } |
| 451 | |
| 452 | // Try numeric index |
| 453 | if idx, err := strconv.Atoi(tok); err == nil { |
| 454 | if idx < 1 || idx > len(options) { |
| 455 | return nil, fmt.Errorf("selection %d out of range (must be 1-%d)", idx, len(options)) |
| 456 | } |
| 457 | val := options[idx-1].value |
| 458 | if _, dup := seen[val]; !dup { |
| 459 | seen[val] = struct{}{} |
| 460 | selected = append(selected, val) |
| 461 | } |
| 462 | continue |
| 463 | } |
| 464 | |
| 465 | // Try value directly |
| 466 | if val, ok := valueSet[tok]; ok { |
| 467 | if _, dup := seen[val]; !dup { |
| 468 | seen[val] = struct{}{} |
| 469 | selected = append(selected, val) |
| 470 | } |
| 471 | continue |
| 472 | } |
| 473 | |
| 474 | return nil, fmt.Errorf("unknown option %q", tok) |
| 475 | } |