|
#![allow(unused)] |
|
|
|
use std::fmt::{self, Display}; |
|
use std::iter::repeat; |
|
|
|
#[macro_export] |
|
macro_rules! hex_compare { |
|
($($name:expr => $data:expr;)*) => { |
|
HexCompare::new(vec![$( |
|
HexColumn::new($name, $data, false), |
|
)*]) |
|
}; |
|
} |
|
|
|
pub struct HexColumn<'a> { |
|
name: String, |
|
data: &'a [u8], |
|
text: bool, |
|
} |
|
|
|
impl<'a> HexColumn<'a> { |
|
pub fn new<S: Into<String>>(name: S, data: &'a [u8], text: bool) -> Self { |
|
HexColumn{ |
|
name: name.into(), |
|
data, |
|
text: false, |
|
} |
|
} |
|
} |
|
|
|
pub struct HexCompare<'a> { |
|
ident: String, |
|
tab_head: bool, |
|
line_num: bool, |
|
data_cols: Option<usize>, |
|
max_line: Option<usize>, |
|
max_width: Option<usize>, |
|
groups: Vec<HexColumn<'a>>, |
|
} |
|
|
|
impl<'a> HexCompare<'a> { |
|
pub fn new(groups: Vec<HexColumn<'a>>) -> Self { |
|
HexCompare { |
|
ident: String::new(), |
|
tab_head: false, |
|
line_num: true, |
|
data_cols: None, |
|
max_line: None, |
|
max_width: None, |
|
groups, |
|
} |
|
} |
|
|
|
pub fn ident(mut self, ident: &str) -> Self { |
|
self.ident = ident.to_owned(); |
|
self |
|
} |
|
|
|
pub fn tab_head(mut self, th: bool) -> Self { |
|
self.tab_head = th; |
|
self |
|
} |
|
|
|
pub fn line_num(mut self, ln: bool) -> Self { |
|
self.line_num = ln; |
|
self |
|
} |
|
|
|
pub fn auto_data_cols(mut self) -> Self { |
|
self.data_cols = None; |
|
self |
|
} |
|
|
|
pub fn data_cols(mut self, pl: usize) -> Self { |
|
self.data_cols = Some(pl); |
|
self |
|
} |
|
|
|
pub fn no_max_line(mut self) -> Self { |
|
self.max_line = None; |
|
self |
|
} |
|
|
|
pub fn max_line(mut self, ml: usize) -> Self { |
|
self.max_line = Some(ml); |
|
self |
|
} |
|
|
|
pub fn max_width(mut self, mw: usize) -> Self { |
|
self.max_width = Some(mw); |
|
self |
|
} |
|
|
|
pub fn auto_width(mut self) -> Self { |
|
self.max_width = None; |
|
self |
|
} |
|
|
|
pub fn text(mut self, name: &str, text: bool) -> Self { |
|
for i in 0..self.groups.len() { |
|
if self.groups[i].name == name { |
|
self.groups[i].text = text; |
|
} |
|
} |
|
self |
|
} |
|
} |
|
|
|
impl<'a> Display for HexCompare<'a> { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
if self.groups.len() == 0 { |
|
return Ok(()); |
|
} |
|
|
|
// g: count of groups, t: count of groups with text, n: maximun data length, |
|
// p: identation length, k: line number column length, w: maximum width, |
|
// c: data column count, l: line count, omit: range of lines to be omited, |
|
let g = self.groups.len(); |
|
let t = self.groups.iter().filter(|d| d.text).count(); |
|
let n = self.groups.iter().map(|d| d.data.len()).max().unwrap(); |
|
let p = self.ident.len(); |
|
let k = if !self.line_num { |
|
0 |
|
} else { |
|
hex_width(n).max(4) + 2 |
|
}; |
|
let mut w = self.max_width.unwrap_or(80); |
|
if w + 1 < p + k + t + 3 * g + t { |
|
w = p + k + t + 3 * g + t - 1; |
|
} |
|
let c = self |
|
.data_cols |
|
.unwrap_or_else(|| (w + 1 - p - k - t) / (3 * g + t)) |
|
.max(1); |
|
let l = if n % c == 0 { n / c } else { n / c + 1 }; |
|
|
|
let mut omit = 0..0; |
|
if let Some(mut m) = self.max_line { |
|
if self.tab_head && m > 1 { |
|
m -= 1; |
|
} |
|
if m > 1 && m < l { |
|
omit = m / 2..m / 2 + (l - m); |
|
} |
|
} |
|
|
|
if self.tab_head { |
|
write!(f, "{}", self.ident)?; |
|
write!(f, "{:>width$}", "|", width = k)?; |
|
for (i, grp) in self.groups.iter().enumerate() { |
|
if i != 0 { |
|
write!(f, "|")?; |
|
} |
|
let w = if grp.text { 4 * c } else { 3 * c - 1 }; |
|
write!(f, "{:^width$}", grp.name, width = w)?; |
|
} |
|
writeln!(f, "")?; |
|
} |
|
|
|
let mut xbuf: Vec<u8> = Vec::with_capacity(c); |
|
let mut tbuf = String::with_capacity(c); |
|
let mut ln = 0; |
|
while ln < l { |
|
write!(f, "{}", self.ident)?; |
|
if ln >= omit.start && ln < omit.end { |
|
write!(f, "{:^width$}|", "...", width = k - 1)?; |
|
for (i, grp) in self.groups.iter().enumerate() { |
|
if i != 0 { |
|
write!(f, "|")?; |
|
} |
|
let w = if grp.text { 4 * c } else { 3 * c - 1 }; |
|
write!(f, "{:^width$}", "...", width = w)?; |
|
} |
|
writeln!(f, "")?; |
|
ln = omit.end; |
|
continue; |
|
} |
|
|
|
let off = ln * c; |
|
if self.line_num { |
|
write!(f, "{:0width$x} |", off, width = k - 2)?; |
|
} |
|
for (i, grp) in self.groups.iter().enumerate() { |
|
if i != 0 { |
|
write!(f, "|")?; |
|
} |
|
|
|
xbuf.truncate(0); |
|
tbuf.truncate(0); |
|
if off + c <= grp.data.len() { |
|
xbuf.extend(&grp.data[off..off + c]); |
|
tbuf.extend(grp.data[off..off + c].iter().map(|&b| view_acsii(b))); |
|
} else if off < grp.data.len() { |
|
xbuf.extend(&grp.data[off..]); |
|
tbuf.extend(grp.data[off..].iter().map(|&b| view_acsii(b))); |
|
} |
|
|
|
for (j, mayb) in xbuf |
|
.iter() |
|
.map(Some) |
|
.chain(repeat(None).take(c - xbuf.len())) |
|
.enumerate() |
|
{ |
|
if j != 0 { |
|
write!(f, " ")?; |
|
} |
|
if let Some(&b) = mayb { |
|
write!(f, "{:02x}", b)?; |
|
} else { |
|
write!(f, " ")?; |
|
} |
|
} |
|
if grp.text { |
|
write!(f, " {:<width$}", tbuf, width = c)?; |
|
} |
|
} |
|
writeln!(f, "")?; |
|
ln += 1; |
|
} |
|
|
|
Ok(()) |
|
} |
|
} |
|
|
|
fn hex_width(x: usize) -> usize { |
|
let mut mask = std::usize::MAX; |
|
while mask != 0 && x & (mask >> 1) == x { |
|
mask >>= 1; |
|
} |
|
let mut n = 0; |
|
while mask != 0 { |
|
mask >>= 1; |
|
n += 1; |
|
} |
|
if n % 4 == 0 { |
|
n / 4 |
|
} else { |
|
n / 4 + 1 |
|
} |
|
} |
|
|
|
fn view_acsii(byte: u8) -> char { |
|
if byte < 32 || byte >= 127 { |
|
'.' |
|
} else { |
|
byte as char |
|
} |
|
} |