use std::collections::HashSet; use std::fmt; use std::hash::Hash; use std::str::FromStr; use super::{FightOutcome, SearchField}; use chrono::Weekday; use evtclib::statistics::gamedata::Boss; pub trait Variants: Copy { type Output: Iterator; fn variants() -> Self::Output; } macro_rules! variants { ($target:ident => $($var:ident),+) => { impl Variants for $target { type Output = ::std::iter::Cloned<::std::slice::Iter<'static, Self>>; fn variants() -> Self::Output { // Exhaustiveness check #[allow(dead_code)] fn exhaustiveness_check(value: $target) { match value { $($target :: $var => ()),+ } } // Actual result [ $($target :: $var),+ ].iter().cloned() } } }; ($target:ident => $($var:ident,)+) => { variants!($target => $($var),+); }; } variants! { SearchField => Account, Character, Guild } variants! { FightOutcome => Success, Wipe } variants! { Weekday => Mon, Tue, Wed, Thu, Fri, Sat, Sun } variants! { Boss => ValeGuardian, Gorseval, Sabetha, Slothasor, Matthias, KeepConstruct, Xera, Cairn, MursaatOverseer, Samarog, Deimos, SoullessHorror, Dhuum, ConjuredAmalgamate, LargosTwins, Qadim, CardinalAdina, CardinalSabir, QadimThePeerless, IcebroodConstruct, VoiceOfTheFallen, FraenirOfJormag, Boneskinner, WhisperOfJormag, Skorvald, Artsariiv, Arkk, MAMA, Siax, Ensolyss, } /// The character that delimits items from each other. const DELIMITER: char = ','; /// The character that negates the result. const NEGATOR: char = '!'; /// A list that is given as comma-separated values. #[derive(Debug, Clone)] pub struct CommaSeparatedList { values: HashSet, } #[derive(Debug, Clone)] pub enum ParseError { Underlying(E), } impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ParseError::Underlying(ref e) => e.fmt(f), } } } impl FromStr for CommaSeparatedList where T: FromStr + Variants + Hash + Eq + fmt::Debug, { type Err = ParseError; fn from_str(input: &str) -> Result { if input == "*" { Ok(CommaSeparatedList { values: T::variants().collect(), }) } else if input.starts_with(NEGATOR) { let no_csl = CommaSeparatedList::from_str(&input[1..])?; let all_values = T::variants().collect::>(); Ok(CommaSeparatedList { values: all_values.difference(&no_csl.values).cloned().collect(), }) } else { let parts = input.split(DELIMITER); let values = parts .map(FromStr::from_str) .collect::, _>>() .map_err(ParseError::Underlying)?; Ok(CommaSeparatedList { values }) } } } impl CommaSeparatedList where T: Hash + Eq + fmt::Debug, { pub fn contains(&self, value: &T) -> bool { self.values.contains(value) } pub fn values(&self) -> &HashSet { &self.values } } // We allow implicit hasher because then it's a zero-cost conversion, as we're just unwrapping the // values. #[allow(clippy::implicit_hasher)] impl From> for HashSet where T: Hash + Eq + fmt::Debug, { fn from(csl: CommaSeparatedList) -> Self { csl.values } }