1use std::io::{self, BufRead, Write};
7
8fn open_tty() -> io::Result<Box<dyn BufRead>> {
10 #[cfg(unix)]
11 {
12 use std::fs::File;
13 let f = File::open("/dev/tty")?;
14 Ok(Box::new(io::BufReader::new(f)))
15 }
16 #[cfg(windows)]
17 {
18 use std::fs::OpenOptions;
19 let f = OpenOptions::new().read(true).open("CONIN$")?;
20 Ok(Box::new(io::BufReader::new(f)))
21 }
22 #[cfg(not(any(unix, windows)))]
23 {
24 Ok(Box::new(io::BufReader::new(io::stdin())))
25 }
26}
27
28fn read_line(tty: &mut dyn BufRead) -> String {
29 let mut buf = String::new();
30 let _ = tty.read_line(&mut buf);
31 buf.trim().to_string()
32}
33
34pub fn ask_yn(prompt: &str, default: bool) -> bool {
38 let suffix = if default { "[Y/n]" } else { "[y/N]" };
39 print!("{} {} ", prompt, suffix);
40 let _ = io::stdout().flush();
41
42 let mut tty = match open_tty() {
43 Ok(t) => t,
44 Err(_) => return default,
45 };
46 let answer = read_line(&mut *tty);
47
48 match answer.as_str() {
49 "" => default,
50 s if s.starts_with('y') || s.starts_with('Y') => true,
51 s if s.starts_with('n') || s.starts_with('N') => false,
52 _ => default,
53 }
54}
55
56pub fn ask_choice(prompt: &str, options: &[&str], default: usize) -> usize {
60 for (i, opt) in options.iter().enumerate() {
61 println!(" {}) {}", i + 1, opt);
62 }
63 println!();
64 print!("{} [{}]: ", prompt, default);
65 let _ = io::stdout().flush();
66
67 let mut tty = match open_tty() {
68 Ok(t) => t,
69 Err(_) => return default,
70 };
71 let answer = read_line(&mut *tty);
72
73 if answer.is_empty() {
74 return default;
75 }
76 match answer.parse::<usize>() {
77 Ok(n) if n >= 1 && n <= options.len() => n,
78 _ => default,
79 }
80}
81
82#[allow(dead_code)]
86pub fn ask_text(prompt: &str, default: &str) -> String {
87 if default.is_empty() {
88 print!("{}: ", prompt);
89 } else {
90 print!("{} [{}]: ", prompt, default);
91 }
92 let _ = io::stdout().flush();
93
94 let mut tty = match open_tty() {
95 Ok(t) => t,
96 Err(_) => return default.to_string(),
97 };
98 let answer = read_line(&mut *tty);
99
100 if answer.is_empty() {
101 default.to_string()
102 } else {
103 answer
104 }
105}