hexyl/
lib.rs

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
27#[derive(Copy, Clone, Debug, Default, ValueEnum)]
28#[non_exhaustive]
29pub enum CharacterTable {
30    /// Show printable ASCII characters as-is, '⋄' for NULL bytes, ' ' for
31    /// space, '_' for other ASCII whitespace, '•' for other ASCII characters,
32    /// and '×' for non-ASCII bytes.
33    #[default]
34    Default,
35
36    /// Show printable ASCII as-is, ' ' for space, '.' for everything else.
37    Ascii,
38
39    /// Show printable EBCDIC as-is, ' ' for space, '.' for everything else.
40    #[value(name = "codepage-1047")]
41    CP1047,
42
43    /// Uses code page 437 (for non-ASCII bytes).
44    #[value(name = "codepage-437")]
45    CP437,
46}
47
48#[derive(Copy, Clone, Debug, Default, ValueEnum)]
49pub enum Endianness {
50    /// Print out groups in little-endian format.
51    Little,
52
53    /// Print out groups in big-endian format.
54    #[default]
55    Big,
56}
57
58#[derive(PartialEq)]
59enum Squeezer {
60    Print,
61    Delete,
62    Ignore,
63    Disabled,
64}
65
66#[derive(Copy, Clone)]
67struct Byte(u8);
68
69impl Byte {
70    fn category(self) -> ByteCategory {
71        if self.0 == 0x00 {
72            ByteCategory::Null
73        } else if self.0.is_ascii_graphic() {
74            ByteCategory::AsciiPrintable
75        } else if self.0.is_ascii_whitespace() {
76            ByteCategory::AsciiWhitespace
77        } else if self.0.is_ascii() {
78            ByteCategory::AsciiOther
79        } else {
80            ByteCategory::NonAscii
81        }
82    }
83
84    fn color(self) -> &'static [u8] {
85        use crate::ByteCategory::*;
86        match self.category() {
87            Null => COLOR_NULL,
88            AsciiPrintable => COLOR_ASCII_PRINTABLE,
89            AsciiWhitespace => COLOR_ASCII_WHITESPACE,
90            AsciiOther => COLOR_ASCII_OTHER,
91            NonAscii => COLOR_NONASCII,
92        }
93    }
94
95    fn as_char(self, character_table: CharacterTable) -> char {
96        use crate::ByteCategory::*;
97        match character_table {
98            CharacterTable::Default => match self.category() {
99                Null => '⋄',
100                AsciiPrintable => self.0 as char,
101                AsciiWhitespace if self.0 == 0x20 => ' ',
102                AsciiWhitespace => '_',
103                AsciiOther => '•',
104                NonAscii => '×',
105            },
106            CharacterTable::Ascii => match self.category() {
107                Null => '.',
108                AsciiPrintable => self.0 as char,
109                AsciiWhitespace if self.0 == 0x20 => ' ',
110                AsciiWhitespace => '.',
111                AsciiOther => '.',
112                NonAscii => '.',
113            },
114            CharacterTable::CP1047 => CP1047[self.0 as usize],
115            CharacterTable::CP437 => CP437[self.0 as usize],
116        }
117    }
118}
119
120struct BorderElements {
121    left_corner: char,
122    horizontal_line: char,
123    column_separator: char,
124    right_corner: char,
125}
126
127#[derive(Clone, Copy, Debug, Default, ValueEnum)]
128pub enum BorderStyle {
129    /// Draw a border with Unicode characters.
130    #[default]
131    Unicode,
132
133    /// Draw a border with ASCII characters.
134    Ascii,
135
136    /// Do not draw a border at all.
137    None,
138}
139
140impl BorderStyle {
141    fn header_elems(&self) -> Option<BorderElements> {
142        match self {
143            BorderStyle::Unicode => Some(BorderElements {
144                left_corner: '┌',
145                horizontal_line: '─',
146                column_separator: '┬',
147                right_corner: '┐',
148            }),
149            BorderStyle::Ascii => Some(BorderElements {
150                left_corner: '+',
151                horizontal_line: '-',
152                column_separator: '+',
153                right_corner: '+',
154            }),
155            BorderStyle::None => None,
156        }
157    }
158
159    fn footer_elems(&self) -> Option<BorderElements> {
160        match self {
161            BorderStyle::Unicode => Some(BorderElements {
162                left_corner: '└',
163                horizontal_line: '─',
164                column_separator: '┴',
165                right_corner: '┘',
166            }),
167            BorderStyle::Ascii => Some(BorderElements {
168                left_corner: '+',
169                horizontal_line: '-',
170                column_separator: '+',
171                right_corner: '+',
172            }),
173            BorderStyle::None => None,
174        }
175    }
176
177    fn outer_sep(&self) -> char {
178        match self {
179            BorderStyle::Unicode => '│',
180            BorderStyle::Ascii => '|',
181            BorderStyle::None => ' ',
182        }
183    }
184
185    fn inner_sep(&self) -> char {
186        match self {
187            BorderStyle::Unicode => '┊',
188            BorderStyle::Ascii => '|',
189            BorderStyle::None => ' ',
190        }
191    }
192}
193
194pub struct PrinterBuilder<'a, Writer: Write> {
195    writer: &'a mut Writer,
196    show_color: bool,
197    show_char_panel: bool,
198    show_position_panel: bool,
199    border_style: BorderStyle,
200    use_squeeze: bool,
201    panels: u64,
202    group_size: u8,
203    base: Base,
204    endianness: Endianness,
205    character_table: CharacterTable,
206}
207
208impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
209    pub fn new(writer: &'a mut Writer) -> Self {
210        PrinterBuilder {
211            writer,
212            show_color: true,
213            show_char_panel: true,
214            show_position_panel: true,
215            border_style: BorderStyle::Unicode,
216            use_squeeze: true,
217            panels: 2,
218            group_size: 1,
219            base: Base::Hexadecimal,
220            endianness: Endianness::Big,
221            character_table: CharacterTable::Default,
222        }
223    }
224
225    pub fn show_color(mut self, show_color: bool) -> Self {
226        self.show_color = show_color;
227        self
228    }
229
230    pub fn show_char_panel(mut self, show_char_panel: bool) -> Self {
231        self.show_char_panel = show_char_panel;
232        self
233    }
234
235    pub fn show_position_panel(mut self, show_position_panel: bool) -> Self {
236        self.show_position_panel = show_position_panel;
237        self
238    }
239
240    pub fn with_border_style(mut self, border_style: BorderStyle) -> Self {
241        self.border_style = border_style;
242        self
243    }
244
245    pub fn enable_squeezing(mut self, enable: bool) -> Self {
246        self.use_squeeze = enable;
247        self
248    }
249
250    pub fn num_panels(mut self, num: u64) -> Self {
251        self.panels = num;
252        self
253    }
254
255    pub fn group_size(mut self, num: u8) -> Self {
256        self.group_size = num;
257        self
258    }
259
260    pub fn with_base(mut self, base: Base) -> Self {
261        self.base = base;
262        self
263    }
264
265    pub fn endianness(mut self, endianness: Endianness) -> Self {
266        self.endianness = endianness;
267        self
268    }
269
270    pub fn character_table(mut self, character_table: CharacterTable) -> Self {
271        self.character_table = character_table;
272        self
273    }
274
275    pub fn build(self) -> Printer<'a, Writer> {
276        Printer::new(
277            self.writer,
278            self.show_color,
279            self.show_char_panel,
280            self.show_position_panel,
281            self.border_style,
282            self.use_squeeze,
283            self.panels,
284            self.group_size,
285            self.base,
286            self.endianness,
287            self.character_table,
288        )
289    }
290}
291
292pub struct Printer<'a, Writer: Write> {
293    idx: u64,
294    /// the buffer containing all the bytes in a line for character printing
295    line_buf: Vec<u8>,
296    writer: &'a mut Writer,
297    show_char_panel: bool,
298    show_position_panel: bool,
299    show_color: bool,
300    curr_color: Option<&'static [u8]>,
301    border_style: BorderStyle,
302    byte_hex_panel: Vec<String>,
303    byte_char_panel: Vec<String>,
304    // same as previous but in Fixed(242) gray color, for position panel
305    byte_hex_panel_g: Vec<String>,
306    squeezer: Squeezer,
307    display_offset: u64,
308    /// The number of panels to draw.
309    panels: u64,
310    squeeze_byte: usize,
311    /// The number of octets per group.
312    group_size: u8,
313    /// The number of digits used to write the base.
314    base_digits: u8,
315    /// Whether to show groups in little or big endian format.
316    endianness: Endianness,
317}
318
319impl<'a, Writer: Write> Printer<'a, Writer> {
320    fn new(
321        writer: &'a mut Writer,
322        show_color: bool,
323        show_char_panel: bool,
324        show_position_panel: bool,
325        border_style: BorderStyle,
326        use_squeeze: bool,
327        panels: u64,
328        group_size: u8,
329        base: Base,
330        endianness: Endianness,
331        character_table: CharacterTable,
332    ) -> Printer<'a, Writer> {
333        Printer {
334            idx: 0,
335            line_buf: vec![0x0; 8 * panels as usize],
336            writer,
337            show_char_panel,
338            show_position_panel,
339            show_color,
340            curr_color: None,
341            border_style,
342            byte_hex_panel: (0u8..=u8::MAX)
343                .map(|i| match base {
344                    Base::Binary => format!("{i:08b}"),
345                    Base::Octal => format!("{i:03o}"),
346                    Base::Decimal => format!("{i:03}"),
347                    Base::Hexadecimal => format!("{i:02x}"),
348                })
349                .collect(),
350            byte_char_panel: (0u8..=u8::MAX)
351                .map(|i| format!("{}", Byte(i).as_char(character_table)))
352                .collect(),
353            byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{i:02x}")).collect(),
354            squeezer: if use_squeeze {
355                Squeezer::Ignore
356            } else {
357                Squeezer::Disabled
358            },
359            display_offset: 0,
360            panels,
361            squeeze_byte: 0x00,
362            group_size,
363            base_digits: match base {
364                Base::Binary => 8,
365                Base::Octal => 3,
366                Base::Decimal => 3,
367                Base::Hexadecimal => 2,
368            },
369            endianness,
370        }
371    }
372
373    pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {
374        self.display_offset = display_offset;
375        self
376    }
377
378    fn panel_sz(&self) -> usize {
379        // add one to include the trailing space of a group
380        let group_sz = self.base_digits as usize * self.group_size as usize + 1;
381        let group_per_panel = 8 / self.group_size as usize;
382        // add one to include the leading space
383        1 + group_sz * group_per_panel
384    }
385
386    fn write_border(&mut self, border_elements: BorderElements) -> io::Result<()> {
387        let h = border_elements.horizontal_line;
388        let c = border_elements.column_separator;
389        let l = border_elements.left_corner;
390        let r = border_elements.right_corner;
391        let h8 = h.to_string().repeat(8);
392        let h_repeat = h.to_string().repeat(self.panel_sz());
393
394        if self.show_position_panel {
395            write!(self.writer, "{l}{h8}{c}")?;
396        } else {
397            write!(self.writer, "{l}")?;
398        }
399
400        for _ in 0..self.panels - 1 {
401            write!(self.writer, "{h_repeat}{c}")?;
402        }
403        if self.show_char_panel {
404            write!(self.writer, "{h_repeat}{c}")?;
405        } else {
406            write!(self.writer, "{h_repeat}")?;
407        }
408
409        if self.show_char_panel {
410            for _ in 0..self.panels - 1 {
411                write!(self.writer, "{h8}{c}")?;
412            }
413            writeln!(self.writer, "{h8}{r}")?;
414        } else {
415            writeln!(self.writer, "{r}")?;
416        }
417
418        Ok(())
419    }
420
421    pub fn print_header(&mut self) -> io::Result<()> {
422        if let Some(e) = self.border_style.header_elems() {
423            self.write_border(e)?
424        }
425        Ok(())
426    }
427
428    pub fn print_footer(&mut self) -> io::Result<()> {
429        if let Some(e) = self.border_style.footer_elems() {
430            self.write_border(e)?
431        }
432        Ok(())
433    }
434
435    fn print_position_panel(&mut self) -> io::Result<()> {
436        self.writer.write_all(
437            self.border_style
438                .outer_sep()
439                .encode_utf8(&mut [0; 4])
440                .as_bytes(),
441        )?;
442        if self.show_color {
443            self.writer.write_all(COLOR_OFFSET)?;
444        }
445        if self.show_position_panel {
446            match self.squeezer {
447                Squeezer::Print => {
448                    self.writer.write_all(&[b'*'])?;
449                    if self.show_color {
450                        self.writer.write_all(COLOR_RESET)?;
451                    }
452                    self.writer.write_all(b"       ")?;
453                }
454                Squeezer::Ignore | Squeezer::Disabled | Squeezer::Delete => {
455                    let byte_index: [u8; 8] = (self.idx + self.display_offset).to_be_bytes();
456                    let mut i = 0;
457                    while byte_index[i] == 0x0 && i < 4 {
458                        i += 1;
459                    }
460                    for &byte in byte_index.iter().skip(i) {
461                        self.writer
462                            .write_all(self.byte_hex_panel_g[byte as usize].as_bytes())?;
463                    }
464                    if self.show_color {
465                        self.writer.write_all(COLOR_RESET)?;
466                    }
467                }
468            }
469            self.writer.write_all(
470                self.border_style
471                    .outer_sep()
472                    .encode_utf8(&mut [0; 4])
473                    .as_bytes(),
474            )?;
475        }
476        Ok(())
477    }
478
479    fn print_char(&mut self, i: u64) -> io::Result<()> {
480        match self.squeezer {
481            Squeezer::Print | Squeezer::Delete => self.writer.write_all(b" ")?,
482            Squeezer::Ignore | Squeezer::Disabled => {
483                if let Some(&b) = self.line_buf.get(i as usize) {
484                    if self.show_color && self.curr_color != Some(Byte(b).color()) {
485                        self.writer.write_all(Byte(b).color())?;
486                        self.curr_color = Some(Byte(b).color());
487                    }
488                    self.writer
489                        .write_all(self.byte_char_panel[b as usize].as_bytes())?;
490                } else {
491                    self.squeezer = Squeezer::Print;
492                }
493            }
494        }
495        if i == 8 * self.panels - 1 {
496            if self.show_color {
497                self.writer.write_all(COLOR_RESET)?;
498                self.curr_color = None;
499            }
500            self.writer.write_all(
501                self.border_style
502                    .outer_sep()
503                    .encode_utf8(&mut [0; 4])
504                    .as_bytes(),
505            )?;
506        } else if i % 8 == 7 {
507            if self.show_color {
508                self.writer.write_all(COLOR_RESET)?;
509                self.curr_color = None;
510            }
511            self.writer.write_all(
512                self.border_style
513                    .inner_sep()
514                    .encode_utf8(&mut [0; 4])
515                    .as_bytes(),
516            )?;
517        }
518
519        Ok(())
520    }
521
522    pub fn print_char_panel(&mut self) -> io::Result<()> {
523        for i in 0..self.line_buf.len() {
524            self.print_char(i as u64)?;
525        }
526        Ok(())
527    }
528
529    fn print_byte(&mut self, i: usize, b: u8) -> io::Result<()> {
530        match self.squeezer {
531            Squeezer::Print => {
532                if !self.show_position_panel && i == 0 {
533                    if self.show_color {
534                        self.writer.write_all(COLOR_OFFSET)?;
535                    }
536                    self.writer
537                        .write_all(self.byte_char_panel[b'*' as usize].as_bytes())?;
538                    if self.show_color {
539                        self.writer.write_all(COLOR_RESET)?;
540                    }
541                } else if i % (self.group_size as usize) == 0 {
542                    self.writer.write_all(b" ")?;
543                }
544                for _ in 0..self.base_digits {
545                    self.writer.write_all(b" ")?;
546                }
547            }
548            Squeezer::Delete => self.writer.write_all(b"   ")?,
549            Squeezer::Ignore | Squeezer::Disabled => {
550                if i % (self.group_size as usize) == 0 {
551                    self.writer.write_all(b" ")?;
552                }
553                if self.show_color && self.curr_color != Some(Byte(b).color()) {
554                    self.writer.write_all(Byte(b).color())?;
555                    self.curr_color = Some(Byte(b).color());
556                }
557                self.writer
558                    .write_all(self.byte_hex_panel[b as usize].as_bytes())?;
559            }
560        }
561        // byte is last in panel
562        if i % 8 == 7 {
563            if self.show_color {
564                self.curr_color = None;
565                self.writer.write_all(COLOR_RESET)?;
566            }
567            self.writer.write_all(b" ")?;
568            // byte is last in last panel
569            if i as u64 % (8 * self.panels) == 8 * self.panels - 1 {
570                self.writer.write_all(
571                    self.border_style
572                        .outer_sep()
573                        .encode_utf8(&mut [0; 4])
574                        .as_bytes(),
575                )?;
576            } else {
577                self.writer.write_all(
578                    self.border_style
579                        .inner_sep()
580                        .encode_utf8(&mut [0; 4])
581                        .as_bytes(),
582                )?;
583            }
584        }
585        Ok(())
586    }
587
588    fn reorder_buffer_to_little_endian(&self, buf: &mut Vec<u8>) {
589        let n = buf.len();
590        let group_sz = self.group_size as usize;
591
592        for idx in (0..n).step_by(group_sz) {
593            let remaining = n - idx;
594            let total = remaining.min(group_sz);
595
596            buf[idx..idx + total].reverse();
597        }
598    }
599
600    pub fn print_bytes(&mut self) -> io::Result<()> {
601        let mut buf = self.line_buf.clone();
602
603        if matches!(self.endianness, Endianness::Little) {
604            self.reorder_buffer_to_little_endian(&mut buf);
605        };
606
607        for (i, &b) in buf.iter().enumerate() {
608            self.print_byte(i, b)?;
609        }
610        Ok(())
611    }
612
613    /// Loop through the given `Reader`, printing until the `Reader` buffer
614    /// is exhausted.
615    pub fn print_all<Reader: Read>(&mut self, reader: Reader) -> io::Result<()> {
616        let mut is_empty = true;
617
618        let mut buf = BufReader::new(reader);
619
620        let leftover = loop {
621            // read a maximum of 8 * self.panels bytes from the reader
622            if let Ok(n) = buf.read(&mut self.line_buf) {
623                if n > 0 && n < 8 * self.panels as usize {
624                    // if less are read, that indicates end of file after
625                    if is_empty {
626                        self.print_header()?;
627                        is_empty = false;
628                    }
629                    let mut leftover = n;
630                    // loop until input is ceased
631                    if let Some(s) = loop {
632                        if let Ok(n) = buf.read(&mut self.line_buf[leftover..]) {
633                            leftover += n;
634                            // there is no more input being read
635                            if n == 0 {
636                                self.line_buf.resize(leftover, 0);
637                                break Some(leftover);
638                            }
639                            // amount read has exceeded line buffer
640                            if leftover >= 8 * self.panels as usize {
641                                break None;
642                            }
643                        }
644                    } {
645                        break Some(s);
646                    };
647                } else if n == 0 {
648                    // if no bytes are read, that indicates end of file
649                    if self.squeezer == Squeezer::Delete {
650                        // empty the last line when ending is squeezed
651                        self.line_buf.clear();
652                        break Some(0);
653                    }
654                    break None;
655                }
656            }
657            if is_empty {
658                self.print_header()?;
659            }
660
661            // squeeze is active, check if the line is the same
662            // skip print if still squeezed, otherwise print and deactivate squeeze
663            if matches!(self.squeezer, Squeezer::Print | Squeezer::Delete) {
664                if self
665                    .line_buf
666                    .chunks_exact(std::mem::size_of::<usize>())
667                    .all(|w| usize::from_ne_bytes(w.try_into().unwrap()) == self.squeeze_byte)
668                {
669                    if self.squeezer == Squeezer::Delete {
670                        self.idx += 8 * self.panels;
671                        continue;
672                    }
673                } else {
674                    self.squeezer = Squeezer::Ignore;
675                }
676            }
677
678            // print the line
679            self.print_position_panel()?;
680            self.print_bytes()?;
681            if self.show_char_panel {
682                self.print_char_panel()?;
683            }
684            self.writer.write_all(b"\n")?;
685
686            if is_empty {
687                self.writer.flush()?;
688                is_empty = false;
689            }
690
691            // increment index to next line
692            self.idx += 8 * self.panels;
693
694            // change from print to delete if squeeze is still active
695            if self.squeezer == Squeezer::Print {
696                self.squeezer = Squeezer::Delete;
697            }
698
699            // repeat the first byte in the line until it's a usize
700            // compare that usize with each usize chunk in the line
701            // if they are all the same, change squeezer to print
702            let repeat_byte = (self.line_buf[0] as usize) * (usize::MAX / 255);
703            if !matches!(self.squeezer, Squeezer::Disabled | Squeezer::Delete)
704                && self
705                    .line_buf
706                    .chunks_exact(std::mem::size_of::<usize>())
707                    .all(|w| usize::from_ne_bytes(w.try_into().unwrap()) == repeat_byte)
708            {
709                self.squeezer = Squeezer::Print;
710                self.squeeze_byte = repeat_byte;
711            };
712        };
713
714        // special ending
715
716        if is_empty {
717            self.base_digits = 2;
718            self.print_header()?;
719            if self.show_position_panel {
720                write!(self.writer, "{0:9}", "│")?;
721            }
722            write!(
723                self.writer,
724                "{0:2}{1:2$}{0}{0:>3$}",
725                "│",
726                "No content",
727                self.panel_sz() - 1,
728                self.panel_sz() + 1,
729            )?;
730            if self.show_char_panel {
731                write!(self.writer, "{0:>9}{0:>9}", "│")?;
732            }
733            writeln!(self.writer)?;
734        } else if let Some(n) = leftover {
735            // last line is incomplete
736            self.print_position_panel()?;
737            self.squeezer = Squeezer::Ignore;
738            self.print_bytes()?;
739            self.squeezer = Squeezer::Print;
740            for i in n..8 * self.panels as usize {
741                self.print_byte(i, 0)?;
742            }
743            if self.show_char_panel {
744                self.squeezer = Squeezer::Ignore;
745                self.print_char_panel()?;
746                self.squeezer = Squeezer::Print;
747                for i in n..8 * self.panels as usize {
748                    self.print_char(i as u64)?;
749                }
750            }
751            self.writer.write_all(b"\n")?;
752        }
753
754        self.print_footer()?;
755
756        self.writer.flush()?;
757
758        Ok(())
759    }
760}
761
762#[cfg(test)]
763mod tests {
764    use std::io;
765    use std::str;
766
767    use super::*;
768
769    fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) {
770        let mut output = vec![];
771        let mut printer = Printer::new(
772            &mut output,
773            false,
774            true,
775            true,
776            BorderStyle::Unicode,
777            true,
778            2,
779            1,
780            Base::Hexadecimal,
781            Endianness::Big,
782            CharacterTable::Default,
783        );
784
785        printer.print_all(input).unwrap();
786
787        let actual_string: &str = str::from_utf8(&output).unwrap();
788        assert_eq!(actual_string, expected_string,)
789    }
790
791    #[test]
792    fn empty_file_passes() {
793        let input = io::empty();
794        let expected_string = "\
795┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
796│        │ No content              │                         │        │        │
797└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
798"
799        .to_owned();
800        assert_print_all_output(input, expected_string);
801    }
802
803    #[test]
804    fn short_input_passes() {
805        let input = io::Cursor::new(b"spam");
806        let expected_string = "\
807┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
808│00000000│ 73 70 61 6d             ┊                         │spam    ┊        │
809└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
810"
811        .to_owned();
812        assert_print_all_output(input, expected_string);
813    }
814
815    #[test]
816    fn display_offset() {
817        let input = io::Cursor::new(b"spamspamspamspamspam");
818        let expected_string = "\
819┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
820│deadbeef│ 73 70 61 6d 73 70 61 6d ┊ 73 70 61 6d 73 70 61 6d │spamspam┊spamspam│
821│deadbeff│ 73 70 61 6d             ┊                         │spam    ┊        │
822└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
823"
824        .to_owned();
825
826        let mut output = vec![];
827        let mut printer: Printer<Vec<u8>> = Printer::new(
828            &mut output,
829            false,
830            true,
831            true,
832            BorderStyle::Unicode,
833            true,
834            2,
835            1,
836            Base::Hexadecimal,
837            Endianness::Big,
838            CharacterTable::Default,
839        );
840        printer.display_offset(0xdeadbeef);
841
842        printer.print_all(input).unwrap();
843
844        let actual_string: &str = str::from_utf8(&output).unwrap();
845        assert_eq!(actual_string, expected_string)
846    }
847
848    #[test]
849    fn multiple_panels() {
850        let input = io::Cursor::new(b"supercalifragilisticexpialidocioussupercalifragilisticexpialidocioussupercalifragilisticexpialidocious");
851        let expected_string = "\
852┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┬────────┐
853│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│
854│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│
855│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│
856│00000060│ 6f 63 69 6f 75 73       ┊                         ┊                         ┊                         │ocious  ┊        ┊        ┊        │
857└────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┴────────┘
858"
859        .to_owned();
860
861        let mut output = vec![];
862        let mut printer: Printer<Vec<u8>> = Printer::new(
863            &mut output,
864            false,
865            true,
866            true,
867            BorderStyle::Unicode,
868            true,
869            4,
870            1,
871            Base::Hexadecimal,
872            Endianness::Big,
873            CharacterTable::Default,
874        );
875
876        printer.print_all(input).unwrap();
877
878        let actual_string: &str = str::from_utf8(&output).unwrap();
879        assert_eq!(actual_string, expected_string)
880    }
881
882    #[test]
883    fn squeeze_works() {
884        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");
885        let expected_string = "\
886┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
887│00000000│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
888│*       │                         ┊                         │        ┊        │
889│00000020│ 00                      ┊                         │⋄       ┊        │
890└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
891"
892        .to_owned();
893        assert_print_all_output(input, expected_string);
894    }
895
896    #[test]
897    fn squeeze_nonzero() {
898        let input = io::Cursor::new(b"000000000000000000000000000000000");
899        let expected_string = "\
900┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
901│00000000│ 30 30 30 30 30 30 30 30 ┊ 30 30 30 30 30 30 30 30 │00000000┊00000000│
902│*       │                         ┊                         │        ┊        │
903│00000020│ 30                      ┊                         │0       ┊        │
904└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
905"
906        .to_owned();
907        assert_print_all_output(input, expected_string);
908    }
909
910    #[test]
911    fn squeeze_multiple_panels() {
912        let input = io::Cursor::new(b"0000000000000000000000000000000000000000000000000");
913        let expected_string = "\
914┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┐
915│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│
916│*       │                         ┊                         ┊                         │        ┊        ┊        │
917│00000030│ 30                      ┊                         ┊                         │0       ┊        ┊        │
918└────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┘
919"
920        .to_owned();
921
922        let mut output = vec![];
923        let mut printer: Printer<Vec<u8>> = Printer::new(
924            &mut output,
925            false,
926            true,
927            true,
928            BorderStyle::Unicode,
929            true,
930            3,
931            1,
932            Base::Hexadecimal,
933            Endianness::Big,
934            CharacterTable::Default,
935        );
936
937        printer.print_all(input).unwrap();
938
939        let actual_string: &str = str::from_utf8(&output).unwrap();
940        assert_eq!(actual_string, expected_string)
941    }
942}