use std::io::{self, BufRead, Write};
fn open_tty() -> io::Result<Box<dyn BufRead>> {
#[cfg(unix)]
{
use std::fs::File;
let f = File::open("/dev/tty")?;
Ok(Box::new(io::BufReader::new(f)))
}
#[cfg(windows)]
{
use std::fs::OpenOptions;
let f = OpenOptions::new().read(true).open("CONIN$")?;
Ok(Box::new(io::BufReader::new(f)))
}
#[cfg(not(any(unix, windows)))]
{
Ok(Box::new(io::BufReader::new(io::stdin())))
}
}
fn read_line(tty: &mut dyn BufRead) -> String {
let mut buf = String::new();
let _ = tty.read_line(&mut buf);
buf.trim().to_string()
}
pub fn ask_yn(prompt: &str, default: bool) -> bool {
let suffix = if default { "[Y/n]" } else { "[y/N]" };
print!("{} {} ", prompt, suffix);
let _ = io::stdout().flush();
let mut tty = match open_tty() {
Ok(t) => t,
Err(_) => return default,
};
let answer = read_line(&mut *tty);
match answer.as_str() {
"" => default,
s if s.starts_with('y') || s.starts_with('Y') => true,
s if s.starts_with('n') || s.starts_with('N') => false,
_ => default,
}
}
pub fn ask_choice(prompt: &str, options: &[&str], default: usize) -> usize {
for (i, opt) in options.iter().enumerate() {
println!(" {}) {}", i + 1, opt);
}
println!();
print!("{} [{}]: ", prompt, default);
let _ = io::stdout().flush();
let mut tty = match open_tty() {
Ok(t) => t,
Err(_) => return default,
};
let answer = read_line(&mut *tty);
if answer.is_empty() {
return default;
}
match answer.parse::<usize>() {
Ok(n) if n >= 1 && n <= options.len() => n,
_ => default,
}
}
#[allow(dead_code)]
pub fn ask_text(prompt: &str, default: &str) -> String {
if default.is_empty() {
print!("{}: ", prompt);
} else {
print!("{} [{}]: ", prompt, default);
}
let _ = io::stdout().flush();
let mut tty = match open_tty() {
Ok(t) => t,
Err(_) => return default.to_string(),
};
let answer = read_line(&mut *tty);
if answer.is_empty() {
default.to_string()
} else {
answer
}
}