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