Skip to main content

fancy_regex/
replacer.rs

1use crate::Captures;
2use std::borrow::Cow;
3
4/// Replacer describes types that can be used to replace matches in a string.
5///
6/// In general, users of this crate shouldn't need to implement this trait,
7/// since implementations are already provided for `&str` along with other
8/// variants of string types and `FnMut(&Captures) -> String` (or any
9/// `FnMut(&Captures) -> T` where `T: AsRef<str>`), which covers most use cases.
10pub trait Replacer {
11    /// Appends text to `dst` to replace the current match.
12    ///
13    /// The current match is represented by `caps`, which is guaranteed to
14    /// have a match at capture group `0`.
15    ///
16    /// For example, a no-op replacement would be
17    /// `dst.push_str(caps.get(0).unwrap().as_str())`.
18    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String);
19
20    /// Return a fixed unchanging replacement string.
21    ///
22    /// When doing replacements, if access to `Captures` is not needed (e.g.,
23    /// the replacement byte string does not need `$` expansion), then it can
24    /// be beneficial to avoid finding sub-captures.
25    ///
26    /// In general, this is called once for every call to `replacen`.
27    fn no_expansion(&mut self) -> Option<Cow<str>> {
28        None
29    }
30
31    /// Return a `Replacer` that borrows and wraps this `Replacer`.
32    ///
33    /// This is useful when you want to take a generic `Replacer` (which might
34    /// not be cloneable) and use it without consuming it, so it can be used
35    /// more than once.
36    ///
37    /// # Example
38    ///
39    /// ```
40    /// use fancy_regex::{Regex, Replacer};
41    ///
42    /// fn replace_all_twice<R: Replacer>(
43    ///     re: Regex,
44    ///     src: &str,
45    ///     mut rep: R,
46    /// ) -> String {
47    ///     let dst = re.replace_all(src, rep.by_ref());
48    ///     let dst = re.replace_all(&dst, rep.by_ref());
49    ///     dst.into_owned()
50    /// }
51    /// ```
52    fn by_ref(&mut self) -> ReplacerRef<Self> {
53        ReplacerRef(self)
54    }
55}
56
57/// By-reference adaptor for a `Replacer`
58///
59/// Returned by [`Replacer::by_ref`](trait.Replacer.html#method.by_ref).
60#[derive(Debug)]
61pub struct ReplacerRef<'a, R: ?Sized>(&'a mut R);
62
63impl<'a, R: Replacer + ?Sized + 'a> Replacer for ReplacerRef<'a, R> {
64    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
65        self.0.replace_append(caps, dst)
66    }
67    fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
68        self.0.no_expansion()
69    }
70}
71
72impl<'a> Replacer for &'a str {
73    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
74        caps.expand(*self, dst);
75    }
76
77    fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
78        no_expansion(self)
79    }
80}
81
82impl<'a> Replacer for &'a String {
83    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
84        self.as_str().replace_append(caps, dst)
85    }
86
87    fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
88        no_expansion(self)
89    }
90}
91
92impl Replacer for String {
93    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
94        self.as_str().replace_append(caps, dst)
95    }
96
97    fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
98        no_expansion(self)
99    }
100}
101
102impl<'a> Replacer for Cow<'a, str> {
103    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
104        self.as_ref().replace_append(caps, dst)
105    }
106
107    fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
108        no_expansion(self)
109    }
110}
111
112impl<'a> Replacer for &'a Cow<'a, str> {
113    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
114        self.as_ref().replace_append(caps, dst)
115    }
116
117    fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
118        no_expansion(self)
119    }
120}
121
122fn no_expansion<T: AsRef<str>>(t: &T) -> Option<Cow<'_, str>> {
123    let s = t.as_ref();
124    if s.contains('$') {
125        None
126    } else {
127        Some(Cow::Borrowed(s))
128    }
129}
130
131impl<F, T> Replacer for F
132where
133    F: FnMut(&Captures<'_>) -> T,
134    T: AsRef<str>,
135{
136    fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
137        dst.push_str((*self)(caps).as_ref());
138    }
139}
140
141/// `NoExpand` indicates literal string replacement.
142///
143/// It can be used with `replace` and `replace_all` to do a literal string
144/// replacement without expanding `$name` to their corresponding capture
145/// groups. This can be both convenient (to avoid escaping `$`, for example)
146/// and performant (since capture groups don't need to be found).
147///
148/// `'t` is the lifetime of the literal text.
149#[derive(Clone, Debug)]
150pub struct NoExpand<'t>(pub &'t str);
151
152impl<'t> Replacer for NoExpand<'t> {
153    fn replace_append(&mut self, _: &Captures<'_>, dst: &mut String) {
154        dst.push_str(self.0);
155    }
156
157    fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
158        Some(Cow::Borrowed(self.0))
159    }
160}