1use std::io::{self, BufRead, Write};
2
3use anyhow::{Context, Result};
4
5pub fn confirm(prompt: &str) -> Result<bool> {
6 prompt_yes_no(prompt, false)
7}
8
9pub fn confirm_default_yes(prompt: &str) -> Result<bool> {
12 prompt_yes_no(prompt, true)
13}
14
15fn prompt_yes_no(prompt: &str, default_yes: bool) -> Result<bool> {
22 loop {
23 print!("{prompt}");
24 io::stdout().flush().context("failed to flush stdout")?;
25
26 let mut answer = String::new();
27 let read = io::stdin()
28 .lock()
29 .read_line(&mut answer)
30 .context("failed to read confirmation")?;
31 if read == 0 {
32 return Ok(default_yes);
34 }
35
36 match answer.trim().to_ascii_lowercase().as_str() {
37 "y" | "yes" => return Ok(true),
38 "n" | "no" => return Ok(false),
39 "" => return Ok(default_yes),
40 _ => anstream::eprintln!("please answer y or n"),
41 }
42 }
43}
44
45pub fn pick(title: &str, options: &[String]) -> Result<Option<usize>> {
49 anstream::eprintln!("{title}");
50 for (index, option) in options.iter().enumerate() {
51 let number = crate::style::paint(crate::style::DIM, &format!("{}.", index + 1));
52 anstream::eprintln!(" {number} {option}");
53 }
54 eprint!("pick [1-{}]: ", options.len());
55 io::stderr().flush().context("failed to flush stderr")?;
56
57 let mut answer = String::new();
58 io::stdin()
59 .lock()
60 .read_line(&mut answer)
61 .context("failed to read choice")?;
62
63 Ok(answer
64 .trim()
65 .parse::<usize>()
66 .ok()
67 .filter(|choice| (1..=options.len()).contains(choice))
68 .map(|choice| choice - 1))
69}