use anyhow::{Context, Result};
use regex::Regex;
pub struct Matcher {
regex: Regex,
only_matching: bool,
invert_match: bool,
}
impl Matcher {
pub fn new(
pattern: &str,
case_insensitive: bool,
word_regexp: bool,
fixed_strings: bool,
only_matching: bool,
invert_match: bool,
) -> Result<Self> {
let mut final_pattern = if fixed_strings {
regex::escape(pattern)
} else {
pattern.to_string()
};
if word_regexp {
final_pattern = format!(r"\b{final_pattern}\b");
}
if case_insensitive {
final_pattern = format!("(?i){final_pattern}");
}
let regex = Regex::new(&final_pattern).context("Failed to compile regex pattern")?;
Ok(Self {
regex,
only_matching,
invert_match,
})
}
pub fn find<'a>(&self, text: &'a str) -> Option<(&'a str, usize, usize)> {
let has_match = self.regex.is_match(text);
if self.invert_match {
if !has_match {
return Some((text, 0, text.len()));
} else {
return None;
}
}
if !has_match {
return None;
}
if self.only_matching {
self.regex
.find(text)
.map(|m| (m.as_str(), m.start(), m.end()))
} else {
self.regex.find(text).map(|m| (text, m.start(), m.end()))
}
}
pub fn is_match(&self, text: &str) -> bool {
if self.invert_match {
!self.regex.is_match(text)
} else {
self.regex.is_match(text)
}
}
pub fn find_all<'a>(&self, text: &'a str) -> Vec<(&'a str, usize, usize)> {
self.regex
.find_iter(text)
.map(|m| (m.as_str(), m.start(), m.end()))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_matcher_basic() {
let matcher = Matcher::new("import", false, false, false, false, false);
assert!(matcher.is_ok());
if let Ok(matcher) = matcher {
assert!(matcher.is_match("import pandas"));
assert!(!matcher.is_match("define function"));
}
}
#[test]
fn test_matcher_case_insensitive() {
let matcher = Matcher::new("IMPORT", true, false, false, false, false);
assert!(matcher.is_ok());
if let Ok(matcher) = matcher {
assert!(matcher.is_match("import pandas"));
assert!(matcher.is_match("Import numpy"));
assert!(matcher.is_match("IMPORT os"));
}
}
#[test]
fn test_matcher_find() {
let matcher = Matcher::new("import", false, false, false, true, false);
assert!(matcher.is_ok());
if let Ok(matcher) = matcher {
let result = matcher.find("import pandas as pd");
assert!(result.is_some());
if let Some((matched, start, end)) = result {
assert_eq!(matched, "import");
assert_eq!(start, 0);
assert_eq!(end, 6);
}
}
}
#[test]
fn test_matcher_regex() {
let matcher = Matcher::new(r"import \w+", false, false, false, true, false);
assert!(matcher.is_ok());
if let Ok(matcher) = matcher {
let result = matcher.find("import pandas as pd");
assert!(result.is_some());
if let Some((matched, _, _)) = result {
assert_eq!(matched, "import pandas");
}
}
}
}