diff --git a/src/bin/hexyl.rs b/src/bin/hexyl.rs index 9934645..dd41e14 100644 --- a/src/bin/hexyl.rs +++ b/src/bin/hexyl.rs @@ -5,398 +5,18 @@ use atty; use ctrlc; use std::fs::File; -use std::io::{self, prelude::*, StdoutLock}; +use std::io::{self, prelude::*}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use clap::{App, AppSettings, Arg}; -use ansi_term::Color; -use ansi_term::Color::Fixed; - use atty::Stream; -use hexyl::squeezer::{SqueezeAction, Squeezer}; + +use hexyl::{BorderStyle, Printer}; const BUFFER_SIZE: usize = 256; -const COLOR_NULL: Color = Fixed(242); // grey -const COLOR_OFFSET: Color = Fixed(242); // grey -const COLOR_ASCII_PRINTABLE: Color = Color::Cyan; -const COLOR_ASCII_WHITESPACE: Color = Color::Green; -const COLOR_ASCII_OTHER: Color = Color::Purple; -const COLOR_NONASCII: Color = Color::Yellow; - -enum ByteCategory { - Null, - AsciiPrintable, - AsciiWhitespace, - AsciiOther, - NonAscii, -} - -#[derive(Copy, Clone)] -struct Byte(u8); - -impl Byte { - fn category(self) -> ByteCategory { - if self.0 == 0x00 { - ByteCategory::Null - } else if self.0.is_ascii_alphanumeric() - || self.0.is_ascii_punctuation() - || self.0.is_ascii_graphic() - { - ByteCategory::AsciiPrintable - } else if self.0.is_ascii_whitespace() { - ByteCategory::AsciiWhitespace - } else if self.0.is_ascii() { - ByteCategory::AsciiOther - } else { - ByteCategory::NonAscii - } - } - - fn color(self) -> &'static Color { - use crate::ByteCategory::*; - - match self.category() { - Null => &COLOR_NULL, - AsciiPrintable => &COLOR_ASCII_PRINTABLE, - AsciiWhitespace => &COLOR_ASCII_WHITESPACE, - AsciiOther => &COLOR_ASCII_OTHER, - NonAscii => &COLOR_NONASCII, - } - } - - fn as_char(self) -> char { - use crate::ByteCategory::*; - - match self.category() { - Null => '0', - AsciiPrintable => self.0 as char, - AsciiWhitespace if self.0 == 0x20 => ' ', - AsciiWhitespace => '_', - AsciiOther => '•', - NonAscii => '×', - } - } -} - -struct BorderElements { - left_corner: char, - horizontal_line: char, - column_separator: char, - right_corner: char, -} - -enum BorderStyle { - Unicode, - Ascii, - None, -} - -impl BorderStyle { - fn header_elems(&self) -> Option { - match self { - BorderStyle::Unicode => Some(BorderElements { - left_corner: '┌', - horizontal_line: '─', - column_separator: '┬', - right_corner: '┐', - }), - BorderStyle::Ascii => Some(BorderElements { - left_corner: '+', - horizontal_line: '-', - column_separator: '+', - right_corner: '+', - }), - BorderStyle::None => None, - } - } - - fn footer_elems(&self) -> Option { - match self { - BorderStyle::Unicode => Some(BorderElements { - left_corner: '└', - horizontal_line: '─', - column_separator: '┴', - right_corner: '┘', - }), - BorderStyle::Ascii => Some(BorderElements { - left_corner: '+', - horizontal_line: '-', - column_separator: '+', - right_corner: '+', - }), - BorderStyle::None => None, - } - } - - fn outer_sep(&self) -> char { - match self { - BorderStyle::Unicode => '│', - BorderStyle::Ascii => '|', - BorderStyle::None => ' ', - } - } - - fn inner_sep(&self) -> char { - match self { - BorderStyle::Unicode => '┊', - BorderStyle::Ascii => '|', - BorderStyle::None => ' ', - } - } -} - -struct Printer<'a> { - idx: usize, - /// The raw bytes used as input for the current line. - raw_line: Vec, - /// The buffered line built with each byte, ready to print to stdout. - buffer_line: Vec, - stdout: StdoutLock<'a>, - show_color: bool, - border_style: BorderStyle, - header_was_printed: bool, - byte_hex_table: Vec, - byte_char_table: Vec, - squeezer: Squeezer, -} - -impl<'a> Printer<'a> { - fn new( - stdout: StdoutLock<'_>, - show_color: bool, - border_style: BorderStyle, - use_squeeze: bool, - ) -> Printer<'_> { - Printer { - idx: 1, - raw_line: vec![], - buffer_line: vec![], - stdout, - show_color, - border_style, - header_was_printed: false, - byte_hex_table: (0u8..=u8::max_value()) - .map(|i| { - let byte_hex = format!("{:02x} ", i); - if show_color { - Byte(i).color().paint(byte_hex).to_string() - } else { - byte_hex - } - }) - .collect(), - byte_char_table: (0u8..=u8::max_value()) - .map(|i| { - let byte_char = format!("{}", Byte(i).as_char()); - if show_color { - Byte(i).color().paint(byte_char).to_string() - } else { - byte_char - } - }) - .collect(), - squeezer: Squeezer::new(use_squeeze), - } - } - - fn header(&mut self) { - if let Some(border_elements) = self.border_style.header_elems() { - let h = border_elements.horizontal_line; - let h8 = h.to_string().repeat(8); - let h25 = h.to_string().repeat(25); - - writeln!( - self.stdout, - "{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}", - l = border_elements.left_corner, - c = border_elements.column_separator, - r = border_elements.right_corner, - h8 = h8, - h25 = h25 - ) - .ok(); - } - } - - fn footer(&mut self) { - if let Some(border_elements) = self.border_style.footer_elems() { - let h = border_elements.horizontal_line; - let h8 = h.to_string().repeat(8); - let h25 = h.to_string().repeat(25); - - writeln!( - self.stdout, - "{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}", - l = border_elements.left_corner, - c = border_elements.column_separator, - r = border_elements.right_corner, - h8 = h8, - h25 = h25 - ) - .ok(); - } - } - - fn print_position_indicator(&mut self) { - if !self.header_was_printed { - self.header(); - self.header_was_printed = true; - } - - let style = COLOR_OFFSET.normal(); - let byte_index = format!("{:08x}", self.idx - 1); - let formatted_string = if self.show_color { - format!("{}", style.paint(byte_index)) - } else { - byte_index - }; - let _ = write!( - &mut self.buffer_line, - "{}{}{} ", - self.border_style.outer_sep(), - formatted_string, - self.border_style.outer_sep() - ); - } - - fn print_byte(&mut self, b: u8) -> io::Result<()> { - if self.idx % 16 == 1 { - self.print_position_indicator(); - } - - write!(&mut self.buffer_line, "{}", self.byte_hex_table[b as usize])?; - self.raw_line.push(b); - - self.squeezer.process(b, self.idx); - - match self.idx % 16 { - 8 => { - let _ = write!(&mut self.buffer_line, "{} ", self.border_style.inner_sep()); - } - 0 => { - self.print_textline()?; - } - _ => {} - } - - self.idx += 1; - - Ok(()) - } - - fn print_textline(&mut self) -> io::Result<()> { - let len = self.raw_line.len(); - - if len == 0 { - if self.squeezer.active() { - self.print_position_indicator(); - let _ = writeln!( - &mut self.buffer_line, - "{0:1$}{4}{0:2$}{5}{0:3$}{4}{0:3$}{5}", - "", - 24, - 25, - 8, - self.border_style.inner_sep(), - self.border_style.outer_sep(), - ); - self.stdout.write_all(&self.buffer_line)?; - } - return Ok(()); - } - - if len < 8 { - let _ = write!( - &mut self.buffer_line, - "{0:1$}{3}{0:2$}{4}", - "", - 3 * (8 - len), - 1 + 3 * 8, - self.border_style.inner_sep(), - self.border_style.outer_sep(), - ); - } else { - let _ = write!( - &mut self.buffer_line, - "{0:1$}{2}", - "", - 3 * (16 - len), - self.border_style.outer_sep() - ); - } - - let mut idx = 1; - for &b in self.raw_line.iter() { - let _ = write!( - &mut self.buffer_line, - "{}", - self.byte_char_table[b as usize] - ); - - if idx == 8 { - let _ = write!(&mut self.buffer_line, "{}", self.border_style.inner_sep()); - } - - idx += 1; - } - - if len < 8 { - let _ = writeln!( - &mut self.buffer_line, - "{0:1$}{3}{0:2$}{4} ", - "", - 8 - len, - 8, - self.border_style.inner_sep(), - self.border_style.outer_sep(), - ); - } else { - let _ = writeln!( - &mut self.buffer_line, - "{0:1$}{2}", - "", - 16 - len, - self.border_style.outer_sep() - ); - } - - match self.squeezer.action() { - SqueezeAction::Print => { - self.buffer_line.clear(); - let style = COLOR_OFFSET.normal(); - let asterisk = if self.show_color { - format!("{}", style.paint("*")) - } else { - String::from("*") - }; - let _ = writeln!( - &mut self.buffer_line, - "{5}{0}{1:2$}{5}{1:3$}{6}{1:3$}{5}{1:4$}{6}{1:4$}{5}", - asterisk, - "", - 7, - 25, - 8, - self.border_style.outer_sep(), - self.border_style.inner_sep(), - ); - } - SqueezeAction::Delete => self.buffer_line.clear(), - SqueezeAction::Ignore => (), - } - - self.stdout.write_all(&self.buffer_line)?; - - self.raw_line.clear(); - self.buffer_line.clear(); - - Ok(()) - } -} - fn run() -> Result<(), Box> { let app = App::new(crate_name!()) .setting(AppSettings::ColorAuto) @@ -524,7 +144,7 @@ fn run() -> Result<(), Box> { // Finish last line printer.print_textline().ok(); - if !printer.header_was_printed { + if !printer.header_was_printed() { printer.header(); } printer.footer(); diff --git a/src/lib.rs b/src/lib.rs index c08e964..5860b4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,390 @@ pub mod squeezer; + +use std::io::{self, StdoutLock, Write}; + +use ansi_term::Color; +use ansi_term::Color::Fixed; + +use squeezer::{SqueezeAction, Squeezer}; + +const COLOR_NULL: Color = Fixed(242); // grey +const COLOR_OFFSET: Color = Fixed(242); // grey +const COLOR_ASCII_PRINTABLE: Color = Color::Cyan; +const COLOR_ASCII_WHITESPACE: Color = Color::Green; +const COLOR_ASCII_OTHER: Color = Color::Purple; +const COLOR_NONASCII: Color = Color::Yellow; + +pub enum ByteCategory { + Null, + AsciiPrintable, + AsciiWhitespace, + AsciiOther, + NonAscii, +} + +#[derive(Copy, Clone)] +struct Byte(u8); + +impl Byte { + fn category(self) -> ByteCategory { + if self.0 == 0x00 { + ByteCategory::Null + } else if self.0.is_ascii_alphanumeric() + || self.0.is_ascii_punctuation() + || self.0.is_ascii_graphic() + { + ByteCategory::AsciiPrintable + } else if self.0.is_ascii_whitespace() { + ByteCategory::AsciiWhitespace + } else if self.0.is_ascii() { + ByteCategory::AsciiOther + } else { + ByteCategory::NonAscii + } + } + + fn color(self) -> &'static Color { + use crate::ByteCategory::*; + + match self.category() { + Null => &COLOR_NULL, + AsciiPrintable => &COLOR_ASCII_PRINTABLE, + AsciiWhitespace => &COLOR_ASCII_WHITESPACE, + AsciiOther => &COLOR_ASCII_OTHER, + NonAscii => &COLOR_NONASCII, + } + } + + fn as_char(self) -> char { + use crate::ByteCategory::*; + + match self.category() { + Null => '0', + AsciiPrintable => self.0 as char, + AsciiWhitespace if self.0 == 0x20 => ' ', + AsciiWhitespace => '_', + AsciiOther => '•', + NonAscii => '×', + } + } +} + +struct BorderElements { + left_corner: char, + horizontal_line: char, + column_separator: char, + right_corner: char, +} + +pub enum BorderStyle { + Unicode, + Ascii, + None, +} + +impl BorderStyle { + fn header_elems(&self) -> Option { + match self { + BorderStyle::Unicode => Some(BorderElements { + left_corner: '┌', + horizontal_line: '─', + column_separator: '┬', + right_corner: '┐', + }), + BorderStyle::Ascii => Some(BorderElements { + left_corner: '+', + horizontal_line: '-', + column_separator: '+', + right_corner: '+', + }), + BorderStyle::None => None, + } + } + + fn footer_elems(&self) -> Option { + match self { + BorderStyle::Unicode => Some(BorderElements { + left_corner: '└', + horizontal_line: '─', + column_separator: '┴', + right_corner: '┘', + }), + BorderStyle::Ascii => Some(BorderElements { + left_corner: '+', + horizontal_line: '-', + column_separator: '+', + right_corner: '+', + }), + BorderStyle::None => None, + } + } + + fn outer_sep(&self) -> char { + match self { + BorderStyle::Unicode => '│', + BorderStyle::Ascii => '|', + BorderStyle::None => ' ', + } + } + + fn inner_sep(&self) -> char { + match self { + BorderStyle::Unicode => '┊', + BorderStyle::Ascii => '|', + BorderStyle::None => ' ', + } + } +} + +pub struct Printer<'a> { + idx: usize, + /// The raw bytes used as input for the current line. + raw_line: Vec, + /// The buffered line built with each byte, ready to print to stdout. + buffer_line: Vec, + stdout: StdoutLock<'a>, + show_color: bool, + border_style: BorderStyle, + header_was_printed: bool, + byte_hex_table: Vec, + byte_char_table: Vec, + squeezer: Squeezer, +} + +impl<'a> Printer<'a> { + pub fn new( + stdout: StdoutLock<'_>, + show_color: bool, + border_style: BorderStyle, + use_squeeze: bool, + ) -> Printer<'_> { + Printer { + idx: 1, + raw_line: vec![], + buffer_line: vec![], + stdout, + show_color, + border_style, + header_was_printed: false, + byte_hex_table: (0u8..=u8::max_value()) + .map(|i| { + let byte_hex = format!("{:02x} ", i); + if show_color { + Byte(i).color().paint(byte_hex).to_string() + } else { + byte_hex + } + }) + .collect(), + byte_char_table: (0u8..=u8::max_value()) + .map(|i| { + let byte_char = format!("{}", Byte(i).as_char()); + if show_color { + Byte(i).color().paint(byte_char).to_string() + } else { + byte_char + } + }) + .collect(), + squeezer: Squeezer::new(use_squeeze), + } + } + + pub fn header(&mut self) { + if let Some(border_elements) = self.border_style.header_elems() { + let h = border_elements.horizontal_line; + let h8 = h.to_string().repeat(8); + let h25 = h.to_string().repeat(25); + + writeln!( + self.stdout, + "{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}", + l = border_elements.left_corner, + c = border_elements.column_separator, + r = border_elements.right_corner, + h8 = h8, + h25 = h25 + ) + .ok(); + } + } + + pub fn footer(&mut self) { + if let Some(border_elements) = self.border_style.footer_elems() { + let h = border_elements.horizontal_line; + let h8 = h.to_string().repeat(8); + let h25 = h.to_string().repeat(25); + + writeln!( + self.stdout, + "{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}", + l = border_elements.left_corner, + c = border_elements.column_separator, + r = border_elements.right_corner, + h8 = h8, + h25 = h25 + ) + .ok(); + } + } + + fn print_position_indicator(&mut self) { + if !self.header_was_printed { + self.header(); + self.header_was_printed = true; + } + + let style = COLOR_OFFSET.normal(); + let byte_index = format!("{:08x}", self.idx - 1); + let formatted_string = if self.show_color { + format!("{}", style.paint(byte_index)) + } else { + byte_index + }; + let _ = write!( + &mut self.buffer_line, + "{}{}{} ", + self.border_style.outer_sep(), + formatted_string, + self.border_style.outer_sep() + ); + } + + pub fn print_byte(&mut self, b: u8) -> io::Result<()> { + if self.idx % 16 == 1 { + self.print_position_indicator(); + } + + write!(&mut self.buffer_line, "{}", self.byte_hex_table[b as usize])?; + self.raw_line.push(b); + + self.squeezer.process(b, self.idx); + + match self.idx % 16 { + 8 => { + let _ = write!(&mut self.buffer_line, "{} ", self.border_style.inner_sep()); + } + 0 => { + self.print_textline()?; + } + _ => {} + } + + self.idx += 1; + + Ok(()) + } + + pub fn print_textline(&mut self) -> io::Result<()> { + let len = self.raw_line.len(); + + if len == 0 { + if self.squeezer.active() { + self.print_position_indicator(); + let _ = writeln!( + &mut self.buffer_line, + "{0:1$}{4}{0:2$}{5}{0:3$}{4}{0:3$}{5}", + "", + 24, + 25, + 8, + self.border_style.inner_sep(), + self.border_style.outer_sep(), + ); + self.stdout.write_all(&self.buffer_line)?; + } + return Ok(()); + } + + if len < 8 { + let _ = write!( + &mut self.buffer_line, + "{0:1$}{3}{0:2$}{4}", + "", + 3 * (8 - len), + 1 + 3 * 8, + self.border_style.inner_sep(), + self.border_style.outer_sep(), + ); + } else { + let _ = write!( + &mut self.buffer_line, + "{0:1$}{2}", + "", + 3 * (16 - len), + self.border_style.outer_sep() + ); + } + + let mut idx = 1; + for &b in self.raw_line.iter() { + let _ = write!( + &mut self.buffer_line, + "{}", + self.byte_char_table[b as usize] + ); + + if idx == 8 { + let _ = write!(&mut self.buffer_line, "{}", self.border_style.inner_sep()); + } + + idx += 1; + } + + if len < 8 { + let _ = writeln!( + &mut self.buffer_line, + "{0:1$}{3}{0:2$}{4} ", + "", + 8 - len, + 8, + self.border_style.inner_sep(), + self.border_style.outer_sep(), + ); + } else { + let _ = writeln!( + &mut self.buffer_line, + "{0:1$}{2}", + "", + 16 - len, + self.border_style.outer_sep() + ); + } + + match self.squeezer.action() { + SqueezeAction::Print => { + self.buffer_line.clear(); + let style = COLOR_OFFSET.normal(); + let asterisk = if self.show_color { + format!("{}", style.paint("*")) + } else { + String::from("*") + }; + let _ = writeln!( + &mut self.buffer_line, + "{5}{0}{1:2$}{5}{1:3$}{6}{1:3$}{5}{1:4$}{6}{1:4$}{5}", + asterisk, + "", + 7, + 25, + 8, + self.border_style.outer_sep(), + self.border_style.inner_sep(), + ); + } + SqueezeAction::Delete => self.buffer_line.clear(), + SqueezeAction::Ignore => (), + } + + self.stdout.write_all(&self.buffer_line)?; + + self.raw_line.clear(); + self.buffer_line.clear(); + + Ok(()) + } + + pub fn header_was_printed(&self) -> bool { + self.header_was_printed + } +}