1extern crate regex;
2extern crate fancy_regex;
3
4use regex::Regex;
5use fancy_regex::Regex as FancyRegex;
6
7const DEFAULT_DELIMITER: char = '/';
11
12pub struct Options {
13 delimiter: char,
14 whitelist: Vec<String>,
15 strict: bool,
16 sensitive: bool,
17 end: bool,
18 start: bool,
19 ends_with: Vec<String>
20}
21impl Default for Options {
22 fn default () -> Options {
23 Options {
24 delimiter: DEFAULT_DELIMITER,
25 whitelist: Vec::new(),
26 strict: false,
27 sensitive: false,
28 end: true,
29 start: true,
30 ends_with: Vec::new()
31 }
32 }
33}
34
35#[derive(Debug, Clone)]
36pub struct Token {
37 name: String,
38 prefix: String,
39 delimiter: char,
40 optional: bool,
41 repeat: bool,
42 pattern: String
43}
44
45#[derive(Debug)]
46pub struct Match {
47 name: String,
48 value: String
49}
50
51#[derive(Debug, Clone)]
52pub struct Container {
53 token: Option<Token>,
54 path: String
55}
56
57fn escape_string (string: String) -> String {
64 let re = Regex::new(r"([.+*?=^!:${}()[\]|/\\]])").unwrap();
65 re.replace_all(string.as_str(), r"\$1").into_owned()
66}
67
68fn escape_group (group: String) -> String {
75 let re = Regex::new(r"([=!:$/()])").unwrap();
76 re.replace_all(group.as_str(), r"\$1").into_owned()
77}
78
79fn flags (route: &str, options: Options) -> String {
86 if !options.sensitive {
87 format!("(?i){}", route)
88 } else {
89 String::from(route)
90 }
91}
92
93pub fn parse (text: &str, options: Options) -> Vec<Container> {
101 let default_delimiter: char = options.delimiter;
102 let whitelist: &Vec<String> = &options.whitelist;
103 let path_regexp: Regex = Regex::new(vec![
104 r"(\\.)",
107 r"(?::(\w+)(?:\(((?:\\.|[^\\()])+)\))?|\(((?:\\.|[^\\()])+)\))([+*?])?"
113 ].join("|").as_str()).unwrap();
114 let mut index = 0;
115 let mut key = -1;
116 let mut path = String::new();
117 let mut path_escaped = false;
118 let mut containers: Vec<Container> = vec![];
119
120 fn unwrap_match_to_str (m: Option<regex::Match>) -> &str {
121 if m != None {
122 m.unwrap().as_str()
123 } else {
124 ""
125 }
126 }
127
128 if !path_regexp.is_match(text) {
129 return containers;
130 }
131
132 for res in path_regexp.captures_iter(text) {
133 let m = res.get(0).unwrap();
134 let escaped = res.get(1);
135 let offset = m.start();
136
137 path.push_str(text.get(index..offset).unwrap());
138 index = offset + m.as_str().len();
139
140 if escaped != None {
142 path.push_str(escaped.unwrap().as_str());
143 path_escaped = true;
144 continue;
145 }
146
147 let mut prev: String = String::new();
148 let name = unwrap_match_to_str(res.get(2));
149 let capture = unwrap_match_to_str(res.get(3));
150 let group = res.get(4);
151 let modifier = unwrap_match_to_str(res.get(5));
152
153 if !path_escaped && path.len() > 0 {
154 let k = path.len();
155 let c = String::from(path.get(k-1..k).unwrap());
156 let matches: bool = if whitelist.len() > 0 {
157 whitelist.into_iter().find(|&x| x == &c) != None
158 } else {
159 false
160 };
161
162 if matches {
163 prev = c;
164 path = String::from(path.get(0..k).unwrap());
165 }
166 }
167
168 if path != "" {
170 containers.push(Container {
171 path,
172 token: None
173 });
174 path = String::new();
175 path_escaped = false;
176 }
177
178 let repeat = modifier == "+" || modifier == "*";
179 let optional = modifier == "?" || modifier == "*";
180 let pattern = if capture.len() > 0 {
181 capture
182 } else if group != None {
183 group.unwrap().as_str()
184 } else {
185 ""
186 };
187 let delimiter: char = if prev != "" {
188 prev.chars().next().unwrap()
189 } else {
190 default_delimiter
191 };
192
193 containers.push(Container {
194 path: String::new(),
195 token: Some(Token {
196 name: if name != "" {
197 name.to_owned()
198 } else {
199 key += 1;
200 key.to_string()
201 },
202 prefix: prev,
203 delimiter: delimiter,
204 optional: optional,
205 repeat: repeat,
206 pattern: if pattern != "" {
207 escape_group(pattern.to_owned())
208 } else {
209 let pattern_delimiter = if delimiter == default_delimiter {
210 delimiter.to_string()
211 } else {
212 vec![delimiter, default_delimiter].into_iter().collect()
213 };
214
215 format!(r"[^\{}]+?", escape_string(pattern_delimiter))
216 }
217 })
218 });
219 }
220
221 if path.len() > 0 || index < text.len() {
223 path.push_str(text.get(index..text.len()).unwrap());
224 containers.push(Container {
225 path,
226 token: None
227 });
228 }
229
230 containers
231}
232
233pub fn to_regexp (containers: &Vec<Container>, options: Options) -> FancyRegex {
241 let strict = options.strict;
242 let start = options.start;
243 let end = options.end;
244 let delimiter = options.delimiter;
245 let ends_with = if options.ends_with.len() > 0 {
246 let mut _ends_with_vec = &options.ends_with;
247 let mut _ends_with: Vec<String> = _ends_with_vec.into_iter().map(|s| {
248 escape_string(s.to_string())
249 }).collect();
250 _ends_with.push(String::from("$"));
251 _ends_with.join("|")
252 } else {
253 String::from("$")
254 };
255 let mut route = if start == true {
256 String::from("^")
257 } else {
258 String::from("")
259 };
260
261 for i in 0..containers.len() {
263 let container = &containers[i];
264
265 if !container.path.is_empty() {
266 route.push_str(escape_string(container.path.to_string()).as_str());
267 } else {
268 let token = container.token.as_ref().unwrap();
269 let prefix = String::from(token.prefix.as_str());
270 let capture = if token.repeat == true {
271 format!("(?:{})(?:{}(?:{}))*", token.pattern.as_str(), escape_string(token.delimiter.to_string()).as_str(), token.pattern.as_str())
272 } else {
273 format!("{}", token.pattern.as_str())
274 };
275
276 if token.optional {
277 if token.prefix != "" {
278 route.push_str(format!("({})", capture.as_str()).as_str());
279 } else {
280 route.push_str(format!("(?:{}({}))?", escape_string(prefix).as_str(), capture.as_str()).as_str());
281 }
282 } else {
283 route.push_str(format!("{}({})", escape_string(prefix).as_str(), capture.as_str()).as_str());
284 }
285 }
286 }
287
288 if end {
289 if !strict {
290 route.push_str(format!("(?:{})?", escape_string(delimiter.to_string())).as_str());
291 }
292
293 if ends_with == "$" {
294 route.push_str("$");
295 } else {
296 route.push_str(format!("(?={})", ends_with).as_str());
297 };
298 } else {
299 let last_container: &Container = containers.last().unwrap();
300 let is_end_delimited = if !last_container.path.is_empty() {
301 let last_path_char = last_container.path.get(last_container.path.len() - 1..last_container.path.len()).unwrap();
302 last_path_char.to_string()
303 } else {
304 String::new()
305 };
306
307 if !strict {
308 route.push_str(format!("(?:{}(?={}))?", escape_string(delimiter.to_string()), ends_with).as_str());
309 }
310
311 if is_end_delimited.is_empty() {
312 route.push_str(format!("(?={}|{})", escape_string(delimiter.to_string()), ends_with).as_str());
313 }
314 }
315
316 let regex_str = format!(r"{}", flags(route.as_str(), options).as_str());
317
318 FancyRegex::new(regex_str.as_str()).unwrap()
319}
320
321pub fn match_str (text: &str, regexp: FancyRegex, containers: Vec<Container>) -> Vec<Match> {
330 let mut matches: Vec<Match> = vec![];
331
332 if !regexp.is_match(text).unwrap() {
333 return matches;
334 }
335
336 let containers: Vec<Container> = containers.into_iter()
337 .filter(|container| container.path == "")
338 .collect();
339
340 if let Some(caps) = regexp.captures_from_pos(&text, 0).unwrap() {
341 for i in 0..caps.len() {
342 let cap = caps.at(i).unwrap();
343
344 if cap.len() == text.len() {
345 continue;
346 }
347
348 let container = containers.get(i-1).unwrap();
349 if let Some(token) = &container.token {
350 matches.push(Match {
351 name: String::from(token.name.as_str()),
352 value: cap.to_owned()
353 });
354 }
355 }
356 }
357
358 matches
359}