use similar::{ChangeTag, TextDiff};
#[must_use]
pub fn diff_lines(expected: &str, actual: &str) -> String {
let diff = TextDiff::from_lines(expected, actual);
let mut out = String::new();
for change in diff.iter_all_changes() {
let marker = match change.tag() {
ChangeTag::Delete => '-',
ChangeTag::Insert => '+',
ChangeTag::Equal => ' ',
};
out.push(marker);
let value = change.value();
out.push_str(value);
if !value.ends_with('\n') {
out.push('\n');
}
}
if out.ends_with('\n') {
out.pop();
}
out
}
#[cfg(test)]
mod tests {
use test_better_core::TestResult;
use super::*;
use crate::{check, eq, is_false};
#[test]
fn equal_input_is_all_context_lines() -> TestResult {
check!(diff_lines("one\ntwo", "one\ntwo")).satisfies(eq(" one\n two".to_string()))?;
Ok(())
}
#[test]
fn a_changed_line_becomes_a_delete_then_an_insert() -> TestResult {
let diff = diff_lines("keep\nold\nkeep", "keep\nnew\nkeep");
check!(diff).satisfies(eq(" keep\n-old\n+new\n keep".to_string()))?;
Ok(())
}
#[test]
fn has_no_trailing_newline() -> TestResult {
let diff = diff_lines("a\n", "b\n");
check!(diff.ends_with('\n')).satisfies(is_false())?;
Ok(())
}
}