use std::io::{self, BufRead, Write};
use crate::style::{self, sym};
#[must_use]
pub fn ask_yes_default_yes(question: &str) -> bool {
print_question(question);
let stdin = io::stdin();
let mut reader = stdin.lock();
let mut buf = String::new();
let read = reader.read_line(&mut buf);
ask_yes_default_yes_with(read.ok().map(|_| buf))
}
#[must_use]
pub fn ask_yes_default_yes_with(line: Option<String>) -> bool {
let Some(line) = line else {
return false;
};
let answer = clean_prompt_answer(&line);
if answer.is_empty() {
return true; }
!(answer == "n" || answer == "no")
}
fn print_question(question: &str) {
print!(
" {} {} {} ",
style::emerald(sym::TIP),
question,
style::pewter("[Y/n]"),
);
let _ = io::stdout().flush();
}
fn clean_prompt_answer(line: &str) -> String {
line.trim_matches(|c: char| c.is_whitespace() || c == '\0' || c == '\u{feff}')
.to_ascii_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_input_is_default_yes() {
assert!(ask_yes_default_yes_with(Some("\n".to_owned())));
assert!(ask_yes_default_yes_with(Some(String::new())));
}
#[test]
fn lowercase_n_declines_offer() {
assert!(!ask_yes_default_yes_with(Some("n\n".to_owned())));
assert!(!ask_yes_default_yes_with(Some("N\n".to_owned())));
assert!(!ask_yes_default_yes_with(Some("no\r\n".to_owned())));
assert!(!ask_yes_default_yes_with(Some("NO".to_owned())));
}
#[test]
fn any_yes_variant_accepts_offer() {
for ans in ["y", "Y", "yes", "YES", " yes ", "yeah"] {
assert!(
ask_yes_default_yes_with(Some(ans.to_owned())),
"expected yes for {ans:?}",
);
}
}
#[test]
fn eof_or_read_error_declines_offer() {
assert!(!ask_yes_default_yes_with(None));
}
#[test]
fn windows_control_bytes_are_stripped_before_decision() {
assert!(!ask_yes_default_yes_with(Some("\u{feff}n\0\r\n".to_owned())));
assert!(ask_yes_default_yes_with(Some("\u{feff}\0\r\n".to_owned())));
}
}