skimmer/engine/
factory.rs1use crate::engine::all::MatchAllEngine;
2use crate::engine::andor::{AndEngine, OrEngine};
3use crate::engine::exact::{ExactEngine, ExactMatchingParam};
4use crate::engine::fuzzy::{FuzzyAlgorithm, FuzzyEngine};
5use crate::engine::regexp::RegexEngine;
6use crate::item::RankBuilder;
7use crate::{CaseMatching, MatchEngine, MatchEngineFactory};
8use regex::Regex;
9use std::sync::Arc;
10
11lazy_static! {
12 static ref RE_AND: Regex = Regex::new(r"([^ |]+( +\| +[^ |]*)+)|( +)").unwrap();
13 static ref RE_OR: Regex = Regex::new(r" +\| +").unwrap();
14}
15pub struct ExactOrFuzzyEngineFactory {
18 exact_mode: bool,
19 fuzzy_algorithm: FuzzyAlgorithm,
20 rank_builder: Arc<RankBuilder>,
21}
22
23impl ExactOrFuzzyEngineFactory {
24 #[must_use]
25 pub fn builder() -> Self {
26 Self {
27 exact_mode: false,
28 fuzzy_algorithm: FuzzyAlgorithm::SkimV2,
29 rank_builder: Default::default(),
30 }
31 }
32
33 #[must_use]
34 pub fn exact_mode(mut self, exact_mode: bool) -> Self {
35 self.exact_mode = exact_mode;
36 self
37 }
38
39 #[must_use]
40 pub fn fuzzy_algorithm(mut self, fuzzy_algorithm: FuzzyAlgorithm) -> Self {
41 self.fuzzy_algorithm = fuzzy_algorithm;
42 self
43 }
44
45 #[must_use]
46 pub fn rank_builder(mut self, rank_builder: Arc<RankBuilder>) -> Self {
47 self.rank_builder = rank_builder;
48 self
49 }
50
51 #[must_use]
52 pub fn build(self) -> Self {
53 self
54 }
55}
56
57impl MatchEngineFactory for ExactOrFuzzyEngineFactory {
58 #[must_use]
59 fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
60 let mut query = query;
69 let mut exact = false;
70 let mut param = ExactMatchingParam::default();
71 param.case = case;
72
73 if query.starts_with('\'') {
74 if self.exact_mode {
75 return Box::new(
76 FuzzyEngine::builder()
77 .query(&query[1..])
78 .algorithm(self.fuzzy_algorithm)
79 .case(case)
80 .rank_builder(self.rank_builder.clone())
81 .build(),
82 );
83 }
84 exact = true;
85 query = &query[1..];
86 }
87
88 if query.starts_with('!') {
89 query = &query[1..];
90 exact = true;
91 param.inverse = true;
92 }
93
94 if query.is_empty() {
95 return Box::new(
97 MatchAllEngine::builder()
98 .rank_builder(self.rank_builder.clone())
99 .build(),
100 );
101 }
102
103 if query.starts_with('^') {
104 query = &query[1..];
105 exact = true;
106 param.prefix = true;
107 }
108
109 if query.ends_with('$') {
110 query = &query[..(query.len() - 1)];
111 exact = true;
112 param.postfix = true;
113 }
114
115 if self.exact_mode {
116 exact = true;
117 }
118
119 if exact {
120 Box::new(
121 ExactEngine::builder(query, param)
122 .rank_builder(self.rank_builder.clone())
123 .build(),
124 )
125 } else {
126 Box::new(
127 FuzzyEngine::builder()
128 .query(query)
129 .algorithm(self.fuzzy_algorithm)
130 .case(case)
131 .rank_builder(self.rank_builder.clone())
132 .build(),
133 )
134 }
135 }
136}
137
138pub struct AndOrEngineFactory {
140 inner: Box<dyn MatchEngineFactory>,
141}
142
143impl AndOrEngineFactory {
144 #[must_use]
145 pub fn new(factory: impl MatchEngineFactory + 'static) -> Self {
146 Self {
147 inner: Box::new(factory),
148 }
149 }
150
151 #[must_use]
155 fn parse_or(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
156 if query.trim().is_empty() {
157 self.inner.create_engine_with_case(query, case)
158 } else {
159 let engines = RE_OR
160 .split(&self.mask_escape_space(query))
161 .map(|q| self.parse_and(q, case))
162 .collect();
163 Box::new(OrEngine::builder().engines(engines).build())
164 }
165 }
166
167 #[must_use]
168 fn parse_and(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
169 let query_trim = query.trim_matches(|c| c == ' ' || c == '|');
170 let mut engines = vec![];
171 let mut last = 0;
172 for mat in RE_AND.find_iter(query_trim) {
173 let (start, end) = (mat.start(), mat.end());
174 let term = query_trim[last..start].trim_matches(|c| c == ' ' || c == '|');
175 let term = self.unmask_escape_space(term);
176 if !term.is_empty() {
177 engines.push(self.inner.create_engine_with_case(&term, case));
178 }
179
180 if !mat.as_str().trim().is_empty() {
181 engines.push(self.parse_or(mat.as_str().trim(), case));
182 }
183 last = end;
184 }
185
186 let term = query_trim[last..].trim_matches(|c| c == ' ' || c == '|');
187 let term = self.unmask_escape_space(term);
188 if !term.is_empty() {
189 engines.push(self.inner.create_engine_with_case(&term, case));
190 }
191 Box::new(AndEngine::builder().engines(engines).build())
192 }
193
194 #[must_use]
195 fn mask_escape_space(&self, string: &str) -> String {
196 string.replace("\\ ", "\0")
197 }
198
199 #[must_use]
200 fn unmask_escape_space(&self, string: &str) -> String {
201 string.replace('\0', " ")
202 }
203}
204
205impl MatchEngineFactory for AndOrEngineFactory {
206 #[must_use]
207 fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
208 self.parse_or(query, case)
209 }
210}
211
212pub struct RegexEngineFactory {
214 rank_builder: Arc<RankBuilder>,
215}
216
217impl RegexEngineFactory {
218 #[must_use]
219 pub fn builder() -> Self {
220 Self {
221 rank_builder: Arc::default(),
222 }
223 }
224
225 #[must_use]
226 pub fn rank_builder(mut self, rank_builder: Arc<RankBuilder>) -> Self {
227 self.rank_builder = rank_builder;
228 self
229 }
230
231 #[must_use]
232 pub fn build(self) -> Self {
233 self
234 }
235}
236
237impl MatchEngineFactory for RegexEngineFactory {
238 #[must_use]
239 fn create_engine_with_case(&self, query: &str, case: CaseMatching) -> Box<dyn MatchEngine> {
240 Box::new(
241 RegexEngine::builder(query, case)
242 .rank_builder(self.rank_builder.clone())
243 .build(),
244 )
245 }
246}
247
248mod test {
249 #[test]
250 fn test_engine_factory() {
251 use super::*;
252 let exact_or_fuzzy = ExactOrFuzzyEngineFactory::builder().build();
253 let x = exact_or_fuzzy.create_engine("'abc");
254 assert_eq!(format!("{}", x), "(Exact|(?i)abc)");
255
256 let x = exact_or_fuzzy.create_engine("^abc");
257 assert_eq!(format!("{}", x), "(Exact|(?i)^abc)");
258
259 let x = exact_or_fuzzy.create_engine("abc$");
260 assert_eq!(format!("{}", x), "(Exact|(?i)abc$)");
261
262 let x = exact_or_fuzzy.create_engine("^abc$");
263 assert_eq!(format!("{}", x), "(Exact|(?i)^abc$)");
264
265 let x = exact_or_fuzzy.create_engine("!abc");
266 assert_eq!(format!("{}", x), "(Exact|!(?i)abc)");
267
268 let x = exact_or_fuzzy.create_engine("!^abc");
269 assert_eq!(format!("{}", x), "(Exact|!(?i)^abc)");
270
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 regex_factory = RegexEngineFactory::builder();
278 let and_or_factory = AndOrEngineFactory::new(exact_or_fuzzy);
279
280 let x = and_or_factory.create_engine("'abc | def ^gh ij | kl mn");
281 assert_eq!(
282 format!("{}", x),
283 "(Or: (And: (Exact|(?i)abc)), (And: (Fuzzy: def), (Exact|(?i)^gh), (Fuzzy: ij)), (And: (Fuzzy: kl), (Fuzzy: mn)))"
284 );
285
286 let x = regex_factory.create_engine("'abc | def ^gh ij | kl mn");
287 assert_eq!(format!("{}", x), "(Regex: 'abc | def ^gh ij | kl mn)");
288 }
289}