use std::borrow::Cow;
use std::fmt;
use std::ops::Range;
use crate::api::{Captures, FindIter, Match, Regex};
pub trait Replacer {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String);
fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
None
}
fn by_ref(&mut self) -> &mut Self
where
Self: Sized,
{
self
}
}
fn literal_template(template: &str) -> Option<Cow<'_, str>> {
(!template.contains('$')).then_some(Cow::Borrowed(template))
}
impl Replacer for &str {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
caps.expand(self, dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
literal_template(self)
}
}
impl Replacer for String {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
caps.expand(self, dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
literal_template(self)
}
}
impl Replacer for &String {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
caps.expand(self, dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
literal_template(self)
}
}
impl Replacer for Cow<'_, str> {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
caps.expand(self, dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, str>> {
literal_template(self)
}
}
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());
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NoExpand<'t>(pub &'t 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))
}
}
pub trait ReplacerBytes {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut Vec<u8>);
fn no_expansion(&mut self) -> Option<Cow<'_, [u8]>> {
None
}
}
fn literal_bytes_template(template: &[u8]) -> Option<Cow<'_, [u8]>> {
(!template.contains(&b'$')).then_some(Cow::Borrowed(template))
}
impl ReplacerBytes for &[u8] {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut Vec<u8>) {
caps.expand_bytes(self, dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, [u8]>> {
literal_bytes_template(self)
}
}
impl ReplacerBytes for Vec<u8> {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut Vec<u8>) {
caps.expand_bytes(self, dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, [u8]>> {
literal_bytes_template(self)
}
}
impl ReplacerBytes for &Vec<u8> {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut Vec<u8>) {
caps.expand_bytes(self, dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, [u8]>> {
literal_bytes_template(self)
}
}
impl ReplacerBytes for Cow<'_, [u8]> {
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut Vec<u8>) {
caps.expand_bytes(self, dst);
}
fn no_expansion(&mut self) -> Option<Cow<'_, [u8]>> {
literal_bytes_template(self)
}
}
impl<F, T> ReplacerBytes for F
where
F: FnMut(&Captures<'_>) -> T,
T: AsRef<[u8]>,
{
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut Vec<u8>) {
dst.extend_from_slice(self(caps).as_ref());
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NoExpandBytes<'t>(pub &'t [u8]);
impl ReplacerBytes for NoExpandBytes<'_> {
fn replace_append(&mut self, _caps: &Captures<'_>, dst: &mut Vec<u8>) {
dst.extend_from_slice(self.0);
}
fn no_expansion(&mut self) -> Option<Cow<'_, [u8]>> {
Some(Cow::Borrowed(self.0))
}
}
#[derive(Debug, PartialEq, Eq)]
enum Piece<'a> {
Literal(&'a [u8]),
Group(&'a str),
}
struct Pieces<'a> {
rest: &'a [u8],
}
fn pieces(template: &[u8]) -> Pieces<'_> {
Pieces { rest: template }
}
impl<'a> Iterator for Pieces<'a> {
type Item = Piece<'a>;
fn next(&mut self) -> Option<Piece<'a>> {
if self.rest.is_empty() {
return None;
}
let Some(dollar) = self.rest.iter().position(|&byte| byte == b'$') else {
let text = self.rest;
self.rest = &[];
return Some(Piece::Literal(text));
};
if dollar > 0 {
let (text, rest) = self.rest.split_at(dollar);
self.rest = rest;
return Some(Piece::Literal(text));
}
if self.rest.get(1) == Some(&b'$') {
self.rest = &self.rest[2..];
return Some(Piece::Literal(b"$"));
}
match reference(self.rest) {
Some((name, len)) => {
self.rest = &self.rest[len..];
Some(Piece::Group(name))
}
None => {
self.rest = &self.rest[1..];
Some(Piece::Literal(b"$"))
}
}
}
}
fn is_reference_byte(byte: u8) -> bool {
byte == b'_' || byte.is_ascii_alphanumeric()
}
fn reference(rest: &[u8]) -> Option<(&str, usize)> {
if rest.get(1) == Some(&b'{') {
let close = rest[2..].iter().position(|&byte| byte == b'}')?;
let name = std::str::from_utf8(&rest[2..2 + close]).ok()?;
Some((name, close + 3))
} else {
let name_len = rest[1..]
.iter()
.take_while(|&&byte| is_reference_byte(byte))
.count();
if name_len == 0 {
return None;
}
let name = std::str::from_utf8(&rest[1..=name_len]).ok()?;
Some((name, name_len + 1))
}
}
fn referenced_group<'t>(caps: &Captures<'t>, name: &str) -> Option<Match<'t>> {
match name.parse::<usize>() {
Ok(index) => caps.get(index),
Err(_) => caps.name(name),
}
}
impl Captures<'_> {
pub fn expand(&self, replacement: &str, dst: &mut String) {
for piece in pieces(replacement.as_bytes()) {
match piece {
Piece::Literal(text) => dst.push_str(
std::str::from_utf8(text).expect("a str template splits at ASCII bytes"),
),
Piece::Group(name) => {
if let Some(group) = referenced_group(self, name) {
dst.push_str(group.as_str());
}
}
}
}
}
pub fn expand_bytes(&self, replacement: &[u8], dst: &mut Vec<u8>) {
for piece in pieces(replacement) {
match piece {
Piece::Literal(text) => dst.extend_from_slice(text),
Piece::Group(name) => {
if let Some(group) = referenced_group(self, name) {
dst.extend_from_slice(group.as_bytes());
}
}
}
}
}
}
fn replacement_limit(limit: usize) -> usize {
if limit == 0 { usize::MAX } else { limit }
}
impl Regex {
pub fn replace<'h, R: Replacer>(&self, text: &'h str, rep: R) -> Cow<'h, str> {
self.replacen(text, 1, rep)
}
pub fn replace_all<'h, R: Replacer>(&self, text: &'h str, rep: R) -> Cow<'h, str> {
self.replacen(text, 0, rep)
}
pub fn replacen<'h, R: Replacer>(
&self,
text: &'h str,
limit: usize,
mut rep: R,
) -> Cow<'h, str> {
if let Some(literal) = rep.no_expansion() {
let mut out: Option<String> = None;
let mut last = 0;
for m in self.find_iter(text).take(replacement_limit(limit)) {
let out = out.get_or_insert_with(|| String::with_capacity(text.len()));
out.push_str(&text[last..m.start()]);
out.push_str(&literal);
last = m.end();
}
return finish(text, out, last);
}
let mut out: Option<String> = None;
let mut last = 0;
for caps in self.captures_iter(text).take(replacement_limit(limit)) {
let whole = caps.get(0).expect("group 0 is the match");
let out = out.get_or_insert_with(|| String::with_capacity(text.len()));
out.push_str(&text[last..whole.start()]);
rep.replace_append(&caps, out);
last = whole.end();
}
finish(text, out, last)
}
pub fn replace_bytes<'h, R: ReplacerBytes>(&self, text: &'h [u8], rep: R) -> Cow<'h, [u8]> {
self.replacen_bytes(text, 1, rep)
}
pub fn replace_all_bytes<'h, R: ReplacerBytes>(&self, text: &'h [u8], rep: R) -> Cow<'h, [u8]> {
self.replacen_bytes(text, 0, rep)
}
pub fn replacen_bytes<'h, R: ReplacerBytes>(
&self,
text: &'h [u8],
limit: usize,
mut rep: R,
) -> Cow<'h, [u8]> {
if let Some(literal) = rep.no_expansion() {
let mut out: Option<Vec<u8>> = None;
let mut last = 0;
for m in self.find_iter_bytes(text).take(replacement_limit(limit)) {
let out = out.get_or_insert_with(|| Vec::with_capacity(text.len()));
out.extend_from_slice(&text[last..m.start()]);
out.extend_from_slice(&literal);
last = m.end();
}
return finish_bytes(text, out, last);
}
let mut out: Option<Vec<u8>> = None;
let mut last = 0;
for caps in self
.captures_iter_bytes(text)
.take(replacement_limit(limit))
{
let whole = caps.get(0).expect("group 0 is the match");
let out = out.get_or_insert_with(|| Vec::with_capacity(text.len()));
out.extend_from_slice(&text[last..whole.start()]);
rep.replace_append(&caps, out);
last = whole.end();
}
finish_bytes(text, out, last)
}
pub fn split<'r, 'h>(&'r self, text: &'h str) -> Split<'r, 'h> {
Split {
ranges: SplitRanges::new(self.find_iter(text), text.len()),
text,
}
}
pub fn splitn<'r, 'h>(&'r self, text: &'h str, limit: usize) -> SplitN<'r, 'h> {
SplitN {
split: self.split(text),
remaining: limit,
}
}
pub fn split_bytes<'r, 'h>(&'r self, text: &'h [u8]) -> SplitBytes<'r, 'h> {
SplitBytes {
ranges: SplitRanges::new(self.find_iter_bytes(text), text.len()),
text,
}
}
pub fn splitn_bytes<'r, 'h>(&'r self, text: &'h [u8], limit: usize) -> SplitNBytes<'r, 'h> {
SplitNBytes {
split: self.split_bytes(text),
remaining: limit,
}
}
}
fn finish<'h>(text: &'h str, out: Option<String>, last: usize) -> Cow<'h, str> {
match out {
None => Cow::Borrowed(text),
Some(mut out) => {
out.push_str(&text[last..]);
Cow::Owned(out)
}
}
}
fn finish_bytes(text: &[u8], out: Option<Vec<u8>>, last: usize) -> Cow<'_, [u8]> {
match out {
None => Cow::Borrowed(text),
Some(mut out) => {
out.extend_from_slice(&text[last..]);
Cow::Owned(out)
}
}
}
struct SplitRanges<'r, 't> {
matches: FindIter<'r, 't>,
last: usize,
len: usize,
done: bool,
}
impl<'r, 't> SplitRanges<'r, 't> {
fn new(matches: FindIter<'r, 't>, len: usize) -> Self {
SplitRanges {
matches,
last: 0,
len,
done: false,
}
}
fn next_range(&mut self) -> Option<Range<usize>> {
if self.done {
return None;
}
match self.matches.next() {
Some(m) => {
let piece = self.last..m.start();
self.last = m.end();
Some(piece)
}
None => {
self.done = true;
Some(self.last..self.len)
}
}
}
fn rest_range(&mut self) -> Option<Range<usize>> {
if self.done {
return None;
}
self.done = true;
Some(self.last..self.len)
}
}
pub struct Split<'r, 'h> {
ranges: SplitRanges<'r, 'h>,
text: &'h str,
}
impl<'h> Iterator for Split<'_, 'h> {
type Item = &'h str;
fn next(&mut self) -> Option<&'h str> {
let text = self.text;
self.ranges.next_range().map(|range| &text[range])
}
}
impl std::iter::FusedIterator for Split<'_, '_> {}
impl fmt::Debug for Split<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Split").finish_non_exhaustive()
}
}
pub struct SplitN<'r, 'h> {
split: Split<'r, 'h>,
remaining: usize,
}
impl<'h> Iterator for SplitN<'_, 'h> {
type Item = &'h str;
fn next(&mut self) -> Option<&'h str> {
match self.remaining {
0 => None,
1 => {
self.remaining = 0;
let text = self.split.text;
self.split.ranges.rest_range().map(|range| &text[range])
}
n => {
self.remaining = n - 1;
self.split.next()
}
}
}
}
impl std::iter::FusedIterator for SplitN<'_, '_> {}
impl fmt::Debug for SplitN<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SplitN").finish_non_exhaustive()
}
}
pub struct SplitBytes<'r, 'h> {
ranges: SplitRanges<'r, 'h>,
text: &'h [u8],
}
impl<'h> Iterator for SplitBytes<'_, 'h> {
type Item = &'h [u8];
fn next(&mut self) -> Option<&'h [u8]> {
let text = self.text;
self.ranges.next_range().map(|range| &text[range])
}
}
impl std::iter::FusedIterator for SplitBytes<'_, '_> {}
impl fmt::Debug for SplitBytes<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SplitBytes").finish_non_exhaustive()
}
}
pub struct SplitNBytes<'r, 'h> {
split: SplitBytes<'r, 'h>,
remaining: usize,
}
impl<'h> Iterator for SplitNBytes<'_, 'h> {
type Item = &'h [u8];
fn next(&mut self) -> Option<&'h [u8]> {
match self.remaining {
0 => None,
1 => {
self.remaining = 0;
let text = self.split.text;
self.split.ranges.rest_range().map(|range| &text[range])
}
n => {
self.remaining = n - 1;
self.split.next()
}
}
}
}
impl std::iter::FusedIterator for SplitNBytes<'_, '_> {}
impl fmt::Debug for SplitNBytes<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SplitNBytes").finish_non_exhaustive()
}
}