use crate::{utils, Result};
use regex::bytes::Regex;
pub(crate) struct Replacer {
regex: Regex,
replace_with: Vec<u8>,
is_literal: bool,
replacements: usize,
}
impl Replacer {
pub(crate) fn new(
look_for: String,
replace_with: String,
is_literal: bool,
flags: Option<String>,
replacements: Option<usize>,
) -> Result<Self> {
let (look_for, replace_with) = if is_literal {
(regex::escape(&look_for), replace_with.into_bytes())
} else {
(
look_for,
utils::unescape(&replace_with)
.unwrap_or(replace_with)
.into_bytes(),
)
};
let look_for = if let Some(ref flags) = flags {
if flags.contains('w') {
format!("\\b{}\\b", look_for)
} else {
look_for
}
} else {
look_for
};
let mut regex = regex::bytes::RegexBuilder::new(&look_for);
regex.multi_line(true);
if let Some(flags) = flags {
for c in flags.chars() {
#[rustfmt::skip]
match c {
'c' => { regex.case_insensitive(false); },
'i' => { regex.case_insensitive(true); },
'm' => {},
'e' => { regex.multi_line(false); },
's' => {
if !flags.contains('m') {
regex.multi_line(false);
}
regex.dot_matches_new_line(true);
},
'w' => {},
_ => {
eprintln!("Warning: unknown flag '{}'", c);
},
};
}
};
Ok(Self {
regex: regex.build()?,
replace_with,
is_literal,
replacements: replacements.unwrap_or(0),
})
}
pub(crate) fn replace<'a>(&'a self, content: &'a [u8]) -> std::borrow::Cow<'a, [u8]> {
if self.is_literal {
self.regex.replacen(
content,
self.replacements,
regex::bytes::NoExpand(&self.replace_with),
)
} else {
self.regex
.replacen(content, self.replacements, &*self.replace_with)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn replace<'a>(
look_for: impl Into<String>,
replace_with: impl Into<String>,
literal: bool,
flags: Option<&'static str>,
src: &'static str,
target: &'static str,
) {
let replacer = Replacer::new(
look_for.into(),
replace_with.into(),
literal,
flags.map(ToOwned::to_owned),
None,
)
.unwrap();
assert_eq!(
std::str::from_utf8(&replacer.replace(src.as_bytes())),
Ok(target)
);
}
#[test]
fn default_global() {
replace("a", "b", false, None, "aaa", "bbb");
}
#[test]
fn escaped_char_preservation() {
replace("a", "b", false, None, "a\\n", "b\\n");
}
#[test]
fn case_sensitive_default() {
replace("abc", "x", false, None, "abcABC", "xABC");
replace("abc", "x", true, None, "abcABC", "xABC");
}
#[test]
fn sanity_check_literal_replacements() {
replace("((special[]))", "x", true, None, "((special[]))y", "xy");
}
#[test]
fn unescape_regex_replacements() {
replace("test", r"\n", false, None, "testtest", "\n\n");
}
#[test]
fn no_unescape_literal_replacements() {
replace("test", r"\n", true, None, "testtest", r"\n\n");
}
#[test]
fn full_word_replace() {
replace("abc", "def", false, Some("w"), "abcd abc", "abcd def");
}
#[test]
fn full_word_case_insensitive() {
replace("abc", "def", false, Some("iw"), "abcd ABC", "abcd def");
replace("abc", "def", false, Some("wi"), "abcd ABC", "abcd def");
}
}