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}