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