1use serde::de::{Deserialize, Deserializer};
2use serde::ser::{Serialize, Serializer};
3use std::error::Error;
4use std::sync::OnceLock;
5
6#[derive(Debug)]
12pub struct Regex {
13 regex_str: String,
14 regex: OnceLock<regex_impl::Regex>,
15 regex_not_empty: OnceLock<Option<regex_impl::Regex>>,
25}
26
27#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct Region {
30 region: regex_impl::Region,
31}
32
33impl Regex {
34 pub fn new(regex_str: String) -> Self {
39 Self {
40 regex_str,
41 regex: OnceLock::new(),
42 regex_not_empty: OnceLock::new(),
43 }
44 }
45
46 pub fn try_compile(regex_str: &str) -> Option<Box<dyn Error + Send + Sync + 'static>> {
48 regex_impl::Regex::new(regex_str).err()
49 }
50
51 pub fn regex_str(&self) -> &str {
53 &self.regex_str
54 }
55
56 pub fn is_match(&self, text: &str) -> bool {
58 self.regex().is_match(text)
59 }
60
61 pub fn search(
75 &self,
76 text: &str,
77 begin: usize,
78 end: usize,
79 region: Option<&mut Region>,
80 allow_empty: bool,
81 ) -> bool {
82 if allow_empty {
83 return self
84 .regex()
85 .search(text, begin, end, region.map(|r| &mut r.region));
86 }
87 match self.regex_not_empty() {
88 Some(regex) => regex.search(text, begin, end, region.map(|r| &mut r.region)),
89 None => false,
90 }
91 }
92
93 fn regex(&self) -> ®ex_impl::Regex {
94 self.regex.get_or_init(|| {
95 regex_impl::Regex::new(&self.regex_str).expect("regex string should be pre-tested")
96 })
97 }
98
99 fn regex_not_empty(&self) -> Option<®ex_impl::Regex> {
100 self.regex_not_empty
101 .get_or_init(|| regex_impl::Regex::new_find_not_empty(&self.regex_str).ok())
102 .as_ref()
103 }
104}
105
106impl Clone for Regex {
107 fn clone(&self) -> Self {
108 Regex {
109 regex_str: self.regex_str.clone(),
110 regex: OnceLock::new(),
111 regex_not_empty: OnceLock::new(),
112 }
113 }
114}
115
116impl PartialEq for Regex {
117 fn eq(&self, other: &Regex) -> bool {
118 self.regex_str == other.regex_str
119 }
120}
121
122impl Eq for Regex {}
123
124impl Serialize for Regex {
125 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126 where
127 S: Serializer,
128 {
129 serializer.serialize_str(&self.regex_str)
130 }
131}
132
133impl<'de> Deserialize<'de> for Regex {
134 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
135 where
136 D: Deserializer<'de>,
137 {
138 let regex_str = String::deserialize(deserializer)?;
139 Ok(Regex::new(regex_str))
140 }
141}
142
143impl Region {
144 pub fn new() -> Self {
145 Self {
146 region: regex_impl::new_region(),
147 }
148 }
149
150 pub fn pos(&self, index: usize) -> Option<(usize, usize)> {
155 self.region.pos(index)
156 }
157}
158
159impl Default for Region {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165#[cfg(feature = "regex-onig")]
166mod regex_impl {
167 pub use onig::Region;
168 use onig::{MatchParam, RegexOptions, SearchOptions, Syntax};
169 use std::error::Error;
170
171 #[derive(Debug)]
172 pub struct Regex {
173 regex: onig::Regex,
174 }
175
176 pub fn new_region() -> Region {
177 Region::with_capacity(8)
178 }
179
180 impl Regex {
181 pub fn new(regex_str: &str) -> Result<Regex, Box<dyn Error + Send + Sync + 'static>> {
182 let result = onig::Regex::with_options(
183 regex_str,
184 RegexOptions::REGEX_OPTION_CAPTURE_GROUP,
185 Syntax::default(),
186 );
187 match result {
188 Ok(regex) => Ok(Regex { regex }),
189 Err(error) => Err(Box::new(error)),
190 }
191 }
192
193 pub fn new_find_not_empty(
194 regex_str: &str,
195 ) -> Result<Regex, Box<dyn Error + Send + Sync + 'static>> {
196 let result = onig::Regex::with_options(
197 regex_str,
198 RegexOptions::REGEX_OPTION_CAPTURE_GROUP
199 | RegexOptions::REGEX_OPTION_FIND_NOT_EMPTY,
200 Syntax::default(),
201 );
202 match result {
203 Ok(regex) => Ok(Regex { regex }),
204 Err(error) => Err(Box::new(error)),
205 }
206 }
207
208 pub fn is_match(&self, text: &str) -> bool {
209 self.regex
210 .match_with_options(text, 0, SearchOptions::SEARCH_OPTION_NONE, None)
211 .is_some()
212 }
213
214 pub fn search(
215 &self,
216 text: &str,
217 begin: usize,
218 end: usize,
219 region: Option<&mut Region>,
220 ) -> bool {
221 let matched = self.regex.search_with_param(
222 text,
223 begin,
224 end,
225 SearchOptions::SEARCH_OPTION_NONE,
226 region,
227 MatchParam::default(),
228 );
229
230 matches!(matched, Ok(Some(_)))
234 }
235 }
236}
237
238#[cfg(all(feature = "regex-fancy", not(feature = "regex-onig")))]
240mod regex_impl {
241 use std::error::Error;
242
243 #[derive(Debug)]
244 pub struct Regex {
245 regex: fancy_regex::Regex,
246 }
247
248 #[derive(Clone, Debug, Eq, PartialEq)]
249 pub struct Region {
250 positions: Vec<Option<(usize, usize)>>,
251 }
252
253 pub fn new_region() -> Region {
254 Region {
255 positions: Vec::with_capacity(8),
256 }
257 }
258
259 impl Regex {
260 pub fn new(regex_str: &str) -> Result<Regex, Box<dyn Error + Send + Sync + 'static>> {
261 let result = fancy_regex::RegexBuilder::new(regex_str)
262 .oniguruma_mode(true)
263 .build();
264 match result {
265 Ok(regex) => Ok(Regex { regex }),
266 Err(error) => Err(Box::new(error)),
267 }
268 }
269
270 pub fn new_find_not_empty(
271 regex_str: &str,
272 ) -> Result<Regex, Box<dyn Error + Send + Sync + 'static>> {
273 let result = fancy_regex::RegexBuilder::new(regex_str)
274 .oniguruma_mode(true)
275 .find_not_empty(true)
276 .build();
277 match result {
278 Ok(regex) => Ok(Regex { regex }),
279 Err(error) => Err(Box::new(error)),
280 }
281 }
282
283 pub fn is_match(&self, text: &str) -> bool {
284 self.regex.is_match(text).unwrap_or(false)
286 }
287
288 pub fn search(
289 &self,
290 text: &str,
291 begin: usize,
292 end: usize,
293 region: Option<&mut Region>,
294 ) -> bool {
295 if let Ok(Some(captures)) = self.regex.captures_from_pos(&text[..end], begin) {
299 if let Some(region) = region {
300 region.init_from_captures(&captures);
301 }
302 true
303 } else {
304 false
305 }
306 }
307 }
308
309 impl Region {
310 fn init_from_captures(&mut self, captures: &fancy_regex::Captures) {
311 self.positions.clear();
312 for i in 0..captures.len() {
313 let pos = captures.get(i).map(|m| (m.start(), m.end()));
314 self.positions.push(pos);
315 }
316 }
317
318 pub fn pos(&self, i: usize) -> Option<(usize, usize)> {
319 if i < self.positions.len() {
320 self.positions[i]
321 } else {
322 None
323 }
324 }
325 }
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn caches_compiled_regex() {
334 let regex = Regex::new(String::from(r"\w+"));
335
336 assert!(regex.regex.get().is_none());
337 assert!(regex.is_match("test"));
338 assert!(regex.regex.get().is_some());
339 }
340
341 #[test]
342 fn serde_as_string() {
343 let pattern: Regex = serde_json::from_str("\"just a string\"").unwrap();
344 assert_eq!(pattern.regex_str(), "just a string");
345 let back_to_str = serde_json::to_string(&pattern).unwrap();
346 assert_eq!(back_to_str, "\"just a string\"");
347 }
348}