Skip to main content

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
27pub enum IncludeMode {
28    File(String), // filename
29    Stdin,
30    Slice,
31    Off,
32}
33
34#[derive(Copy, Clone, Debug, Default, ValueEnum)]
35#[non_exhaustive]
36pub enum CharacterTable {
37    /// Show printable ASCII characters as-is, '⋄' for NULL bytes, ' ' for
38    /// space, '_' for other ASCII whitespace, '•' for other ASCII characters,
39    /// and '×' for non-ASCII bytes.
40    #[default]
41    Default,
42
43    /// Show printable ASCII as-is, ' ' for space, '.' for everything else.
44    Ascii,
45
46    /// Show printable EBCDIC as-is, ' ' for space, '.' for everything else.
47    #[value(name = "codepage-1047")]
48    CP1047,
49
50    /// Uses code page 437 (for non-ASCII bytes).
51    #[value(name = "codepage-437")]
52    CP437,
53
54    /// Uses braille characters for non-printable bytes.
55    Braille,
56}
57
58#[derive(Copy, Clone, Debug, Default, ValueEnum)]
59#[non_exhaustive]
60pub enum ColorScheme {
61    /// Show the default colors: bright black for NULL bytes, green for ASCII
62    /// space characters and non-printable ASCII, cyan for printable ASCII characters,
63    /// and yellow for non-ASCII bytes.
64    #[default]
65    Default,
66
67    /// Show bright black for NULL bytes, cyan for printable ASCII characters, a gradient
68    /// from pink to violet for non-printable ASCII characters and a heatmap-like gradient
69    /// from red to yellow to white for non-ASCII bytes.
70    Gradient,
71}
72
73#[derive(Copy, Clone, Debug, Default, ValueEnum)]
74pub enum Endianness {
75    /// Print out groups in little-endian format.
76    Little,
77
78    /// Print out groups in big-endian format.
79    #[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 is important enough to get its own symbol
158                Null => '⋄',
159                AsciiPrintable => self.0 as char,
160                AsciiWhitespace if self.0 == b' ' => ' ',
161                // `\t`, `\n` and `\r` are important enough to get their own symbols
162                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                    /// Adjust the bits from the original number to a new number.
167                    ///
168                    /// Bit positions in braille are adjusted as follows:
169                    ///
170                    /// ```text
171                    /// 0 3 => 0 1
172                    /// 1 4 => 2 3
173                    /// 2 5 => 4 5
174                    /// 6 7 => 6 7
175                    /// ```
176                    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    /// Draw a border with Unicode characters.
201    #[default]
202    Unicode,
203
204    /// Draw a border with ASCII characters.
205    Ascii,
206
207    /// Do not draw a border at all.
208    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    /// the buffer containing all the bytes in a line for character printing
407    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    // same as previous but in Fixed(242) gray color, for position panel
418    byte_hex_panel_g: Vec<String>,
419    squeezer: Squeezer,
420    display_offset: u64,
421    /// The number of panels to draw.
422    panels: u64,
423    squeeze_byte: usize,
424    /// The number of octets per group.
425    group_size: u8,
426    /// The number of digits used to write the base.
427    base_digits: u8,
428    /// Whether to show groups in little or big endian format.
429    endianness: Endianness,
430    /// Whether to output in C include file style.
431    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        // add one to include the trailing space of a group
442        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        // add one to include the leading space
445        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        // byte is last in panel
625        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            // byte is last in last panel
632            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    /// Loop through the given `Reader`, printing until the `Reader` buffer
677    /// is exhausted.
678    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        // special handler for include mode
684        match &self.include_mode {
685            // Input from a file
686            // Output like `unsigned char <filename>[] = { ... }; unsigned int <filename>_len = ...;`
687            IncludeMode::File(filename) => {
688                // convert non-alphanumeric characters to '_'
689                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            // read a maximum of 8 * self.panels bytes from the reader
715            if let Ok(n) = buf.read(&mut self.line_buf) {
716                if n > 0 && n < 8 * self.panels as usize {
717                    // if less are read, that indicates end of file after
718                    if is_empty {
719                        self.print_header()?;
720                        is_empty = false;
721                    }
722                    let mut leftover = n;
723                    // loop until input is ceased
724                    if let Some(s) = loop {
725                        if let Ok(n) = buf.read(&mut self.line_buf[leftover..]) {
726                            leftover += n;
727                            // there is no more input being read
728                            if n == 0 {
729                                self.line_buf.resize(leftover, 0);
730                                break Some(leftover);
731                            }
732                            // amount read has exceeded line buffer
733                            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 no bytes are read, that indicates end of file
742                    if self.squeezer == Squeezer::Delete {
743                        // empty the last line when ending is squeezed
744                        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            // squeeze is active, check if the line is the same
755            // skip print if still squeezed, otherwise print and deactivate squeeze
756            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            // print the line
772            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            // increment index to next line
785            self.idx += 8 * self.panels;
786
787            // change from print to delete if squeeze is still active
788            if self.squeezer == Squeezer::Print {
789                self.squeezer = Squeezer::Delete;
790            }
791
792            // repeat the first byte in the line until it's a usize
793            // compare that usize with each usize chunk in the line
794            // if they are all the same, change squeezer to print
795            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        // special ending
808
809        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            // last line is incomplete
829            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    /// Print the bytes in C include file style
855    /// Return the number of bytes read  
856    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, // EOF
867                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                            // indentation of first line
876                            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    // issue#238
1079    #[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}