use similar::{ChangeTag, TextDiff};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct LineDelta {
insertions: usize,
deletions: usize,
}
impl LineDelta {
#[must_use]
pub fn between(original: &str, formatted: &str) -> Self {
let mut delta = Self::default();
for change in TextDiff::from_lines(original, formatted).iter_all_changes() {
match change.tag() {
ChangeTag::Insert => delta.insertions += 1,
ChangeTag::Delete => delta.deletions += 1,
ChangeTag::Equal => {}
}
}
delta
}
#[must_use]
pub const fn insertions(self) -> usize { self.insertions }
#[must_use]
pub const fn deletions(self) -> usize { self.deletions }
#[must_use]
pub const fn has_changes(self) -> bool { self.insertions > 0 || self.deletions > 0 }
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::LineDelta;
#[rstest]
#[case::pure_insertion("alpha\n", "alpha\nbeta\n", 1, 0, true)]
#[case::pure_deletion("alpha\nbeta\n", "alpha\n", 0, 1, true)]
#[case::replacement("alpha\n", "beta\n", 1, 1, true)]
#[case::identical("alpha\nbeta\n", "alpha\nbeta\n", 0, 0, false)]
#[case::line_ending_change("alpha\n", "alpha\r\n", 1, 1, true)]
#[case::lone_carriage_return("alpha\rbeta\n", "alpha\rgamma\n", 1, 1, true)]
fn between_counts_lines(
#[case] original: &str,
#[case] formatted: &str,
#[case] insertions: usize,
#[case] deletions: usize,
#[case] changes: bool,
) {
let delta = LineDelta::between(original, formatted);
assert_eq!(
(delta.insertions(), delta.deletions()),
(insertions, deletions)
);
assert_eq!(delta.has_changes(), changes);
}
}