1pub fn format_hex_row(offset: usize, bytes: &[u8]) -> String {
15 debug_assert!(bytes.len() <= 16, "hex row must be <= 16 bytes");
16 let mut out = String::with_capacity(80);
17 out.push_str(&format!("{:08x}: ", offset));
18 for i in 0..16 {
19 if i > 0 && i % 2 == 0 {
20 out.push(' ');
21 }
22 if i < bytes.len() {
23 out.push_str(&format!("{:02x}", bytes[i]));
24 } else {
25 out.push_str(" ");
26 }
27 }
28 out.push_str(" ");
29 for b in bytes {
30 if (0x20..=0x7E).contains(b) {
31 out.push(*b as char);
32 } else {
33 out.push('.');
34 }
35 }
36 out
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 #[test]
44 fn aligned_input_16_bytes_renders_full_row() {
45 let bytes = b"Hello world. tes";
46 let row = format_hex_row(0, bytes);
47 assert_eq!(
48 row,
49 "00000000: 4865 6c6c 6f20 776f 726c 642e 2074 6573 Hello world. tes"
50 );
51 }
52
53 #[test]
54 fn short_tail_pads_ascii_gutter_columns() {
55 let bytes = b"t.";
56 let row = format_hex_row(0x10, bytes);
57 assert!(row.starts_with("00000010: 742e "));
58 assert!(row.ends_with(" t."));
59 let ascii_start = row.find(" t.").unwrap();
60 let full_row = format_hex_row(0, b"0123456789abcdef");
61 let full_ascii_start = full_row.rfind(" ").unwrap();
62 assert_eq!(ascii_start, full_ascii_start,
63 "short-row ASCII column should align with full-row ASCII column");
64 }
65
66 #[test]
67 fn all_printable_bytes_show_in_gutter() {
68 let bytes = b"abcdefghijklmnop";
69 let row = format_hex_row(0, bytes);
70 assert!(row.ends_with(" abcdefghijklmnop"));
71 }
72
73 #[test]
74 fn all_non_printable_bytes_show_as_dots() {
75 let bytes = &[0x00, 0x01, 0x02, 0x1f, 0x7f, 0x80, 0xff];
76 let row = format_hex_row(0, bytes);
77 assert!(row.ends_with(" ......."));
78 }
79
80 #[test]
81 fn utf8_multibyte_renders_as_dots_in_gutter() {
82 let bytes = "ä".as_bytes();
83 let row = format_hex_row(0, bytes);
84 assert!(row.contains("c3a4"));
85 assert!(row.ends_with(" .."));
86 }
87
88 #[test]
89 fn offset_grows_past_0x10000() {
90 let bytes = b"X";
91 let row = format_hex_row(0x123456, bytes);
92 assert!(row.starts_with("00123456: "));
93 }
94
95 #[test]
96 fn offset_grows_past_8_digits() {
97 let bytes = b"X";
98 let row = format_hex_row(0x1_2345_6789, bytes);
99 assert!(row.starts_with("123456789: "));
100 }
101}