1use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8
9const SEPERATOR: char = '/';
11
12const NAME_PLACEHOLDER: char = '*';
14
15#[derive(Deserialize, Serialize, Debug)]
61pub struct Describer {
62 descriptions: HashMap<String, String>,
63 patterns: HashMap<String, String>,
64}
65
66impl Describer {
67 pub fn new() -> Describer {
69 Describer {
70 descriptions: HashMap::new(),
71 patterns: HashMap::new(),
72 }
73 }
74
75 pub fn new_with(d: HashMap<String, String>, p: HashMap<String, String>) -> Describer {
82 Describer {
83 descriptions: d,
84 patterns: p,
85 }
86 }
87
88 pub fn new_from_json(json: &str) -> Result<Describer, serde_json::Error> {
96 serde_json::from_str::<Describer>(json)
97 }
98
99 pub fn describe(&self, path: &str) -> Option<String> {
103 match self.descriptions.get(path) {
104 Some(d) => Some(d.clone()),
105 None => self.describe_using_pattern(path),
106 }
107 }
108
109 fn describe_using_pattern(&self, path: &str) -> Option<String> {
112 let parent: Vec<&str> = path.rsplitn(2, SEPERATOR).collect();
113 if parent.len() != 2 {
114 None
115 } else {
116 match self.patterns.get(parent[1]) {
117 Some(p) => Some(p.replace(NAME_PLACEHOLDER, parent[0])),
118 None => None,
119 }
120 }
121 }
122
123 pub fn add_description(&mut self, path: &str, desc: &str) {
125 self.descriptions.insert(path.to_string(), desc.to_string());
126 }
127
128 pub fn add_pattern(&mut self, path: &str, desc: &str) {
130 self.patterns.insert(path.to_string(), desc.to_string());
131 }
132
133 pub fn to_json(&self, pretty: bool) -> Result<String, serde_json::Error> {
140 if pretty {
141 serde_json::to_string_pretty(self)
142 } else {
143 serde_json::to_string(self)
144 }
145 }
146}
147
148impl Default for Describer {
149 fn default() -> Describer {
150 Describer::new()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn new_describe_test() {
160 let mut descriptions: HashMap<String, String> = HashMap::new();
161 let mut patterns: HashMap<String, String> = HashMap::new();
162 for (path, desc, is_pattern) in [
163 ("/path/to/dir", "This is /path/to/dir.", false),
164 ("/another/dir", "This is /another/dir.", false),
165 ("/yet/another/path", "This is /yet/another/path.", false),
166 ("/path/to/dir", "* is in /path/to/dir.", true),
167 ("/yet/another/path", "* is in /yet/another/path.", true),
168 ("/obvious", "* is *", true),
169 ("/yet/another", "* is in /yet/another/path.", true),
170 ]
171 .iter()
172 {
173 if *is_pattern {
174 patterns.insert(path.to_string(), desc.to_string());
175 } else {
176 descriptions.insert(path.to_string(), desc.to_string());
177 }
178 }
179
180 describe_tester(&Describer::new_with(descriptions, patterns));
181 }
182
183 #[test]
184 fn new_from_json_describe_test() {
185 match Describer::new_from_json(
186 "
187 {
188 \"descriptions\": {
189 \"/path/to/dir\": \"This is /path/to/dir.\",
190 \"/another/dir\": \"This is /another/dir.\",
191 \"/yet/another/path\": \"This is /yet/another/path.\"
192 },
193 \"patterns\": {
194 \"/path/to/dir\": \"* is in /path/to/dir.\",
195 \"/yet/another/path\": \"* is in /yet/another/path.\",
196 \"/obvious\": \"* is *\",
197 \"/yet/another\": \"* is in /yet/another/path.\"
198 }
199 }",
200 ) {
201 Ok(d) => describe_tester(&d),
202 Err(e) => panic!(e),
203 };
204 }
205
206 #[test]
207 fn add_test() {
208 let mut d = Describer::new();
209 d.add_description("path/to/directory", "This is an empty directory.");
210 d.add_pattern("parent/directory", "* is a child of parent/directory.");
211 assert_eq!(
212 d.to_json(false).unwrap(),
213 format!(
214 "{}{}{}{}",
215 "{\"descriptions\":",
216 "{\"path/to/directory\":\"This is an empty directory.\"},",
217 "\"patterns\":",
218 "{\"parent/directory\":\"* is a child of parent/directory.\"}}"
219 )
220 );
221 }
222
223 fn describe_tester(describer: &Describer) {
224 for (path, desc, is_none) in [
225 ("/path/to/dir", "This is /path/to/dir.", false),
226 ("/another/dir", "This is /another/dir.", false),
227 ("/yet/another/path", "This is /yet/another/path.", false),
228 ("/path/to/dir/1", "1 is in /path/to/dir.", false),
229 ("/path/to/dir/things", "things is in /path/to/dir.", false),
230 ("/yet/another/path/1", "1 is in /yet/another/path.", false),
231 ("/yet/another/path/$", "$ is in /yet/another/path.", false),
232 ("/obvious/obviously", "obviously is obviously", false),
233 ("/doesn't/exist", "", true),
234 ]
235 .iter()
236 {
237 assert_eq!(
238 describer.describe(path),
239 if *is_none {
240 None
241 } else {
242 Some(desc.to_string())
243 }
244 );
245 }
246 }
247}