Skip to main content

onepass_base/
fmt.rs

1use core::fmt::{self, Display, Formatter, Write};
2
3use digest::Update;
4
5/// Adapter type that allows a hasher to be treated like a [`impl Write`][Write], assuming
6/// [`fmt::Write`] is imported.
7pub 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
16/// Wrapper type that formats an iterator as newline-separated values. It can be combined with a
17/// [`map(TsvField)`][TsvField] on the iterator to ensure that the fields themselves do not contain
18/// newlines.
19pub 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
33/// Wrapper type that formats a `T` as an escaped tab-separated-values field.
34pub 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
42/// This type wrapps a [`Formatter`] and implements simple escaping of the semantically meaningful
43/// tab-separated-values characters `'\t'`, `'\n'`, `'\r'` and `'\\'`. Writes to instances of this
44/// type are forwarded to the underlying `Formatter` except for these characters, which have their
45/// ANSI C backslash escaped forms emitted instead.
46pub 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}