1use similar::TextDiff;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum Outcome {
10 Current,
11 Drift(String),
13}
14
15fn normalize(text: &str) -> String {
16 text.replace("\r\n", "\n")
17}
18
19pub fn compare(committed: &str, fresh: &str, label: &str) -> Outcome {
22 let committed = normalize(committed);
23 let fresh = normalize(fresh);
24 if committed == fresh {
25 return Outcome::Current;
26 }
27 let diff = TextDiff::from_lines(&committed, &fresh)
28 .unified_diff()
29 .header(&format!("a/{label}"), &format!("b/{label}"))
30 .to_string();
31 Outcome::Drift(diff)
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn identical_text_is_current() {
40 assert_eq!(compare("a\nb\n", "a\nb\n", "m.c4"), Outcome::Current);
41 }
42
43 #[test]
44 fn crlf_in_the_committed_text_is_not_drift() {
45 assert_eq!(compare("a\r\nb\r\n", "a\nb\n", "m.c4"), Outcome::Current);
46 }
47
48 #[test]
49 fn a_changed_line_is_drift_with_both_versions_and_the_label_in_the_header() {
50 match compare("a\nb\nc\n", "a\nB\nc\n", "docs/model.c4") {
51 Outcome::Drift(diff) => {
52 assert!(
53 diff.starts_with("--- a/docs/model.c4\n+++ b/docs/model.c4\n"),
54 "{diff}"
55 );
56 assert!(diff.contains("\n-b\n"), "{diff}");
57 assert!(diff.contains("\n+B\n"), "{diff}");
58 }
59 other => panic!("expected Drift, got {other:?}"),
60 }
61 }
62
63 #[test]
64 fn an_added_line_is_drift() {
65 assert!(matches!(compare("a\n", "a\nb\n", "m"), Outcome::Drift(_)));
66 }
67}