Skip to main content

pubhubs/misc/
fmt_ext.rs

1//! Tools for formatting
2
3use std::fmt;
4use std::fmt::{Display, Formatter};
5
6/// [`Display`] given type `T` by serializing it to json.
7pub struct Json<T: serde::Serialize>(pub T);
8
9impl<T: serde::Serialize> Display for Json<T> {
10    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
11        write!(
12            f,
13            "{}",
14            serde_json::to_string(&self.0).expect("failed to format")
15        )
16    }
17}
18
19/// [`Display`] given bytes, if possible, by printing the ascii characters ` ` through `~` (except
20/// `\`) as is, and escaping `'\r'`, `'\n'`, `'\t'`, and `'\\'`.
21/// If other characters are present everything is printed hexadecimal.
22///
23/// Intended to be used with precision to limit the size of the printed string.
24/// ```
25/// use pubhubs::misc::fmt_ext;
26///
27/// // use precision to limit size to 9 characters
28/// assert_eq!(format!("{:.9}", fmt_ext::Bytes(b"1234567890")).as_str(), "123456...");
29///
30/// // hex is used if any irregular bytes are found
31/// assert_eq!(format!("{}", fmt_ext::Bytes(b"\0 <- zero")).as_str(), "00203c2d207a65726f");
32///
33/// // tabs and newlines are `\`es are escaped
34/// assert_eq!(format!("{}", fmt_ext::Bytes(b"\t\r\n\\")).as_str(), "\\t\\r\\n\\\\");
35///
36/// // otherwise the bytes are left unchanged:
37/// assert_eq!(format!("{}", fmt_ext::Bytes((b' '..=b'~').collect::<Vec<u8>>().as_slice())).as_str(),
38///     " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~");
39/// ```
40pub struct Bytes<'a>(pub &'a [u8]);
41
42impl Display for Bytes<'_> {
43    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
44        let mut truncated = false;
45        let mut bytes = self.0;
46
47        // disregard anything that won't fit anyhow
48        if let Some(precision) = f.precision()
49            && precision < bytes.len()
50        {
51            bytes = &bytes[..precision];
52            truncated = true;
53        }
54
55        let mut buf: Vec<u8> = if bytes
56            .iter()
57            .all(|&c| matches!(c, b' '..=b'~' | b'\n' | b'\r' | b'\t'))
58        {
59            let mut byte_iter = bytes.iter();
60            let mut todo: Option<u8> = None;
61
62            std::iter::from_fn(|| -> Option<u8> {
63                if todo.is_some() {
64                    return std::mem::take(&mut todo);
65                }
66
67                let (ret, next) = match byte_iter.next()? {
68                    b'\n' => (b'\\', Some(b'n')),
69                    b'\t' => (b'\\', Some(b't')),
70                    b'\r' => (b'\\', Some(b'r')),
71                    b'\\' => (b'\\', Some(b'\\')),
72                    oth => (*oth, None),
73                };
74
75                todo = next;
76                Some(ret)
77            })
78            .collect::<Vec<u8>>()
79        } else {
80            base16ct::lower::encode_string(bytes).into_bytes()
81        };
82
83        let mut result: &mut [u8] = buf.as_mut();
84
85        if let Some(precision) = f.precision()
86            && precision < result.len()
87        {
88            result = &mut result[..precision];
89            truncated = true;
90        }
91
92        if truncated && result.len() >= 3 {
93            let result_len = result.len();
94            result[result_len - 3..].copy_from_slice(b"...");
95        }
96
97        write!(f, "{}", std::str::from_utf8(result).unwrap())
98    }
99}