1use anyhow::{Context, Result};
4use regex::Regex;
5
6pub struct Matcher {
8 regex: Regex,
9 only_matching: bool,
10 invert_match: bool,
11}
12
13impl Matcher {
14 pub fn new(
16 pattern: &str,
17 case_insensitive: bool,
18 word_regexp: bool,
19 fixed_strings: bool,
20 only_matching: bool,
21 invert_match: bool,
22 ) -> Result<Self> {
23 let mut final_pattern = if fixed_strings {
25 regex::escape(pattern)
26 } else {
27 pattern.to_string()
28 };
29
30 if word_regexp {
32 final_pattern = format!(r"\b{final_pattern}\b");
33 }
34
35 if case_insensitive {
37 final_pattern = format!("(?i){final_pattern}");
38 }
39
40 let regex = Regex::new(&final_pattern).context("Failed to compile regex pattern")?;
41
42 Ok(Self {
43 regex,
44 only_matching,
45 invert_match,
46 })
47 }
48
49 pub fn find<'a>(&self, text: &'a str) -> Option<(&'a str, usize, usize)> {
52 let has_match = self.regex.is_match(text);
53
54 if self.invert_match {
56 if !has_match {
57 return Some((text, 0, text.len()));
59 } else {
60 return None;
61 }
62 }
63
64 if !has_match {
66 return None;
67 }
68
69 if self.only_matching {
71 self.regex
72 .find(text)
73 .map(|m| (m.as_str(), m.start(), m.end()))
74 } else {
75 self.regex.find(text).map(|m| (text, m.start(), m.end()))
77 }
78 }
79
80 pub fn is_match(&self, text: &str) -> bool {
82 if self.invert_match {
83 !self.regex.is_match(text)
84 } else {
85 self.regex.is_match(text)
86 }
87 }
88
89 pub fn find_all<'a>(&self, text: &'a str) -> Vec<(&'a str, usize, usize)> {
91 self.regex
92 .find_iter(text)
93 .map(|m| (m.as_str(), m.start(), m.end()))
94 .collect()
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn test_matcher_basic() {
104 let matcher = Matcher::new("import", false, false, false, false, false);
105 assert!(matcher.is_ok());
106 if let Ok(matcher) = matcher {
107 assert!(matcher.is_match("import pandas"));
108 assert!(!matcher.is_match("define function"));
109 }
110 }
111
112 #[test]
113 fn test_matcher_case_insensitive() {
114 let matcher = Matcher::new("IMPORT", true, false, false, false, false);
115 assert!(matcher.is_ok());
116 if let Ok(matcher) = matcher {
117 assert!(matcher.is_match("import pandas"));
118 assert!(matcher.is_match("Import numpy"));
119 assert!(matcher.is_match("IMPORT os"));
120 }
121 }
122
123 #[test]
124 fn test_matcher_find() {
125 let matcher = Matcher::new("import", false, false, false, true, false);
126 assert!(matcher.is_ok());
127 if let Ok(matcher) = matcher {
128 let result = matcher.find("import pandas as pd");
129 assert!(result.is_some());
130 if let Some((matched, start, end)) = result {
131 assert_eq!(matched, "import");
132 assert_eq!(start, 0);
133 assert_eq!(end, 6);
134 }
135 }
136 }
137
138 #[test]
139 fn test_matcher_regex() {
140 let matcher = Matcher::new(r"import \w+", false, false, false, true, false);
141 assert!(matcher.is_ok());
142 if let Ok(matcher) = matcher {
143 let result = matcher.find("import pandas as pd");
144 assert!(result.is_some());
145 if let Some((matched, _, _)) = result {
146 assert_eq!(matched, "import pandas");
147 }
148 }
149 }
150}