#[macro_export]
macro_rules! assert_text_eq {
($left: expr, $right: expr $(,)?) => {
$crate::assert_text_eq!($left, $right, "assertion failed")
};
($left: expr, $right: expr, $($arg:tt)+) => {
match (&$left, &$right) {
(left_val, right_val) => {
let left_val: &str = left_val.as_ref();
let right_val: &str = right_val.as_ref();
if left_val != right_val {
$crate::print_diff_github_style(right_val, left_val);
panic!($($arg)+)
}
}
}
};
}
#[macro_export]
macro_rules! assert_text_starts_with {
($left: expr, $right: expr $(,)?) => {
$crate::assert_text_starts_with!($left, $right, "assertion failed")
};
($left: expr, $right: expr, $($arg:tt)+) => {
match (&$left, &$right) {
(left_val, right_val) => {
let left_val: &str = left_val.as_ref();
let right_val: &str = right_val.as_ref();
if !left_val.starts_with(right_val) {
let right_chars = right_val.chars().count();
let limit = left_val
.char_indices()
.nth(right_chars)
.map(|(idx, _)| idx)
.unwrap_or_else(|| left_val.len());
let edit = &left_val[..limit];
$crate::print_diff_github_style(right_val, edit);
panic!($($arg)+)
}
}
}
};
}
#[macro_export]
macro_rules! assert_text_ends_with {
($left: expr, $right: expr $(,)?) => {
$crate::assert_text_ends_with!($left, $right, "assertion failed")
};
($left: expr, $right: expr, $($arg:tt)+) => {
match (&$left, &$right) {
(left_val, right_val) => {
let left_val: &str = left_val.as_ref();
let right_val: &str = right_val.as_ref();
if !left_val.ends_with(right_val) {
let right_chars = right_val.chars().count();
let total_chars = left_val.chars().count();
let skip_chars = total_chars.saturating_sub(right_chars);
let limit = left_val
.char_indices()
.nth(skip_chars)
.map(|(idx, _)| idx)
.unwrap_or(0);
let edit = &left_val[limit..];
$crate::print_diff_github_style(right_val, edit);
panic!($($arg)+)
}
}
}
};
}
#[macro_export]
macro_rules! assert_text_contains {
($left: expr, $right: expr $(,)?) => {
match (&$left, &$right) {
(left_val, right_val) => {
let left_val: &str = left_val.as_ref();
let right_val: &str = right_val.as_ref();
if !left_val.contains(right_val) {
$crate::assert_text_contains!(
left_val,
right_val,
concat!("assertion failed\n", " left: \"{}\"\n", " right: \"{}\""),
left_val.escape_debug(),
right_val.escape_debug(),
)
}
}
}
};
($left: expr, $right: expr, $($arg:tt)+) => {
match (&$left, &$right) {
(left_val, right_val) => {
let left_val: &str = left_val.as_ref();
let right_val: &str = right_val.as_ref();
if !left_val.contains(right_val) {
panic!($($arg)+);
}
}
}
};
}
#[macro_export]
macro_rules! assert_text_match {
($left: expr, $right: expr $(,)?) => {
match (&$left, &$right) {
(left_val, right_val) => {
let left_val: &str = left_val.as_ref();
let right_val: &str = right_val.as_ref();
let re = regex::Regex::new(right_val).unwrap();
if !re.is_match(left_val) {
$crate::assert_text_match!(
left_val,
right_val,
concat!("assertion failed\n", " left: \"{}\"\n", " regex: \"{}\""),
left_val.escape_debug(),
right_val.escape_debug(),
)
}
}
}
};
($left: expr, $right: expr, $($arg:tt)+) => {
match (&$left, &$right) {
(left_val, right_val) => {
let left_val: &str = left_val.as_ref();
let right_val: &str = right_val.as_ref();
let re = regex::Regex::new(right_val).unwrap();
if !re.is_match(left_val) {
panic!($($arg)+);
}
}
}
};
}
use difference::{Changeset, Difference};
pub fn print_diff_github_style(text1: &str, text2: &str) {
let use_color = std::env::var("NO_COLOR").is_err();
let color_green = if use_color { "\x1b[32m" } else { "" };
let color_red = if use_color { "\x1b[31m" } else { "" };
let color_bright_green = if use_color { "\x1b[1;32m" } else { "" };
let color_reverse_red = if use_color { "\x1b[31;7m" } else { "" };
let color_reverse_green = if use_color { "\x1b[32;7m" } else { "" };
let color_end = if use_color { "\x1b[0m" } else { "" };
let mut out_s = String::new();
let Changeset { diffs, .. } = Changeset::new(text1, text2, "\n");
for i in 0..diffs.len() {
let s = match diffs[i] {
Difference::Same(ref y) => format_diff_line_same(y),
Difference::Add(ref y) => {
let opt = if i > 0 {
if let Difference::Rem(ref x) = diffs[i - 1] {
Some(format_diff_add_rem(
"+",
x,
y,
color_green,
color_reverse_green,
color_end,
))
} else {
None
}
} else {
None
};
match opt {
Some(a) => a,
None => format_diff_line_mark("+", y, color_bright_green, color_end),
}
}
Difference::Rem(ref y) => {
let opt = if i < diffs.len() - 1 {
if let Difference::Add(ref x) = diffs[i + 1] {
Some(format_diff_add_rem(
"-",
x,
y,
color_red,
color_reverse_red,
color_end,
))
} else {
None
}
} else {
None
};
match opt {
Some(a) => a,
None => format_diff_line_mark("-", y, color_red, color_end),
}
}
};
out_s.push_str(s.as_str());
}
print!("{}", out_s.as_str());
}
#[inline(never)]
fn format_diff_line_same(y: &str) -> String {
let mut s = String::with_capacity(y.len() + 2);
for line in y.split_terminator('\n') {
s.reserve(line.len() + 2);
s.push(' ');
s.push_str(line);
s.push('\n');
}
s
}
#[inline(never)]
fn format_diff_line_mark(
mark: &str, y: &str,
color_start: &str,
color_end: &str,
) -> String {
let line_count = y.split_terminator('\n').count();
let extra_per_line = color_start.len() + mark.len() + color_end.len() + 1;
let mut s = String::with_capacity(y.len() + (line_count * extra_per_line));
for line in y.split_terminator('\n') {
s.push_str(color_start);
s.push_str(mark);
s.push_str(line);
s.push_str(color_end);
s.push('\n');
}
s
}
#[inline(never)]
fn format_diff_add_rem(
mark: &str, x: &str,
y: &str,
color_fore: &str,
color_reverse: &str,
color_end: &str,
) -> String {
#[derive(PartialEq, Copy, Clone)]
enum Cattr {
None,
Fore,
Reve,
}
let mut ca_v: Vec<(Cattr, &str)> = vec![(Cattr::Fore, mark)];
let changeset = Changeset::new(x, y, " ");
for c in &changeset.diffs {
match c {
Difference::Same(ref z) => {
for line in z.split_terminator('\n') {
ca_v.push((Cattr::Fore, line));
ca_v.push((Cattr::None, "\n"));
ca_v.push((Cattr::Fore, mark));
}
let bytes = z.as_bytes();
let len = bytes.len();
if len >= 1 && bytes[len - 1] != b'\n' {
ca_v.pop();
ca_v.pop();
}
ca_v.push((Cattr::Fore, " "));
}
Difference::Add(ref z) => {
for line in z.split_terminator('\n') {
ca_v.push((Cattr::Reve, line));
ca_v.push((Cattr::None, "\n"));
ca_v.push((Cattr::Fore, mark));
}
let bytes = z.as_bytes();
let len = bytes.len();
if len >= 1 && bytes[len - 1] != b'\n' {
ca_v.pop();
ca_v.pop();
}
ca_v.push((Cattr::Fore, " "));
}
_ => {}
};
}
let mut out_s = String::with_capacity(x.len().max(y.len()) * 2);
let mut prev_a: Cattr = Cattr::None;
for (cat, st) in &ca_v {
if prev_a != *cat {
if prev_a != Cattr::None {
out_s.push_str(color_end)
}
if *cat == Cattr::Fore {
out_s.push_str(color_fore);
} else if *cat == Cattr::Reve {
out_s.push_str(color_reverse);
}
prev_a = *cat;
}
out_s.push_str(st);
}
if prev_a != Cattr::None {
out_s.push_str(color_end);
}
out_s.push('\n');
out_s
}