1#[cfg(feature = "handparser")]
2mod hand_parser;
3#[cfg(feature = "with-nom")]
4mod nom_parser;
5use regex::Regex;
6use thiserror::Error;
7
8#[derive(Error, Debug, PartialEq, Eq)]
9pub enum ParseError {
10 #[error("Cannot parse expression")]
11 ParseError(String),
12 #[error("Embedded regex cannot compile")]
13 BadRegex(String),
14}
15
16#[cfg(feature = "handparser")]
17pub fn compile_impl(pattern: &str, dstart: char, dend: char) -> Result<Regex, ParseError> {
18 let parsed = hand_parser::parse(pattern, dstart, dend)
19 .map_err(|err| ParseError::ParseError(err.to_string()))?;
20 let mut out = String::new();
21
22 for exp in parsed {
23 match exp {
24 hand_parser::Token::Regex(re) => out.push_str(&re),
25 hand_parser::Token::String(s) => {
26 out.push_str(®ex::escape(&s));
27 }
28 }
29 }
30
31 out = format!("^{}$", out);
32 Regex::new(&out).map_err(|err| ParseError::BadRegex(err.to_string()))
33}
34
35#[cfg(feature = "with-nom")]
36fn compile_impl(pattern: &str, dstart: char, dend: char) -> Result<Regex, ParseError> {
37 let (rest, parsed) = nom_parser::parse(pattern, dstart, dend)
38 .map_err(|err| ParseError::ParseError(err.to_string()))?;
39 if !rest.is_empty() {
40 return Err(ParseError::ParseError(format!(
41 "input not consumed entirely: {}",
42 rest
43 )));
44 }
45 let mut out = String::new();
46
47 for exp in parsed {
48 match exp {
49 nom_parser::Token::Regex(re) => out.push_str(re),
50 nom_parser::Token::String(s) => {
51 out.push_str(®ex::escape(s));
52 }
53 }
54 }
55 out = format!("^{}$", out);
56 Regex::new(&out).map_err(|err| ParseError::BadRegex(err.to_string()))
57}
58
59pub fn compile(pattern: &str, dstart: char, dend: char) -> Result<Regex, ParseError> {
65 compile_impl(pattern, dstart, dend)
66}
67pub fn is_match(pattern: &str, dstart: char, dend: char, text: &str) -> Result<bool, ParseError> {
73 let re = compile(pattern, dstart, dend)?;
74
75 Ok(re.is_match(text))
76}
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use pretty_assertions::assert_eq;
81
82 #[test]
83 fn match_glob() {
84 assert_eq!(is_match("foo/<.*>", '<', '>', "foo/bar").unwrap(), true);
85 }
86
87 #[test]
88 fn match_nested() {
89 assert_eq!(
90 is_match("foo/{a{1,4}}/", '{', '}', "foo/aaa/").unwrap(),
91 true
92 );
93 }
94
95 #[test]
96 fn dont_match_nested() {
97 assert_eq!(
98 is_match("foo/{a{1,4}}/", '{', '}', "foo/aaaaaaaa/").unwrap(),
99 false
100 );
101 }
102
103 #[test]
104 fn match_multiple() {
105 assert_eq!(
106 is_match("foo/{b{1,4}}/{[0-9]+}", '{', '}', "foo/bbb/123").unwrap(),
107 true
108 );
109 }
110}