use std::borrow::Cow;
use crate::error::Error;
use crate::matches::Captures;
use crate::pattern::{Regex, expect};
impl Regex {
#[must_use]
pub fn replace<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str> {
expect(self.try_replacen(text, 1, rep))
}
#[must_use]
pub fn replace_all<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str> {
expect(self.try_replacen(text, 0, rep))
}
#[must_use]
pub fn replacen<'t, R: Replacer>(&self, text: &'t str, limit: usize, rep: R) -> Cow<'t, str> {
expect(self.try_replacen(text, limit, rep))
}
pub fn try_replacen<'t, R: Replacer>(
&self,
text: &'t str,
limit: usize,
mut rep: R,
) -> Result<Cow<'t, str>, Error> {
let found = self.try_find_iter(text)?;
if found.len() == 0 {
return Ok(Cow::Borrowed(text));
}
let ceiling = if limit == 0 { usize::MAX } else { limit };
let literal = rep.no_expansion().map(Cow::into_owned);
let mut out = String::with_capacity(text.len());
let mut cut = 0;
for span in found.take(ceiling) {
out.push_str(&text[cut..span.start()]);
match &literal {
Some(text) => out.push_str(text),
None => {
let caps = self.captures_at(text, span.start(), span.end())?;
rep.replace_append(&Captures::new(self, text, caps), &mut out);
},
}
cut = span.end();
}
out.push_str(&text[cut..]);
Ok(Cow::Owned(out))
}
}
pub trait Replacer {
fn replace_append(&mut self, caps: &Captures<'_, '_>, dst: &mut String);
fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
None
}
}
#[derive(Clone, Copy, Debug)]
pub struct NoExpand<'a>(pub &'a str);
impl Replacer for NoExpand<'_> {
fn replace_append(&mut self, _caps: &Captures<'_, '_>, dst: &mut String) {
dst.push_str(self.0);
}
fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
Some(Cow::Borrowed(self.0))
}
}
fn literal<T: AsRef<str> + ?Sized>(replacement: &T) -> Option<Cow<'_, str>> {
let text = replacement.as_ref();
(!text.contains('$')).then_some(Cow::Borrowed(text))
}
macro_rules! string_replacer {
($($ty:ty),+ $(,)?) => { $(
impl Replacer for $ty {
fn replace_append(&mut self, caps: &Captures<'_, '_>, dst: &mut String) {
expand(caps, self.as_ref(), dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
literal(&**self)
}
}
)+ };
}
string_replacer!(&str, String, &String, Cow<'_, str>);
impl<F, T> Replacer for F
where
F: FnMut(&Captures<'_, '_>) -> T,
T: AsRef<str>,
{
fn replace_append(&mut self, caps: &Captures<'_, '_>, dst: &mut String) {
dst.push_str(self(caps).as_ref());
}
}
pub(crate) fn expand(caps: &Captures<'_, '_>, template: &str, dst: &mut String) {
let bytes = template.as_bytes();
let mut at = 0;
while at < bytes.len() {
let Some(offset) = bytes[at..].iter().position(|b| *b == b'$') else {
dst.push_str(&template[at..]);
return;
};
dst.push_str(&template[at..at + offset]);
at += offset + 1;
if bytes.get(at) == Some(&b'$') {
dst.push('$');
at += 1;
continue;
}
match reference(&template[at..]) {
None => dst.push('$'),
Some((name, taken)) => {
at += taken;
if let Some(found) = lookup(caps, name) {
dst.push_str(found.as_str());
}
},
}
}
}
fn reference(rest: &str) -> Option<(&str, usize)> {
if let Some(body) = rest.strip_prefix('{') {
let close = body.find('}')?;
return Some((&body[..close], close + 2));
}
let taken = rest
.bytes()
.take_while(|b| b.is_ascii_alphanumeric() || *b == b'_')
.count();
(taken > 0).then(|| (&rest[..taken], taken))
}
fn lookup<'t>(caps: &Captures<'_, 't>, name: &str) -> Option<crate::Match<'t>> {
match name.parse::<usize>() {
Ok(index) => caps.get(index),
Err(_) => caps.name(name),
}
}