1use core::fmt;
2use nu_ansi_term::{Color, Style};
3
4pub fn simple_hex<T: AsRef<[u8]>>(source: &T) -> String {
7 let mut writer = String::new();
8 hex_write(&mut writer, source, HexConfig::simple(), None).unwrap_or(());
9 writer
10}
11
12pub fn simple_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result
14where
15 T: AsRef<[u8]>,
16 W: fmt::Write,
17{
18 hex_write(writer, source, HexConfig::simple(), None)
19}
20
21pub fn pretty_hex<T: AsRef<[u8]>>(source: &T) -> String {
24 let mut writer = String::new();
25 hex_write(&mut writer, source, HexConfig::default(), Some(true)).unwrap_or(());
26 writer
27}
28
29pub fn pretty_hex_write<T, W>(writer: &mut W, source: &T) -> fmt::Result
32where
33 T: AsRef<[u8]>,
34 W: fmt::Write,
35{
36 hex_write(writer, source, HexConfig::default(), Some(true))
37}
38
39pub fn config_hex<T: AsRef<[u8]>>(source: &T, cfg: HexConfig) -> String {
41 let mut writer = String::new();
42 hex_write(&mut writer, source, cfg, Some(true)).unwrap_or(());
43 writer
44}
45
46#[derive(Clone, Copy, Debug)]
48pub struct HexConfig {
49 pub title: bool,
51 pub ascii: bool,
53 pub width: usize,
55 pub group: usize,
57 pub chunk: usize,
59 pub address_offset: usize,
61 pub skip: Option<usize>,
63 pub length: Option<usize>,
65 pub styles: HexStyles,
67}
68
69impl Default for HexConfig {
72 fn default() -> HexConfig {
73 HexConfig {
74 title: true,
75 ascii: true,
76 width: 16,
77 group: 4,
78 chunk: 1,
79 address_offset: 0,
80 skip: None,
81 length: None,
82 styles: HexStyles::default(),
83 }
84 }
85}
86
87impl HexConfig {
88 pub fn simple() -> Self {
90 HexConfig::default().to_simple()
91 }
92
93 fn delimiter(&self, i: usize) -> &'static str {
94 if i > 0 && self.chunk > 0 && i.is_multiple_of(self.chunk) {
95 if self.group > 0 && i.is_multiple_of(self.group * self.chunk) {
96 " "
97 } else {
98 " "
99 }
100 } else {
101 ""
102 }
103 }
104
105 fn to_simple(self) -> Self {
106 HexConfig {
107 title: false,
108 ascii: false,
109 width: 0,
110 ..self
111 }
112 }
113}
114
115pub fn categorize_byte(byte: &u8, styles: &HexStyles) -> (Style, Option<char>) {
116 let null_char = Some('0');
117 let ascii_printable = None;
118 let ascii_space = Some(' ');
119 let ascii_whitespace = Some('_');
120 let ascii_other = Some('•');
121 let non_ascii = Some('×'); if byte == &0 {
124 (styles.null_char, null_char)
125 } else if byte.is_ascii_graphic() {
126 (styles.printable, ascii_printable)
127 } else if byte.is_ascii_whitespace() {
128 if byte == &32 {
130 (styles.whitespace, ascii_space)
131 } else {
132 (styles.whitespace, ascii_whitespace)
133 }
134 } else if byte.is_ascii() {
135 (styles.ascii_other, ascii_other)
136 } else {
137 (styles.non_ascii, non_ascii)
138 }
139}
140
141#[derive(Clone, Copy, Debug)]
144pub struct HexStyles {
145 pub null_char: Style,
147 pub printable: Style,
149 pub whitespace: Style,
151 pub ascii_other: Style,
153 pub non_ascii: Style,
155}
156
157impl Default for HexStyles {
158 fn default() -> Self {
159 Self {
160 null_char: Style::default().fg(Color::Fixed(242)),
161 printable: Style::default().fg(Color::Cyan).bold(),
162 whitespace: Style::default().fg(Color::Green).bold(),
163 ascii_other: Style::default().fg(Color::Purple).bold(),
164 non_ascii: Style::default().fg(Color::Yellow).bold(),
165 }
166 }
167}
168
169pub fn hex_write<T, W>(
171 writer: &mut W,
172 source: &T,
173 cfg: HexConfig,
174 with_color: Option<bool>,
175) -> fmt::Result
176where
177 T: AsRef<[u8]>,
178 W: fmt::Write,
179{
180 let use_color = with_color.unwrap_or(false);
181
182 if source.as_ref().is_empty() {
183 return Ok(());
184 }
185
186 let amount = cfg.length.unwrap_or_else(|| source.as_ref().len());
187
188 let skip = cfg.skip.unwrap_or(0);
189
190 let address_offset = cfg.address_offset;
191
192 let source_part_vec: Vec<u8> = source
193 .as_ref()
194 .iter()
195 .skip(skip)
196 .take(amount)
197 .copied()
198 .collect();
199
200 if cfg.title {
201 write_title(
202 writer,
203 HexConfig {
204 length: Some(source_part_vec.len()),
205 ..cfg
206 },
207 use_color,
208 )?;
209 }
210
211 let lines = source_part_vec.chunks(if cfg.width > 0 {
212 cfg.width
213 } else {
214 source_part_vec.len()
215 });
216
217 let lines_len = lines.len();
218
219 for (i, row) in lines.enumerate() {
220 if cfg.width > 0 {
221 let style = Style::default().fg(Color::Cyan);
222 if use_color {
223 write!(
224 writer,
225 "{}{:08x}{}: ",
226 style.prefix(),
227 i * cfg.width + skip + address_offset,
228 style.suffix()
229 )?;
230 } else {
231 write!(writer, "{:08x}: ", i * cfg.width + skip + address_offset,)?;
232 }
233 }
234 for (i, x) in row.as_ref().iter().enumerate() {
235 if use_color {
236 let (style, _char) = categorize_byte(x, &cfg.styles);
237 write!(
238 writer,
239 "{}{}{:02x}{}",
240 cfg.delimiter(i),
241 style.prefix(),
242 x,
243 style.suffix()
244 )?;
245 } else {
246 write!(writer, "{}{:02x}", cfg.delimiter(i), x,)?;
247 }
248 }
249 if cfg.ascii {
250 for j in row.len()..cfg.width {
251 write!(writer, "{} ", cfg.delimiter(j))?;
252 }
253 write!(writer, " ")?;
254 for x in row {
255 let (style, a_char) = categorize_byte(x, &cfg.styles);
256 let replacement_char = a_char.unwrap_or(*x as char);
257 if use_color {
258 write!(
259 writer,
260 "{}{}{}",
261 style.prefix(),
262 replacement_char,
263 style.suffix()
264 )?;
265 } else {
266 write!(writer, "{replacement_char}",)?;
267 }
268 }
269 }
270 if i + 1 < lines_len {
271 writeln!(writer)?;
272 }
273 }
274 Ok(())
275}
276
277pub fn write_title<W>(writer: &mut W, cfg: HexConfig, use_color: bool) -> Result<(), fmt::Error>
279where
280 W: fmt::Write,
281{
282 let write = |writer: &mut W, length: fmt::Arguments<'_>| {
283 if use_color {
284 writeln!(
285 writer,
286 "Length: {length} | {} {} {} {} {}",
287 cfg.styles.null_char.paint("null_char"),
288 cfg.styles.printable.paint("printable"),
289 cfg.styles.whitespace.paint("whitespace"),
290 cfg.styles.ascii_other.paint("ascii_other"),
291 cfg.styles.non_ascii.paint("non_ascii"),
292 )
293 } else {
294 writeln!(writer, "Length: {length}")
295 }
296 };
297
298 if let Some(len) = cfg.length {
299 write(writer, format_args!("{len} (0x{len:x}) bytes"))
300 } else {
301 write(writer, format_args!("unknown (stream)"))
302 }
303}
304
305pub struct Hex<'a, T: 'a>(&'a T, HexConfig);
307
308impl<'a, T: 'a + AsRef<[u8]>> fmt::Display for Hex<'a, T> {
309 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311 hex_write(f, self.0, self.1.to_simple(), None)
312 }
313}
314
315impl<'a, T: 'a + AsRef<[u8]>> fmt::Debug for Hex<'a, T> {
316 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
318 hex_write(f, self.0, self.1, None)
319 }
320}
321
322pub trait PrettyHex: Sized {
324 fn hex_dump(&self) -> Hex<'_, Self>;
327
328 fn hex_conf(&self, cfg: HexConfig) -> Hex<'_, Self>;
331}
332
333impl<T> PrettyHex for T
334where
335 T: AsRef<[u8]>,
336{
337 fn hex_dump(&self) -> Hex<'_, Self> {
338 Hex(self, HexConfig::default())
339 }
340 fn hex_conf(&self, cfg: HexConfig) -> Hex<'_, Self> {
341 Hex(self, cfg)
342 }
343}