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}