use {
core::{
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
str::FromStr,
},
std::{
collections::HashSet,
io::{Error, ErrorKind},
},
crate::Result,
};
#[derive(Debug, Eq)]
pub enum Answer<'a, T> where T: Eq + PartialEq + Hash + Display {
Yes(Option<&'a str>),
No(Option<&'a str>),
Retry(Option<&'a str>),
Next(Option<&'a str>),
Cancel(Option<&'a str>),
UserDefined(T),
}
impl<T> PartialEq for Answer<'_, T> where T: Eq + PartialEq + Hash + Display {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Answer::Yes(_), Answer::Yes(_)) => true,
(Answer::No(_), Answer::No(_)) => true,
(Answer::Retry(_), Answer::Retry(_)) => true,
(Answer::Next(_), Answer::Next(_)) => true,
(Answer::Cancel(_), Answer::Cancel(_)) => true,
(Answer::UserDefined(first), Answer::UserDefined(second)) => first == second,
_ => false,
}
}
}
impl<T> Hash for Answer<'_, T> where T: Eq + PartialEq + Hash + Display {
fn hash<H>(&self, h: &mut H) where H: Hasher {
let id = match self {
Answer::Yes(_) => "yes",
Answer::No(_) => "no",
Answer::Retry(_) => "retry",
Answer::Next(_) => "next",
Answer::Cancel(_) => "cancel",
Answer::UserDefined(t) => {
t.hash(h);
return;
},
};
crate::ID.hash(h);
id.hash(h);
}
}
#[test]
fn test_answers() {
let answers = &[
Answer::Yes(Some(concat!())), Answer::Yes(None),
Answer::No(Some(concat!())), Answer::No(None),
Answer::Retry(Some(concat!())), Answer::Retry(None),
Answer::Next(Some(concat!())), Answer::Next(None),
Answer::Cancel(Some(concat!())), Answer::Cancel(None),
Answer::UserDefined("first"), Answer::UserDefined("second"),
Answer::UserDefined("second"), Answer::UserDefined("first"),
];
assert_eq!(answers.iter().collect::<HashSet<_>>().len(), 7);
}
impl<T> Display for Answer<'_, T> where T: Eq + PartialEq + Hash + Display {
fn fmt(&self, f: &mut Formatter) -> core::result::Result<(), fmt::Error> {
match self {
Answer::Yes(Some(s)) | Answer::No(Some(s)) | Answer::Retry(Some(s)) | Answer::Next(Some(s)) | Answer::Cancel(Some(s))
=> f.write_str(s),
Answer::UserDefined(t) => f.write_str(&t.to_string()),
Answer::Yes(None) => f.write_str("Yes"),
Answer::No(None) => f.write_str("No"),
Answer::Retry(None) => f.write_str("Retry"),
Answer::Next(None) => f.write_str("Next"),
Answer::Cancel(None) => f.write_str("Cancel"),
}
}
}
pub fn ask_user<'a, 'b, S, T>(question: S, answers: &'a[Answer<'b, T>]) -> Result<&'a Answer<'b, T>>
where S: AsRef<str>, T: Eq + PartialEq + Hash + Display {
if answers.is_empty() {
return Err(Error::new(ErrorKind::InvalidData, "There are no answers"));
}
if answers.iter().collect::<HashSet<_>>().len() != answers.len() {
return Err(Error::new(ErrorKind::InvalidData, "There are duplicate answers"));
}
let question = question.as_ref().trim();
loop {
crate::lock_write_out(format!("{}\n\n", question));
for (idx, answer) in answers.iter().enumerate() {
crate::lock_write_out(format!("[{idx}] {answer}\n", idx=idx + 1, answer=answer));
}
crate::lock_write_out("\n-> ");
let user_choice = crate::read_line::<String>()?;
if user_choice.is_empty() {
continue;
}
match usize::from_str(&user_choice).map(|i| i.checked_sub(1)) {
Ok(Some(idx)) => if idx < answers.len() {
return Ok(&answers[idx]);
},
Ok(None) => continue,
Err(_) => {
let user_choice = user_choice.to_lowercase();
match answers.iter().try_fold(None, |found_answer, answer| if answer.to_string().to_lowercase().contains(&user_choice) {
match found_answer {
None => Ok(Some(answer)),
Some(_) => Err("User choice matches more than one answer"),
}
} else {
Ok(found_answer)
}) {
Ok(Some(answer)) => return Ok(answer),
_ => continue,
};
},
};
}
}