1use std::io::{self, BufRead, Write};
2
3use anyhow::{Context, Result};
4
5pub fn confirm(prompt: &str) -> Result<bool> {
6 print!("{prompt}");
7 io::stdout().flush().context("failed to flush stdout")?;
8
9 let mut answer = String::new();
10 io::stdin()
11 .lock()
12 .read_line(&mut answer)
13 .context("failed to read confirmation")?;
14
15 Ok(matches!(answer.trim(), "y" | "Y" | "yes" | "Yes" | "YES"))
16}
17
18pub fn confirm_default_yes(prompt: &str) -> Result<bool> {
21 print!("{prompt}");
22 io::stdout().flush().context("failed to flush stdout")?;
23
24 let mut answer = String::new();
25 io::stdin()
26 .lock()
27 .read_line(&mut answer)
28 .context("failed to read confirmation")?;
29
30 Ok(matches!(
31 answer.trim().to_ascii_lowercase().as_str(),
32 "" | "y" | "yes"
33 ))
34}
35
36pub fn pick(title: &str, options: &[String]) -> Result<Option<usize>> {
40 anstream::eprintln!("{title}");
41 for (index, option) in options.iter().enumerate() {
42 let number = crate::style::paint(crate::style::DIM, &format!("{}.", index + 1));
43 anstream::eprintln!(" {number} {option}");
44 }
45 eprint!("pick [1-{}]: ", options.len());
46 io::stderr().flush().context("failed to flush stderr")?;
47
48 let mut answer = String::new();
49 io::stdin()
50 .lock()
51 .read_line(&mut answer)
52 .context("failed to read choice")?;
53
54 Ok(answer
55 .trim()
56 .parse::<usize>()
57 .ok()
58 .filter(|choice| (1..=options.len()).contains(choice))
59 .map(|choice| choice - 1))
60}