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
95
96
97
98
99
100
101
102
103
use regex::Regex;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct Config {
pub follow_web_links: bool,
pub traverse_parent_directories: bool,
#[serde(with = "regex_serde")]
pub exclude: Vec<Regex>,
}
impl Config {
pub fn should_skip(&self, link: &str) -> bool {
self.exclude.iter().any(|pat| pat.is_match(link))
}
}
mod regex_serde {
use regex::Regex;
use serde::de::{Deserialize, Deserializer, Error};
use serde::ser::{SerializeSeq, Serializer};
pub fn serialize<S>(re: &Vec<Regex>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = ser.serialize_seq(Some(re.len()))?;
for pattern in re {
seq.serialize_element(pattern.as_str())?;
}
seq.end()
}
pub fn deserialize<'de, D>(de: D) -> Result<Vec<Regex>, D::Error>
where
D: Deserializer<'de>,
{
let raw = Vec::<String>::deserialize(de)?;
let mut patterns = Vec::new();
for pat in raw {
let re = Regex::new(&pat).map_err(D::Error::custom)?;
patterns.push(re);
}
Ok(patterns)
}
}
impl PartialEq for Config {
fn eq(&self, other: &Config) -> bool {
let Config {
follow_web_links,
traverse_parent_directories,
ref exclude,
} = self;
*follow_web_links == other.follow_web_links
&& *traverse_parent_directories == other.traverse_parent_directories
&& exclude.len() == other.exclude.len()
&& exclude
.iter()
.zip(other.exclude.iter())
.all(|(l, r)| l.as_str() == r.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
use toml;
const CONFIG: &str = r#"follow-web-links = true
traverse-parent-directories = true
exclude = ["google\\.com"]
"#;
#[test]
fn deserialize_a_config() {
let should_be = Config {
follow_web_links: true,
traverse_parent_directories: true,
exclude: vec![Regex::new(r"google\.com").unwrap()],
};
let got: Config = toml::from_str(CONFIG).unwrap();
assert_eq!(got, should_be);
}
#[test]
fn round_trip_config() {
let deserialized: Config = toml::from_str(CONFIG).unwrap();
let reserialized = toml::to_string(&deserialized).unwrap();
assert_eq!(reserialized, CONFIG);
}
}