use std::io::{self, IsTerminal};
use inquire::{InquireError, MultiSelect, Select, Text};
fn resolve<T>(res: Result<T, InquireError>, fallback: impl FnOnce() -> T) -> T {
match res {
Ok(value) => value,
Err(InquireError::OperationInterrupted) => {
eprintln!();
std::process::exit(130); }
Err(_) => fallback(),
}
}
pub struct Prompt {
enabled: bool,
}
impl Prompt {
pub fn new(no_input: bool) -> Self {
let enabled = !no_input
&& std::env::var_os("DAY_NO_INPUT").is_none()
&& io::stdin().is_terminal()
&& io::stderr().is_terminal();
Prompt { enabled }
}
pub fn enabled(&self) -> bool {
self.enabled
}
pub fn line(&self, question: &str, default: Option<&str>) -> String {
if !self.enabled {
return default.unwrap_or_default().to_string();
}
let text = match default {
Some(d) => Text::new(question).with_default(d),
None => Text::new(question).with_validator(inquire::required!()),
};
resolve(text.prompt(), || default.unwrap_or_default().to_string())
}
pub fn choose(&self, question: &str, options: &[String], default: usize) -> usize {
if !self.enabled || options.is_empty() {
return default.min(options.len().saturating_sub(1));
}
let start = default.min(options.len() - 1);
let picked = Select::new(question, options.to_vec())
.with_starting_cursor(start)
.raw_prompt();
resolve(picked.map(|opt| opt.index), || start)
}
pub fn choose_multi(
&self,
question: &str,
options: &[String],
preselected: &[usize],
) -> Vec<usize> {
let normalize = |idxs: &[usize]| -> Vec<usize> {
(0..options.len()).filter(|i| idxs.contains(i)).collect()
};
if !self.enabled || options.is_empty() {
return normalize(preselected);
}
let picked = MultiSelect::new(question, options.to_vec())
.with_default(preselected)
.raw_prompt();
resolve(
picked.map(|opts| normalize(&opts.iter().map(|opt| opt.index).collect::<Vec<_>>())),
|| normalize(preselected),
)
}
}