1use core::fmt;
8
9use inquire::{Confirm, InquireError, Password, PasswordDisplayMode, Select, Text};
10use secrecy::SecretString;
11use thiserror::Error;
12
13use crate::validator::{U16Validator, UsizeValidator};
14
15#[derive(Debug, Error)]
17pub enum PromptError {
18 #[error("cannot prompt unsigned integer (u16)")]
20 U16(#[source] InquireError),
21 #[error("cannot prompt unsigned integer (usize)")]
23 Usize(#[source] InquireError),
24 #[error("cannot prompt secret")]
26 Secret(#[source] InquireError),
27 #[error("cannot prompt password")]
29 Password(#[source] InquireError),
30 #[error("cannot prompt text")]
32 Text(#[source] InquireError),
33 #[error("cannot prompt boolean")]
35 Bool(#[source] InquireError),
36 #[error("cannot prompt item from list")]
38 Item(#[source] InquireError),
39}
40
41pub type PromptResult<T> = Result<T, PromptError>;
43
44pub fn u16(prompt: impl AsRef<str>, default: Option<u16>) -> PromptResult<u16> {
46 let prompt = Text::new(prompt.as_ref()).with_validator(U16Validator);
47
48 let number = if let Some(default) = default {
49 prompt.with_default(&default.to_string()).prompt()
50 } else {
51 prompt.prompt()
52 };
53
54 match number {
55 Ok(number) => Ok(number.parse().unwrap()),
56 Err(err) => Err(PromptError::U16(err)),
57 }
58}
59
60pub fn usize(prompt: impl AsRef<str>, default: Option<usize>) -> PromptResult<usize> {
62 let prompt = Text::new(prompt.as_ref()).with_validator(UsizeValidator);
63
64 let number = if let Some(default) = default {
65 prompt.with_default(&default.to_string()).prompt()
66 } else {
67 prompt.prompt()
68 };
69
70 match number {
71 Ok(number) => Ok(number.parse().unwrap()),
72 Err(err) => Err(PromptError::Usize(err)),
73 }
74}
75
76pub fn secret(prompt: impl AsRef<str>) -> PromptResult<String> {
78 Password::new(prompt.as_ref())
79 .with_display_mode(PasswordDisplayMode::Masked)
80 .without_confirmation()
81 .prompt()
82 .map_err(PromptError::Secret)
83}
84
85pub fn some_secret(prompt: impl AsRef<str>) -> PromptResult<Option<SecretString>> {
87 Password::new(prompt.as_ref())
88 .with_display_mode(PasswordDisplayMode::Masked)
89 .without_confirmation()
90 .prompt_skippable()
91 .map(|secret| secret.map(Into::into))
92 .map_err(PromptError::Secret)
93}
94
95pub fn password(prompt: impl AsRef<str>, confirm: impl AsRef<str>) -> PromptResult<SecretString> {
97 Password::new(prompt.as_ref())
98 .with_display_mode(PasswordDisplayMode::Masked)
99 .with_custom_confirmation_message(confirm.as_ref())
100 .prompt()
101 .map(Into::into)
102 .map_err(PromptError::Password)
103}
104
105pub fn text<T: AsRef<str>>(prompt: T, default: Option<T>) -> PromptResult<String> {
107 let mut prompt = Text::new(prompt.as_ref());
108
109 if let Some(default) = default.as_ref() {
110 prompt = prompt.with_default(default.as_ref())
111 }
112
113 prompt.prompt().map_err(PromptError::Text)
114}
115
116pub fn some_text<T: AsRef<str>>(prompt: T, default: Option<T>) -> PromptResult<Option<String>> {
118 let mut prompt = Text::new(prompt.as_ref());
119
120 if let Some(default) = default.as_ref() {
121 prompt = prompt.with_default(default.as_ref())
122 }
123
124 prompt.prompt_skippable().map_err(PromptError::Text)
125}
126
127pub fn bool(prompt: impl AsRef<str>, default: bool) -> PromptResult<bool> {
129 Confirm::new(prompt.as_ref())
130 .with_default(default)
131 .prompt()
132 .map_err(PromptError::Bool)
133}
134
135pub fn item<T: fmt::Display + Eq>(
138 prompt: impl AsRef<str>,
139 items: impl IntoIterator<Item = T>,
140 default: Option<T>,
141) -> PromptResult<T> {
142 let items: Vec<_> = items.into_iter().collect();
143
144 let default = if let Some(default) = default.as_ref() {
145 items
146 .iter()
147 .enumerate()
148 .find_map(|(i, item)| if item == default { Some(i) } else { None })
149 } else {
150 None
151 };
152
153 let mut prompt = Select::new(prompt.as_ref(), items);
154
155 if let Some(default) = default.as_ref() {
156 prompt = prompt.with_starting_cursor(*default);
157 }
158
159 prompt.prompt().map_err(PromptError::Item)
160}