1use std::path::Path;
2use similar::{ChangeTag, TextDiff};
3
4#[derive(Debug, Default)]
6pub struct DiffStats {
7 pub files_changed: usize,
8 pub lines_added: usize,
9 pub lines_removed: usize,
10}
11
12impl DiffStats {
13 pub fn add(&mut self, other: &DiffStats) {
14 self.files_changed += other.files_changed;
15 self.lines_added += other.lines_added;
16 self.lines_removed += other.lines_removed;
17 }
18
19 pub fn print_summary(&self) {
20 println!("\nSummary:");
21 println!("Files changed: {}", self.files_changed);
22 println!("Lines added: {}", self.lines_added);
23 println!("Lines removed: {}", self.lines_removed);
24 }
25}
26
27pub fn generate_unified_diff(
37 path: &Path,
38 original: &str,
39 modified: &str,
40 context_lines: usize,
41) -> (String, DiffStats) {
42 let diff = TextDiff::from_lines(original, modified);
43
44 let mut output = String::new();
45 let mut stats = DiffStats::default();
46
47 let path_str = path.display().to_string();
49 output.push_str(&format!("--- {}\n", path_str));
50 output.push_str(&format!("+++ {}\n", path_str));
51
52 for change in diff.iter_all_changes() {
54 match change.tag() {
55 ChangeTag::Insert => stats.lines_added += 1,
56 ChangeTag::Delete => stats.lines_removed += 1,
57 ChangeTag::Equal => {}
58 }
59 }
60
61 let unified = diff.unified_diff()
63 .context_radius(context_lines)
64 .to_string();
65
66 output.push_str(&unified);
67
68 if stats.lines_added > 0 || stats.lines_removed > 0 {
69 stats.files_changed = 1;
70 }
71
72 (output, stats)
73}
74
75pub fn print_diff(path: &Path, original: &str, modified: &str) -> DiffStats {
86 let (diff_output, stats) = generate_unified_diff(path, original, modified, 3);
87
88 if stats.files_changed > 0 {
90 print!("{}", diff_output);
91 }
92
93 stats
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use std::path::PathBuf;
100
101 #[test]
102 fn test_generate_unified_diff() {
103 let original = "pub struct User {\n id: u64,\n name: String,\n}\n";
104 let modified = "pub struct User {\n id: u64,\n age: u32,\n name: String,\n}\n";
105 let path = PathBuf::from("src/user.rs");
106
107 let (diff, stats) = generate_unified_diff(&path, original, modified, 3);
108
109 assert!(diff.contains("--- src/user.rs"));
111 assert!(diff.contains("+++ src/user.rs"));
112
113 assert!(diff.contains("+ age: u32,"));
115
116 assert_eq!(stats.files_changed, 1);
118 assert_eq!(stats.lines_added, 1);
119 assert_eq!(stats.lines_removed, 0);
120 }
121
122 #[test]
123 fn test_generate_unified_diff_no_changes() {
124 let content = "pub struct User {\n id: u64,\n}\n";
125 let path = PathBuf::from("src/user.rs");
126
127 let (diff, stats) = generate_unified_diff(&path, content, content, 3);
128
129 assert!(diff.contains("--- src/user.rs"));
131 assert!(diff.contains("+++ src/user.rs"));
132
133 assert_eq!(stats.files_changed, 0);
135 assert_eq!(stats.lines_added, 0);
136 assert_eq!(stats.lines_removed, 0);
137 }
138
139 #[test]
140 fn test_generate_unified_diff_with_removal() {
141 let original = "pub struct User {\n id: u64,\n name: String,\n email: String,\n}\n";
142 let modified = "pub struct User {\n id: u64,\n name: String,\n}\n";
143 let path = PathBuf::from("src/user.rs");
144
145 let (diff, stats) = generate_unified_diff(&path, original, modified, 3);
146
147 assert!(diff.contains("- email: String,"));
149
150 assert_eq!(stats.files_changed, 1);
152 assert_eq!(stats.lines_added, 0);
153 assert_eq!(stats.lines_removed, 1);
154 }
155
156 #[test]
157 fn test_diff_stats_add() {
158 let mut stats1 = DiffStats {
159 files_changed: 1,
160 lines_added: 5,
161 lines_removed: 2,
162 };
163
164 let stats2 = DiffStats {
165 files_changed: 2,
166 lines_added: 3,
167 lines_removed: 1,
168 };
169
170 stats1.add(&stats2);
171
172 assert_eq!(stats1.files_changed, 3);
173 assert_eq!(stats1.lines_added, 8);
174 assert_eq!(stats1.lines_removed, 3);
175 }
176
177 #[test]
178 fn test_print_diff_returns_stats() {
179 let original = "line1\nline2\n";
180 let modified = "line1\nline2\nline3\n";
181 let path = PathBuf::from("test.txt");
182
183 let stats = print_diff(&path, original, modified);
184
185 assert_eq!(stats.files_changed, 1);
186 assert_eq!(stats.lines_added, 1);
187 assert_eq!(stats.lines_removed, 0);
188 }
189}