1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#![warn(missing_docs)]

//! An extension of [`Regex`] supporting [`Result`] in `replace` methods.
//!
//! The [`replace`], [`replace_all`] and [`replacen`] methods of [`Regex`] accept a function
//! returning the replacement for each matched substring, but they do not allow this
//! function to return a [`Result`]. This crate provides [`try_replace`], [`try_replace_all`],
//! and [`try_replacen`] which fill this gap.
//!
//! # Use
//! Include `use regex_try::RegexTry` to use the additional methods provided by this crate.
//!
//! ## Example
//!
//! ```edition2018
//! use regex::Regex;
//! use regex_try::RegexTry;
//!
//! pub fn main() -> Result<(), std::io::Error> {
//!   let re = Regex::new(r"load! (\w+);").unwrap();
//!   let template = std::fs::read_to_string("Cargo.toml")?;
//!   let result = re.try_replace_all(&template, |captures|
//!     // read_to_string returns a Result, and so it couldn't
//!     // be used with re.replace_all
//!     std::fs::read_to_string(&captures[1])
//!   )?;
//!   println!("{}", result);
//!   Ok(())
//! }
//! ```
//!
//! [`Result`]: https://doc.rust-lang.org/std/result/
//! [`Regex`]: https://docs.rs/regex/*/regex/struct.Regex.html
//! [`replace`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace
//! [`replace_all`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace_all
//! [`replacen`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replacen
//! [`try_replace`]: ./trait.RegexTry.html#tymethod.replace
//! [`try_replace_all`]: ./trait.RegexTry.html#tymethod.replace_all
//! [`try_replacen`]: ./trait.RegexTry.html#tymethod.replacen

use regex::Captures;
use regex::Regex;
use std::borrow::Cow;

/// Defines the additional methods for Regex.
///
/// The replacer is always a function of type `FnMut(&Captures) -> Result<String, E>`.
pub trait RegexTry<F, E> {
  /// See [`Regex::replacen`]
  ///
  /// [`Regex::replacen`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replacen
  fn try_replacen<'t>(&self, text: &'t str, limit: usize, rep: F) -> Result<Cow<'t, str>, E>;

  /// See [`Regex::replace`]
  ///
  /// [`Regex::replace`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace
  fn try_replace<'t>(&self, text: &'t str, rep: F) -> Result<Cow<'t, str>, E>;

  /// See [`Regex::replace_all`]
  ///
  /// [`Regex::replace_all`]: https://docs.rs/regex/*/regex/struct.Regex.html#method.replace_all
  fn try_replace_all<'t>(&self, text: &'t str, rep: F) -> Result<Cow<'t, str>, E>;
}

impl<F, E> RegexTry<F, E> for Regex
where
  F: FnMut(&Captures) -> Result<String, E>,
{
  fn try_replacen<'t>(&self, text: &'t str, limit: usize, mut rep: F) -> Result<Cow<'t, str>, E> {
    let mut it = self.captures_iter(text).enumerate().peekable();
    if it.peek().is_none() {
      return Ok(Cow::Borrowed(text));
    }
    let mut new = String::with_capacity(text.len());
    let mut last_match = 0;
    for (i, cap) in it {
      if limit > 0 && i >= limit {
        break;
      }
      // unwrap on 0 is OK because captures only reports matches
      let m = cap.get(0).unwrap();
      new.push_str(&text[last_match..m.start()]);
      let replacement = rep(&cap)?;
      new.push_str(&replacement);
      last_match = m.end();
    }
    new.push_str(&text[last_match..]);
    Ok(Cow::Owned(new))
  }

  fn try_replace<'t>(&self, text: &'t str, rep: F) -> Result<Cow<'t, str>, E> {
    self.try_replacen(text, 1, rep)
  }

  fn try_replace_all<'t>(&self, text: &'t str, rep: F) -> Result<Cow<'t, str>, E> {
    self.try_replacen(text, 0, rep)
  }
}