use similar::{ChangeTag, TextDiff};
#[derive(Debug, Clone)]
pub struct DiffResult {
pub changed: bool,
pub diff_text: String,
pub diff_size: usize,
pub additions: usize,
pub deletions: usize,
pub summary: Option<String>,
}
pub fn diff(old: &str, new: &str) -> DiffResult {
if old == new {
return DiffResult {
changed: false,
diff_text: String::new(),
diff_size: 0,
additions: 0,
deletions: 0,
summary: None,
};
}
let text_diff = TextDiff::from_words(old, new);
let mut diff_text = String::new();
let mut additions = 0;
let mut deletions = 0;
for change in text_diff.iter_all_changes() {
match change.tag() {
ChangeTag::Delete => {
diff_text.push_str(&format!("[-{}]", change.value()));
deletions += 1;
}
ChangeTag::Insert => {
diff_text.push_str(&format!("[+{}]", change.value()));
additions += 1;
}
ChangeTag::Equal => {
let value = change.value();
if value.len() <= 20 {
diff_text.push_str(value);
}
}
}
}
let diff_size = diff_text.len();
let summary = generate_summary(additions, deletions);
DiffResult {
changed: true,
diff_text,
diff_size,
additions,
deletions,
summary,
}
}
fn generate_summary(additions: usize, deletions: usize) -> Option<String> {
if additions > 0 && deletions > 0 {
Some(format!("+{} / -{} changes", additions, deletions))
} else if additions > 0 {
Some(format!("+{} additions", additions))
} else if deletions > 0 {
Some(format!("-{} removals", deletions))
} else {
None
}
}
pub fn unified_diff(old: &str, new: &str, context_lines: usize) -> String {
let text_diff = TextDiff::from_lines(old, new);
text_diff
.unified_diff()
.context_radius(context_lines)
.header("old", "new")
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_change() {
let result = diff("Hello World", "Hello World");
assert!(!result.changed);
assert_eq!(result.diff_size, 0);
}
#[test]
fn test_simple_change() {
let result = diff("Hello World", "Hello Universe");
assert!(result.changed);
assert!(result.diff_text.contains("[-World]"));
assert!(result.diff_text.contains("[+Universe]"));
}
#[test]
fn test_additions_deletions() {
let result = diff("one two three", "one four three");
assert!(result.changed);
assert_eq!(result.deletions, 1);
assert_eq!(result.additions, 1);
}
#[test]
fn test_unified_diff() {
let old = "line1\nline2\nline3";
let new = "line1\nmodified\nline3";
let unified = unified_diff(old, new, 1);
assert!(unified.contains("-line2"));
assert!(unified.contains("+modified"));
}
#[test]
fn test_generic_summary() {
let result = diff("foo bar baz", "foo qux baz");
assert!(result.changed);
assert!(result.summary.is_some());
let summary = result.summary.unwrap();
assert!(summary.contains("+") || summary.contains("-"));
}
}