use std::collections::HashSet; use std::hash::Hash; use std::str::FromStr; use std::fmt; use super::{SearchField, FightOutcome}; 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() } } } } variants! { SearchField => Account, Character } variants! { FightOutcome => Success, Wipe } /// 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) } }