blob: b980733bba05424382b630d6843363eebd66de32 [file] [log] [blame]
use crate::report::Styled;
pub fn write_diff(
writer: &mut dyn std::fmt::Write,
expected: &crate::Data,
actual: &crate::Data,
expected_name: Option<&dyn std::fmt::Display>,
actual_name: Option<&dyn std::fmt::Display>,
palette: crate::report::Palette,
) -> Result<(), std::fmt::Error> {
#[allow(unused_mut)]
let mut rendered = false;
#[cfg(feature = "diff")]
if let (Some(expected), Some(actual)) = (expected.render(), actual.render()) {
write_diff_inner(
writer,
&expected,
&actual,
expected_name,
actual_name,
palette,
)?;
rendered = true;
}
if !rendered {
if let Some(expected_name) = expected_name {
writeln!(writer, "{} {}:", expected_name, palette.error("(expected)"))?;
} else {
writeln!(writer, "{}:", palette.error("Expected"))?;
}
writeln!(writer, "{}", palette.error(&expected))?;
if let Some(actual_name) = actual_name {
writeln!(writer, "{} {}:", actual_name, palette.info("(actual)"))?;
} else {
writeln!(writer, "{}:", palette.info("Actual"))?;
}
writeln!(writer, "{}", palette.info(&actual))?;
}
Ok(())
}
#[cfg(feature = "diff")]
fn write_diff_inner(
writer: &mut dyn std::fmt::Write,
expected: &str,
actual: &str,
expected_name: Option<&dyn std::fmt::Display>,
actual_name: Option<&dyn std::fmt::Display>,
palette: crate::report::Palette,
) -> Result<(), std::fmt::Error> {
let timeout = std::time::Duration::from_millis(500);
let min_elide = 20;
let context = 5;
let changes = similar::TextDiff::configure()
.algorithm(similar::Algorithm::Patience)
.timeout(timeout)
.newline_terminated(false)
.diff_lines(expected, actual);
writeln!(writer)?;
if let Some(expected_name) = expected_name {
writeln!(
writer,
"{}",
palette.error(format_args!("{:->4} expected: {}", "", expected_name))
)?;
} else {
writeln!(writer, "{}", palette.error(format_args!("--- Expected")))?;
}
if let Some(actual_name) = actual_name {
writeln!(
writer,
"{}",
palette.info(format_args!("{:+>4} actual: {}", "", actual_name))
)?;
} else {
writeln!(writer, "{}", palette.info(format_args!("+++ Actual")))?;
}
let changes = changes
.ops()
.iter()
.flat_map(|op| changes.iter_inline_changes(op))
.collect::<Vec<_>>();
let tombstones = if min_elide < changes.len() {
let mut tombstones = vec![true; changes.len()];
let mut counter = context;
for (i, change) in changes.iter().enumerate() {
match change.tag() {
similar::ChangeTag::Insert | similar::ChangeTag::Delete => {
counter = context;
tombstones[i] = false;
}
similar::ChangeTag::Equal => {
if counter != 0 {
tombstones[i] = false;
counter -= 1;
}
}
}
}
let mut counter = context;
for (i, change) in changes.iter().enumerate().rev() {
match change.tag() {
similar::ChangeTag::Insert | similar::ChangeTag::Delete => {
counter = context;
tombstones[i] = false;
}
similar::ChangeTag::Equal => {
if counter != 0 {
tombstones[i] = false;
counter -= 1;
}
}
}
}
tombstones
} else {
Vec::new()
};
let mut elided = false;
for (i, change) in changes.into_iter().enumerate() {
if tombstones.get(i).copied().unwrap_or(false) {
if !elided {
let sign = "⋮";
write!(writer, "{:>4} ", " ",)?;
write!(writer, "{:>4} ", " ",)?;
writeln!(writer, "{}", palette.hint(sign))?;
}
elided = true;
} else {
elided = false;
match change.tag() {
similar::ChangeTag::Insert => {
write_change(writer, change, "+", palette.actual, palette.info, palette)?;
}
similar::ChangeTag::Delete => {
write_change(
writer,
change,
"-",
palette.expected,
palette.error,
palette,
)?;
}
similar::ChangeTag::Equal => {
write_change(writer, change, "|", palette.hint, palette.hint, palette)?;
}
}
}
}
Ok(())
}
#[cfg(feature = "diff")]
fn write_change(
writer: &mut dyn std::fmt::Write,
change: similar::InlineChange<str>,
sign: &str,
em_style: crate::report::Style,
style: crate::report::Style,
palette: crate::report::Palette,
) -> Result<(), std::fmt::Error> {
if let Some(index) = change.old_index() {
write!(writer, "{:>4} ", palette.hint(index + 1),)?;
} else {
write!(writer, "{:>4} ", " ",)?;
}
if let Some(index) = change.new_index() {
write!(writer, "{:>4} ", palette.hint(index + 1),)?;
} else {
write!(writer, "{:>4} ", " ",)?;
}
write!(writer, "{} ", Styled::new(sign, style))?;
for &(emphasized, change) in change.values() {
let cur_style = if emphasized { em_style } else { style };
write!(writer, "{}", Styled::new(change, cur_style))?;
}
if change.missing_newline() {
writeln!(writer, "{}", Styled::new("∅", em_style))?;
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[cfg(feature = "diff")]
#[test]
fn diff_eq() {
let expected = "Hello\nWorld\n";
let expected_name = "A";
let actual = "Hello\nWorld\n";
let actual_name = "B";
let palette = crate::report::Palette::plain();
let mut actual_diff = String::new();
write_diff_inner(
&mut actual_diff,
expected,
actual,
Some(&expected_name),
Some(&actual_name),
palette,
)
.unwrap();
let expected_diff = "
---- expected: A
++++ actual: B
1 1 | Hello
2 2 | World
";
assert_eq!(expected_diff, actual_diff);
}
#[cfg(feature = "diff")]
#[test]
fn diff_ne_line_missing() {
let expected = "Hello\nWorld\n";
let expected_name = "A";
let actual = "Hello\n";
let actual_name = "B";
let palette = crate::report::Palette::plain();
let mut actual_diff = String::new();
write_diff_inner(
&mut actual_diff,
expected,
actual,
Some(&expected_name),
Some(&actual_name),
palette,
)
.unwrap();
let expected_diff = "
---- expected: A
++++ actual: B
1 1 | Hello
2 - World
";
assert_eq!(expected_diff, actual_diff);
}
#[cfg(feature = "diff")]
#[test]
fn diff_eq_trailing_extra_newline() {
let expected = "Hello\nWorld";
let expected_name = "A";
let actual = "Hello\nWorld\n";
let actual_name = "B";
let palette = crate::report::Palette::plain();
let mut actual_diff = String::new();
write_diff_inner(
&mut actual_diff,
expected,
actual,
Some(&expected_name),
Some(&actual_name),
palette,
)
.unwrap();
let expected_diff = "
---- expected: A
++++ actual: B
1 1 | Hello
2 - World
2 + World
";
assert_eq!(expected_diff, actual_diff);
}
#[cfg(feature = "diff")]
#[test]
fn diff_eq_trailing_newline_missing() {
let expected = "Hello\nWorld\n";
let expected_name = "A";
let actual = "Hello\nWorld";
let actual_name = "B";
let palette = crate::report::Palette::plain();
let mut actual_diff = String::new();
write_diff_inner(
&mut actual_diff,
expected,
actual,
Some(&expected_name),
Some(&actual_name),
palette,
)
.unwrap();
let expected_diff = "
---- expected: A
++++ actual: B
1 1 | Hello
2 - World
2 + World
";
assert_eq!(expected_diff, actual_diff);
}
#[cfg(feature = "diff")]
#[test]
fn diff_eq_elided() {
let mut expected = String::new();
expected.push_str("Hello\n");
for i in 0..20 {
expected.push_str(&i.to_string());
expected.push('\n');
}
expected.push_str("World\n");
for i in 0..20 {
expected.push_str(&i.to_string());
expected.push('\n');
}
expected.push_str("!\n");
let expected_name = "A";
let mut actual = String::new();
actual.push_str("Goodbye\n");
for i in 0..20 {
actual.push_str(&i.to_string());
actual.push('\n');
}
actual.push_str("Moon\n");
for i in 0..20 {
actual.push_str(&i.to_string());
actual.push('\n');
}
actual.push_str("?\n");
let actual_name = "B";
let palette = crate::report::Palette::plain();
let mut actual_diff = String::new();
write_diff_inner(
&mut actual_diff,
&expected,
&actual,
Some(&expected_name),
Some(&actual_name),
palette,
)
.unwrap();
let expected_diff = "
---- expected: A
++++ actual: B
1 - Hello
1 + Goodbye
2 2 | 0
3 3 | 1
4 4 | 2
5 5 | 3
6 6 | 4
17 17 | 15
18 18 | 16
19 19 | 17
20 20 | 18
21 21 | 19
22 - World
22 + Moon
23 23 | 0
24 24 | 1
25 25 | 2
26 26 | 3
27 27 | 4
38 38 | 15
39 39 | 16
40 40 | 17
41 41 | 18
42 42 | 19
43 - !
43 + ?
";
assert_eq!(expected_diff, actual_diff);
}
}