use frizbee::{Scoring, smith_waterman::SmithWatermanMatcher};
use crate::{
CaseMatching,
fuzzy_matcher::{FuzzyMatcher, MatchIndices, ScoreType},
};
const RESPECT_CASE_BONUS: u16 = 10000;
#[derive(Default)]
pub struct FrizbeeMatcher {
case: CaseMatching,
max_typos: Option<u16>,
}
impl FrizbeeMatcher {
pub fn max_typos(mut self, typos: Option<usize>) -> Self {
self.max_typos = Some(typos.map(|x| x.try_into().unwrap()).unwrap_or(0));
self
}
pub fn case(mut self, case: CaseMatching) -> Self {
self.case = case;
self
}
}
impl FuzzyMatcher for FrizbeeMatcher {
fn fuzzy_indices(&self, choice: &str, pattern: &str) -> Option<(ScoreType, MatchIndices)> {
let scoring = Scoring {
matching_case_bonus: match self.case {
CaseMatching::Respect => RESPECT_CASE_BONUS,
CaseMatching::Ignore => 0,
CaseMatching::Smart => {
if pattern.chars().any(|c| c.is_uppercase()) {
RESPECT_CASE_BONUS
} else {
0
}
}
},
..Default::default()
};
let mut matcher = SmithWatermanMatcher::new(pattern.as_bytes(), &scoring);
matcher
.match_haystack_indices(choice.as_bytes(), 0, self.max_typos)
.and_then(|(m, mut indices)| {
debug!("{choice}: {m} ({})", scoring.matching_case_bonus);
if m > scoring.matching_case_bonus.saturating_mul(
pattern
.chars()
.count()
.saturating_sub(self.max_typos.unwrap_or(0).into())
.try_into()
.unwrap(),
) {
indices.reverse();
Some((m.into(), MatchIndices::from(indices)))
} else {
None
}
})
}
fn fuzzy_match(&self, choice: &str, pattern: &str) -> Option<i64> {
let scoring = Scoring {
matching_case_bonus: match self.case {
CaseMatching::Respect => RESPECT_CASE_BONUS,
CaseMatching::Ignore => 0,
CaseMatching::Smart => {
if pattern.chars().any(|c| c.is_uppercase()) {
RESPECT_CASE_BONUS
} else {
0
}
}
},
..Default::default()
};
let mut matcher = SmithWatermanMatcher::new(pattern.as_bytes(), &scoring);
matcher
.match_haystack(choice.as_bytes(), self.max_typos)
.map(|x| x as ScoreType)
}
}