1pub(crate) mod colors;
2pub(crate) mod input;
3
4pub use colors::*;
5pub use input::*;
6
7use std::io::{self, BufReader, Read, Write};
8
9use clap::ValueEnum;
10
11pub enum Base {
12 Binary,
13 Octal,
14 Decimal,
15 Hexadecimal,
16}
17
18#[derive(Copy, Clone)]
19pub enum ByteCategory {
20 Null,
21 AsciiPrintable,
22 AsciiWhitespace,
23 AsciiOther,
24 NonAscii,
25}
26
27pub enum IncludeMode {
28 File(String), Stdin,
30 Slice,
31 Off,
32}
33
34#[derive(Copy, Clone, Debug, Default, ValueEnum)]
35#[non_exhaustive]
36pub enum CharacterTable {
37 #[default]
41 Default,
42
43 Ascii,
45
46 #[value(name = "codepage-1047")]
48 CP1047,
49
50 #[value(name = "codepage-437")]
52 CP437,
53
54 Braille,
56}
57
58#[derive(Copy, Clone, Debug, Default, ValueEnum)]
59#[non_exhaustive]
60pub enum ColorScheme {
61 #[default]
65 Default,
66
67 Gradient,
71}
72
73#[derive(Copy, Clone, Debug, Default, ValueEnum)]
74pub enum Endianness {
75 Little,
77
78 #[default]
80 Big,
81}
82
83#[derive(PartialEq)]
84enum Squeezer {
85 Print,
86 Delete,
87 Ignore,
88 Disabled,
89}
90
91#[derive(Copy, Clone)]
92struct Byte(u8);
93
94impl Byte {
95 fn category(self) -> ByteCategory {
96 if self.0 == 0x00 {
97 ByteCategory::Null
98 } else if self.0.is_ascii_graphic() {
99 ByteCategory::AsciiPrintable
100 } else if self.0.is_ascii_whitespace() {
101 ByteCategory::AsciiWhitespace
102 } else if self.0.is_ascii() {
103 ByteCategory::AsciiOther
104 } else {
105 ByteCategory::NonAscii
106 }
107 }
108
109 fn color(self, color_scheme: ColorScheme) -> &'static [u8] {
110 use crate::ByteCategory::*;
111 match color_scheme {
112 ColorScheme::Default => match self.category() {
113 Null => COLOR_NULL.as_bytes(),
114 AsciiPrintable => COLOR_ASCII_PRINTABLE.as_bytes(),
115 AsciiWhitespace => COLOR_ASCII_WHITESPACE.as_bytes(),
116 AsciiOther => COLOR_ASCII_OTHER.as_bytes(),
117 NonAscii => COLOR_NONASCII.as_bytes(),
118 },
119 ColorScheme::Gradient => match self.category() {
120 Null => COLOR_NULL_RGB,
121 AsciiWhitespace if self.0 == b' ' => &COLOR_GRADIENT_ASCII_PRINTABLE[0],
122 AsciiPrintable => &COLOR_GRADIENT_ASCII_PRINTABLE[(self.0 - b' ') as usize],
123 AsciiWhitespace | AsciiOther => {
124 if self.0 == 0x7f {
125 COLOR_DEL
126 } else {
127 &COLOR_GRADIENT_ASCII_NONPRINTABLE[self.0 as usize - 1]
128 }
129 }
130 NonAscii => &COLOR_GRADIENT_NONASCII[(self.0 - 128) as usize],
131 },
132 }
133 }
134
135 fn as_char(self, character_table: CharacterTable) -> char {
136 use crate::ByteCategory::*;
137 match character_table {
138 CharacterTable::Default => match self.category() {
139 Null => '⋄',
140 AsciiPrintable => self.0 as char,
141 AsciiWhitespace if self.0 == 0x20 => ' ',
142 AsciiWhitespace => '_',
143 AsciiOther => '•',
144 NonAscii => '×',
145 },
146 CharacterTable::Ascii => match self.category() {
147 Null => '.',
148 AsciiPrintable => self.0 as char,
149 AsciiWhitespace if self.0 == 0x20 => ' ',
150 AsciiWhitespace => '.',
151 AsciiOther => '.',
152 NonAscii => '.',
153 },
154 CharacterTable::CP1047 => CP1047[self.0 as usize],
155 CharacterTable::CP437 => CP437[self.0 as usize],
156 CharacterTable::Braille => match self.category() {
157 Null => '⋄',
159 AsciiPrintable => self.0 as char,
160 AsciiWhitespace if self.0 == b' ' => ' ',
161 AsciiWhitespace if self.0 == b'\t' => '→',
163 AsciiWhitespace if self.0 == b'\n' => '↵',
164 AsciiWhitespace if self.0 == b'\r' => '←',
165 AsciiWhitespace | AsciiOther | NonAscii => {
166 fn to_braille_bits(byte: u8) -> u8 {
177 let mut out = 0;
178 for (from, to) in [0, 3, 1, 4, 2, 5, 6, 7].into_iter().enumerate() {
179 out |= (byte >> from & 1) << to;
180 }
181 out
182 }
183
184 char::from_u32(0x2800 + to_braille_bits(self.0) as u32).unwrap()
185 }
186 },
187 }
188 }
189}
190
191struct BorderElements {
192 left_corner: char,
193 horizontal_line: char,
194 column_separator: char,
195 right_corner: char,
196}
197
198#[derive(Clone, Copy, Debug, Default, ValueEnum)]
199pub enum BorderStyle {
200 #[default]
202 Unicode,
203
204 Ascii,
206
207 None,
209}
210
211impl BorderStyle {
212 fn header_elems(&self) -> Option<BorderElements> {
213 match self {
214 BorderStyle::Unicode => Some(BorderElements {
215 left_corner: '┌',
216 horizontal_line: '─',
217 column_separator: '┬',
218 right_corner: '┐',
219 }),
220 BorderStyle::Ascii => Some(BorderElements {
221 left_corner: '+',
222 horizontal_line: '-',
223 column_separator: '+',
224 right_corner: '+',
225 }),
226 BorderStyle::None => None,
227 }
228 }
229
230 fn footer_elems(&self) -> Option<BorderElements> {
231 match self {
232 BorderStyle::Unicode => Some(BorderElements {
233 left_corner: '└',
234 horizontal_line: '─',
235 column_separator: '┴',
236 right_corner: '┘',
237 }),
238 BorderStyle::Ascii => Some(BorderElements {
239 left_corner: '+',
240 horizontal_line: '-',
241 column_separator: '+',
242 right_corner: '+',
243 }),
244 BorderStyle::None => None,
245 }
246 }
247
248 fn outer_sep(&self) -> char {
249 match self {
250 BorderStyle::Unicode => '│',
251 BorderStyle::Ascii => '|',
252 BorderStyle::None => ' ',
253 }
254 }
255
256 fn inner_sep(&self) -> char {
257 match self {
258 BorderStyle::Unicode => '┊',
259 BorderStyle::Ascii => '|',
260 BorderStyle::None => ' ',
261 }
262 }
263}
264
265pub struct PrinterBuilder<'a, Writer: Write> {
266 writer: &'a mut Writer,
267 show_color: bool,
268 show_char_panel: bool,
269 show_position_panel: bool,
270 border_style: BorderStyle,
271 use_squeeze: bool,
272 panels: u64,
273 group_size: u8,
274 base: Base,
275 endianness: Endianness,
276 character_table: CharacterTable,
277 include_mode: IncludeMode,
278 color_scheme: ColorScheme,
279}
280
281impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
282 pub fn new(writer: &'a mut Writer) -> Self {
283 PrinterBuilder {
284 writer,
285 show_color: true,
286 show_char_panel: true,
287 show_position_panel: true,
288 border_style: BorderStyle::Unicode,
289 use_squeeze: true,
290 panels: 2,
291 group_size: 1,
292 base: Base::Hexadecimal,
293 endianness: Endianness::Big,
294 character_table: CharacterTable::Default,
295 include_mode: IncludeMode::Off,
296 color_scheme: ColorScheme::Default,
297 }
298 }
299
300 pub fn show_color(mut self, show_color: bool) -> Self {
301 self.show_color = show_color;
302 self
303 }
304
305 pub fn show_char_panel(mut self, show_char_panel: bool) -> Self {
306 self.show_char_panel = show_char_panel;
307 self
308 }
309
310 pub fn show_position_panel(mut self, show_position_panel: bool) -> Self {
311 self.show_position_panel = show_position_panel;
312 self
313 }
314
315 pub fn with_border_style(mut self, border_style: BorderStyle) -> Self {
316 self.border_style = border_style;
317 self
318 }
319
320 pub fn enable_squeezing(mut self, enable: bool) -> Self {
321 self.use_squeeze = enable;
322 self
323 }
324
325 pub fn num_panels(mut self, num: u64) -> Self {
326 self.panels = num;
327 self
328 }
329
330 pub fn group_size(mut self, num: u8) -> Self {
331 self.group_size = num;
332 self
333 }
334
335 pub fn with_base(mut self, base: Base) -> Self {
336 self.base = base;
337 self
338 }
339
340 pub fn endianness(mut self, endianness: Endianness) -> Self {
341 self.endianness = endianness;
342 self
343 }
344
345 pub fn character_table(mut self, character_table: CharacterTable) -> Self {
346 self.character_table = character_table;
347 self
348 }
349
350 pub fn include_mode(mut self, include: IncludeMode) -> Self {
351 self.include_mode = include;
352 self
353 }
354
355 pub fn color_scheme(mut self, color_scheme: ColorScheme) -> Self {
356 self.color_scheme = color_scheme;
357 self
358 }
359
360 pub fn build(self) -> Printer<'a, Writer> {
361 Printer {
362 idx: 0,
363 line_buf: vec![0x0; 8 * self.panels as usize],
364 writer: self.writer,
365 show_char_panel: self.show_char_panel,
366 show_position_panel: self.show_position_panel,
367 show_color: self.show_color,
368 curr_color: None,
369 color_scheme: self.color_scheme,
370 border_style: self.border_style,
371 byte_hex_panel: (0u8..=u8::MAX)
372 .map(|i| match self.base {
373 Base::Binary => format!("{i:08b}"),
374 Base::Octal => format!("{i:03o}"),
375 Base::Decimal => format!("{i:03}"),
376 Base::Hexadecimal => format!("{i:02x}"),
377 })
378 .collect(),
379 byte_char_panel: (0u8..=u8::MAX)
380 .map(|i| format!("{}", Byte(i).as_char(self.character_table)))
381 .collect(),
382 byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{i:02x}")).collect(),
383 squeezer: if self.use_squeeze {
384 Squeezer::Ignore
385 } else {
386 Squeezer::Disabled
387 },
388 display_offset: 0,
389 panels: self.panels,
390 squeeze_byte: 0x00,
391 group_size: self.group_size,
392 base_digits: match self.base {
393 Base::Binary => 8,
394 Base::Octal => 3,
395 Base::Decimal => 3,
396 Base::Hexadecimal => 2,
397 },
398 endianness: self.endianness,
399 include_mode: self.include_mode,
400 }
401 }
402}
403
404pub struct Printer<'a, Writer: Write> {
405 idx: u64,
406 line_buf: Vec<u8>,
408 writer: &'a mut Writer,
409 show_char_panel: bool,
410 show_position_panel: bool,
411 show_color: bool,
412 curr_color: Option<&'static [u8]>,
413 color_scheme: ColorScheme,
414 border_style: BorderStyle,
415 byte_hex_panel: Vec<String>,
416 byte_char_panel: Vec<String>,
417 byte_hex_panel_g: Vec<String>,
419 squeezer: Squeezer,
420 display_offset: u64,
421 panels: u64,
423 squeeze_byte: usize,
424 group_size: u8,
426 base_digits: u8,
428 endianness: Endianness,
430 include_mode: IncludeMode,
432}
433
434impl<'a, Writer: Write> Printer<'a, Writer> {
435 pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {
436 self.display_offset = display_offset;
437 self
438 }
439
440 fn panel_sz(&self) -> usize {
441 let group_sz = self.base_digits as usize * self.group_size as usize + 1;
443 let group_per_panel = 8 / self.group_size as usize;
444 1 + group_sz * group_per_panel
446 }
447
448 fn write_border(&mut self, border_elements: BorderElements) -> io::Result<()> {
449 let h = border_elements.horizontal_line;
450 let c = border_elements.column_separator;
451 let l = border_elements.left_corner;
452 let r = border_elements.right_corner;
453 let h8 = h.to_string().repeat(8);
454 let h_repeat = h.to_string().repeat(self.panel_sz());
455
456 if self.show_position_panel {
457 write!(self.writer, "{l}{h8}{c}")?;
458 } else {
459 write!(self.writer, "{l}")?;
460 }
461
462 for _ in 0..self.panels - 1 {
463 write!(self.writer, "{h_repeat}{c}")?;
464 }
465 if self.show_char_panel {
466 write!(self.writer, "{h_repeat}{c}")?;
467 } else {
468 write!(self.writer, "{h_repeat}")?;
469 }
470
471 if self.show_char_panel {
472 for _ in 0..self.panels - 1 {
473 write!(self.writer, "{h8}{c}")?;
474 }
475 writeln!(self.writer, "{h8}{r}")?;
476 } else {
477 writeln!(self.writer, "{r}")?;
478 }
479
480 Ok(())
481 }
482
483 pub fn print_header(&mut self) -> io::Result<()> {
484 if let Some(e) = self.border_style.header_elems() {
485 self.write_border(e)?
486 }
487 Ok(())
488 }
489
490 pub fn print_footer(&mut self) -> io::Result<()> {
491 if let Some(e) = self.border_style.footer_elems() {
492 self.write_border(e)?
493 }
494 Ok(())
495 }
496
497 fn print_position_panel(&mut self) -> io::Result<()> {
498 self.writer.write_all(
499 self.border_style
500 .outer_sep()
501 .encode_utf8(&mut [0; 4])
502 .as_bytes(),
503 )?;
504 if self.show_color {
505 self.writer.write_all(COLOR_OFFSET.as_bytes())?;
506 }
507 if self.show_position_panel {
508 match self.squeezer {
509 Squeezer::Print => {
510 self.writer.write_all(b"*")?;
511 if self.show_color {
512 self.writer.write_all(COLOR_RESET.as_bytes())?;
513 }
514 self.writer.write_all(b" ")?;
515 }
516 Squeezer::Ignore | Squeezer::Disabled | Squeezer::Delete => {
517 let byte_index: [u8; 8] = (self.idx + self.display_offset).to_be_bytes();
518 let mut i = 0;
519 while byte_index[i] == 0x0 && i < 4 {
520 i += 1;
521 }
522 for &byte in byte_index.iter().skip(i) {
523 self.writer
524 .write_all(self.byte_hex_panel_g[byte as usize].as_bytes())?;
525 }
526 if self.show_color {
527 self.writer.write_all(COLOR_RESET.as_bytes())?;
528 }
529 }
530 }
531 self.writer.write_all(
532 self.border_style
533 .outer_sep()
534 .encode_utf8(&mut [0; 4])
535 .as_bytes(),
536 )?;
537 }
538 Ok(())
539 }
540
541 fn print_char(&mut self, i: u64) -> io::Result<()> {
542 match self.squeezer {
543 Squeezer::Print | Squeezer::Delete => self.writer.write_all(b" ")?,
544 Squeezer::Ignore | Squeezer::Disabled => {
545 if let Some(&b) = self.line_buf.get(i as usize) {
546 if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme))
547 {
548 self.writer.write_all(Byte(b).color(self.color_scheme))?;
549 self.curr_color = Some(Byte(b).color(self.color_scheme));
550 }
551 self.writer
552 .write_all(self.byte_char_panel[b as usize].as_bytes())?;
553 } else {
554 self.squeezer = Squeezer::Print;
555 }
556 }
557 }
558 if i == 8 * self.panels - 1 {
559 if self.show_color {
560 self.writer.write_all(COLOR_RESET.as_bytes())?;
561 self.curr_color = None;
562 }
563 self.writer.write_all(
564 self.border_style
565 .outer_sep()
566 .encode_utf8(&mut [0; 4])
567 .as_bytes(),
568 )?;
569 } else if i % 8 == 7 {
570 if self.show_color {
571 self.writer.write_all(COLOR_RESET.as_bytes())?;
572 self.curr_color = None;
573 }
574 self.writer.write_all(
575 self.border_style
576 .inner_sep()
577 .encode_utf8(&mut [0; 4])
578 .as_bytes(),
579 )?;
580 }
581
582 Ok(())
583 }
584
585 pub fn print_char_panel(&mut self) -> io::Result<()> {
586 for i in 0..self.line_buf.len() {
587 self.print_char(i as u64)?;
588 }
589 Ok(())
590 }
591
592 fn print_byte(&mut self, i: usize, b: u8) -> io::Result<()> {
593 match self.squeezer {
594 Squeezer::Print => {
595 if !self.show_position_panel && i == 0 {
596 if self.show_color {
597 self.writer.write_all(COLOR_OFFSET.as_bytes())?;
598 }
599 self.writer
600 .write_all(self.byte_char_panel[b'*' as usize].as_bytes())?;
601 if self.show_color {
602 self.writer.write_all(COLOR_RESET.as_bytes())?;
603 }
604 } else if i.is_multiple_of(self.group_size as usize) {
605 self.writer.write_all(b" ")?;
606 }
607 for _ in 0..self.base_digits {
608 self.writer.write_all(b" ")?;
609 }
610 }
611 Squeezer::Delete => self.writer.write_all(b" ")?,
612 Squeezer::Ignore | Squeezer::Disabled => {
613 if i.is_multiple_of(self.group_size as usize) {
614 self.writer.write_all(b" ")?;
615 }
616 if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme)) {
617 self.writer.write_all(Byte(b).color(self.color_scheme))?;
618 self.curr_color = Some(Byte(b).color(self.color_scheme));
619 }
620 self.writer
621 .write_all(self.byte_hex_panel[b as usize].as_bytes())?;
622 }
623 }
624 if i % 8 == 7 {
626 if self.show_color {
627 self.curr_color = None;
628 self.writer.write_all(COLOR_RESET.as_bytes())?;
629 }
630 self.writer.write_all(b" ")?;
631 if i as u64 % (8 * self.panels) == 8 * self.panels - 1 {
633 self.writer.write_all(
634 self.border_style
635 .outer_sep()
636 .encode_utf8(&mut [0; 4])
637 .as_bytes(),
638 )?;
639 } else {
640 self.writer.write_all(
641 self.border_style
642 .inner_sep()
643 .encode_utf8(&mut [0; 4])
644 .as_bytes(),
645 )?;
646 }
647 }
648 Ok(())
649 }
650
651 fn reorder_buffer_to_little_endian(&self, buf: &mut [u8]) {
652 let n = buf.len();
653 let group_sz = self.group_size as usize;
654
655 for idx in (0..n).step_by(group_sz) {
656 let remaining = n - idx;
657 let total = remaining.min(group_sz);
658
659 buf[idx..idx + total].reverse();
660 }
661 }
662
663 pub fn print_bytes(&mut self) -> io::Result<()> {
664 let mut buf = self.line_buf.clone();
665
666 if matches!(self.endianness, Endianness::Little) {
667 self.reorder_buffer_to_little_endian(&mut buf);
668 };
669
670 for (i, &b) in buf.iter().enumerate() {
671 self.print_byte(i, b)?;
672 }
673 Ok(())
674 }
675
676 pub fn print_all<Reader: Read>(&mut self, reader: Reader) -> io::Result<()> {
679 let mut is_empty = true;
680
681 let mut buf = BufReader::new(reader);
682
683 match &self.include_mode {
685 IncludeMode::File(filename) => {
688 let var_name = filename
690 .chars()
691 .map(|c| if c.is_alphanumeric() { c } else { '_' })
692 .collect::<String>();
693
694 writeln!(self.writer, "unsigned char {}[] = {{", var_name)?;
695
696 let total_bytes = self.print_bytes_in_include_style(&mut buf)?;
697
698 writeln!(self.writer, "}};")?;
699 writeln!(
700 self.writer,
701 "unsigned int {}_len = {};",
702 var_name, total_bytes
703 )?;
704 return Ok(());
705 }
706 IncludeMode::Stdin | IncludeMode::Slice => {
707 self.print_bytes_in_include_style(&mut buf)?;
708 return Ok(());
709 }
710 IncludeMode::Off => {}
711 }
712
713 let leftover = loop {
714 if let Ok(n) = buf.read(&mut self.line_buf) {
716 if n > 0 && n < 8 * self.panels as usize {
717 if is_empty {
719 self.print_header()?;
720 is_empty = false;
721 }
722 let mut leftover = n;
723 if let Some(s) = loop {
725 if let Ok(n) = buf.read(&mut self.line_buf[leftover..]) {
726 leftover += n;
727 if n == 0 {
729 self.line_buf.resize(leftover, 0);
730 break Some(leftover);
731 }
732 if leftover >= 8 * self.panels as usize {
734 break None;
735 }
736 }
737 } {
738 break Some(s);
739 };
740 } else if n == 0 {
741 if self.squeezer == Squeezer::Delete {
743 self.line_buf.clear();
745 break Some(0);
746 }
747 break None;
748 }
749 }
750 if is_empty {
751 self.print_header()?;
752 }
753
754 if matches!(self.squeezer, Squeezer::Print | Squeezer::Delete) {
757 if self
758 .line_buf
759 .chunks_exact(std::mem::size_of::<usize>())
760 .all(|w| usize::from_ne_bytes(w.try_into().unwrap()) == self.squeeze_byte)
761 {
762 if self.squeezer == Squeezer::Delete {
763 self.idx += 8 * self.panels;
764 continue;
765 }
766 } else {
767 self.squeezer = Squeezer::Ignore;
768 }
769 }
770
771 self.print_position_panel()?;
773 self.print_bytes()?;
774 if self.show_char_panel {
775 self.print_char_panel()?;
776 }
777 self.writer.write_all(b"\n")?;
778
779 if is_empty {
780 self.writer.flush()?;
781 is_empty = false;
782 }
783
784 self.idx += 8 * self.panels;
786
787 if self.squeezer == Squeezer::Print {
789 self.squeezer = Squeezer::Delete;
790 }
791
792 let repeat_byte = (self.line_buf[0] as usize) * (usize::MAX / 255);
796 if !matches!(self.squeezer, Squeezer::Disabled | Squeezer::Delete)
797 && self
798 .line_buf
799 .chunks_exact(std::mem::size_of::<usize>())
800 .all(|w| usize::from_ne_bytes(w.try_into().unwrap()) == repeat_byte)
801 {
802 self.squeezer = Squeezer::Print;
803 self.squeeze_byte = repeat_byte;
804 };
805 };
806
807 if is_empty {
810 self.base_digits = 2;
811 self.print_header()?;
812 if self.show_position_panel {
813 write!(self.writer, "{0:9}", "│")?;
814 }
815 write!(
816 self.writer,
817 "{0:2}{1:2$}{0}{0:>3$}",
818 "│",
819 "No content",
820 self.panel_sz() - 1,
821 self.panel_sz() + 1,
822 )?;
823 if self.show_char_panel {
824 write!(self.writer, "{0:>9}{0:>9}", "│")?;
825 }
826 writeln!(self.writer)?;
827 } else if let Some(n) = leftover {
828 self.squeezer = Squeezer::Ignore;
830 self.print_position_panel()?;
831 self.print_bytes()?;
832 self.squeezer = Squeezer::Print;
833 for i in n..8 * self.panels as usize {
834 self.print_byte(i, 0)?;
835 }
836 if self.show_char_panel {
837 self.squeezer = Squeezer::Ignore;
838 self.print_char_panel()?;
839 self.squeezer = Squeezer::Print;
840 for i in n..8 * self.panels as usize {
841 self.print_char(i as u64)?;
842 }
843 }
844 self.writer.write_all(b"\n")?;
845 }
846
847 self.print_footer()?;
848
849 self.writer.flush()?;
850
851 Ok(())
852 }
853
854 fn print_bytes_in_include_style<Reader: Read>(
857 &mut self,
858 buf: &mut BufReader<Reader>,
859 ) -> Result<usize, io::Error> {
860 let mut buffer = [0; 1024];
861 let mut total_bytes = 0;
862 let mut is_first_chunk = true;
863 let mut line_counter = 0;
864 loop {
865 match buf.read(&mut buffer) {
866 Ok(0) => break, Ok(bytes_read) => {
868 total_bytes += bytes_read;
869
870 for &byte in &buffer[..bytes_read] {
871 if line_counter % 12 == 0 {
872 if !is_first_chunk || line_counter > 0 {
873 writeln!(self.writer, ",")?;
874 }
875 write!(self.writer, " ")?;
877 is_first_chunk = false;
878 } else {
879 write!(self.writer, ", ")?;
880 }
881 write!(self.writer, "0x{:02x}", byte)?;
882 line_counter += 1;
883 }
884 }
885 Err(e) => return Err(e),
886 }
887 }
888 writeln!(self.writer)?;
889 Ok(total_bytes)
890 }
891}
892
893#[cfg(test)]
894mod tests {
895 use std::io;
896 use std::str;
897
898 use super::*;
899
900 fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) {
901 let mut output = vec![];
902 let mut printer = PrinterBuilder::new(&mut output)
903 .show_color(false)
904 .show_char_panel(true)
905 .show_position_panel(true)
906 .with_border_style(BorderStyle::Unicode)
907 .enable_squeezing(true)
908 .num_panels(2)
909 .group_size(1)
910 .with_base(Base::Hexadecimal)
911 .endianness(Endianness::Big)
912 .character_table(CharacterTable::Default)
913 .include_mode(IncludeMode::Off)
914 .color_scheme(ColorScheme::Default)
915 .build();
916
917 printer.print_all(input).unwrap();
918
919 let actual_string: &str = str::from_utf8(&output).unwrap();
920 assert_eq!(actual_string, expected_string,)
921 }
922
923 #[test]
924 fn empty_file_passes() {
925 let input = io::empty();
926 let expected_string = "\
927┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
928│ │ No content │ │ │ │
929└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
930"
931 .to_owned();
932 assert_print_all_output(input, expected_string);
933 }
934
935 #[test]
936 fn short_input_passes() {
937 let input = io::Cursor::new(b"spam");
938 let expected_string = "\
939┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
940│00000000│ 73 70 61 6d ┊ │spam ┊ │
941└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
942"
943 .to_owned();
944 assert_print_all_output(input, expected_string);
945 }
946
947 #[test]
948 fn display_offset() {
949 let input = io::Cursor::new(b"spamspamspamspamspam");
950 let expected_string = "\
951┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
952│deadbeef│ 73 70 61 6d 73 70 61 6d ┊ 73 70 61 6d 73 70 61 6d │spamspam┊spamspam│
953│deadbeff│ 73 70 61 6d ┊ │spam ┊ │
954└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
955"
956 .to_owned();
957
958 let mut output = vec![];
959 let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
960 .show_color(false)
961 .show_char_panel(true)
962 .show_position_panel(true)
963 .with_border_style(BorderStyle::Unicode)
964 .enable_squeezing(true)
965 .num_panels(2)
966 .group_size(1)
967 .with_base(Base::Hexadecimal)
968 .endianness(Endianness::Big)
969 .character_table(CharacterTable::Default)
970 .include_mode(IncludeMode::Off)
971 .color_scheme(ColorScheme::Default)
972 .build();
973 printer.display_offset(0xdeadbeef);
974
975 printer.print_all(input).unwrap();
976
977 let actual_string: &str = str::from_utf8(&output).unwrap();
978 assert_eq!(actual_string, expected_string)
979 }
980
981 #[test]
982 fn multiple_panels() {
983 let input = io::Cursor::new(b"supercalifragilisticexpialidocioussupercalifragilisticexpialidocioussupercalifragilisticexpialidocious");
984 let expected_string = "\
985┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┬────────┐
986│00000000│ 73 75 70 65 72 63 61 6c ┊ 69 66 72 61 67 69 6c 69 ┊ 73 74 69 63 65 78 70 69 ┊ 61 6c 69 64 6f 63 69 6f │supercal┊ifragili┊sticexpi┊alidocio│
987│00000020│ 75 73 73 75 70 65 72 63 ┊ 61 6c 69 66 72 61 67 69 ┊ 6c 69 73 74 69 63 65 78 ┊ 70 69 61 6c 69 64 6f 63 │ussuperc┊alifragi┊listicex┊pialidoc│
988│00000040│ 69 6f 75 73 73 75 70 65 ┊ 72 63 61 6c 69 66 72 61 ┊ 67 69 6c 69 73 74 69 63 ┊ 65 78 70 69 61 6c 69 64 │ioussupe┊rcalifra┊gilistic┊expialid│
989│00000060│ 6f 63 69 6f 75 73 ┊ ┊ ┊ │ocious ┊ ┊ ┊ │
990└────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┴────────┘
991"
992 .to_owned();
993
994 let mut output = vec![];
995 let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
996 .show_color(false)
997 .show_char_panel(true)
998 .show_position_panel(true)
999 .with_border_style(BorderStyle::Unicode)
1000 .enable_squeezing(true)
1001 .num_panels(4)
1002 .group_size(1)
1003 .with_base(Base::Hexadecimal)
1004 .endianness(Endianness::Big)
1005 .character_table(CharacterTable::Default)
1006 .include_mode(IncludeMode::Off)
1007 .color_scheme(ColorScheme::Default)
1008 .build();
1009
1010 printer.print_all(input).unwrap();
1011
1012 let actual_string: &str = str::from_utf8(&output).unwrap();
1013 assert_eq!(actual_string, expected_string)
1014 }
1015
1016 #[test]
1017 fn squeeze_works() {
1018 let input = io::Cursor::new(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
1019 let expected_string = "\
1020┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
1021│00000000│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
1022│* │ ┊ │ ┊ │
1023│00000020│ 00 ┊ │⋄ ┊ │
1024└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
1025"
1026 .to_owned();
1027 assert_print_all_output(input, expected_string);
1028 }
1029
1030 #[test]
1031 fn squeeze_nonzero() {
1032 let input = io::Cursor::new(b"000000000000000000000000000000000");
1033 let expected_string = "\
1034┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
1035│00000000│ 30 30 30 30 30 30 30 30 ┊ 30 30 30 30 30 30 30 30 │00000000┊00000000│
1036│* │ ┊ │ ┊ │
1037│00000020│ 30 ┊ │0 ┊ │
1038└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
1039"
1040 .to_owned();
1041 assert_print_all_output(input, expected_string);
1042 }
1043
1044 #[test]
1045 fn squeeze_multiple_panels() {
1046 let input = io::Cursor::new(b"0000000000000000000000000000000000000000000000000");
1047 let expected_string = "\
1048┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┐
1049│00000000│ 30 30 30 30 30 30 30 30 ┊ 30 30 30 30 30 30 30 30 ┊ 30 30 30 30 30 30 30 30 │00000000┊00000000┊00000000│
1050│* │ ┊ ┊ │ ┊ ┊ │
1051│00000030│ 30 ┊ ┊ │0 ┊ ┊ │
1052└────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┘
1053"
1054 .to_owned();
1055
1056 let mut output = vec![];
1057 let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
1058 .show_color(false)
1059 .show_char_panel(true)
1060 .show_position_panel(true)
1061 .with_border_style(BorderStyle::Unicode)
1062 .enable_squeezing(true)
1063 .num_panels(3)
1064 .group_size(1)
1065 .with_base(Base::Hexadecimal)
1066 .endianness(Endianness::Big)
1067 .character_table(CharacterTable::Default)
1068 .include_mode(IncludeMode::Off)
1069 .color_scheme(ColorScheme::Default)
1070 .build();
1071
1072 printer.print_all(input).unwrap();
1073
1074 let actual_string: &str = str::from_utf8(&output).unwrap();
1075 assert_eq!(actual_string, expected_string)
1076 }
1077
1078 #[test]
1080 fn display_offset_in_last_line() {
1081 let input = io::Cursor::new(b"AAAAAAAAAAAAAAAACCCC");
1082 let expected_string = "\
1083┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
1084│00000000│ 41 41 41 41 41 41 41 41 ┊ 41 41 41 41 41 41 41 41 │AAAAAAAA┊AAAAAAAA│
1085│00000010│ 43 43 43 43 ┊ │CCCC ┊ │
1086└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
1087"
1088 .to_owned();
1089 assert_print_all_output(input, expected_string);
1090 }
1091
1092 #[test]
1093 fn include_mode_from_file() {
1094 let input = io::Cursor::new(b"spamspamspamspamspam");
1095 let expected_string = "unsigned char test_txt[] = {
1096 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,
1097 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d
1098};
1099unsigned int test_txt_len = 20;
1100"
1101 .to_owned();
1102 let mut output = vec![];
1103 let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
1104 .show_color(false)
1105 .show_char_panel(true)
1106 .show_position_panel(true)
1107 .with_border_style(BorderStyle::Unicode)
1108 .enable_squeezing(true)
1109 .num_panels(2)
1110 .group_size(1)
1111 .with_base(Base::Hexadecimal)
1112 .endianness(Endianness::Big)
1113 .character_table(CharacterTable::Default)
1114 .include_mode(IncludeMode::File("test.txt".to_owned()))
1115 .color_scheme(ColorScheme::Default)
1116 .build();
1117
1118 printer.print_all(input).unwrap();
1119
1120 let actual_string: &str = str::from_utf8(&output).unwrap();
1121 assert_eq!(actual_string, expected_string)
1122 }
1123
1124 #[test]
1125 fn include_mode_from_stdin() {
1126 let input = io::Cursor::new(b"spamspamspamspamspam");
1127 let expected_string =
1128 " 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,
1129 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d
1130"
1131 .to_owned();
1132 let mut output = vec![];
1133 let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
1134 .show_color(false)
1135 .show_char_panel(true)
1136 .show_position_panel(true)
1137 .with_border_style(BorderStyle::Unicode)
1138 .enable_squeezing(true)
1139 .num_panels(2)
1140 .group_size(1)
1141 .with_base(Base::Hexadecimal)
1142 .endianness(Endianness::Big)
1143 .character_table(CharacterTable::Default)
1144 .include_mode(IncludeMode::Stdin)
1145 .color_scheme(ColorScheme::Default)
1146 .build();
1147
1148 printer.print_all(input).unwrap();
1149
1150 let actual_string: &str = str::from_utf8(&output).unwrap();
1151 assert_eq!(actual_string, expected_string)
1152 }
1153}