Skip to main content

pimalaya_cli/
prompt.rs

1//! Interactive prompt helpers wrapping the inquire crate.
2//!
3//! Thin typed wrappers for prompting free text, booleans and a choice
4//! from a list, each returning a [`PromptResult`].
5
6use core::fmt;
7
8use inquire::{Confirm, InquireError, Select, Text};
9use thiserror::Error;
10
11/// Error raised when a prompt fails or is cancelled.
12#[derive(Debug, Error)]
13pub enum PromptError {
14    /// Prompting free text failed.
15    #[error("cannot prompt text")]
16    Text(#[source] InquireError),
17    /// Prompting a yes/no confirmation failed.
18    #[error("cannot prompt boolean")]
19    Bool(#[source] InquireError),
20    /// Prompting a choice from a list failed.
21    #[error("cannot prompt item from list")]
22    Item(#[source] InquireError),
23}
24
25/// Result of a prompt helper.
26pub type PromptResult<T> = Result<T, PromptError>;
27
28/// Prompts for free text, with an optional default.
29pub fn text<T: AsRef<str>>(prompt: T, default: Option<T>) -> PromptResult<String> {
30    let mut prompt = Text::new(prompt.as_ref());
31
32    if let Some(default) = default.as_ref() {
33        prompt = prompt.with_default(default.as_ref())
34    }
35
36    prompt.prompt().map_err(PromptError::Text)
37}
38
39/// Prompts for free text, returning `None` when skipped.
40pub fn some_text<T: AsRef<str>>(prompt: T, default: Option<T>) -> PromptResult<Option<String>> {
41    let mut prompt = Text::new(prompt.as_ref());
42
43    if let Some(default) = default.as_ref() {
44        prompt = prompt.with_default(default.as_ref())
45    }
46
47    prompt.prompt_skippable().map_err(PromptError::Text)
48}
49
50/// Prompts for a yes/no confirmation, with a default.
51pub fn bool(prompt: impl AsRef<str>, default: bool) -> PromptResult<bool> {
52    Confirm::new(prompt.as_ref())
53        .with_default(default)
54        .prompt()
55        .map_err(PromptError::Bool)
56}
57
58/// Prompts for one item chosen from a list, with an optional default
59/// selection.
60pub fn item<T: fmt::Display + Eq>(
61    prompt: impl AsRef<str>,
62    items: impl IntoIterator<Item = T>,
63    default: Option<T>,
64) -> PromptResult<T> {
65    let items: Vec<_> = items.into_iter().collect();
66
67    let default = if let Some(default) = default.as_ref() {
68        items
69            .iter()
70            .enumerate()
71            .find_map(|(i, item)| if item == default { Some(i) } else { None })
72    } else {
73        None
74    };
75
76    let mut prompt = Select::new(prompt.as_ref(), items).without_filtering();
77
78    if let Some(default) = default.as_ref() {
79        prompt = prompt.with_starting_cursor(*default);
80    }
81
82    prompt.prompt().map_err(PromptError::Item)
83}