use crate::core::Process;
use crate::error::{ProcError, Result};
use crate::ui::{interrupt, out};
use crossterm::{cursor, execute};
use dialoguer::Confirm;
pub fn ask(
action: &str,
processes: &[Process],
not_found: Option<&[String]>,
question: &str,
show: impl FnOnce(),
) -> Result<bool> {
let shown = list_shown(out::stdout_is_terminal(), show);
answer_if_shown(shown, action, processes, not_found, || interact(question))
}
fn list_shown(stdout_is_terminal: bool, show: impl FnOnce()) -> bool {
if stdout_is_terminal {
show();
out::stdout_usable()
} else {
out::on_stderr(show);
out::stderr_usable()
}
}
fn answer_if_shown(
shown: bool,
action: &str,
processes: &[Process],
not_found: Option<&[String]>,
answer: impl FnOnce() -> Result<bool>,
) -> Result<bool> {
if !shown {
return Err(ProcError::ConfirmationRequired {
message: format!(
"{action} asks before changing processes, and the list of what it would \
change could not be shown; pass --yes (or --dry-run to preview)"
),
processes: processes.to_vec(),
not_found: not_found.map(<[String]>::to_vec),
});
}
answer()
}
#[allow(clippy::disallowed_methods)]
fn interact(question: &str) -> Result<bool> {
let _protected = interrupt::protect_prompt();
let answer = Confirm::new()
.with_prompt(question)
.default(false)
.interact();
if let Err(dialoguer::Error::IO(error)) = &answer {
if error.kind() == std::io::ErrorKind::Interrupted {
interrupt::ctrl_c();
}
}
let _ = execute!(out::stderr_unrecorded(), cursor::Show);
Ok(answer?)
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
#[test]
fn the_list_is_shown_on_either_stream_and_the_question_waits_for_it() {
for stdout_is_terminal in [true, false] {
let shown = Cell::new(0);
assert!(list_shown(stdout_is_terminal, || shown.set(shown.get() + 1)));
assert_eq!(shown.get(), 1);
}
}
#[test]
fn a_list_that_was_not_shown_is_a_refusal_with_what_would_have_changed() {
let missing = vec!["ghost".to_string()];
let refused = answer_if_shown(false, "kill", &[], Some(&missing), || panic!("asked"))
.expect_err("nothing is asked when the list was not shown");
assert_eq!(crate::error::ExitCode::from(&refused) as i32, 4);
assert_eq!(refused.reason(), Some("needs_confirmation"));
assert!(
refused.to_string().contains("could not be shown"),
"{refused}"
);
assert!(format!("{refused:?}").contains("ghost"), "{refused:?}");
}
#[test]
fn a_shown_list_leaves_the_answer_to_the_person() {
assert!(answer_if_shown(true, "kill", &[], None, || Ok(true)).unwrap());
assert!(!answer_if_shown(true, "kill", &[], None, || Ok(false)).unwrap());
}
}