1 pub(crate) mod input;
2 pub mod squeezer;
3 
4 pub use input::*;
5 
6 use std::io::{self, Read, Write};
7 
8 use ansi_term::Color;
9 use ansi_term::Color::Fixed;
10 
11 use crate::squeezer::{SqueezeAction, Squeezer};
12 
13 const BUFFER_SIZE: usize = 256;
14 
15 const COLOR_NULL: Color = Fixed(242); // grey
16 const COLOR_OFFSET: Color = Fixed(242); // grey
17 const COLOR_ASCII_PRINTABLE: Color = Color::Cyan;
18 const COLOR_ASCII_WHITESPACE: Color = Color::Green;
19 const COLOR_ASCII_OTHER: Color = Color::Purple;
20 const COLOR_NONASCII: Color = Color::Yellow;
21 
22 pub enum ByteCategory {
23     Null,
24     AsciiPrintable,
25     AsciiWhitespace,
26     AsciiOther,
27     NonAscii,
28 }
29 
30 #[derive(Copy, Clone)]
31 struct Byte(u8);
32 
33 impl Byte {
category(self) -> ByteCategory34     fn category(self) -> ByteCategory {
35         if self.0 == 0x00 {
36             ByteCategory::Null
37         } else if self.0.is_ascii_graphic() {
38             ByteCategory::AsciiPrintable
39         } else if self.0.is_ascii_whitespace() {
40             ByteCategory::AsciiWhitespace
41         } else if self.0.is_ascii() {
42             ByteCategory::AsciiOther
43         } else {
44             ByteCategory::NonAscii
45         }
46     }
47 
color(self) -> &'static Color48     fn color(self) -> &'static Color {
49         use crate::ByteCategory::*;
50 
51         match self.category() {
52             Null => &COLOR_NULL,
53             AsciiPrintable => &COLOR_ASCII_PRINTABLE,
54             AsciiWhitespace => &COLOR_ASCII_WHITESPACE,
55             AsciiOther => &COLOR_ASCII_OTHER,
56             NonAscii => &COLOR_NONASCII,
57         }
58     }
59 
as_char(self) -> char60     fn as_char(self) -> char {
61         use crate::ByteCategory::*;
62 
63         match self.category() {
64             Null => '0',
65             AsciiPrintable => self.0 as char,
66             AsciiWhitespace if self.0 == 0x20 => ' ',
67             AsciiWhitespace => '_',
68             AsciiOther => '•',
69             NonAscii => '×',
70         }
71     }
72 }
73 
74 struct BorderElements {
75     left_corner: char,
76     horizontal_line: char,
77     column_separator: char,
78     right_corner: char,
79 }
80 
81 pub enum BorderStyle {
82     Unicode,
83     Ascii,
84     None,
85 }
86 
87 impl BorderStyle {
header_elems(&self) -> Option<BorderElements>88     fn header_elems(&self) -> Option<BorderElements> {
89         match self {
90             BorderStyle::Unicode => Some(BorderElements {
91                 left_corner: '┌',
92                 horizontal_line: '─',
93                 column_separator: '┬',
94                 right_corner: '┐',
95             }),
96             BorderStyle::Ascii => Some(BorderElements {
97                 left_corner: '+',
98                 horizontal_line: '-',
99                 column_separator: '+',
100                 right_corner: '+',
101             }),
102             BorderStyle::None => None,
103         }
104     }
105 
footer_elems(&self) -> Option<BorderElements>106     fn footer_elems(&self) -> Option<BorderElements> {
107         match self {
108             BorderStyle::Unicode => Some(BorderElements {
109                 left_corner: '└',
110                 horizontal_line: '─',
111                 column_separator: '┴',
112                 right_corner: '┘',
113             }),
114             BorderStyle::Ascii => Some(BorderElements {
115                 left_corner: '+',
116                 horizontal_line: '-',
117                 column_separator: '+',
118                 right_corner: '+',
119             }),
120             BorderStyle::None => None,
121         }
122     }
123 
outer_sep(&self) -> char124     fn outer_sep(&self) -> char {
125         match self {
126             BorderStyle::Unicode => '│',
127             BorderStyle::Ascii => '|',
128             BorderStyle::None => ' ',
129         }
130     }
131 
inner_sep(&self) -> char132     fn inner_sep(&self) -> char {
133         match self {
134             BorderStyle::Unicode => '┊',
135             BorderStyle::Ascii => '|',
136             BorderStyle::None => ' ',
137         }
138     }
139 }
140 
141 pub struct Printer<'a, Writer: Write> {
142     idx: u64,
143     /// The raw bytes used as input for the current line.
144     raw_line: Vec<u8>,
145     /// The buffered line built with each byte, ready to print to writer.
146     buffer_line: Vec<u8>,
147     writer: &'a mut Writer,
148     show_color: bool,
149     border_style: BorderStyle,
150     header_was_printed: bool,
151     byte_hex_table: Vec<String>,
152     byte_char_table: Vec<String>,
153     squeezer: Squeezer,
154     display_offset: u64,
155 }
156 
157 impl<'a, Writer: Write> Printer<'a, Writer> {
new( writer: &'a mut Writer, show_color: bool, border_style: BorderStyle, use_squeeze: bool, ) -> Printer<'a, Writer>158     pub fn new(
159         writer: &'a mut Writer,
160         show_color: bool,
161         border_style: BorderStyle,
162         use_squeeze: bool,
163     ) -> Printer<'a, Writer> {
164         Printer {
165             idx: 1,
166             raw_line: vec![],
167             buffer_line: vec![],
168             writer,
169             show_color,
170             border_style,
171             header_was_printed: false,
172             byte_hex_table: (0u8..=u8::max_value())
173                 .map(|i| {
174                     let byte_hex = format!("{:02x} ", i);
175                     if show_color {
176                         Byte(i).color().paint(byte_hex).to_string()
177                     } else {
178                         byte_hex
179                     }
180                 })
181                 .collect(),
182             byte_char_table: (0u8..=u8::max_value())
183                 .map(|i| {
184                     let byte_char = format!("{}", Byte(i).as_char());
185                     if show_color {
186                         Byte(i).color().paint(byte_char).to_string()
187                     } else {
188                         byte_char
189                     }
190                 })
191                 .collect(),
192             squeezer: Squeezer::new(use_squeeze),
193             display_offset: 0,
194         }
195     }
196 
display_offset(&mut self, display_offset: u64) -> &mut Self197     pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {
198         self.display_offset = display_offset;
199         self
200     }
201 
header(&mut self)202     pub fn header(&mut self) {
203         if let Some(border_elements) = self.border_style.header_elems() {
204             let h = border_elements.horizontal_line;
205             let h8 = h.to_string().repeat(8);
206             let h25 = h.to_string().repeat(25);
207 
208             writeln!(
209                 self.writer,
210                 "{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}",
211                 l = border_elements.left_corner,
212                 c = border_elements.column_separator,
213                 r = border_elements.right_corner,
214                 h8 = h8,
215                 h25 = h25
216             )
217             .ok();
218         }
219     }
220 
footer(&mut self)221     pub fn footer(&mut self) {
222         if let Some(border_elements) = self.border_style.footer_elems() {
223             let h = border_elements.horizontal_line;
224             let h8 = h.to_string().repeat(8);
225             let h25 = h.to_string().repeat(25);
226 
227             writeln!(
228                 self.writer,
229                 "{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}",
230                 l = border_elements.left_corner,
231                 c = border_elements.column_separator,
232                 r = border_elements.right_corner,
233                 h8 = h8,
234                 h25 = h25
235             )
236             .ok();
237         }
238     }
239 
print_position_indicator(&mut self)240     fn print_position_indicator(&mut self) {
241         if !self.header_was_printed {
242             self.header();
243             self.header_was_printed = true;
244         }
245 
246         let style = COLOR_OFFSET.normal();
247         let byte_index = format!("{:08x}", self.idx - 1 + self.display_offset);
248         let formatted_string = if self.show_color {
249             format!("{}", style.paint(byte_index))
250         } else {
251             byte_index
252         };
253         let _ = write!(
254             &mut self.buffer_line,
255             "{}{}{} ",
256             self.border_style.outer_sep(),
257             formatted_string,
258             self.border_style.outer_sep()
259         );
260     }
261 
print_byte(&mut self, b: u8) -> io::Result<()>262     pub fn print_byte(&mut self, b: u8) -> io::Result<()> {
263         if self.idx % 16 == 1 {
264             self.print_position_indicator();
265         }
266 
267         write!(&mut self.buffer_line, "{}", self.byte_hex_table[b as usize])?;
268         self.raw_line.push(b);
269 
270         self.squeezer.process(b, self.idx);
271 
272         match self.idx % 16 {
273             8 => {
274                 let _ = write!(&mut self.buffer_line, "{} ", self.border_style.inner_sep());
275             }
276             0 => {
277                 self.print_textline()?;
278             }
279             _ => {}
280         }
281 
282         self.idx += 1;
283 
284         Ok(())
285     }
286 
print_textline(&mut self) -> io::Result<()>287     pub fn print_textline(&mut self) -> io::Result<()> {
288         let len = self.raw_line.len();
289 
290         if len == 0 {
291             if self.squeezer.active() {
292                 self.print_position_indicator();
293                 let _ = writeln!(
294                     &mut self.buffer_line,
295                     "{0:1$}{4}{0:2$}{5}{0:3$}{4}{0:3$}{5}",
296                     "",
297                     24,
298                     25,
299                     8,
300                     self.border_style.inner_sep(),
301                     self.border_style.outer_sep(),
302                 );
303                 self.writer.write_all(&self.buffer_line)?;
304             }
305             return Ok(());
306         }
307 
308         let squeeze_action = self.squeezer.action();
309 
310         if squeeze_action != SqueezeAction::Delete {
311             if len < 8 {
312                 let _ = write!(
313                     &mut self.buffer_line,
314                     "{0:1$}{3}{0:2$}{4}",
315                     "",
316                     3 * (8 - len),
317                     1 + 3 * 8,
318                     self.border_style.inner_sep(),
319                     self.border_style.outer_sep(),
320                 );
321             } else {
322                 let _ = write!(
323                     &mut self.buffer_line,
324                     "{0:1$}{2}",
325                     "",
326                     3 * (16 - len),
327                     self.border_style.outer_sep()
328                 );
329             }
330 
331             let mut idx = 1;
332             for &b in self.raw_line.iter() {
333                 let _ = write!(
334                     &mut self.buffer_line,
335                     "{}",
336                     self.byte_char_table[b as usize]
337                 );
338 
339                 if idx == 8 {
340                     let _ = write!(&mut self.buffer_line, "{}", self.border_style.inner_sep());
341                 }
342 
343                 idx += 1;
344             }
345 
346             if len < 8 {
347                 let _ = writeln!(
348                     &mut self.buffer_line,
349                     "{0:1$}{3}{0:2$}{4}",
350                     "",
351                     8 - len,
352                     8,
353                     self.border_style.inner_sep(),
354                     self.border_style.outer_sep(),
355                 );
356             } else {
357                 let _ = writeln!(
358                     &mut self.buffer_line,
359                     "{0:1$}{2}",
360                     "",
361                     16 - len,
362                     self.border_style.outer_sep()
363                 );
364             }
365         }
366 
367         match squeeze_action {
368             SqueezeAction::Print => {
369                 self.buffer_line.clear();
370                 let style = COLOR_OFFSET.normal();
371                 let asterisk = if self.show_color {
372                     format!("{}", style.paint("*"))
373                 } else {
374                     String::from("*")
375                 };
376                 let _ = writeln!(
377                     &mut self.buffer_line,
378                     "{5}{0}{1:2$}{5}{1:3$}{6}{1:3$}{5}{1:4$}{6}{1:4$}{5}",
379                     asterisk,
380                     "",
381                     7,
382                     25,
383                     8,
384                     self.border_style.outer_sep(),
385                     self.border_style.inner_sep(),
386                 );
387             }
388             SqueezeAction::Delete => self.buffer_line.clear(),
389             SqueezeAction::Ignore => (),
390         }
391 
392         self.writer.write_all(&self.buffer_line)?;
393 
394         self.raw_line.clear();
395         self.buffer_line.clear();
396 
397         self.squeezer.advance();
398 
399         Ok(())
400     }
401 
header_was_printed(&self) -> bool402     pub fn header_was_printed(&self) -> bool {
403         self.header_was_printed
404     }
405 
406     /// Loop through the given `Reader`, printing until the `Reader` buffer
407     /// is exhausted.
print_all<Reader: Read>( &mut self, mut reader: Reader, ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>408     pub fn print_all<Reader: Read>(
409         &mut self,
410         mut reader: Reader,
411     ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
412         let mut buffer = [0; BUFFER_SIZE];
413         'mainloop: loop {
414             let size = reader.read(&mut buffer)?;
415             if size == 0 {
416                 break;
417             }
418 
419             for b in &buffer[..size] {
420                 let res = self.print_byte(*b);
421 
422                 if res.is_err() {
423                     // Broken pipe
424                     break 'mainloop;
425                 }
426             }
427         }
428 
429         // Finish last line
430         self.print_textline().ok();
431 
432         if !self.header_was_printed() {
433             self.header();
434             writeln!(
435                 self.writer,
436                 "│        │ No content to print     │                         │        │        │"
437             )
438             .ok();
439         }
440         self.footer();
441 
442         Ok(())
443     }
444 }
445 
446 #[cfg(test)]
447 mod tests {
448     use std::io;
449     use std::str;
450 
451     use super::*;
452 
assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) -> ()453     fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) -> () {
454         let mut output = vec![];
455         let mut printer = Printer::new(&mut output, false, BorderStyle::Unicode, true);
456 
457         printer.print_all(input).unwrap();
458 
459         let actual_string: &str = str::from_utf8(&output).unwrap();
460         assert_eq!(actual_string, expected_string)
461     }
462 
463     #[test]
empty_file_passes()464     fn empty_file_passes() {
465         let input = io::empty();
466         let expected_string = "\
467 ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
468 │        │ No content to print     │                         │        │        │
469 └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
470 "
471         .to_owned();
472         assert_print_all_output(input, expected_string);
473     }
474 
475     #[test]
short_input_passes()476     fn short_input_passes() {
477         let input = io::Cursor::new(b"spam");
478         let expected_string = "\
479 ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
480 │00000000│ 73 70 61 6d             ┊                         │spam    ┊        │
481 └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
482 "
483         .to_owned();
484         assert_print_all_output(input, expected_string);
485     }
486 
487     #[test]
display_offset()488     fn display_offset() {
489         let input = io::Cursor::new(b"spamspamspamspamspam");
490         let expected_string = "\
491 ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
492 │deadbeef│ 73 70 61 6d 73 70 61 6d ┊ 73 70 61 6d 73 70 61 6d │spamspam┊spamspam│
493 │deadbeff│ 73 70 61 6d             ┊                         │spam    ┊        │
494 └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
495 "
496         .to_owned();
497 
498         let mut output = vec![];
499         let mut printer: Printer<Vec<u8>> =
500             Printer::new(&mut output, false, BorderStyle::Unicode, true);
501         printer.display_offset(0xdeadbeef);
502 
503         printer.print_all(input).unwrap();
504 
505         let actual_string: &str = str::from_utf8(&output).unwrap();
506         assert_eq!(actual_string, expected_string)
507     }
508 }
509