use std::fmt::{Debug, Formatter};
use std::io;
use std::mem;
use std::sync::Arc;
pub fn mapped<W: io::Write, F: (Fn(Vec<u8>) -> Vec<u8>) + Sync + Send + 'static>(
w: W,
marker_byte: u8,
f: F,
) -> MappedWrite<W> {
MappedWrite {
inner: w,
marker_byte,
buffer: vec![],
mapping_fn: Arc::new(f),
}
}
pub fn line_mapped<W: io::Write, F: (Fn(Vec<u8>) -> Vec<u8>) + Sync + Send + 'static>(
w: W,
f: F,
) -> MappedWrite<W> {
mapped(w, NEWLINE_ASCII_BYTE, f)
}
pub fn tee<A: io::Write, B: io::Write>(a: A, b: B) -> TeeWrite<A, B> {
TeeWrite {
inner_a: a,
inner_b: b,
}
}
#[derive(Clone)]
pub struct MappedWrite<W: io::Write> {
inner: W,
marker_byte: u8,
buffer: Vec<u8>,
mapping_fn: Arc<dyn (Fn(Vec<u8>) -> Vec<u8>) + Sync + Send>,
}
#[derive(Debug, Clone)]
pub struct TeeWrite<A: io::Write, B: io::Write> {
inner_a: A,
inner_b: B,
}
impl<W: io::Write> MappedWrite<W> {
fn map_and_write_current_buffer(&mut self) -> io::Result<()> {
self.inner
.write_all(&(self.mapping_fn)(mem::take(&mut self.buffer)))
}
}
impl<W: io::Write> io::Write for MappedWrite<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
for byte in buf {
self.buffer.push(*byte);
if *byte == self.marker_byte {
self.map_and_write_current_buffer()?;
}
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<W: io::Write> Drop for MappedWrite<W> {
fn drop(&mut self) {
let _result = self.map_and_write_current_buffer();
}
}
impl<W: io::Write + Debug> Debug for MappedWrite<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MappedWrite")
.field("inner", &self.inner)
.field("marker_byte", &self.marker_byte)
.field("buffer", &self.buffer)
.finish()
}
}
impl<A: io::Write, B: io::Write> io::Write for TeeWrite<A, B> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner_a.write_all(buf)?;
self.inner_b.write_all(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.inner_a.flush()?;
self.inner_b.flush()
}
}
const NEWLINE_ASCII_BYTE: u8 = 0x0Au8;
#[cfg(test)]
mod test {
use super::tee;
use crate::write::line_mapped;
#[test]
fn test_tee_write() {
let mut a = vec![];
let mut b = vec![];
let mut input = "foo bar baz".as_bytes();
std::io::copy(&mut input, &mut tee(&mut a, &mut b)).unwrap();
assert_eq!(a, "foo bar baz".as_bytes());
assert_eq!(a, b);
}
#[test]
fn test_mapped_write() {
let mut output = vec![];
let mut input = "foo\nbar\nbaz".as_bytes();
std::io::copy(
&mut input,
&mut line_mapped(&mut output, |line| line.repeat(2)),
)
.unwrap();
assert_eq!(output, "foo\nfoo\nbar\nbar\nbazbaz".as_bytes());
}
}
pub mod mappers {
pub fn add_prefix<P: Into<Vec<u8>>>(prefix: P) -> impl Fn(Vec<u8>) -> Vec<u8> {
let prefix = prefix.into();
move |mut input| {
let mut result = prefix.clone();
result.append(&mut input);
result
}
}
pub fn map_utf8_lossy<F: Fn(String) -> String>(f: F) -> impl Fn(Vec<u8>) -> Vec<u8> {
move |input| f(String::from_utf8_lossy(&input).to_string()).into_bytes()
}
#[cfg(test)]
mod test {
use super::add_prefix;
use super::map_utf8_lossy;
#[test]
fn test_add_prefix() {
let result = (add_prefix(">> "))(String::from("Hello World!").into_bytes());
assert_eq!(result, String::from(">> Hello World!").into_bytes());
}
#[test]
fn test_map_utf8_lossy() {
let result = (map_utf8_lossy(|input| input.replace("foo", "bar")))(
String::from("foo = foo").into_bytes(),
);
assert_eq!(result, String::from("bar = bar").into_bytes());
}
}
}