diff --git a/src/main.rs b/src/main.rs index 34fe918..4437633 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ extern crate ansi_term; extern crate atty; extern crate ctrlc; +mod squeezer; + use std::fs::File; use std::io::{self, prelude::*, StdoutLock}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -15,6 +17,7 @@ use ansi_term::Color; use ansi_term::Color::Fixed; use atty::Stream; +use squeezer::{SqueezeAction, Squeezer}; const BUFFER_SIZE: usize = 256; @@ -159,10 +162,16 @@ struct Printer<'a> { 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) -> Printer { + fn new( + stdout: StdoutLock, + show_color: bool, + border_style: BorderStyle, + use_squeeze: bool, + ) -> Printer { Printer { idx: 1, raw_line: vec![], @@ -191,6 +200,7 @@ impl<'a> Printer<'a> { } }) .collect(), + squeezer: Squeezer::new(use_squeeze), } } @@ -232,32 +242,38 @@ impl<'a> Printer<'a> { } } + 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 { - 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() - ); + 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()); @@ -277,6 +293,20 @@ impl<'a> Printer<'a> { 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(()); } @@ -334,6 +364,27 @@ impl<'a> Printer<'a> { self.border_style.outer_sep() ); } + + match self.squeezer.action() { + SqueezeAction::Print => { + self.buffer_line.clear(); + let style = COLOR_OFFSET.normal(); + let _ = writeln!( + &mut self.buffer_line, + "{5}{0}{1:2$}{5}{1:3$}{6}{1:3$}{5}{1:4$}{6}{1:4$}{5}", + style.paint("*"), + "", + 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(); @@ -368,6 +419,16 @@ fn run() -> Result<(), Box<::std::error::Error>> { .value_name("N") .help("An alias for -n/--length"), ) + .arg( + Arg::with_name("nosqueezing") + .short("v") + .long("no-squeezing") + .help( + "Displays all input data. Otherwise any number of groups of output \ + lines which would be identical to the preceding group of lines, are \ + replaced with a line comprised of a single asterisk.", + ), + ) .arg( Arg::with_name("color") .long("color") @@ -422,6 +483,8 @@ fn run() -> Result<(), Box<::std::error::Error>> { _ => BorderStyle::None, }; + let squeeze = !matches.is_present("nosqueezing"); + // Set up Ctrl-C handler let cancelled = Arc::new(AtomicBool::new(false)); let c = cancelled.clone(); @@ -432,7 +495,7 @@ fn run() -> Result<(), Box<::std::error::Error>> { .expect("Error setting Ctrl-C handler"); let stdout = io::stdout(); - let mut printer = Printer::new(stdout.lock(), show_color, border_style); + let mut printer = Printer::new(stdout.lock(), show_color, border_style, squeeze); let mut buffer = [0; BUFFER_SIZE]; 'mainloop: loop { diff --git a/src/squeezer.rs b/src/squeezer.rs new file mode 100644 index 0000000..a40ec47 --- /dev/null +++ b/src/squeezer.rs @@ -0,0 +1,260 @@ +#[derive(Debug, PartialEq)] +enum SqueezeState { + /// not enabled + Disabled, + /// Will be set from all states if equal condition can't be hold up. + /// Set if previous byte is not equal the current processed byte. + NoSqueeze, + /// Valid for a whole line to identify if it is candidate for squeezing + Probe, + /// Squeeze line parsing is active, but EOL is not reached yet + SqueezeActive, + /// Squeeze line, EOL is reached, will influence the action + Squeeze, + /// same as Squeeze, however this is only for the first line after + /// the squeeze candidate has been set. + SqueezeFirstLine, + /// same as SqueezeActive, however this is only for the first line after + /// the squeeze candidate has been set. + SqueezeActiveFirstLine, +} + +pub struct Squeezer { + state: SqueezeState, + byte: u8, +} + +#[derive(Debug, PartialEq)] +pub enum SqueezeAction { + Ignore, + Print, + Delete, +} + +/// line size +const LSIZE: usize = 16; + +impl Squeezer { + pub fn new(enabled: bool) -> Squeezer { + Squeezer { + state: if enabled { + SqueezeState::Probe + } else { + SqueezeState::Disabled + }, + byte: 0, + } + } + + pub fn process(&mut self, b: &u8, i: &usize) { + use self::SqueezeState::*; + if self.state == Disabled { + return; + } + let eq = *b == self.byte; + + if i % LSIZE == 0 { + self.state = match self.state { + NoSqueeze => Probe, + Probe => SqueezeActiveFirstLine, + SqueezeActiveFirstLine => SqueezeFirstLine, + SqueezeFirstLine => SqueezeActive, + SqueezeActive => Squeeze, + Squeeze => SqueezeActive, + Disabled => Disabled, + }; + } else if !eq { + if (i % LSIZE == 1 && self.state != Probe) || i % LSIZE != 1 { + self.state = NoSqueeze; + } + } + + self.byte = b.clone(); + } + + pub fn active(&self) -> bool { + use self::SqueezeState::*; + match self.state { + Squeeze | SqueezeActive | SqueezeFirstLine | SqueezeActiveFirstLine => true, + _ => false, + } + } + + pub fn action(&mut self) -> SqueezeAction { + match self.state { + SqueezeState::SqueezeFirstLine => { + self.state = SqueezeState::SqueezeActive; + SqueezeAction::Print + } + SqueezeState::Squeeze => { + self.state = SqueezeState::SqueezeActive; + SqueezeAction::Delete + } + _ => SqueezeAction::Ignore, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn three_same_lines() { + const LINES: usize = 3; + let v = vec![0u8; LINES * LSIZE]; + let mut s = Squeezer::new(true); + // just initialized + assert_eq!(s.action(), SqueezeAction::Ignore); + + let exp = vec![ + SqueezeAction::Ignore, // first line, print as is + SqueezeAction::Print, // print squeeze symbol + SqueezeAction::Delete, // delete reoccurring line + ]; + + for z in 0..LINES { + for i in z * LSIZE..(z + 1) * LSIZE { + s.process(&v[i], &(i + 1)); + } + assert_eq!(s.action(), exp[z]); + } + } + + #[test] + fn incomplete_while_squeeze() { + const LINES: usize = 3; + // third line only has 12 bytes and should be printed + let v = vec![0u8; LINES * LSIZE + 12]; + let mut s = Squeezer::new(true); + // just initialized + assert_eq!(s.action(), SqueezeAction::Ignore); + + let exp = vec![ + SqueezeAction::Ignore, // first line, print as is + SqueezeAction::Print, // print squeeze symbol + SqueezeAction::Delete, // delete reoccurring line + SqueezeAction::Ignore, // last line only 12 bytes, print it + ]; + + for z in 0..LINES { + for i in z * LSIZE..(z + 1) * LSIZE { + s.process(&v[i], &(i + 1)); + } + assert_eq!(s.action(), exp[z]); + } + + for i in LSIZE * LINES..LINES * LSIZE + 12 { + s.process(&v[i], &(i + 1)); + } + assert_eq!(s.action(), exp[3]); + } + + #[test] + /// all three lines are different, print all + fn three_different_lines() { + const LINES: usize = 3; + let mut v: Vec = vec![]; + v.extend(vec![0u8; 16]); + v.extend(vec![1u8; 16]); + v.extend(vec![2u8; 16]); + + let mut s = Squeezer::new(true); + // just initialized + assert_eq!(s.action(), SqueezeAction::Ignore); + + let exp = vec![ + SqueezeAction::Ignore, // first line, print as is + SqueezeAction::Ignore, // different + SqueezeAction::Ignore, // different + ]; + + for z in 0..LINES { + for i in z * LSIZE..(z + 1) * LSIZE { + s.process(&v[i], &(i + 1)); + } + assert_eq!(s.action(), exp[z]); + } + } + + #[test] + /// first two lines same, hence squeeze symbol, third line diff, hence + /// print + fn one_squeeze_no_delete() { + const LINES: usize = 3; + let mut v = vec![0u8; (LINES - 1) * LSIZE]; + v.extend(vec![1u8; 16]); + + let mut s = Squeezer::new(true); + // just initialized + assert_eq!(s.action(), SqueezeAction::Ignore); + + let exp = vec![ + SqueezeAction::Ignore, // first line, print as is + SqueezeAction::Print, // print squeeze symbol + SqueezeAction::Ignore, // different lines, print again + ]; + + for z in 0..LINES { + for i in z * LSIZE..(z + 1) * LSIZE { + s.process(&v[i], &(i + 1)); + } + assert_eq!(s.action(), exp[z]); + } + } + + #[test] + /// First line all eq, 2nd half eq with first line, then change + fn second_line_different() { + const LINES: usize = 2; + let mut v = vec![0u8; (LINES - 1) * LSIZE]; + v.extend(vec![0u8; 8]); + v.extend(vec![1u8; 8]); + + let mut s = Squeezer::new(true); + // just initialized + assert_eq!(s.action(), SqueezeAction::Ignore); + + let exp = vec![ + SqueezeAction::Ignore, // first line, print as is + SqueezeAction::Ignore, // print squeeze symbol + ]; + + for z in 0..LINES { + for i in z * LSIZE..(z + 1) * LSIZE { + s.process(&v[i], &(i + 1)); + } + assert_eq!(s.action(), exp[z]); + } + } + + #[test] + /// all three lines never become squeeze candidate (diff within line) + fn never_squeeze_candidate() { + const LINES: usize = 3; + let mut v = vec![]; + v.extend(vec![0u8; 8]); + v.extend(vec![1u8; 8]); + v.extend(vec![0u8; 8]); + v.extend(vec![1u8; 8]); + v.extend(vec![0u8; 8]); + v.extend(vec![1u8; 8]); + + let mut s = Squeezer::new(true); + // just initialized + assert_eq!(s.action(), SqueezeAction::Ignore); + + let exp = vec![ + SqueezeAction::Ignore, // first line, print as is + SqueezeAction::Ignore, // print squeeze symbol + SqueezeAction::Ignore, // print squeeze symbol + ]; + + for z in 0..LINES { + for i in z * LSIZE..(z + 1) * LSIZE { + s.process(&v[i], &(i + 1)); + } + assert_eq!(s.action(), exp[z]); + } + } +}