Skip to main content

acme_proxy_admin/admin/
prompt.rs

1//! The `[y/N]` confirmation every destructive CLI command asks.
2//!
3//! Anything but `y` or `yes` — including an empty line or a closed stdin — is
4//! a no, so a script that forgot `--yes` stops rather than deleting.
5
6use std::io::{BufRead, Write};
7
8/// Prints `"{prompt} [y/N] "` and reads one line from `reader`.
9pub fn confirm(prompt: &str, assume_yes: bool, reader: &mut impl BufRead) -> bool {
10    if assume_yes {
11        return true;
12    }
13    print!("{prompt} [y/N] ");
14    let _ = std::io::stdout().flush();
15    let mut line = String::new();
16    if reader.read_line(&mut line).unwrap_or(0) == 0 {
17        return false;
18    }
19    matches!(line.trim().to_lowercase().as_str(), "y" | "yes")
20}
21
22#[cfg(test)]
23mod tests {
24    use super::*;
25
26    #[test]
27    fn confirm_accepts_y_variants() {
28        for input in ["y\n", "Y\n", "yes\n", "YES\n", "  yes  \n"] {
29            let mut reader = input.as_bytes();
30            assert!(
31                confirm("proceed?", false, &mut reader),
32                "expected {input:?} to confirm"
33            );
34        }
35    }
36
37    #[test]
38    fn confirm_rejects_everything_else() {
39        for input in ["n\n", "no\n", "\n", "garbage\n"] {
40            let mut reader = input.as_bytes();
41            assert!(
42                !confirm("proceed?", false, &mut reader),
43                "expected {input:?} to reject"
44            );
45        }
46    }
47
48    #[test]
49    fn confirm_rejects_eof() {
50        let mut reader: &[u8] = &[];
51        assert!(!confirm("proceed?", false, &mut reader));
52    }
53
54    #[test]
55    fn confirm_assume_yes_skips_reading() {
56        let mut reader: &[u8] = &[];
57        assert!(confirm("proceed?", true, &mut reader));
58    }
59}