pretty_hex/
pretty_hex.rs
1#[cfg(feature = "alloc")]
2use alloc::string::String;
3use core::{default::Default, fmt};
4
5#[cfg(feature = "alloc")]
8pub fn simple_hex<T: AsRef<[u8]>>(source: &T) -> String {
9 let mut writer = String::new();
10 hex_write(&mut writer, source, HexConfig::simple()).unwrap_or(());
11 writer
12}
13
14pub fn simple_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result
16where
17 T: AsRef<[u8]>,
18 W: fmt::Write,
19{
20 hex_write(writer, source, HexConfig::simple())
21}
22
23#[cfg(feature = "alloc")]
26pub fn pretty_hex<T: AsRef<[u8]>>(source: &T) -> String {
27 let mut writer = String::new();
28 hex_write(&mut writer, source, HexConfig::default()).unwrap_or(());
29 writer
30}
31
32pub fn pretty_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result
35where
36 T: AsRef<[u8]>,
37 W: fmt::Write,
38{
39 hex_write(writer, source, HexConfig::default())
40}
41
42#[cfg(feature = "alloc")]
44pub fn config_hex<T: AsRef<[u8]>>(source: &T, cfg: HexConfig) -> String {
45 let mut writer = String::new();
46 hex_write(&mut writer, source, cfg).unwrap_or(());
47 writer
48}
49
50#[derive(Clone, Copy, Debug)]
52pub struct HexConfig {
53 pub title: bool,
55 pub ascii: bool,
57 pub width: usize,
59 pub group: usize,
61 pub chunk: usize,
63 pub max_bytes: usize,
65}
66
67impl Default for HexConfig {
70 fn default() -> HexConfig {
71 HexConfig {
72 title: true,
73 ascii: true,
74 width: 16,
75 group: 4,
76 chunk: 1,
77 max_bytes: usize::MAX,
78 }
79 }
80}
81
82impl HexConfig {
83 pub fn simple() -> Self {
85 HexConfig::default().to_simple()
86 }
87
88 fn delimiter(&self, i: usize) -> &'static str {
89 if i > 0 && self.chunk > 0 && i % self.chunk == 0 {
90 if self.group > 0 && i % (self.group * self.chunk) == 0 {
91 " "
92 } else {
93 " "
94 }
95 } else {
96 ""
97 }
98 }
99
100 fn to_simple(self) -> Self {
101 HexConfig {
102 title: false,
103 ascii: false,
104 width: 0,
105 ..self
106 }
107 }
108}
109
110const NON_ASCII: char = '.';
111
112pub fn hex_write<T, W>(writer: &mut W, source: &T, cfg: HexConfig) -> fmt::Result
114where
115 T: AsRef<[u8]> + ?Sized,
116 W: fmt::Write,
117{
118 let mut source = source.as_ref();
119 if cfg.title {
120 writeln!(writer, "Length: {0} (0x{0:x}) bytes", source.len())?;
121 }
122
123 if source.is_empty() {
124 return Ok(());
125 }
126
127 let omitted = source.len().checked_sub(cfg.max_bytes);
128 if omitted.is_some() {
129 source = &source[..cfg.max_bytes];
130 }
131 let lines = source.chunks(if cfg.width > 0 {
132 cfg.width
133 } else {
134 source.len()
135 });
136 let lines_len = lines.len();
137 for (i, row) in lines.enumerate() {
138 if cfg.width > 0 {
139 write!(writer, "{:04x}: ", i * cfg.width)?;
140 }
141 for (i, x) in row.as_ref().iter().enumerate() {
142 write!(writer, "{}{:02x}", cfg.delimiter(i), x)?;
143 }
144 if cfg.ascii {
145 for j in row.len()..cfg.width {
146 write!(writer, "{} ", cfg.delimiter(j))?;
147 }
148 write!(writer, " ")?;
149 for x in row {
150 if x.is_ascii() && !x.is_ascii_control() {
151 writer.write_char((*x).into())?;
152 } else {
153 writer.write_char(NON_ASCII)?;
154 }
155 }
156 }
157 if i + 1 < lines_len {
158 writeln!(writer)?;
159 }
160 }
161 if let Some(o) = omitted {
162 write!(writer, "\n...{0} (0x{0:x}) bytes not shown...", o)?;
163 }
164 Ok(())
165}
166
167pub struct Hex<'a, T: 'a + ?Sized>(&'a T, HexConfig);
169
170impl<'a, T: 'a + AsRef<[u8]> + ?Sized> fmt::Display for Hex<'a, T> {
171 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173 hex_write(f, self.0, self.1.to_simple())
174 }
175}
176
177impl<'a, T: 'a + AsRef<[u8]> + ?Sized> fmt::Debug for Hex<'a, T> {
178 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180 hex_write(f, self.0, self.1)
181 }
182}
183
184pub trait PrettyHex {
186 fn hex_dump(&self) -> Hex<Self>;
189
190 fn hex_conf(&self, cfg: HexConfig) -> Hex<Self>;
193}
194
195impl<T> PrettyHex for T
196where
197 T: AsRef<[u8]> + ?Sized,
198{
199 fn hex_dump(&self) -> Hex<Self> {
200 Hex(self, HexConfig::default())
201 }
202 fn hex_conf(&self, cfg: HexConfig) -> Hex<Self> {
203 Hex(self, cfg)
204 }
205}