lazy_regex/lib.rs
1/*!
2
3With lazy-regex macros, regular expressions
4
5* are checked at compile time, with clear error messages
6* are wrapped in `once_cell` lazy static initializers so that they're compiled only once
7* can hold flags as suffix: `let case_insensitive_regex = regex!("ab*"i);`
8* are defined in a less verbose way
9
10The [`regex!`] macro returns references to normal instances of [`regex::Regex`] or [`regex::bytes::Regex`] so all the usual features are available.
11
12But most often, you won't even use the `regex!` macro but the other macros which are specialized for testing a match, replacing, or capturing groups in some common situations:
13
14* [Test a match](#test-a-match) with [`regex_is_match!`]
15* [Extract a value](#extract-a-value) with [`regex_find!`]
16* [Capture](#capture) with [`regex_captures!`]
17* [Iter on captures](#iter-on-captures) with [`regex_captures_iter!`]
18* [Replace with captured groups](#replace-with-captured-groups) with [`regex_replace!`] and [`regex_replace_all!`]
19* [Remove part(s) of a string](#remove-part-of-a-string) with [`regex_remove!`] and [`regex_remove_all!`]
20* [Switch over patterns](#switch-over-patterns) with [`regex_switch!`]
21
22They support the `B` flag for the `regex::bytes::Regex` variant.
23
24All macros exist with a `bytes_` prefix for building `bytes::Regex`, so you also have [`bytes_regex!`], [`bytes_regex_is_match!`], [`bytes_regex_find!`], [`bytes_regex_captures!`], [`bytes_regex_replace!`], [`bytes_regex_replace_all!`], and [`bytes_regex_switch!`].
25
26Some structs of the regex crate are reexported to ease dependency managment.
27
28# Build Regexes
29
30Build a simple regex:
31
32```rust
33# use lazy_regex::regex;
34let r = regex!("sa+$");
35assert_eq!(r.is_match("Saa"), false);
36```
37
38Build a regex with flag(s):
39
40```rust
41# use lazy_regex::regex;
42let r = regex!("sa+$"i);
43assert_eq!(r.is_match("Saa"), true);
44```
45You can use a raw literal:
46
47```rust
48# use lazy_regex::regex;
49let r = regex!(r#"^"+$"#);
50assert_eq!(r.is_match("\"\""), true);
51```
52
53Or a raw literal with flag(s):
54```rust
55# use lazy_regex::regex;
56let r = regex!(r#"^\s*("[a-t]*"\s*)+$"#i);
57assert_eq!(r.is_match(r#" "Aristote" "Platon" "#), true);
58```
59
60Build a regex that operates on `&[u8]`:
61```rust
62# use lazy_regex::regex;
63let r = regex!("(byte)?string$"B);
64assert_eq!(r.is_match(b"bytestring"), true);
65```
66
67There's no problem using the multiline definition syntax:
68```rust
69# use lazy_regex::regex;
70let r = regex!(r"(?x)
71 (?P<name>\w+)
72 -
73 (?P<version>[0-9.]+)
74");
75assert_eq!(r.find("This is lazy_regex-2.2!").unwrap().as_str(), "lazy_regex-2.2");
76```
77
78(look at the `regex_captures!` macro to easily extract the groups)
79
80This line doesn't compile because the regex is invalid:
81```compile_fail
82let r = regex!("(unclosed");
83
84```
85Supported regex flags: [`i`, `m`, `s`, `x`, `U`][regex::RegexBuilder], and you may also use `B` to build a bytes regex.
86
87The following regexes are equivalent:
88* `bytes_regex!("^ab+$"i)`
89* `bytes_regex!("(?i)^ab+$")`
90* `regex!("^ab+$"iB)`
91* `regex!("(?i)^ab+$"B)`
92
93They're all case insensitive instances of `regex::bytes::Regex`.
94
95
96# Test a match
97
98```rust
99# use lazy_regex::*;
100let b = regex_is_match!("[ab]+", "car");
101assert_eq!(b, true);
102let b = bytes_regex_is_match!("[ab]+", b"car");
103assert_eq!(b, true);
104```
105
106See [`regex_is_match!`]
107
108
109# Extract a value
110
111```rust
112# use lazy_regex::regex_find;
113let f_word = regex_find!(r"\bf\w+\b", "The fox jumps.");
114assert_eq!(f_word, Some("fox"));
115let f_word = regex_find!(r"\bf\w+\b"B, b"The forest is silent.");
116assert_eq!(f_word, Some(b"forest" as &[u8]));
117```
118
119See [`regex_find!`]
120
121# Capture
122
123```rust
124# use lazy_regex::regex_captures;
125let (_, letter) = regex_captures!("([a-z])[0-9]+"i, "form A42").unwrap();
126assert_eq!(letter, "A");
127
128let (whole, name, version) = regex_captures!(
129 r"(\w+)-([0-9.]+)", // a literal regex
130 "This is lazy_regex-2.0!", // any expression
131).unwrap();
132assert_eq!(whole, "lazy_regex-2.0");
133assert_eq!(name, "lazy_regex");
134assert_eq!(version, "2.0");
135```
136
137There's no limit to the size of the tuple.
138It's checked at compile time to ensure you have the right number of capturing groups.
139
140You receive `""` for optional groups with no value.
141
142See [`regex_captures!`]
143
144# Iter on captures
145
146```rust
147# use lazy_regex::regex_captures_iter;
148let hay = "'Citizen Kane' (1941), 'The Wizard of Oz' (1939), 'M' (1931).";
149let mut movies = vec![];
150let iter = regex_captures_iter!(r"'([^']+)'\s+\(([0-9]{4})\)", hay);
151for (_, [title, year]) in iter.map(|c| c.extract()) {
152 movies.push((title, year.parse::<i64>().unwrap()));
153}
154assert_eq!(movies, vec![
155 ("Citizen Kane", 1941),
156 ("The Wizard of Oz", 1939),
157 ("M", 1931),
158]);
159```
160
161See [`regex_captures_iter!`]
162
163# Replace with captured groups
164
165The [`regex_replace!`] and [`regex_replace_all!`] macros bring once compilation and compilation time checks to the `replace` and `replace_all` functions.
166
167## Replace with a closure
168
169```rust
170# use lazy_regex::regex_replace_all;
171let text = "Foo8 fuu3";
172let text = regex_replace_all!(
173 r"\bf(\w+)(\d)"i,
174 text,
175 |_, name, digit| format!("F<{}>{}", name, digit),
176);
177assert_eq!(text, "F<oo>8 F<uu>3");
178```
179The number of arguments given to the closure is checked at compilation time to match the number of groups in the regular expression.
180
181If it doesn't match you get a clear error message at compilation time.
182
183## Replace with another kind of Replacer
184
185```rust
186# use lazy_regex::regex_replace_all;
187let text = "UwU";
188let output = regex_replace_all!("U", text, "O");
189assert_eq!(&output, "OwO");
190```
191
192# Remove part of a string
193
194[`regex_remove!`] is cleaner than using `regex_replace!` with an empty string.
195
196Contrary to `replace`, lazy-regex removing macros don't allocate a new string if the match is at an end of the input, which makes it especially useful for trimming suffixes or prefixes.
197
198```rust
199# use lazy_regex::regex_remove;
200let text = "lazy-regex-3.5.0";
201let name = regex_remove!(
202 r"-[0-9]+(\.[0-9]+)*$",
203 text,
204);
205assert_eq!(name, "lazy-regex");
206assert!(matches!(name, std::borrow::Cow::Borrowed(_)));
207```
208
209You can also remove all matches of a regex with [`regex_remove_all!`].
210
211```rust
212# use lazy_regex::regex_remove_all;
213assert_eq!(
214 regex_remove_all!(r"\s+", " ab c d e "),
215 "abcde"
216);
217```
218
219Whenever possible, no new string is allocated and a borrowed slice is returned, even when several matches are removed
220(if they're all either at the start or end of the text).
221
222```rust
223# use lazy_regex::regex_remove_all;
224let input = "154681string63731";
225let output = regex_remove_all!(r"\d", input);
226assert_eq!(output, "string");
227assert!(matches!(output, std::borrow::Cow::Borrowed("string")));
228```
229
230# Switch over patterns
231
232Execute the expression bound to the first matching regex, with named captured groups declared as variables:
233
234```rust
235use lazy_regex::regex_switch;
236#[derive(Debug, PartialEq)]
237pub enum ScrollCommand {
238 Top,
239 Bottom,
240 Lines(i32),
241 Pages(i32),
242 JumpTo(String),
243}
244impl std::str::FromStr for ScrollCommand {
245 type Err = &'static str;
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
247 regex_switch!(s,
248 "^scroll-to-top$" => Self::Top,
249 "^scroll-to-bottom$" => Self::Bottom,
250 r"^scroll-lines?\((?<n>[+-]?\d{1,4})\)$" => Self::Lines(n.parse().unwrap()),
251 r"^scroll-pages?\((?<n>[+-]?\d{1,4})\)$" => Self::Pages(n.parse().unwrap()),
252 r"^jump-to\((?<name>\w+)\)$" => Self::JumpTo(name.to_string()),
253 ).ok_or("unknown command")
254 }
255}
256assert_eq!("scroll-lines(42)".parse(), Ok(ScrollCommand::Lines(42)));
257assert_eq!("scroll-lines(XLII)".parse::<ScrollCommand>(), Err("unknown command"));
258```
259
260See [`regex_switch!`]
261
262# Shared lazy static
263
264When a regular expression is used in several functions, you sometimes don't want
265to repeat it but have a shared static instance.
266
267The [`regex!`] macro, while being backed by a lazy static regex, returns a reference.
268
269If you want to have a shared lazy static regex, use the [`lazy_regex!`] macro:
270
271```rust
272# use lazy_regex::*;
273
274pub static GLOBAL_REX: Lazy<Regex> = lazy_regex!("^ab+$"i);
275```
276
277Like for the other macros, the regex is static, checked at compile time, and lazily built at first use.
278
279See [`lazy_regex!`]
280
281*/
282
283mod remove;
284
285pub use {
286 lazy_regex_proc_macros::{
287 lazy_regex,
288 regex,
289 regex_captures,
290 regex_captures_iter,
291 regex_find,
292 regex_if,
293 regex_is_match,
294 regex_replace,
295 regex_replace_all,
296 regex_switch,
297 bytes_lazy_regex,
298 bytes_regex,
299 bytes_regex_captures,
300 bytes_regex_find,
301 bytes_regex_if,
302 bytes_regex_is_match,
303 bytes_regex_replace,
304 bytes_regex_replace_all,
305 bytes_regex_switch,
306 },
307 once_cell::sync::Lazy,
308 remove::{
309 remove_match,
310 remove_all_matches,
311 },
312};
313
314#[cfg(not(feature = "lite"))]
315pub use {
316 regex::{
317 self,
318 Captures, Regex, RegexBuilder,
319 bytes::{
320 Regex as BytesRegex,
321 RegexBuilder as BytesRegexBuilder
322 },
323 },
324 remove::{
325 bytes_remove_match,
326 bytes_remove_all_matches,
327 },
328};
329
330#[cfg(feature = "lite")]
331pub use {
332 regex_lite::{
333 self as regex,
334 Captures, Regex, RegexBuilder,
335 },
336};
337
338/// Remove the first match of a regex from the text, returning a borrowed slice when possible
339///
340/// ```rust
341/// # use lazy_regex::regex_remove;
342/// let name = regex_remove!(
343/// r"-[0-9]+(\.[0-9]+)*$",
344/// "lazy-regex-3.5.2",
345/// );
346/// assert_eq!(name, "lazy-regex");
347/// assert!(matches!(name, std::borrow::Cow::Borrowed(_)));
348/// ```
349#[macro_export]
350macro_rules! regex_remove {
351 ($rex:tt, $text:expr $(,)?) => {{
352 let rex = $crate::regex!($rex);
353 $crate::remove_match(
354 &rex,
355 $text,
356 )
357 }};
358}
359
360#[macro_export]
361#[cfg(not(feature = "lite"))]
362macro_rules! bytes_regex_remove {
363 ($rex:tt, $text:expr $(,)?) => {{
364 let rex = $crate::bytes_regex!($rex);
365 $crate::bytes_remove_match(
366 &rex,
367 $text,
368 )
369 }};
370}
371
372/// Remove all matches of a regex from the text
373///
374/// ```rust
375/// # use lazy_regex::regex_remove_all;
376/// assert_eq!(
377/// regex_remove_all!(r"\s+", " ab c d e "),
378/// "abcde"
379/// );
380/// ```
381///
382/// Whenever possible, no new string is allocated and a borrowed slice is returned, even when several matches are removed
383/// (if they're all either at the start or end of the text).
384///
385/// ```rust
386/// # use lazy_regex::regex_remove_all;
387/// let input = "154681string63731";
388/// let output = regex_remove_all!(r"\d", input);
389/// assert_eq!(output, "string");
390/// assert!(matches!(output, std::borrow::Cow::Borrowed("string")));
391/// ```
392#[macro_export]
393macro_rules! regex_remove_all {
394 ($rex:tt, $text:expr $(,)?) => {{
395 let rex = $crate::regex!($rex);
396 $crate::remove_all_matches(
397 &rex,
398 $text,
399 )
400 }};
401}
402
403#[macro_export]
404macro_rules! bytes_regex_remove_all {
405 ($rex:tt, $text:expr $(,)?) => {{
406 let rex = $crate::bytes_regex!($rex);
407 $crate::bytes_remove_all_matches(
408 &rex,
409 $text,
410 )
411 }};
412}
413