regex_try/lib.rs
1#![warn(missing_docs)]
2
3//! An extension of [`Regex`] supporting [`Result`] in `replace` methods.
4//!
5//! The [`replace`], [`replace_all`] and [`replacen`] methods of [`Regex`] accept a function
6//! returning the replacement for each matched substring, but they do not allow this
7//! function to return a [`Result`]. This crate provides [`try_replace`], [`try_replace_all`],
8//! and [`try_replacen`] which fill this gap.
9//!
10//! # Use
11//! Include `use regex_try::RegexTry` to use the additional methods provided by this crate.
12//!
13//! ## Example
14//!
15//! ```edition2018
16//! use regex::Regex;
17//! use regex_try::RegexTry;
18//!
19//! pub fn main() -> Result<(), std::io::Error> {
20//! let re = Regex::new(r"load! (\w+);").unwrap();
21//! let template = std::fs::read_to_string("Cargo.toml")?;
22//! let result = re.try_replace_all(&template, |captures|
23//! // read_to_string returns a Result, and so it couldn't
24//! // be used with re.replace_all
25//! std::fs::read_to_string(&captures[1])
26//! )?;
27//! println!("{}", result);
28//! Ok(())
29//! }
30//! ```
31//!
32//! [`Result`]: https://doc.rust-lang.org/std/result/
33//! [`Regex`]: https://docs.rs/regex/*/regex/struct.Regex.html
34//! [`replace`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace
35//! [`replace_all`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace_all
36//! [`replacen`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replacen
37//! [`try_replace`]: ./trait.RegexTry.html#tymethod.replace
38//! [`try_replace_all`]: ./trait.RegexTry.html#tymethod.replace_all
39//! [`try_replacen`]: ./trait.RegexTry.html#tymethod.replacen
40
41use regex::Captures;
42use regex::Regex;
43use std::borrow::Cow;
44
45/// Defines the additional methods for Regex.
46///
47/// The replacer is always a function of type `FnMut(&Captures) -> Result<String, E>`.
48pub trait RegexTry<F, E> {
49 /// See [`Regex::replacen`]
50 ///
51 /// [`Regex::replacen`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replacen
52 fn try_replacen<'t>(&self, text: &'t str, limit: usize, rep: F) -> Result<Cow<'t, str>, E>;
53
54 /// See [`Regex::replace`]
55 ///
56 /// [`Regex::replace`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace
57 fn try_replace<'t>(&self, text: &'t str, rep: F) -> Result<Cow<'t, str>, E>;
58
59 /// See [`Regex::replace_all`]
60 ///
61 /// [`Regex::replace_all`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace_all
62 fn try_replace_all<'t>(&self, text: &'t str, rep: F) -> Result<Cow<'t, str>, E>;
63}
64
65impl<F, E> RegexTry<F, E> for Regex
66where
67 F: FnMut(&Captures) -> Result<String, E>,
68{
69 fn try_replacen<'t>(&self, text: &'t str, limit: usize, mut rep: F) -> Result<Cow<'t, str>, E> {
70 let mut it = self.captures_iter(text).enumerate().peekable();
71 if it.peek().is_none() {
72 return Ok(Cow::Borrowed(text));
73 }
74 let mut new = String::with_capacity(text.len());
75 let mut last_match = 0;
76 for (i, cap) in it {
77 if limit > 0 && i >= limit {
78 break;
79 }
80 // unwrap on 0 is OK because captures only reports matches
81 let m = cap.get(0).unwrap();
82 new.push_str(&text[last_match..m.start()]);
83 let replacement = rep(&cap)?;
84 new.push_str(&replacement);
85 last_match = m.end();
86 }
87 new.push_str(&text[last_match..]);
88 Ok(Cow::Owned(new))
89 }
90
91 fn try_replace<'t>(&self, text: &'t str, rep: F) -> Result<Cow<'t, str>, E> {
92 self.try_replacen(text, 1, rep)
93 }
94
95 fn try_replace_all<'t>(&self, text: &'t str, rep: F) -> Result<Cow<'t, str>, E> {
96 self.try_replacen(text, 0, rep)
97 }
98}