cumaea/
lib.rs

1//! # Cumaea
2//!
3//! `cumaea` is a crate which handles prompting for user input.
4//! It's named after the Cumaean Sibyl, who sold the Sibylline
5//! books to the last king of Rome.
6
7use colored::*;
8use std::io::{stdout, Write};
9
10/// An enum that represents colors from the `colored` crate.
11pub enum ChoiceColor {
12    Black,
13    Red,
14    Green,
15    Yellow,
16    Blue,
17    Magenta,
18    Cyan,
19    White,
20}
21
22/// An enum that represents stylings from the `colored` crate.
23pub enum Choice {
24    Normal(ChoiceColor),
25    On(ChoiceColor),
26    Bright(ChoiceColor),
27    OnBright(ChoiceColor),
28}
29
30/// Prompts for input text given a plain &str, a colored &str,
31/// and the [`Option<Choice>`] to use for the colored prompt.
32/// It looks like this:
33/// ```
34/// {Enter a} {NAME}:
35/// ```
36/// where the first part is plain_prompt, the second, prompt,
37/// and the coloration, colored.
38///
39/// # Examples
40///
41/// ```rust
42/// let the_text = prompt_text(
43///        "Enter your",
44///        "name",
45///        Some(Choice::Normal(ChoiceColor::Green)),
46///    );
47/// ```
48///
49/// Notice that the formatting is controlled by the crate. Use
50/// a trimmed string as input.
51///
52/// # Panics
53///
54/// Panics on failure of `stdin().read_line()` or `stdout().flush()`.
55pub fn prompt_text(plain_prompt: &str, prompt: &str, colored: Option<Choice>) -> String {
56    let mut input = String::new();
57    match colored {
58        Some(ref color_choice) => match color_choice {
59            Choice::Normal(color) => match color {
60                ChoiceColor::Black => print!("{} {}: ", plain_prompt, prompt.to_string().black()),
61                ChoiceColor::Red => print!("{} {}: ", plain_prompt, prompt.to_string().red()),
62                ChoiceColor::Green => print!("{} {}: ", plain_prompt, prompt.to_string().green()),
63                ChoiceColor::Yellow => print!("{} {}: ", plain_prompt, prompt.to_string().yellow()),
64                ChoiceColor::Blue => print!("{} {}: ", plain_prompt, prompt.to_string().blue()),
65                ChoiceColor::Magenta => {
66                    print!("{} {}: ", plain_prompt, prompt.to_string().magenta())
67                }
68                ChoiceColor::Cyan => print!("{} {}: ", plain_prompt, prompt.to_string().cyan()),
69                ChoiceColor::White => print!("{} {}: ", plain_prompt, prompt.to_string().white()),
70            },
71            Choice::On(color) => match color {
72                ChoiceColor::Black => {
73                    print!("{} {}: ", plain_prompt, prompt.to_string().on_black())
74                }
75                ChoiceColor::Red => print!("{} {}: ", plain_prompt, prompt.to_string().on_red()),
76                ChoiceColor::Green => {
77                    print!("{} {}: ", plain_prompt, prompt.to_string().on_green())
78                }
79                ChoiceColor::Yellow => {
80                    print!("{} {}: ", plain_prompt, prompt.to_string().on_yellow())
81                }
82                ChoiceColor::Blue => print!("{} {}: ", plain_prompt, prompt.to_string().on_blue()),
83                ChoiceColor::Magenta => {
84                    print!("{} {}: ", plain_prompt, prompt.to_string().on_magenta())
85                }
86                ChoiceColor::Cyan => print!("{} {}: ", plain_prompt, prompt.to_string().on_cyan()),
87                ChoiceColor::White => {
88                    print!("{} {}: ", plain_prompt, prompt.to_string().on_white())
89                }
90            },
91            Choice::Bright(color) => match color {
92                ChoiceColor::Black => {
93                    print!("{} {}: ", plain_prompt, prompt.to_string().bright_black())
94                }
95                ChoiceColor::Red => {
96                    print!("{} {}: ", plain_prompt, prompt.to_string().bright_red())
97                }
98                ChoiceColor::Green => {
99                    print!("{} {}: ", plain_prompt, prompt.to_string().bright_green())
100                }
101                ChoiceColor::Yellow => {
102                    print!("{} {}: ", plain_prompt, prompt.to_string().bright_yellow())
103                }
104                ChoiceColor::Blue => {
105                    print!("{} {}: ", plain_prompt, prompt.to_string().bright_blue())
106                }
107                ChoiceColor::Magenta => {
108                    print!("{} {}: ", plain_prompt, prompt.to_string().bright_magenta())
109                }
110                ChoiceColor::Cyan => {
111                    print!("{} {}: ", plain_prompt, prompt.to_string().bright_cyan())
112                }
113                ChoiceColor::White => {
114                    print!("{} {}: ", plain_prompt, prompt.to_string().bright_white())
115                }
116            },
117            Choice::OnBright(color) => match color {
118                ChoiceColor::Black => print!(
119                    "{} {}: ",
120                    plain_prompt,
121                    prompt.to_string().on_bright_black()
122                ),
123                ChoiceColor::Red => {
124                    print!("{} {}: ", plain_prompt, prompt.to_string().on_bright_red())
125                }
126                ChoiceColor::Green => print!(
127                    "{} {}: ",
128                    plain_prompt,
129                    prompt.to_string().on_bright_green()
130                ),
131                ChoiceColor::Yellow => print!(
132                    "{} {}: ",
133                    plain_prompt,
134                    prompt.to_string().on_bright_yellow()
135                ),
136                ChoiceColor::Blue => {
137                    print!("{} {}: ", plain_prompt, prompt.to_string().on_bright_blue())
138                }
139                ChoiceColor::Magenta => print!(
140                    "{} {}: ",
141                    plain_prompt,
142                    prompt.to_string().on_bright_magenta()
143                ),
144                ChoiceColor::Cyan => {
145                    print!("{} {}: ", plain_prompt, prompt.to_string().on_bright_cyan())
146                }
147                ChoiceColor::White => print!(
148                    "{} {}: ",
149                    plain_prompt,
150                    prompt.to_string().on_bright_white()
151                ),
152            },
153        },
154        None => todo!(),
155    }
156
157    stdout().flush().expect("Flushing line failed.");
158    input.clear();
159    std::io::stdin()
160        .read_line(&mut input)
161        .expect("Failed to read line.");
162
163    input.trim().to_owned()
164}
165
166/// Prompts for a true/false value given a prompt, color option, and default value.
167/// Loops until the input is valid.
168///
169/// # Examples
170///
171/// ```rust
172/// let the_bool = prompt_tf_default(
173///        "Approved? (Y/n) >>> ",
174///        Some(Choice::Normal(ChoiceColor::Green)),
175///        true,
176///    );
177/// ```
178///
179/// Notice how the default option in the prompt is capitalized. The caller has
180/// complete responsibility for formatting the prompt; the crate makes no changes
181/// besides the color.
182///
183/// # Panics
184///
185/// Panics on failure of `stdin().read_line()` or `stdout().flush()`.
186pub fn prompt_tf_default(prompt: &str, colored: Option<Choice>, default: bool) -> bool {
187    let mut input = String::new();
188    loop {
189        match colored {
190            Some(ref color_choice) => match color_choice {
191                Choice::Normal(color) => match color {
192                    ChoiceColor::Black => print!("{}", prompt.to_string().black()),
193                    ChoiceColor::Red => print!("{}", prompt.to_string().red()),
194                    ChoiceColor::Green => print!("{}", prompt.to_string().green()),
195                    ChoiceColor::Yellow => print!("{}", prompt.to_string().yellow()),
196                    ChoiceColor::Blue => print!("{}", prompt.to_string().blue()),
197                    ChoiceColor::Magenta => print!("{}", prompt.to_string().magenta()),
198                    ChoiceColor::Cyan => print!("{}", prompt.to_string().cyan()),
199                    ChoiceColor::White => print!("{}", prompt.to_string().white()),
200                },
201                Choice::On(color) => match color {
202                    ChoiceColor::Black => print!("{}", prompt.to_string().on_black()),
203                    ChoiceColor::Red => print!("{}", prompt.to_string().on_red()),
204                    ChoiceColor::Green => print!("{}", prompt.to_string().on_green()),
205                    ChoiceColor::Yellow => print!("{}", prompt.to_string().on_yellow()),
206                    ChoiceColor::Blue => print!("{}", prompt.to_string().on_blue()),
207                    ChoiceColor::Magenta => print!("{}", prompt.to_string().on_magenta()),
208                    ChoiceColor::Cyan => print!("{}", prompt.to_string().on_cyan()),
209                    ChoiceColor::White => print!("{}", prompt.to_string().on_white()),
210                },
211                Choice::Bright(color) => match color {
212                    ChoiceColor::Black => print!("{}", prompt.to_string().bright_black()),
213                    ChoiceColor::Red => print!("{}", prompt.to_string().bright_red()),
214                    ChoiceColor::Green => print!("{}", prompt.to_string().bright_green()),
215                    ChoiceColor::Yellow => print!("{}", prompt.to_string().bright_yellow()),
216                    ChoiceColor::Blue => print!("{}", prompt.to_string().bright_blue()),
217                    ChoiceColor::Magenta => print!("{}", prompt.to_string().bright_magenta()),
218                    ChoiceColor::Cyan => print!("{}", prompt.to_string().bright_cyan()),
219                    ChoiceColor::White => print!("{}", prompt.to_string().bright_white()),
220                },
221                Choice::OnBright(color) => match color {
222                    ChoiceColor::Black => print!("{}", prompt.to_string().on_bright_black()),
223                    ChoiceColor::Red => print!("{}", prompt.to_string().on_bright_red()),
224                    ChoiceColor::Green => print!("{}", prompt.to_string().on_bright_green()),
225                    ChoiceColor::Yellow => print!("{}", prompt.to_string().on_bright_yellow()),
226                    ChoiceColor::Blue => print!("{}", prompt.to_string().on_bright_blue()),
227                    ChoiceColor::Magenta => print!("{}", prompt.to_string().on_bright_magenta()),
228                    ChoiceColor::Cyan => print!("{}", prompt.to_string().on_bright_cyan()),
229                    ChoiceColor::White => print!("{}", prompt.to_string().on_bright_white()),
230                },
231            },
232            None => {
233                print!("{}", prompt.trim())
234            }
235        }
236
237        stdout().flush().expect("Flushing line failed.");
238        input.clear();
239        std::io::stdin()
240            .read_line(&mut input)
241            .expect("Failed to read line.");
242        input = input.trim().to_string();
243        if input.eq_ignore_ascii_case("y") || input.eq_ignore_ascii_case("n") || input.is_empty() {
244            break;
245        }
246    }
247
248    // Loop cannot have exited w/o input being valid.
249    match input.as_str() {
250        "Y" | "y" => true,
251        "N" | "n" => false,
252        _ => default,
253    }
254}
255
256/// Prompts for a selection given a prompt, list of choices, color option, and default value.
257/// No looping occurs.
258///
259/// # Examples
260///
261/// ```rust
262/// let the_string = prompt_selection(
263///     "Choose something",
264///     "(a)pples, (b)ananas, (c)arrots, (D)oughnuts",
265///     Some(Choice::Normal(ChoiceColor::Cyan)),
266///     "D",
267/// );
268/// ```
269///
270/// Notice how the default option in the prompt is capitalized. The caller has
271/// partial responsibility for formatting the prompt; the crate prints the
272/// question in default colors, followed by a colon,
273/// with the list in brackets & colorized follwed by another colon and a space.
274/// For example:
275///
276/// ```bash
277/// Choose something: [(a)pples, (b)ananas, (c)arrots, (D)oughnuts]:
278/// ```
279///
280/// # Panics
281///
282/// Panics on failure of `stdin().read_line()` or `stdout().flush()`.
283pub fn prompt_selection(
284    prompt: &str,
285    list: &str,
286    colored: Option<Choice>,
287    default: &str,
288) -> String {
289    let mut input = String::new();
290    match colored {
291        Some(ref color_choice) => match color_choice {
292            Choice::Normal(color) => match color {
293                ChoiceColor::Black => {
294                    print!("{}: [{}]: ", prompt, list.to_string().black())
295                }
296                ChoiceColor::Red => {
297                    print!("{}: [{}]: ", prompt, list.to_string().red())
298                }
299                ChoiceColor::Green => {
300                    print!("{}: [{}]: ", prompt, list.to_string().green())
301                }
302                ChoiceColor::Yellow => {
303                    print!("{}: [{}]: ", prompt, list.to_string().yellow())
304                }
305                ChoiceColor::Blue => {
306                    print!("{}: [{}]: ", prompt, list.to_string().blue())
307                }
308                ChoiceColor::Magenta => {
309                    print!("{}: [{}]: ", prompt, list.to_string().magenta())
310                }
311                ChoiceColor::Cyan => {
312                    print!("{}: [{}]: ", prompt, list.to_string().cyan())
313                }
314                ChoiceColor::White => {
315                    print!("{}: [{}]: ", prompt, list.to_string().white())
316                }
317            },
318            Choice::On(color) => match color {
319                ChoiceColor::Black => print!("{}: [{}]: ", prompt, list.to_string().on_black()),
320                ChoiceColor::Red => {
321                    print!("{}: [{}]: ", prompt, list.to_string().on_red())
322                }
323                ChoiceColor::Green => print!("{}: [{}]: ", prompt, list.to_string().on_green()),
324                ChoiceColor::Yellow => print!("{}: [{}]: ", prompt, list.to_string().on_yellow()),
325                ChoiceColor::Blue => {
326                    print!("{}: [{}]: ", prompt, list.to_string().on_blue())
327                }
328                ChoiceColor::Magenta => print!("{}: [{}]: ", prompt, list.to_string().on_magenta()),
329                ChoiceColor::Cyan => {
330                    print!("{}: [{}]: ", prompt, list.to_string().on_cyan())
331                }
332                ChoiceColor::White => print!("{}: [{}]: ", prompt, list.to_string().on_white()),
333            },
334            Choice::Bright(color) => match color {
335                ChoiceColor::Black => print!("{}: [{}]: ", prompt, list.to_string().bright_black()),
336                ChoiceColor::Red => print!("{}: [{}]: ", prompt, list.to_string().bright_red()),
337                ChoiceColor::Green => print!("{}: [{}]: ", prompt, list.to_string().bright_green()),
338                ChoiceColor::Yellow => {
339                    print!("{}: [{}]: ", prompt, list.to_string().bright_yellow())
340                }
341                ChoiceColor::Blue => print!("{}: [{}]: ", prompt, list.to_string().bright_blue()),
342                ChoiceColor::Magenta => {
343                    print!("{}: [{}]: ", prompt, list.to_string().bright_magenta())
344                }
345                ChoiceColor::Cyan => print!("{}: [{}]: ", prompt, list.to_string().bright_cyan()),
346                ChoiceColor::White => print!("{}: [{}]: ", prompt, list.to_string().bright_white()),
347            },
348            Choice::OnBright(color) => match color {
349                ChoiceColor::Black => {
350                    print!("{}: [{}]: ", prompt, list.to_string().on_bright_black())
351                }
352                ChoiceColor::Red => print!("{}: [{}]: ", prompt, list.to_string().on_bright_red()),
353                ChoiceColor::Green => {
354                    print!("{}: [{}]: ", prompt, list.to_string().on_bright_green())
355                }
356                ChoiceColor::Yellow => {
357                    print!("{}: [{}]: ", prompt, list.to_string().on_bright_yellow())
358                }
359                ChoiceColor::Blue => {
360                    print!("{}: [{}]: ", prompt, list.to_string().on_bright_blue())
361                }
362                ChoiceColor::Magenta => {
363                    print!("{}: [{}]: ", prompt, list.to_string().on_bright_magenta())
364                }
365                ChoiceColor::Cyan => {
366                    print!("{}: [{}]: ", prompt, list.to_string().on_bright_cyan())
367                }
368                ChoiceColor::White => {
369                    print!("{}: [{}]: ", prompt, list.to_string().on_bright_white())
370                }
371            },
372        },
373        None => {
374            print!("{}: [{}]: ", prompt.trim(), list.trim())
375        }
376    }
377
378    stdout().flush().expect("Flushing line failed.");
379    input.clear();
380    std::io::stdin()
381        .read_line(&mut input)
382        .expect("Failed to read line.");
383
384    if input.trim().is_empty() {
385        default.to_string()
386    } else {
387        input.trim().to_string()
388    }
389}