use std::fmt;
use ansi_term::{
Colour::{Fixed, Green, Red},
Style,
};
const SIGN_RIGHT: char = '>'; const SIGN_LEFT: char = '<';
#[cfg(test)]
#[macro_export]
macro_rules! assert_eq_str {
($left:expr_2021, $right:expr_2021$(,)?) => ({
$crate::assert_eq_str!(@ $left, $right, "", "");
});
($left:expr_2021, $right:expr_2021, $($arg:tt)*) => ({
$crate::assert_eq_str!(@ $left, $right, ": ", $($arg)+);
});
(@ $left:expr_2021, $right:expr_2021, $maybe_semicolon:expr_2021, $($arg:tt)*) => ({
match (&($left), &($right)) {
(left, right) => {
if !(*left == *right) {
::std::panic!("assertion failed: `(left == right)`{}{}\
\n\
\n{}\
\n",
$maybe_semicolon,
format_args!($($arg)*),
$crate::util::pretty_diff::MyStrs { left, right }
)
}
}
}
});
}
macro_rules! paint {
($f:expr_2021, $colour:expr_2021, $fmt:expr_2021, $($args:tt)*) => (
write!($f, "{}", $colour.paint(format!($fmt, $($args)*)))
)
}
#[cfg(test)]
pub(crate) struct MyStrs<'a> {
pub(crate) left: &'a str,
pub(crate) right: &'a str,
}
#[cfg(test)]
impl std::fmt::Display for MyStrs<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write_line_diff(f, self.left, self.right)
}
}
pub fn write_line_diff<TWrite: fmt::Write>(
f: &mut TWrite,
expected: &str,
actual: &str,
) -> fmt::Result {
let diff = ::diff::lines(expected, actual);
let mut changes = diff.into_iter().peekable();
let mut previous_deletion = LatentDeletion::default();
while let Some(change) = changes.next() {
match (change, changes.peek()) {
(::diff::Result::Both(value, _), _) => {
previous_deletion.flush(f)?;
writeln!(f, " {value}")?;
}
(::diff::Result::Left(deleted), _) => {
previous_deletion.flush(f)?;
previous_deletion.set(deleted);
}
(::diff::Result::Right(_), Some(::diff::Result::Left(_))) => {
panic!("insertion followed by deletion");
}
(::diff::Result::Right(inserted), Some(::diff::Result::Right(_))) => {
previous_deletion.flush(f)?;
paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?;
writeln!(f)?;
}
(::diff::Result::Right(inserted), _) => {
if let Some(deleted) = previous_deletion.take() {
write_inline_diff(f, deleted, inserted)?;
} else {
previous_deletion.flush(f)?;
paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?;
writeln!(f)?;
}
}
};
}
previous_deletion.flush(f)?;
Ok(())
}
#[derive(Default)]
struct LatentDeletion<'a> {
value: Option<&'a str>,
count: usize,
}
impl<'a> LatentDeletion<'a> {
fn set(&mut self, value: &'a str) {
self.value = Some(value);
self.count += 1;
}
fn take(&mut self) -> Option<&'a str> {
if self.count == 1 {
self.value.take()
} else {
None
}
}
fn flush<TWrite: fmt::Write>(&mut self, f: &mut TWrite) -> fmt::Result {
if let Some(value) = self.value {
paint!(f, Red, "{}{}", SIGN_LEFT, value)?;
writeln!(f)?;
self.value = None;
} else {
self.count = 0;
}
Ok(())
}
}
struct InlineWriter<'a, Writer> {
f: &'a mut Writer,
style: Style,
}
impl<'a, Writer> InlineWriter<'a, Writer>
where
Writer: fmt::Write,
{
fn new(f: &'a mut Writer) -> 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 finish(&mut self) -> fmt::Result {
writeln!(self.f, "{}", self.style.suffix())?;
self.style = Default::default();
Ok(())
}
}
fn write_inline_diff<TWrite: fmt::Write>(f: &mut TWrite, left: &str, right: &str) -> fmt::Result {
let diff = ::diff::chars(left, right);
let mut writer = InlineWriter::new(f);
let light = Red.into();
let heavy = Red.on(Fixed(52)).bold();
writer.write_with_style(&SIGN_LEFT, light)?;
for change in diff.iter() {
match change {
::diff::Result::Both(value, _) => writer.write_with_style(value, light)?,
::diff::Result::Left(value) => writer.write_with_style(value, heavy)?,
_ => (),
}
}
writer.finish()?;
let light = Green.into();
let heavy = Green.on(Fixed(22)).bold();
writer.write_with_style(&SIGN_RIGHT, light)?;
for change in diff.iter() {
match change {
::diff::Result::Both(value, _) => writer.write_with_style(value, light)?,
::diff::Result::Right(value) => writer.write_with_style(value, heavy)?,
_ => (),
}
}
writer.finish()
}