use std::fmt;
use nu_ansi_term::{Color, Color::Red, Style};
#[macro_export]
macro_rules! assert_hex_eq {
($left:expr, $right:expr$(,)?) => ({
$crate::assert_hex_eq!(@ $left, $right, "", "");
});
($left:expr, $right:expr, $($arg:tt)*) => ({
$crate::assert_hex_eq!(@ $left, $right, ": ", $($arg)+);
});
(@ $left:expr, $right:expr, $maybe_semicolon:expr, $($arg:tt)*) => ({
let to_diff = $crate::hex_diff::ToDiff { left: $left, right: $right };
if (to_diff.left != to_diff.right) {
::std::panic!("assertion failed: `(left == right)`{}{}\
\n\
\n{}\
\n",
$maybe_semicolon,
format_args!($($arg)*),
to_diff,
)
}
});
}
pub struct ToDiff<'a> {
pub left: &'a [u8],
pub right: &'a [u8],
}
impl std::fmt::Display for ToDiff<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write_diff(f, self.make_diff(), 4)
}
}
enum DiffItem {
Same(u8),
Different(u8),
Blank,
}
struct DiffResult {
left: Vec<DiffItem>,
right: Vec<DiffItem>,
}
impl ToDiff<'_> {
fn make_diff(&self) -> DiffResult {
let mut left = Vec::new();
let mut right = Vec::new();
for item in diff::slice(self.left, self.right) {
match item {
diff::Result::Both(byte, _) => {
while left.len() != right.len() {
if left.len() < right.len() {
left.push(DiffItem::Blank);
} else {
right.push(DiffItem::Blank);
}
}
left.push(DiffItem::Same(*byte));
right.push(DiffItem::Same(*byte));
}
diff::Result::Left(byte) => left.push(DiffItem::Different(*byte)),
diff::Result::Right(byte) => right.push(DiffItem::Different(*byte)),
}
}
while left.len() < right.len() {
left.push(DiffItem::Blank);
}
while right.len() < left.len() {
right.push(DiffItem::Blank);
}
DiffResult { left, right }
}
}
fn write_diff(f: &mut impl fmt::Write, diff: DiffResult, width: usize) -> fmt::Result {
let DiffResult { left, right } = diff;
assert_eq!(left.len(), right.len());
for (left, right) in left.chunks(width).zip(right.chunks(width)) {
write!(f, " ")?;
let mut writer = InlineWriter::new(f);
for item in left {
writer.write_item(item)?;
}
for _ in 0..width - left.len() {
writer.write_padding()?;
}
writer.write_with_style(&'|', Color::White.dimmed())?;
writer.write_with_style(&' ', Color::White.dimmed())?;
for item in right {
writer.write_item(item)?;
}
for _ in 0..width - left.len() {
writer.write_padding()?;
}
writer.finish()?;
}
Ok(())
}
struct InlineWriter<'a, W> {
f: &'a mut W,
style: Style,
}
impl<'a, W> InlineWriter<'a, W>
where
W: fmt::Write,
{
fn new(f: &'a mut W) -> Self {
InlineWriter {
f,
style: Style::new(),
}
}
fn write_with_style(&mut self, c: &char, style: Style) -> fmt::Result {
if style == self.style {
write!(self.f, "{c}")?;
} else {
write!(self.f, "{}", self.style.suffix())?;
write!(self.f, "{}{c}", style.prefix())?;
self.style = style;
}
Ok(())
}
fn write_padding(&mut self) -> fmt::Result {
write!(self.f, " ")
}
fn write_item(&mut self, item: &DiffItem) -> fmt::Result {
let style = match item {
DiffItem::Same(_) | DiffItem::Blank => Style::default(),
DiffItem::Different(_) => Red.into(),
};
let chars = match item {
DiffItem::Same(val) | DiffItem::Different(val) => to_hex_digits(*val),
DiffItem::Blank => ['_', '_'],
};
self.write_with_style(&chars[0], style)?;
self.write_with_style(&chars[1], style)?;
self.write_with_style(&' ', style)
}
fn finish(&mut self) -> fmt::Result {
writeln!(self.f, "{}", self.style.suffix())?;
self.style = Default::default();
Ok(())
}
}
fn to_hex_digits(byte: u8) -> [char; 2] {
let mut out = ['0', '0'];
let first = byte & 0x0f;
out[1] = to_hex(first);
let second = (byte & 0xf0) >> 4;
out[0] = to_hex(second);
out
}
fn to_hex(int: u8) -> char {
debug_assert!(int <= 15);
if int < 10 {
(b'0' + int).into()
} else {
(b'a' + int - 10).into()
}
}