1 //! Utils related to asking [Y/n] questions to the user.
4 //! "Do you want to overwrite 'archive.tar.gz'? [Y/n]"
14 use super::{strip_cur_dir, to_utf};
16 accessible::is_running_in_accessible_mode,
17 error::{Error, Result},
21 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
22 /// Determines if overwrite questions should be skipped or asked to the user
23 pub enum QuestionPolicy {
24 /// Ask the user every time
26 /// Set by `--yes`, will say 'Y' to all overwrite questions
28 /// Set by `--no`, will say 'N' to all overwrite questions
32 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
33 /// Determines which action is being questioned
34 pub enum QuestionAction {
35 /// question called from a compression function
37 /// question called from a decompression function
41 /// Check if QuestionPolicy flags were set, otherwise, ask user if they want to overwrite.
42 pub fn user_wants_to_overwrite(path: &Path, question_policy: QuestionPolicy) -> crate::Result<bool> {
43 match question_policy {
44 QuestionPolicy::AlwaysYes => Ok(true),
45 QuestionPolicy::AlwaysNo => Ok(false),
46 QuestionPolicy::Ask => {
47 let path = to_utf(strip_cur_dir(path));
48 let path = Some(&*path);
49 let placeholder = Some("FILE");
50 Confirmation::new("Do you want to overwrite 'FILE'?", placeholder).ask(path)
55 /// Create the file if it doesn't exist and if it does then ask to overwrite it.
56 /// If the user doesn't want to overwrite then we return [`Ok(None)`]
57 pub fn create_or_ask_overwrite(path: &Path, question_policy: QuestionPolicy) -> Result<Option<fs::File>> {
58 match fs::OpenOptions::new().write(true).create_new(true).open(path) {
60 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
61 if user_wants_to_overwrite(path, question_policy)? {
63 // We can't just use `fs::File::create(&path)` because it would return io::ErrorKind::IsADirectory
64 // ToDo: Maybe we should emphasise that `path` is a directory and everything inside it will be gone?
65 fs::remove_dir_all(path)?;
67 Ok(Some(fs::File::create(path)?))
72 Err(e) => Err(Error::from(e)),
76 /// Check if QuestionPolicy flags were set, otherwise, ask the user if they want to continue.
77 pub fn user_wants_to_continue(
79 question_policy: QuestionPolicy,
80 question_action: QuestionAction,
81 ) -> crate::Result<bool> {
82 match question_policy {
83 QuestionPolicy::AlwaysYes => Ok(true),
84 QuestionPolicy::AlwaysNo => Ok(false),
85 QuestionPolicy::Ask => {
86 let action = match question_action {
87 QuestionAction::Compression => "compress",
88 QuestionAction::Decompression => "decompress",
90 let path = to_utf(strip_cur_dir(path));
91 let path = Some(&*path);
92 let placeholder = Some("FILE");
93 Confirmation::new(&format!("Do you want to {} 'FILE'?", action), placeholder).ask(path)
98 /// Confirmation dialog for end user with [Y/n] question.
100 /// If the placeholder is found in the prompt text, it will be replaced to form the final message.
101 pub struct Confirmation<'a> {
102 /// The message to be displayed with the placeholder text in it.
103 /// e.g.: "Do you want to overwrite 'FILE'?"
106 /// The placeholder text that will be replaced in the `ask` function:
107 /// e.g.: Some("FILE")
108 pub placeholder: Option<&'a str>,
111 impl<'a> Confirmation<'a> {
112 /// Creates a new Confirmation.
113 pub const fn new(prompt: &'a str, pattern: Option<&'a str>) -> Self {
116 placeholder: pattern,
120 /// Creates user message and receives a boolean input to be used on the program
121 pub fn ask(&self, substitute: Option<&'a str>) -> crate::Result<bool> {
122 let message = match (self.placeholder, substitute) {
123 (None, _) => Cow::Borrowed(self.prompt),
124 (Some(_), None) => unreachable!("dev error, should be reported, we checked this won't happen"),
125 (Some(placeholder), Some(subs)) => Cow::Owned(self.prompt.replace(placeholder, subs)),
128 // Ask the same question to end while no valid answers are given
130 if is_running_in_accessible_mode() {
132 "{} {}yes{}/{}no{}: ",
149 io::stdout().flush()?;
151 let mut answer = String::new();
152 io::stdin().read_line(&mut answer)?;
154 answer.make_ascii_lowercase();
155 match answer.trim() {
156 "" | "y" | "yes" => return Ok(true),
157 "n" | "no" => return Ok(false),
158 _ => continue, // Try again