1use core::fmt::{self, Display, Formatter, Write};
2
3use digest::Update;
4
5pub struct DigestWriter<T: Update>(pub T);
8
9impl<T: Update> Write for DigestWriter<T> {
10 fn write_str(&mut self, s: &str) -> fmt::Result {
11 self.0.update(s.as_bytes());
12 Ok(())
13 }
14}
15
16pub struct Lines<I>(pub I);
20
21impl<I> Display for Lines<I>
22where
23 I: Iterator + Clone,
24 I::Item: Display,
25{
26 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
27 self.0
28 .clone()
29 .try_fold((), |(), line| writeln!(f, "{line}"))
30 }
31}
32
33pub struct TsvField<T>(pub T);
35
36impl<T: Display> Display for TsvField<T> {
37 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
38 write!(TsvEscaper(f), "{}", self.0)
39 }
40}
41
42pub struct TsvEscaper<'a, 'b>(&'a mut Formatter<'b>);
47
48impl TsvEscaper<'_, '_> {
49 fn escape(b: u8) -> Option<&'static str> {
50 Some(match b {
51 b'\\' => r#"\\"#,
52 b'\n' => r#"\n"#,
53 b'\r' => r#"\r"#,
54 b'\t' => r#"\t"#,
55 _ => return None,
56 })
57 }
58}
59
60impl Write for TsvEscaper<'_, '_> {
61 fn write_str(&mut self, s: &str) -> fmt::Result {
62 let mut pos = 0;
63 for (i, b) in s.bytes().enumerate() {
64 let Some(escaped) = Self::escape(b) else {
65 continue;
66 };
67 if pos < i {
68 self.0.write_str(&s[pos..i])?;
69 }
70 self.0.write_str(escaped)?;
71 pos = i + 1;
72 }
73 if pos < s.len() {
74 self.0.write_str(&s[pos..])?;
75 }
76 Ok(())
77 }
78
79 fn write_char(&mut self, c: char) -> fmt::Result {
80 match u8::try_from(c).ok().and_then(Self::escape) {
81 None => self.0.write_char(c),
82 Some(s) => self.0.write_str(s),
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn lines_works() {
93 let lines: &[&str] = &["a\nb", "cd", "e", ""];
94 assert_eq!("a\nb\ncd\ne\n\n", &format!("{}", Lines(lines.iter())));
95 assert_eq!(
96 "a\\nb\ncd\ne\n\n",
97 &format!("{}", Lines(lines.iter().map(TsvField)))
98 );
99 }
100
101 #[test]
102 fn tsv_field_works() {
103 assert_eq!("", &format!("{}", TsvField("")));
104 assert_eq!("abcde\\nf", &format!("{}", TsvField("abcde\nf")));
105 }
106}