Adds squeeze support

The squeeze feature is implement as for hexdump. Reoccurring lines will
be squeezed together and visualized with an asterisk. Further hexyl
received an additional command line argument '-v|--no-squeezing' to
disable squeezing. Squeezing is enabled by default.

If the squeezing goes until the end of the input data, an empty line
with address next to last line will be printed to indicate the highest
address (again similar to hexdump).
This commit is contained in:
Armin Widegreen authored and David Peter committed 2019-04-13 18:19:15 +02:00
1 parent 2c7c857ccc
commit 6978f7a277
2 files changed
+344 -21

No files matched your search

+84 -21
View File
@@ -4,6 +4,8 @@ extern crate ansi_term;
extern crate atty; extern crate atty;
extern crate ctrlc; extern crate ctrlc;
mod squeezer;
use std::fs::File; use std::fs::File;
use std::io::{self, prelude::*, StdoutLock}; use std::io::{self, prelude::*, StdoutLock};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@@ -15,6 +17,7 @@ use ansi_term::Color;
use ansi_term::Color::Fixed; use ansi_term::Color::Fixed;
use atty::Stream; use atty::Stream;
use squeezer::{SqueezeAction, Squeezer};
const BUFFER_SIZE: usize = 256; const BUFFER_SIZE: usize = 256;
@@ -159,10 +162,16 @@ struct Printer<'a> {
header_was_printed: bool, header_was_printed: bool,
byte_hex_table: Vec<String>, byte_hex_table: Vec<String>,
byte_char_table: Vec<String>, byte_char_table: Vec<String>,
squeezer: Squeezer,
} }
impl<'a> Printer<'a> { 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 { Printer {
idx: 1, idx: 1,
raw_line: vec![], raw_line: vec![],
@@ -191,6 +200,7 @@ impl<'a> Printer<'a> {
} }
}) })
.collect(), .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<()> { fn print_byte(&mut self, b: u8) -> io::Result<()> {
if self.idx % 16 == 1 { if self.idx % 16 == 1 {
if !self.header_was_printed { self.print_position_indicator();
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()
);
} }
write!(&mut self.buffer_line, "{}", self.byte_hex_table[b as usize])?; write!(&mut self.buffer_line, "{}", self.byte_hex_table[b as usize])?;
self.raw_line.push(b); self.raw_line.push(b);
self.squeezer.process(&b, &self.idx);
match self.idx % 16 { match self.idx % 16 {
8 => { 8 => {
let _ = write!(&mut self.buffer_line, "{} ", self.border_style.inner_sep()); 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(); let len = self.raw_line.len();
if len == 0 { 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(()); return Ok(());
} }
@@ -334,6 +364,27 @@ impl<'a> Printer<'a> {
self.border_style.outer_sep() 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.stdout.write_all(&self.buffer_line)?;
self.raw_line.clear(); self.raw_line.clear();
@@ -368,6 +419,16 @@ fn run() -> Result<(), Box<::std::error::Error>> {
.value_name("N") .value_name("N")
.help("An alias for -n/--length"), .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(
Arg::with_name("color") Arg::with_name("color")
.long("color") .long("color")
@@ -422,6 +483,8 @@ fn run() -> Result<(), Box<::std::error::Error>> {
_ => BorderStyle::None, _ => BorderStyle::None,
}; };
let squeeze = !matches.is_present("nosqueezing");
// Set up Ctrl-C handler // Set up Ctrl-C handler
let cancelled = Arc::new(AtomicBool::new(false)); let cancelled = Arc::new(AtomicBool::new(false));
let c = cancelled.clone(); let c = cancelled.clone();
@@ -432,7 +495,7 @@ fn run() -> Result<(), Box<::std::error::Error>> {
.expect("Error setting Ctrl-C handler"); .expect("Error setting Ctrl-C handler");
let stdout = io::stdout(); 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]; let mut buffer = [0; BUFFER_SIZE];
'mainloop: loop { 'mainloop: loop {
+260
View File
@@ -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<u8> = 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]);
}
}
}