use std::{
fmt::{Arguments, Debug},
io::{self, Write},
sync::OnceLock,
};
use crate::error;
static PROMPTER: OnceLock<&'static dyn Prompter> = OnceLock::new();
pub fn set_prompter(prompter: &'static dyn Prompter) -> Result<(), &'static dyn Prompter> {
PROMPTER.set(prompter)
}
pub fn get_prompter() -> Option<&'static dyn Prompter> {
PROMPTER.get().copied()
}
#[derive(Debug)]
pub struct CliPrompter;
pub trait Prompter: Sync + Debug {
fn prompt_okay(&self, msg: Arguments);
fn prompt_ask(&self, msg: Arguments, default: bool) -> Option<bool>;
}
impl Prompter for CliPrompter {
fn prompt_okay(&self, msg: Arguments) {
let mut output = io::stdout();
let _ = output.write_fmt(msg);
let _ = output.write_all(b"Press [ENTER] to continue.\n");
let _ = output.flush();
let _ = io::stdin().read_line(&mut String::new());
}
fn prompt_ask(&self, msg: Arguments, default: bool) -> Option<bool> {
let prompt = if default { "[Y/n]" } else { "[y/N]" };
let mut output = io::stdout();
loop {
let _ = output.write_all(b"==> Confirmation\n");
let _ = output.write_fmt(msg);
let _ = output.write(b"\n");
let _ = output.write_all(prompt.as_bytes());
let _ = output.write(b"\n");
let _ = output.flush();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.inspect_err(|err| error!("Could not read stdin: {err}"))
.ok()?;
match input.trim().to_ascii_lowercase().as_str() {
"" => return Some(default),
"y" | "yes" => return Some(true),
"n" | "no" => return Some(false),
_ => {
let _ = output.write_all(b"Please enter 'y' for yes or 'n' for no.");
let _ = output.flush();
}
}
}
}
}
#[macro_export]
macro_rules! okay {
($($arg:tt)*) => {{
if let Some(prompter) = $crate::prompts::get_prompter() {
prompter.prompt_okay(format_args!($($arg)*));
}
}};
}
#[macro_export]
macro_rules! ask {
($default:expr, $($arg:tt)*) => {{
if let Some(prompter) = $crate::prompts::get_prompter() {
prompter.prompt_ask(format_args!($($arg)*), $default)
} else {
None
}
}};
}