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