1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
use std::collections::HashSet;
use std::hash::Hash;
use std::str::FromStr;
use std::fmt;
use super::{SearchField, FightOutcome};
use chrono::Weekday;
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()
}
}
}
}
variants! { SearchField => Account, Character }
variants! { FightOutcome => Success, Wipe }
variants! { Weekday => Mon, Tue, Wed, Thu, Fri, Sat, Sun }
/// 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)
}
}
|