use regex::Regex;
use crate::engine::all::MatchAllEngine;
use crate::engine::andor::{AndEngine, OrEngine};
use crate::engine::exact::{ExactEngine, ExactMatchingParam};
use crate::engine::fuzzy::{FuzzyAlgorithm, FuzzyEngine};
use crate::engine::regexp::RegexEngine;
use crate::item::RankBuilder;
use crate::{CaseMatching, MatchEngine, MatchEngineFactory, Typos};
use std::sync::{Arc, LazyLock};
static RE_OR_WITH_SPACES: LazyLock<Regex> = LazyLock::new(|| Regex::new(r" *\|+ *").unwrap());
pub struct ExactOrFuzzyEngineFactory {
exact_mode: bool,
fuzzy_algorithm: FuzzyAlgorithm,
rank_builder: Arc<RankBuilder>,
typos: Typos,
filter_mode: bool,
last_match: bool,
}
impl ExactOrFuzzyEngineFactory {
pub fn builder() -> Self {
Self {
exact_mode: false,
fuzzy_algorithm: FuzzyAlgorithm::SkimV2,
rank_builder: Default::default(),
typos: Typos::Disabled,
filter_mode: false,
last_match: false,
}
}
pub fn exact_mode(mut self, exact_mode: bool) -> Self {
self.exact_mode = exact_mode;
self
}
pub fn fuzzy_algorithm(mut self, fuzzy_algorithm: FuzzyAlgorithm) -> Self {
self.fuzzy_algorithm = fuzzy_algorithm;
self
}
pub fn rank_builder(mut self, rank_builder: Arc<RankBuilder>) -> Self {
self.rank_builder = rank_builder;
self
}
pub fn typos(mut self, typos: Typos) -> Self {
self.typos = typos;
self
}
pub fn filter_mode(mut self, filter_mode: bool) -> Self {
self.filter_mode = filter_mode;
self
}
pub fn last_match(mut self, last_match: bool) -> Self {
self.last_match = last_match;
self
}
pub fn build(self) -> Self {
self
}
}
impl MatchEngineFactory for ExactOrFuzzyEngineFactory {
fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
let mut query = query;
let mut exact = self.exact_mode;
let mut param = ExactMatchingParam::default();
param.case = case;
if query.starts_with('\'') {
exact = !exact;
query = &query[1..];
}
if query.starts_with('!') {
query = &query[1..];
exact = true;
param.inverse = true;
}
if query.is_empty() {
return Box::new(
MatchAllEngine::builder()
.rank_builder(self.rank_builder.clone())
.build(),
);
}
if query.starts_with('^') {
query = &query[1..];
exact = true;
param.prefix = true;
}
if query.ends_with('$') {
query = &query[..(query.len() - 1)];
exact = true;
param.postfix = true;
}
if exact {
Box::new(
ExactEngine::builder(query, param)
.rank_builder(self.rank_builder.clone())
.build(),
)
} else {
Box::new(
FuzzyEngine::builder()
.query(query)
.algorithm(self.fuzzy_algorithm)
.case(case)
.typos(self.typos)
.filter_mode(self.filter_mode)
.last_match(self.last_match)
.rank_builder(self.rank_builder.clone())
.build(),
)
}
}
}
pub struct AndOrEngineFactory {
inner: Box<dyn MatchEngineFactory>,
}
impl AndOrEngineFactory {
pub fn new(factory: impl MatchEngineFactory + 'static) -> Self {
Self {
inner: Box::new(factory),
}
}
fn parse_andor(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
if query.trim().is_empty() {
return self.inner.create_engine_with_case(query, case);
}
let and_engines = RE_OR_WITH_SPACES
.replace_all(&self.mask_escape_space(query), "|")
.split(' ')
.filter_map(|and_term| {
if and_term.is_empty() {
return None;
}
let or_engines = and_term
.split('|')
.filter_map(|term| {
if term.is_empty() {
return None;
}
debug!("Creating Or engine for {term}");
Some(
self.inner
.create_engine_with_case(&self.unmask_escape_space(term), case),
)
})
.collect::<Vec<_>>();
debug!("Building or matcher engine from Ors");
if or_engines.len() == 1 {
return Some(or_engines.into_iter().next().unwrap());
}
Some(Box::new(OrEngine::builder().engines(or_engines).build()) as Box<dyn MatchEngine>)
})
.collect();
debug!("Creating and matcher engine from Ors");
Box::new(AndEngine::builder().engines(and_engines).build())
}
fn mask_escape_space(&self, string: &str) -> String {
string.replace("\\ ", "\0")
}
fn unmask_escape_space(&self, string: &str) -> String {
string.replace('\0', " ")
}
}
impl MatchEngineFactory for AndOrEngineFactory {
fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
self.parse_andor(query, case)
}
}
pub struct RegexEngineFactory {
rank_builder: Arc<RankBuilder>,
}
impl RegexEngineFactory {
pub fn builder() -> Self {
Self {
rank_builder: Default::default(),
}
}
pub fn rank_builder(mut self, rank_builder: Arc<RankBuilder>) -> Self {
self.rank_builder = rank_builder;
self
}
pub fn build(self) -> Self {
self
}
}
impl MatchEngineFactory for RegexEngineFactory {
fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
Box::new(
RegexEngine::builder(query, case)
.rank_builder(self.rank_builder.clone())
.build(),
)
}
}
#[cfg(test)]
mod test {
#[test]
fn test_engine_factory() {
use super::*;
let exact_or_fuzzy = ExactOrFuzzyEngineFactory::builder().build();
let x = exact_or_fuzzy.create_engine("'abc");
assert_eq!(format!("{x}"), "(Exact|(?i)abc)");
let x = exact_or_fuzzy.create_engine("^abc");
assert_eq!(format!("{x}"), "(Exact|(?i)^abc)");
let x = exact_or_fuzzy.create_engine("abc$");
assert_eq!(format!("{x}"), "(Exact|(?i)abc$)");
let x = exact_or_fuzzy.create_engine("^abc$");
assert_eq!(format!("{x}"), "(Exact|(?i)^abc$)");
let x = exact_or_fuzzy.create_engine("!abc");
assert_eq!(format!("{x}"), "(Exact|!(?i)abc)");
let x = exact_or_fuzzy.create_engine("!^abc");
assert_eq!(format!("{x}"), "(Exact|!(?i)^abc)");
let x = exact_or_fuzzy.create_engine("!abc$");
assert_eq!(format!("{x}"), "(Exact|!(?i)abc$)");
let x = exact_or_fuzzy.create_engine("!^abc$");
assert_eq!(format!("{x}"), "(Exact|!(?i)^abc$)");
let regex_factory = RegexEngineFactory::builder();
let and_or_factory = AndOrEngineFactory::new(exact_or_fuzzy);
let x = and_or_factory.create_engine("'abc | def ^gh ij | kl mn");
assert_eq!(
format!("{x}"),
"(And: (Or: (Exact|(?i)abc), (Fuzzy: def)), (Exact|(?i)^gh), (Or: (Fuzzy: ij), (Fuzzy: kl)), (Fuzzy: mn))"
);
let x = regex_factory.create_engine("'abc | def ^gh ij | kl mn");
assert_eq!(format!("{x}"), "(Regex: 'abc | def ^gh ij | kl mn)");
let x = and_or_factory.create_engine("readme .md$ | .markdown$");
assert_eq!(
format!("{x}"),
"(And: (Fuzzy: readme), (Or: (Exact|(?i)\\.md$), (Exact|(?i)\\.markdown$)))"
);
}
}