use core::fmt::{self, Display, Formatter, Write};
use digest::Update;
pub struct DigestWriter<T: Update>(pub T);
impl<T: Update> Write for DigestWriter<T> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.0.update(s.as_bytes());
Ok(())
}
}
pub struct Lines<I>(pub I);
impl<I> Display for Lines<I>
where
I: Iterator + Clone,
I::Item: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.0
.clone()
.try_fold((), |(), line| writeln!(f, "{line}"))
}
}
pub struct TsvField<T>(pub T);
impl<T: Display> Display for TsvField<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(TsvEscaper(f), "{}", self.0)
}
}
pub struct TsvEscaper<'a, 'b>(&'a mut Formatter<'b>);
impl TsvEscaper<'_, '_> {
fn escape(b: u8) -> Option<&'static str> {
Some(match b {
b'\\' => r#"\\"#,
b'\n' => r#"\n"#,
b'\r' => r#"\r"#,
b'\t' => r#"\t"#,
_ => return None,
})
}
}
impl Write for TsvEscaper<'_, '_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut pos = 0;
for (i, b) in s.bytes().enumerate() {
let Some(escaped) = Self::escape(b) else {
continue;
};
if pos < i {
self.0.write_str(&s[pos..i])?;
}
self.0.write_str(escaped)?;
pos = i + 1;
}
if pos < s.len() {
self.0.write_str(&s[pos..])?;
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
match u8::try_from(c).ok().and_then(Self::escape) {
None => self.0.write_char(c),
Some(s) => self.0.write_str(s),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lines_works() {
let lines: &[&str] = &["a\nb", "cd", "e", ""];
assert_eq!("a\nb\ncd\ne\n\n", &format!("{}", Lines(lines.iter())));
assert_eq!(
"a\\nb\ncd\ne\n\n",
&format!("{}", Lines(lines.iter().map(TsvField)))
);
}
#[test]
fn tsv_field_works() {
assert_eq!("", &format!("{}", TsvField("")));
assert_eq!("abcde\\nf", &format!("{}", TsvField("abcde\nf")));
}
}