Skip to main content

rgx/engine/
fancy.rs

1use super::{
2    CaptureGroup, CompiledRegex, EngineError, EngineFlags, EngineKind, EngineResult, Match,
3    RegexEngine,
4};
5
6pub struct FancyRegexEngine;
7
8impl RegexEngine for FancyRegexEngine {
9    fn kind(&self) -> EngineKind {
10        EngineKind::FancyRegex
11    }
12
13    fn compile(&self, pattern: &str, flags: &EngineFlags) -> EngineResult<Box<dyn CompiledRegex>> {
14        let full_pattern = flags.wrap_pattern(pattern);
15        let re = fancy_regex::Regex::new(&full_pattern)
16            .map_err(|e| EngineError::CompileError(e.to_string()))?;
17
18        Ok(Box::new(FancyCompiledRegex { re }))
19    }
20}
21
22struct FancyCompiledRegex {
23    re: fancy_regex::Regex,
24}
25
26impl CompiledRegex for FancyCompiledRegex {
27    fn find_matches(&self, text: &str) -> EngineResult<Vec<Match>> {
28        let mut matches = Vec::new();
29
30        for result in self.re.captures_iter(text) {
31            let caps = result.map_err(|e| EngineError::MatchError(e.to_string()))?;
32            let overall = caps.get(0).unwrap();
33            let mut captures = Vec::new();
34
35            for (i, name) in self.re.capture_names().enumerate() {
36                if i == 0 {
37                    continue;
38                }
39                if let Some(m) = caps.get(i) {
40                    captures.push(CaptureGroup {
41                        index: i,
42                        name: name.map(String::from),
43                        start: m.start(),
44                        end: m.end(),
45                        text: m.as_str().to_string(),
46                    });
47                }
48            }
49
50            matches.push(Match {
51                start: overall.start(),
52                end: overall.end(),
53                text: overall.as_str().to_string(),
54                captures,
55            });
56        }
57
58        Ok(matches)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_simple_match() {
68        let engine = FancyRegexEngine;
69        let flags = EngineFlags::default();
70        let compiled = engine.compile(r"\d+", &flags).unwrap();
71        let matches = compiled.find_matches("abc 123 def 456").unwrap();
72        assert_eq!(matches.len(), 2);
73        assert_eq!(matches[0].text, "123");
74    }
75
76    #[test]
77    fn test_lookahead() {
78        let engine = FancyRegexEngine;
79        let flags = EngineFlags::default();
80        let compiled = engine.compile(r"\w+(?=@)", &flags).unwrap();
81        let matches = compiled.find_matches("user@example.com").unwrap();
82        assert_eq!(matches.len(), 1);
83        assert_eq!(matches[0].text, "user");
84    }
85
86    #[test]
87    fn test_named_captures() {
88        let engine = FancyRegexEngine;
89        let flags = EngineFlags::default();
90        let compiled = engine
91            .compile(r"(?P<user>\w+)@(?P<domain>\w+)", &flags)
92            .unwrap();
93        let matches = compiled.find_matches("user@example").unwrap();
94        assert_eq!(matches.len(), 1);
95        assert_eq!(matches[0].captures.len(), 2);
96        assert_eq!(matches[0].captures[0].name, Some("user".to_string()));
97        assert_eq!(matches[0].captures[0].text, "user");
98        assert_eq!(matches[0].captures[1].name, Some("domain".to_string()));
99        assert_eq!(matches[0].captures[1].text, "example");
100    }
101
102    #[test]
103    fn test_lookbehind() {
104        let engine = FancyRegexEngine;
105        let flags = EngineFlags::default();
106        let compiled = engine.compile(r"(?<=@)\w+", &flags).unwrap();
107        let matches = compiled.find_matches("user@example.com").unwrap();
108        assert_eq!(matches.len(), 1);
109        assert_eq!(matches[0].text, "example");
110    }
111}