1use regex::Regex;
2
3use crate::engine::all::MatchAllEngine;
4use crate::engine::andor::{AndEngine, OrEngine};
5use crate::engine::exact::{ExactEngine, ExactMatchingParam};
6use crate::engine::fuzzy::{FuzzyAlgorithm, FuzzyEngine};
7use crate::engine::regexp::RegexEngine;
8use crate::item::RankBuilder;
9use crate::{CaseMatching, MatchEngine, MatchEngineFactory, Typos};
10use std::sync::{Arc, LazyLock};
11
12static RE_OR_WITH_SPACES: LazyLock<Regex> = LazyLock::new(|| Regex::new(r" *\|+ *").unwrap());
13
14pub struct ExactOrFuzzyEngineFactory {
18 exact_mode: bool,
19 fuzzy_algorithm: FuzzyAlgorithm,
20 rank_builder: Arc<RankBuilder>,
21 typos: Typos,
22 filter_mode: bool,
23 last_match: bool,
24}
25
26impl ExactOrFuzzyEngineFactory {
27 #[must_use]
29 pub fn builder() -> Self {
30 Self {
31 exact_mode: false,
32 fuzzy_algorithm: FuzzyAlgorithm::SkimV2,
33 rank_builder: Default::default(),
34 typos: Typos::Disabled,
35 filter_mode: false,
36 last_match: false,
37 }
38 }
39
40 #[must_use]
42 pub fn exact_mode(mut self, exact_mode: bool) -> Self {
43 self.exact_mode = exact_mode;
44 self
45 }
46
47 #[must_use]
49 pub fn fuzzy_algorithm(mut self, fuzzy_algorithm: FuzzyAlgorithm) -> Self {
50 self.fuzzy_algorithm = fuzzy_algorithm;
51 self
52 }
53
54 #[must_use]
56 pub fn rank_builder(mut self, rank_builder: Arc<RankBuilder>) -> Self {
57 self.rank_builder = rank_builder;
58 self
59 }
60
61 #[must_use]
67 pub fn typos(mut self, typos: Typos) -> Self {
68 self.typos = typos;
69 self
70 }
71
72 #[must_use]
74 pub fn filter_mode(mut self, filter_mode: bool) -> Self {
75 self.filter_mode = filter_mode;
76 self
77 }
78
79 #[must_use]
81 pub fn last_match(mut self, last_match: bool) -> Self {
82 self.last_match = last_match;
83 self
84 }
85
86 #[must_use]
88 pub fn build(self) -> Self {
89 self
90 }
91}
92
93impl MatchEngineFactory for ExactOrFuzzyEngineFactory {
94 fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
95 let mut query = query;
104 let mut exact = self.exact_mode;
105 let mut param = ExactMatchingParam::default();
106 param.case = case;
107
108 if query.starts_with('\'') {
109 exact = !exact;
110 query = &query[1..];
111 }
112
113 if query.starts_with('!') {
114 query = &query[1..];
115 exact = true;
116 param.inverse = true;
117 }
118
119 if query.is_empty() {
120 return Box::new(
122 MatchAllEngine::builder()
123 .rank_builder(self.rank_builder.clone())
124 .build(),
125 );
126 }
127
128 if query.starts_with('^') {
129 query = &query[1..];
130 exact = true;
131 param.prefix = true;
132 }
133
134 if query.ends_with('$') {
135 query = &query[..(query.len() - 1)];
136 exact = true;
137 param.postfix = true;
138 }
139
140 if exact {
141 Box::new(
142 ExactEngine::builder(query, param)
143 .rank_builder(self.rank_builder.clone())
144 .build(),
145 )
146 } else {
147 Box::new(
148 FuzzyEngine::builder()
149 .query(query)
150 .algorithm(self.fuzzy_algorithm)
151 .case(case)
152 .typos(self.typos)
153 .filter_mode(self.filter_mode)
154 .last_match(self.last_match)
155 .rank_builder(self.rank_builder.clone())
156 .build(),
157 )
158 }
159 }
160}
161
162pub struct AndOrEngineFactory {
165 inner: Box<dyn MatchEngineFactory>,
166}
167
168impl AndOrEngineFactory {
169 pub fn new(factory: impl MatchEngineFactory + 'static) -> Self {
171 Self {
172 inner: Box::new(factory),
173 }
174 }
175
176 fn parse_andor(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
177 if query.trim().is_empty() {
178 return self.inner.create_engine_with_case(query, case);
179 }
180 let and_engines = RE_OR_WITH_SPACES
181 .replace_all(&Self::mask_escape_space(query), "|")
182 .split(' ')
183 .filter_map(|and_term| {
184 if and_term.is_empty() {
185 return None;
186 }
187 let or_engines = and_term
188 .split('|')
189 .filter_map(|term| {
190 if term.is_empty() {
191 return None;
192 }
193 debug!("Creating Or engine for {term}");
194 Some(
195 self.inner
196 .create_engine_with_case(&Self::unmask_escape_space(term), case),
197 )
198 })
199 .collect::<Vec<_>>();
200 debug!("Building or matcher engine from Ors");
201 if or_engines.len() == 1 {
202 return Some(or_engines.into_iter().next().unwrap());
203 }
204 Some(Box::new(OrEngine::builder().engines(or_engines).build()) as Box<dyn MatchEngine>)
205 })
206 .collect();
207 debug!("Creating and matcher engine from Ors");
208 Box::new(AndEngine::builder().engines(and_engines).build())
209 }
210
211 fn mask_escape_space(string: &str) -> String {
212 string.replace("\\ ", "\0")
213 }
214
215 fn unmask_escape_space(string: &str) -> String {
216 string.replace('\0', " ")
217 }
218}
219
220impl MatchEngineFactory for AndOrEngineFactory {
221 fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
222 self.parse_andor(query, case)
223 }
224}
225
226pub struct RegexEngineFactory {
229 rank_builder: Arc<RankBuilder>,
230}
231
232impl RegexEngineFactory {
233 #[must_use]
235 pub fn builder() -> Self {
236 Self {
237 rank_builder: Default::default(),
238 }
239 }
240
241 #[must_use]
243 pub fn rank_builder(mut self, rank_builder: Arc<RankBuilder>) -> Self {
244 self.rank_builder = rank_builder;
245 self
246 }
247
248 #[must_use]
250 pub fn build(self) -> Self {
251 self
252 }
253}
254
255impl MatchEngineFactory for RegexEngineFactory {
256 fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
257 Box::new(
258 RegexEngine::builder(query, case)
259 .rank_builder(self.rank_builder.clone())
260 .build(),
261 )
262 }
263}
264
265#[cfg(test)]
266mod test {
267 #[test]
268 fn test_engine_factory() {
269 use super::*;
270 let exact_or_fuzzy = ExactOrFuzzyEngineFactory::builder().build();
271 let x = exact_or_fuzzy.create_engine("'abc");
272 assert_eq!(format!("{x}"), "(Exact|(?i)abc)");
273
274 let x = exact_or_fuzzy.create_engine("^abc");
275 assert_eq!(format!("{x}"), "(Exact|(?i)^abc)");
276
277 let x = exact_or_fuzzy.create_engine("abc$");
278 assert_eq!(format!("{x}"), "(Exact|(?i)abc$)");
279
280 let x = exact_or_fuzzy.create_engine("^abc$");
281 assert_eq!(format!("{x}"), "(Exact|(?i)^abc$)");
282
283 let x = exact_or_fuzzy.create_engine("!abc");
284 assert_eq!(format!("{x}"), "(Exact|!(?i)abc)");
285
286 let x = exact_or_fuzzy.create_engine("!^abc");
287 assert_eq!(format!("{x}"), "(Exact|!(?i)^abc)");
288
289 let x = exact_or_fuzzy.create_engine("!abc$");
290 assert_eq!(format!("{x}"), "(Exact|!(?i)abc$)");
291
292 let x = exact_or_fuzzy.create_engine("!^abc$");
293 assert_eq!(format!("{x}"), "(Exact|!(?i)^abc$)");
294
295 let regex_factory = RegexEngineFactory::builder();
296 let and_or_factory = AndOrEngineFactory::new(exact_or_fuzzy);
297
298 let x = and_or_factory.create_engine("'abc | def ^gh ij | kl mn");
299 assert_eq!(
300 format!("{x}"),
301 "(And: (Or: (Exact|(?i)abc), (Fuzzy: def)), (Exact|(?i)^gh), (Or: (Fuzzy: ij), (Fuzzy: kl)), (Fuzzy: mn))"
302 );
303
304 let x = regex_factory.create_engine("'abc | def ^gh ij | kl mn");
305 assert_eq!(format!("{x}"), "(Regex: 'abc | def ^gh ij | kl mn)");
306
307 let x = and_or_factory.create_engine("readme .md$ | .markdown$");
308 assert_eq!(
309 format!("{x}"),
310 "(And: (Fuzzy: readme), (Or: (Exact|(?i)\\.md$), (Exact|(?i)\\.markdown$)))"
311 );
312 }
313}