use crate::{impl_display_with_writeable, LengthHint, Writeable};
use core::fmt;
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)] pub struct Replace<A, B, C> {
pub source: A,
pub needle: B,
pub replacement: C,
}
fn get_pi_bytes(needle: &str, matched_bytes: usize) -> usize {
let s = match needle.get(0..matched_bytes) {
Some(s) => s,
None => return 0,
};
for k in s
.char_indices()
.map(|(idx, _)| idx)
.rev()
.filter(|&idx| idx > 0)
{
if let Some(suffix) = s.as_bytes().get(s.len() - k..) {
if s.as_bytes().starts_with(suffix) {
return k;
}
}
}
0
}
struct ReplaceWriter<'a, W: ?Sized, C> {
sink: &'a mut W,
needle: &'a str,
replacement: &'a C,
remaining_needle: &'a str,
}
impl<'a, W, C> ReplaceWriter<'a, W, C>
where
W: fmt::Write + ?Sized,
C: Writeable,
{
fn new(sink: &'a mut W, needle: &'a str, replacement: &'a C) -> Self {
Self {
sink,
needle,
replacement,
remaining_needle: needle,
}
}
fn matched_len(&self) -> usize {
self.needle.len() - self.remaining_needle.len()
}
fn finalize(&mut self) -> fmt::Result {
let matched = self.matched_len();
if matched > 0 {
let slice = self.needle.get(0..matched).ok_or(fmt::Error)?;
self.sink.write_str(slice)?;
self.remaining_needle = self.needle;
}
Ok(())
}
}
impl<'a, W, C> fmt::Write for ReplaceWriter<'a, W, C>
where
W: fmt::Write + ?Sized,
C: Writeable,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
self.write_char(c)?;
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
if self.needle.is_empty() {
return self.sink.write_char(c);
}
let mut matched = self.matched_len();
while matched > 0 && !self.remaining_needle.starts_with(c) {
let old_j = matched;
matched = get_pi_bytes(self.needle, old_j);
let slice = self.needle.get(0..(old_j - matched)).ok_or(fmt::Error)?;
self.sink.write_str(slice)?;
self.remaining_needle = self.needle.get(matched..).ok_or(fmt::Error)?;
}
if self.remaining_needle.starts_with(c) {
self.remaining_needle = self
.remaining_needle
.get(c.len_utf8()..)
.ok_or(fmt::Error)?;
if self.remaining_needle.is_empty() {
self.replacement.write_to(self.sink)?;
self.remaining_needle = self.needle;
}
} else {
self.sink.write_char(c)?;
}
Ok(())
}
}
impl<A, C> Writeable for Replace<A, &str, C>
where
A: Writeable,
C: Writeable,
{
fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
let mut writer = ReplaceWriter::new(sink, self.needle, &self.replacement);
self.source.write_to(&mut writer)?;
writer.finalize()
}
fn writeable_length_hint(&self) -> LengthHint {
let source_hint = self.source.writeable_length_hint();
let needle_len = self.needle.len();
let replacement_hint = self.replacement.writeable_length_hint();
if let Some(r_upper) = replacement_hint.1 {
if replacement_hint.0 == r_upper && needle_len == r_upper {
return source_hint;
}
}
let mut lower = 0;
let mut upper = None;
if replacement_hint.0 >= needle_len {
lower = source_hint.0;
}
if let Some(r_upper) = replacement_hint.1 {
if r_upper <= needle_len {
upper = source_hint.1;
}
}
LengthHint(lower, upper)
}
}
impl_display_with_writeable!(Replace<A, &'a str, C>, #[cfg(feature = "alloc")], where 'a, A: Writeable, C: Writeable);
#[test]
fn test_replace() {
use crate::assert_writeable_eq;
use crate::concat::Concat;
let replace1 = Replace {
source: Concat("Hello", " 10 22 1101 33"),
needle: "10",
replacement: Concat("4", "4"),
};
assert_writeable_eq!(replace1, "Hello 44 22 1441 33");
let replace2 = Replace {
source: "Hello World",
needle: "",
replacement: "X",
};
assert_writeable_eq!(replace2, "Hello World");
let replace3 = Replace {
source: "Hello 10 World 10",
needle: "10",
replacement: "",
};
assert_writeable_eq!(replace3, "Hello World ");
let replace4 = Replace {
source: "Hello World",
needle: "10",
replacement: "X",
};
assert_writeable_eq!(replace4, "Hello World");
let replace5 = Replace {
source: "10 Hello World",
needle: "10",
replacement: "X",
};
assert_writeable_eq!(replace5, "X Hello World");
let replace6 = Replace {
source: "Hello World 10",
needle: "10",
replacement: "X",
};
assert_writeable_eq!(replace6, "Hello World X");
let replace7 = Replace {
source: "ababa",
needle: "aba",
replacement: "X",
};
assert_writeable_eq!(replace7, "Xba");
let replace8 = Replace {
source: "aab",
needle: "aac",
replacement: "X",
};
assert_writeable_eq!(replace8, "aab");
let replace9 = Replace {
source: "🚀 🛸 🚀🚀 🚁",
needle: "🚀",
replacement: "星",
};
assert_writeable_eq!(replace9, "星 🛸 星星 🚁");
let replace10 = Replace {
source: "🚀🚁",
needle: "🚀🛸",
replacement: "星",
};
assert_writeable_eq!(replace10, "🚀🚁");
let replace11 = Replace {
source: "🚀🚀🚁",
needle: "🚀🚀🛸",
replacement: "星",
};
assert_writeable_eq!(replace11, "🚀🚀🚁");
let replace12 = Replace {
source: "🚀🚀🚀🛸",
needle: "🚀🚀🛸",
replacement: "星",
};
assert_writeable_eq!(replace12, "🚀星");
}