Skip to main content

wowlab_common/
prompt.rs

1//! Interactive CLI prompts with consistent styling.
2
3use std::fmt::Display;
4
5use dialoguer::{Confirm, Input, MultiSelect, Password, Select, theme::ColorfulTheme};
6use wowlab_fs::path::PathBuf;
7
8fn theme() -> ColorfulTheme {
9    ColorfulTheme::default()
10}
11
12/// Prompt for a text value with an optional default.
13///
14/// # Panics
15///
16/// Panics when terminal input cannot be read.
17#[must_use]
18pub fn text(label: &str, default: Option<&str>) -> String {
19    let t = theme();
20    let mut input = Input::<String>::with_theme(&t).with_prompt(label);
21
22    if let Some(d) = default {
23        input = input.default(d.to_string());
24    }
25
26    input.interact_text().expect("failed to read input")
27}
28
29/// Prompt for a filesystem path with an optional default; does not validate existence.
30#[must_use]
31pub fn path(label: &str, default: Option<&str>) -> PathBuf {
32    PathBuf::from(text(label, default))
33}
34
35/// Prompt for a yes/no confirmation.
36///
37/// # Panics
38///
39/// Panics when terminal input cannot be read.
40#[must_use]
41pub fn confirm(label: &str, default: bool) -> bool {
42    Confirm::with_theme(&theme())
43        .with_prompt(label)
44        .default(default)
45        .interact()
46        .expect("failed to read confirmation")
47}
48
49/// Prompt the user to select one item from a list, returning its index.
50///
51/// # Panics
52///
53/// Panics when terminal input cannot be read.
54pub fn select<T>(label: &str, items: &[T]) -> usize
55where
56    T: Display,
57{
58    Select::with_theme(&theme())
59        .with_prompt(label)
60        .items(items)
61        .default(0)
62        .interact()
63        .expect("failed to read selection")
64}
65
66/// Prompt the user to select multiple items from a list, returning their indices.
67///
68/// # Panics
69///
70/// Panics when terminal input cannot be read.
71pub fn multi_select<T>(label: &str, items: &[T]) -> Vec<usize>
72where
73    T: Display,
74{
75    MultiSelect::with_theme(&theme())
76        .with_prompt(label)
77        .items(items)
78        .interact()
79        .expect("failed to read multi-selection")
80}
81
82/// Prompt the user to select multiple items, returning the items themselves.
83pub fn multi_select_items<T>(label: &str, items: &[T]) -> Vec<T>
84where
85    T: Display + Copy,
86{
87    let indices = multi_select(label, items);
88    // BOUNDS: indices come from dialoguer which guarantees they are valid for `items`.
89
90    indices.into_iter().map(|i| items[i]).collect()
91}
92
93/// Prompt for a secret value (input is hidden).
94///
95/// # Panics
96///
97/// Panics when terminal input cannot be read.
98#[must_use]
99pub fn password(label: &str) -> String {
100    Password::with_theme(&theme())
101        .with_prompt(label)
102        .interact()
103        .expect("failed to read password")
104}
105
106/// Display a resolved value with a dim label, delegating to [`crate::output::kv`].
107pub fn resolved(label: &str, value: &str) {
108    crate::output::kv(label, value);
109}