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