1use std::{
2 fmt::{Arguments, Debug},
3 io::{self, Write},
4 sync::OnceLock,
5};
6
7use crate::error;
8
9static PROMPTER: OnceLock<&'static dyn Prompter> = OnceLock::new();
10pub fn set_prompter(prompter: &'static dyn Prompter) -> Result<(), &'static dyn Prompter> {
11 PROMPTER.set(prompter)
12}
13pub fn get_prompter() -> Option<&'static dyn Prompter> {
14 PROMPTER.get().copied()
15}
16
17#[derive(Debug)]
19pub struct CliPrompter;
20
21pub trait Prompter: Sync + Debug {
22 fn prompt_okay(&self, msg: Arguments);
27
28 fn prompt_ask(&self, msg: Arguments, default: bool) -> Option<bool>;
35}
36
37impl Prompter for CliPrompter {
38 fn prompt_okay(&self, msg: Arguments) {
39 let mut output = io::stdout();
40 let _ = output.write_fmt(msg);
41 let _ = output.write_all(b"Press [ENTER] to continue.\n");
42 let _ = output.flush();
43 let _ = io::stdin().read_line(&mut String::new());
44 }
45
46 fn prompt_ask(&self, msg: Arguments, default: bool) -> Option<bool> {
47 let prompt = if default { "[Y/n]" } else { "[y/N]" };
48
49 let mut output = io::stdout();
50 loop {
51 let _ = output.write_all(b"==> Confirmation\n");
52 let _ = output.write_fmt(msg);
53 let _ = output.write(b"\n");
54 let _ = output.write_all(prompt.as_bytes());
55 let _ = output.write(b"\n");
56 let _ = output.flush();
57
58 let mut input = String::new();
59 io::stdin()
60 .read_line(&mut input)
61 .inspect_err(|err| error!("Could not read stdin: {err}"))
62 .ok()?;
63
64 match input.trim().to_ascii_lowercase().as_str() {
65 "" => return Some(default),
66 "y" | "yes" => return Some(true),
67 "n" | "no" => return Some(false),
68 _ => {
69 let _ = output.write_all(b"Please enter 'y' for yes or 'n' for no.");
70 let _ = output.flush();
71 }
72 }
73 }
74 }
75}
76
77#[macro_export]
81macro_rules! okay {
82 ($($arg:tt)*) => {{
83 if let Some(prompter) = $crate::prompts::get_prompter() {
84 prompter.prompt_okay(format_args!($($arg)*));
85 }
86 }};
87}
88
89#[macro_export]
93macro_rules! ask {
94 ($default:expr, $($arg:tt)*) => {{
95 if let Some(prompter) = $crate::prompts::get_prompter() {
96 prompter.prompt_ask(format_args!($($arg)*), $default)
97 } else {
98 None
99 }
100 }};
101}