use crate::domain::enclosure_suppressor::{EnclosureContext, EnclosureSuppressor};
use regex::Regex;
#[derive(Debug, Clone)]
pub struct FastPattern {
pub char: char,
pub line_start: bool,
pub before_matcher: Option<String>,
pub after_matcher: Option<String>,
}
#[derive(Debug)]
pub struct CompiledRegexPattern {
regex: Regex,
#[allow(dead_code)]
description: Option<String>,
}
#[derive(Debug)]
pub struct Suppressor {
patterns: Vec<FastPattern>,
regex_patterns: Vec<CompiledRegexPattern>,
}
impl Suppressor {
pub fn new(patterns: Vec<(char, bool, Option<String>, Option<String>)>) -> Self {
let parsed_patterns: Vec<FastPattern> = patterns
.into_iter()
.map(|(ch, line_start, before, after)| FastPattern {
char: ch,
line_start,
before_matcher: before,
after_matcher: after,
})
.collect();
Self {
patterns: parsed_patterns,
regex_patterns: Vec::new(), }
}
pub fn with_regex_patterns(
fast_patterns: Vec<(char, bool, Option<String>, Option<String>)>,
regex_patterns: Vec<(String, Option<String>)>,
) -> Result<Self, regex::Error> {
let parsed_patterns: Vec<FastPattern> = fast_patterns
.into_iter()
.map(|(ch, line_start, before, after)| FastPattern {
char: ch,
line_start,
before_matcher: before,
after_matcher: after,
})
.collect();
let compiled_regex_patterns: Result<Vec<CompiledRegexPattern>, regex::Error> =
regex_patterns
.into_iter()
.map(|(pattern, desc)| {
Ok(CompiledRegexPattern {
regex: Regex::new(&pattern)?,
description: desc,
})
})
.collect();
Ok(Self {
patterns: parsed_patterns,
regex_patterns: compiled_regex_patterns?,
})
}
fn char_matches_pattern(ch: Option<char>, pattern: &str) -> bool {
match (ch, pattern) {
(Some(c), "alpha") => c.is_alphabetic(),
(Some(c), "alnum") => c.is_alphanumeric(),
(Some(c), "digit") => c.is_ascii_digit(),
(Some(c), "whitespace") => c.is_whitespace(),
(None, _) => false,
_ => false,
}
}
fn matches_any_pattern(&self, ch: char, context: &EnclosureContext) -> bool {
for pattern in &self.patterns {
if self.pattern_matches(pattern, ch, context) {
return true;
}
}
false
}
fn pattern_matches(&self, pattern: &FastPattern, ch: char, context: &EnclosureContext) -> bool {
if pattern.char != ch {
return false;
}
if pattern.line_start && context.line_offset > 10 {
return false;
}
if let Some(ref pattern_str) = pattern.before_matcher {
let char_before = context.preceding_chars.last().copied();
if !Self::char_matches_pattern(char_before, pattern_str) {
return false;
}
}
if let Some(ref pattern_str) = pattern.after_matcher {
let char_after = context.following_chars.first().copied();
if !Self::char_matches_pattern(char_after, pattern_str) {
return false;
}
}
true
}
fn matches_any_regex_pattern(&self, ch: char, context: &EnclosureContext) -> bool {
let window = self.build_context_window(ch, context);
for pattern in &self.regex_patterns {
if pattern.regex.is_match(&window) {
return true;
}
}
false
}
fn build_context_window(&self, ch: char, context: &EnclosureContext) -> String {
let mut window = String::new();
for &c in &context.preceding_chars {
window.push(c);
}
window.push(ch);
for &c in &context.following_chars {
window.push(c);
}
window
}
}
impl EnclosureSuppressor for Suppressor {
fn should_suppress_enclosure(&self, ch: char, context: &EnclosureContext) -> bool {
if self.matches_any_pattern(ch, context) {
return true;
}
if self.matches_any_regex_pattern(ch, context) {
return true;
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use smallvec::SmallVec;
fn create_context(
preceding: &[char],
following: &[char],
line_offset: usize,
) -> EnclosureContext<'static> {
let preceding_chars: SmallVec<[char; 3]> = preceding.iter().copied().collect();
let following_chars: SmallVec<[char; 3]> = following.iter().copied().collect();
EnclosureContext {
position: 0,
preceding_chars,
following_chars,
line_offset,
chunk_text: "",
}
}
#[test]
fn test_apostrophe_suppression() {
let suppressor = Suppressor::new(vec![(
'\'',
false,
Some("alpha".to_string()),
Some("alpha".to_string()),
)]);
let context = create_context(&['t'], &['s'], 10);
assert!(suppressor.should_suppress_enclosure('\'', &context));
let context = create_context(&[' '], &['H'], 10);
assert!(!suppressor.should_suppress_enclosure('\'', &context));
}
#[test]
fn test_line_start_suppression() {
let suppressor = Suppressor::new(vec![(')', true, Some("alnum".to_string()), None)]);
let context = create_context(&['1'], &[' '], 0);
assert!(suppressor.should_suppress_enclosure(')', &context));
let context = create_context(&['1'], &[' '], 15);
assert!(!suppressor.should_suppress_enclosure(')', &context));
}
#[test]
fn test_no_patterns() {
let suppressor = Suppressor::new(vec![]);
let context = create_context(&['t'], &['s'], 10);
assert!(!suppressor.should_suppress_enclosure('\'', &context));
}
}