1use crate::console::Console;
4use std::fmt::Display;
5use std::io::{self, Write};
6use std::str::FromStr;
7
8pub struct Prompt<T> {
10 prompt: String,
11 default: Option<T>,
12 password: bool,
13 choices: Option<Vec<T>>,
14 show_default: bool,
15 show_choices: bool,
16 _case_sensitive: bool,
17}
18
19impl<T> Prompt<T>
20where
21 T: FromStr + Display + Clone + PartialEq,
22 T::Err: Display,
23{
24 pub fn new(prompt: &str) -> Self {
26 Self {
27 prompt: prompt.to_string(),
28 default: None,
29 password: false,
30 choices: None,
31 show_default: true,
32 show_choices: true,
33 _case_sensitive: true,
34 }
35 }
36
37 pub fn default(mut self, default: T) -> Self {
39 self.default = Some(default);
40 self
41 }
42
43 pub fn secret(mut self) -> Self {
45 self.password = true;
46 self
47 }
48
49 pub fn choices(mut self, choices: &[T]) -> Self {
51 self.choices = Some(choices.to_vec());
52 self
53 }
54
55 pub fn show_default(mut self, show: bool) -> Self {
57 self.show_default = show;
58 self
59 }
60
61 pub fn show_choices(mut self, show: bool) -> Self {
63 self.show_choices = show;
64 self
65 }
66
67 pub fn ask(&self) -> T {
69 let console = Console::new();
70
71 loop {
72 self.render_prompt(&console);
73 let _ = io::stdout().flush();
74
75 let mut input = String::new();
76 if self.password {
77 io::stdin().read_line(&mut input).unwrap_or_default();
81 } else {
82 io::stdin().read_line(&mut input).unwrap_or_default();
83 }
84
85 let trimmed = input.trim();
86
87 if trimmed.is_empty() {
88 if let Some(ref def) = self.default {
89 return def.clone();
90 }
91 }
92
93 match T::from_str(trimmed) {
94 Ok(val) => {
95 if let Some(ref choices) = self.choices {
96 if !choices.contains(&val) {
97 console
98 .print("[bold red]Please select one of the available options[/]");
99 continue;
100 }
101 }
102 return val;
103 }
104 Err(_) => {
105 console.print("[bold red]Please enter a valid value[/]");
106 }
107 }
108 }
109 }
110
111 fn render_prompt(&self, console: &Console) {
112 let mut text = String::new();
113 text.push_str(&self.prompt);
114
115 if self.show_choices {
116 if let Some(ref choices) = self.choices {
117 let choices_str: Vec<String> = choices.iter().map(|c| c.to_string()).collect();
118 text.push_str(&format!(" [bold magenta][{}][/]", choices_str.join("/")));
119 }
120 }
121
122 if self.show_default {
123 if let Some(ref def) = self.default {
124 if !self.password {
125 text.push_str(&format!(" [bold cyan]({:?})[/]", def.to_string()));
126 }
127 }
128 }
129
130 text.push_str(": ");
131 console.print(&text);
132 }
133}
134
135pub type IntPrompt = Prompt<i64>;
137
138pub type FloatPrompt = Prompt<f64>;
140
141pub struct Confirm;
143
144impl Confirm {
145 pub fn ask(prompt: &str, default: Option<bool>) -> bool {
147 let def_str = default
148 .map(|v| if v { "y" } else { "n" })
149 .unwrap_or("y")
150 .to_string();
151
152 let p = Prompt::<String>::new(prompt)
153 .choices(&[
154 "y".to_string(),
155 "n".to_string(),
156 "yes".to_string(),
157 "no".to_string(),
158 ])
159 .default(def_str);
160
161 let answer = p.ask();
162 answer.to_lowercase().starts_with('y')
163 }
164}