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<Item = Self>; 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<T: Eq + Hash + fmt::Debug> { values: HashSet<T>, } #[derive(Debug, Clone)] pub enum ParseError<E> { Underlying(E), } impl<E: fmt::Display> fmt::Display for ParseError<E> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ParseError::Underlying(ref e) => e.fmt(f), } } } impl<T> FromStr for CommaSeparatedList<T> where T: FromStr + Variants + Hash + Eq + fmt::Debug, { type Err = ParseError<T::Err>; fn from_str(input: &str) -> Result<Self, Self::Err> { 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::<HashSet<_>>(); 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::<Result<HashSet<_>, _>>() .map_err(ParseError::Underlying)?; Ok(CommaSeparatedList { values }) } } } impl<T> CommaSeparatedList<T> where T: Hash + Eq + fmt::Debug, { pub fn contains(&self, value: &T) -> bool { self.values.contains(value) } pub fn values(&self) -> &HashSet<T> { &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<T> From<CommaSeparatedList<T>> for HashSet<T> where T: Hash + Eq + fmt::Debug, { fn from(csl: CommaSeparatedList<T>) -> Self { csl.values } }