1use std::io;
2use std::ops::Range;
3
4use termcolor::{Buffer, BufferWriter, Color, ColorChoice, WriteColor};
5
6use super::byte_mapping;
7use super::color::{ColorRange, ColorlessString, Colors, Spec};
8
9pub struct HexView<'a> {
11 address_offset: usize,
12 codepage: &'a [char],
13 data: &'a [u8],
14 replacement_character: char,
15 row_width: usize,
16 colors: Colors,
17 force_color: bool,
18}
19
20macro_rules! color {
21 ($fmt:ident, $color:ident, $str:expr) => {{
22 $fmt.set_color(&$color)?;
23 write!($fmt, "{}", $str)?;
24 $fmt.reset()
25 }};
26}
27
28impl<'a> HexView<'a> {
29 pub fn print(&self) -> io::Result<()> {
31 let cc = if self.force_color || std::io::IsTerminal::is_terminal(&std::io::stdout()) {
32 ColorChoice::Auto
33 } else {
34 ColorChoice::Never
35 };
36 let writer = BufferWriter::stdout(cc);
37 let mut buffer: Buffer = writer.buffer();
38 self.fmt(&mut buffer)?;
39 writer.print(&buffer)?;
40 Ok(())
41 }
42
43 pub fn new(data: &[u8]) -> HexView {
46 HexView {
47 address_offset: 0,
48 codepage: &byte_mapping::CODEPAGE_0850,
49 data: data,
50 replacement_character: '.',
51 row_width: 16,
52 colors: Colors::new(),
53 force_color: false,
54 }
55 }
56
57 pub fn fmt<W: WriteColor>(&self, buffer: &mut W) -> io::Result<()> {
58 let begin_padding = calculate_begin_padding(self.address_offset, self.row_width);
59 let end_padding = calculate_end_padding(begin_padding + self.data.len(), self.row_width);
60 let mut address = self.address_offset - begin_padding;
61 let mut offset = 0;
62 let mut color_range = ColorRange::new(&self.colors);
63 let mut separator = "";
64
65 if self.data.len() + begin_padding + end_padding <= self.row_width {
66 fmt_line(
67 buffer,
68 address,
69 &self.codepage,
70 self.replacement_character,
71 &self.data,
72 &mut color_range,
73 &Padding::new(begin_padding, end_padding),
74 )?;
75 return Ok(());
76 }
77
78 if begin_padding != 0 {
79 let slice = &self.data[offset..offset + self.row_width - begin_padding];
80 fmt_line(
81 buffer,
82 address,
83 &self.codepage,
84 self.replacement_character,
85 &slice,
86 &mut color_range,
87 &Padding::from_left(begin_padding),
88 )?;
89 offset += self.row_width - begin_padding;
90 address += self.row_width;
91 separator = "\n";
92 color_range.update_offset(offset);
93 }
94
95 while offset + (self.row_width - 1) < self.data.len() {
96 let slice = &self.data[offset..offset + self.row_width];
97 write!(buffer, "{}", separator)?;
98 fmt_line(
99 buffer,
100 address,
101 &self.codepage,
102 self.replacement_character,
103 &slice,
104 &mut color_range,
105 &Padding::default(),
106 )?;
107 offset += self.row_width;
108 address += self.row_width;
109 separator = "\n";
110 color_range.update_offset(offset);
111 }
112
113 if end_padding != 0 {
114 let slice = &self.data[offset..];
115 writeln!(buffer, "")?;
116 fmt_line(
117 buffer,
118 address,
119 &self.codepage,
120 self.replacement_character,
121 &slice,
122 &mut color_range,
123 &Padding::from_right(end_padding),
124 )?;
125 }
126 Ok(())
127 }
128}
129
130pub struct HexViewBuilder<'a> {
132 hex_view: HexView<'a>,
133}
134
135impl<'a> HexViewBuilder<'a> {
136 pub fn new(data: &[u8]) -> HexViewBuilder {
138 HexViewBuilder {
139 hex_view: HexView::new(&data),
140 }
141 }
142
143 pub fn address_offset(mut self, offset: usize) -> HexViewBuilder<'a> {
145 self.hex_view.address_offset = offset;
146 self
147 }
148
149 pub fn force_color(mut self) -> Self {
151 self.hex_view.force_color = true;
152 self
153 }
154
155 pub fn codepage<'b: 'a>(mut self, codepage: &'b [char]) -> HexViewBuilder<'a> {
157 self.hex_view.codepage = codepage;
158 self
159 }
160
161 pub fn replacement_character(mut self, ch: char) -> HexViewBuilder<'a> {
166 self.hex_view.replacement_character = ch;
167 self
168 }
169
170 pub fn row_width(mut self, width: usize) -> HexViewBuilder<'a> {
172 self.hex_view.row_width = width;
173 self
174 }
175 pub fn add_colors(mut self, colors: Colors) -> HexViewBuilder<'a> {
177 self.hex_view.colors.extend(colors);
178 self
179 }
180 pub fn add_color(mut self, color: &str, range: Range<usize>) -> HexViewBuilder<'a> {
182 use std::str::FromStr;
183 self.hex_view.colors.push((
184 Spec::new()
185 .set_fg(Some(Color::from_str(color).unwrap()))
186 .clone(),
187 range,
188 ));
189 self
190 }
191 pub fn finish(mut self) -> HexView<'a> {
193 self.hex_view
194 .colors
195 .sort_by(|&(_, ref r1), &(_, ref r2)| r1.start.cmp(&r2.start));
196 self.hex_view
197 }
198}
199
200#[derive(Default)]
201struct Padding {
202 left: usize,
203 right: usize,
204}
205
206impl Padding {
207 fn new(left_padding: usize, right_padding: usize) -> Padding {
208 Padding {
209 left: left_padding,
210 right: right_padding,
211 }
212 }
213
214 fn from_left(left_padding: usize) -> Padding {
215 Padding {
216 left: left_padding,
217 right: 0,
218 }
219 }
220
221 fn from_right(right_padding: usize) -> Padding {
222 Padding {
223 left: 0,
224 right: right_padding,
225 }
226 }
227}
228
229fn fmt_bytes_as_hex<W: WriteColor>(
230 f: &mut W,
231 bytes: &[u8],
232 color_range: &ColorRange,
233 padding: &Padding,
234) -> io::Result<()> {
235 let mut separator = "";
236
237 for _ in 0..padding.left {
238 write!(f, "{} ", separator)?;
239 separator = " ";
240 }
241
242 for (i, byte) in bytes.iter().enumerate() {
243 match color_range.get(i) {
244 Some(rgb) => {
245 write!(f, "{}", separator)?;
246 color!(f, rgb, format!("{:02X}", byte))?;
247 }
248 None => write!(f, "{}{:02X}", separator, byte)?,
249 }
250 separator = " ";
251 }
252
253 for _ in 0..padding.right {
254 write!(f, "{} ", separator)?;
255 separator = " ";
256 }
257
258 Ok(())
259}
260
261fn fmt_bytes_as_char<W: WriteColor>(
262 f: &mut W,
263 cp: &[char],
264 repl_char: char,
265 bytes: &[u8],
266 color_range: &ColorRange,
267 padding: &Padding,
268) -> io::Result<()> {
269 for _ in 0..padding.left {
270 write!(f, " ")?;
271 }
272
273 for (i, &byte) in bytes.iter().enumerate() {
274 let byte = byte_mapping::as_char(byte, cp, repl_char);
275 match color_range.get(i) {
276 Some(rgb) => {
277 color!(f, rgb, format!("{}", byte))?;
278 }
279 _ => write!(f, "{}", byte)?,
280 }
281 }
282
283 for _ in 0..padding.right {
284 write!(f, " ")?;
285 }
286
287 Ok(())
288}
289
290fn fmt_line<W: WriteColor>(
291 f: &mut W,
292 address: usize,
293 cp: &[char],
294 repl_char: char,
295 bytes: &[u8],
296 color_range: &mut ColorRange,
297 padding: &Padding,
298) -> io::Result<()> {
299 write!(f, "{:0width$X}", address, width = 8)?;
300
301 write!(f, " ")?;
302 fmt_bytes_as_hex(f, bytes, &color_range, &padding)?;
303 write!(f, " ")?;
304
305 write!(f, "| ")?;
306 fmt_bytes_as_char(f, cp, repl_char, bytes, &color_range, &padding)?;
307 write!(f, " |")?;
308
309 Ok(())
310}
311
312fn calculate_begin_padding(address_offset: usize, row_width: usize) -> usize {
313 debug_assert!(
314 row_width != 0,
315 "A zero row width is can not be used to calculate the begin padding"
316 );
317 address_offset % row_width
318}
319
320fn calculate_end_padding(data_size: usize, row_width: usize) -> usize {
321 debug_assert!(
322 row_width != 0,
323 "A zero row width is can not be used to calculate the end padding"
324 );
325 (row_width - data_size % row_width) % row_width
326}
327
328impl<'a> std::fmt::Display for HexView<'a> {
329 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
330 if self.row_width == 0 {
331 write!(f, "Invalid HexView::width")?;
332 return Err(std::fmt::Error);
333 }
334 let mut string = ColorlessString(String::new());
335 match self.fmt(&mut string) {
336 Ok(()) => {
337 write!(f, "{}", string.0)
338 }
339 Err(e) => write!(f, "{}", e),
340 }
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use std;
348
349 #[test]
350 fn test_begin_padding() {
351 assert_eq!(super::calculate_begin_padding(0, 16), 0);
353 assert_eq!(super::calculate_begin_padding(16, 16), 0);
354 assert_eq!(super::calculate_begin_padding(54, 16), 6);
355 }
356
357 #[test]
358 fn a_full_line_is_formatted_as_expected() {
359 let data: Vec<u8> = (0x40..0x40 + 0xF + 1).collect();
360
361 let row_view = HexViewBuilder::new(&data).row_width(data.len()).finish();
362
363 let result = format!("{}", row_view);
364
365 assert_eq!(
366 result,
367 "00000000 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F | @ABCDEFGHIJKLMNO |"
368 );
369 }
370
371 #[test]
372 fn an_incomplete_line_is_padded_on_the_right() {
373 let data = ['a' as u8; 10];
374
375 let row_view = HexViewBuilder::new(&data).row_width(16).finish();
376
377 let result = format!("{}", row_view);
378
379 assert_eq!(
380 result,
381 "00000000 61 61 61 61 61 61 61 61 61 61 | aaaaaaaaaa |"
382 );
383 }
384
385 #[test]
386 fn an_unaligned_address_causes_padded_on_the_left() {
387 let data = ['a' as u8; 11];
388
389 let row_view = HexViewBuilder::new(&data)
390 .address_offset(5)
391 .row_width(16)
392 .finish();
393
394 let result = format!("{}", row_view);
395 println!("{}", result);
396
397 assert_eq!(
398 result,
399 "00000000 61 61 61 61 61 61 61 61 61 61 61 | aaaaaaaaaaa |"
400 );
401 }
402
403 #[test]
404 fn an_unaligned_incomplete_line_causes_padding_on_both_sides() {
405 let data = ['a' as u8; 8];
406
407 let row_view = HexViewBuilder::new(&data)
408 .address_offset(5)
409 .row_width(16)
410 .finish();
411
412 let result = format!("{}", row_view);
413 println!("{}", result);
414
415 assert_eq!(
416 result,
417 "00000000 61 61 61 61 61 61 61 61 | aaaaaaaa |"
418 );
419 }
420
421 #[test]
422 fn decreasing_the_row_width_increases_the_total_character_count() {
423 let data: Vec<u8> = (0..64).collect();
424
425 let short_row_view = HexViewBuilder::new(&data).row_width(1).finish();
426 let long_row_view = HexViewBuilder::new(&data).row_width(16).finish();
427
428 let short_row_result = format!("{}", short_row_view);
429 let long_row_result = format!("{}", long_row_view);
430
431 assert!(long_row_result.len() < short_row_result.len());
432 }
433
434 #[test]
435 fn the_address_offset_is_zero_by_default() {
436 let data = [99; 16];
437
438 let row_view = HexViewBuilder::new(&data).row_width(16).finish();
439
440 let result = format!("{}", row_view);
441 let address_offset_str = format!("{:X}", 0);
442
443 assert!(result.contains(&address_offset_str));
444 }
445
446 #[test]
447 fn the_address_offset_is_used_when_given() {
448 let data = [0; 16];
449
450 let address_offset = data.len() * 10;
451 let row_view = HexViewBuilder::new(&data)
452 .row_width(16)
453 .address_offset(address_offset)
454 .finish();
455
456 let result = format!("{}", row_view);
457 let address_offset_str = format!("{:X}", address_offset);
458
459 assert!(result.contains(&address_offset_str));
460 }
461
462 #[test]
463 fn the_address_offset_increases_by_the_row_width_for_each_row() {
464 let data = [0; 16 * 5];
465
466 let address_offset = data.len() * 10;
467 let row_view = HexViewBuilder::new(&data)
468 .row_width(16)
469 .address_offset(address_offset)
470 .finish();
471
472 let result = format!("{}", row_view);
473 let row_2_address_offset_str = format!("{:X}", address_offset + 2 * row_view.row_width);
474 let row_4_address_offset_str = format!("{:X}", address_offset + 4 * row_view.row_width);
475
476 assert!(result.contains(&row_2_address_offset_str));
477 assert!(result.contains(&row_4_address_offset_str));
478 }
479
480 #[test]
481 fn there_is_no_superfluous_whitespace() {
482 let data = [0; 17];
483
484 let one_line_result = format!("{}", HexViewBuilder::new(&data[0..16]).finish());
485 let two_line_result = format!("{}", HexViewBuilder::new(&data[0..17]).finish());
486
487 println!("{}", one_line_result);
488 println!("{}", two_line_result);
489
490 assert_eq!(one_line_result, one_line_result.trim());
491 assert_eq!(two_line_result, two_line_result.trim());
492 }
493
494 #[test]
495 fn the_row_width_is_16_by_default() {
496 let data = [0; 17];
497
498 let one_line_result = format!("{}", HexViewBuilder::new(&data[0..16]).finish());
499 let two_line_result = format!("{}", HexViewBuilder::new(&data[0..17]).finish());
500
501 println!("{}", one_line_result);
502 println!("{}", two_line_result);
503
504 assert_eq!(1, one_line_result.lines().count());
505 assert_eq!(2, two_line_result.lines().count());
506 }
507
508 #[test]
509 fn the_replacement_character_is_dot_by_default() {
510 let data = [0; 1];
511 let empty_cp = [];
512
513 let result = format!(
514 "{}",
515 HexViewBuilder::new(&data).codepage(&empty_cp).finish()
516 );
517
518 println!("{}", result);
519
520 assert!(result.contains('.'));
521 }
522
523 #[test]
524 fn the_replacement_character_can_be_changed() {
525 let data = [0; 1];
526 let empty_cp = [];
527
528 let result = format!(
529 "{}",
530 HexViewBuilder::new(&data)
531 .codepage(&empty_cp)
532 .replacement_character(std::char::REPLACEMENT_CHARACTER)
533 .finish()
534 );
535
536 println!("{}", result);
537
538 assert!(result.contains(std::char::REPLACEMENT_CHARACTER));
539 }
540
541 #[test]
542 fn all_characters_can_be_printed() {
543 let data: Vec<u8> = (0u16..256u16).map(|v| v as u8).collect();
544
545 let dump_view = HexViewBuilder::new(&data)
546 .address_offset(20)
547 .row_width(8)
548 .finish();
549
550 let result = format!("{}", dump_view);
551 println!("{}", result);
552
553 assert!(!result.is_empty());
554 }
555}