oxihuman_core/
patch_generator.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct DiffHunk {
10 pub old_start: usize,
11 pub old_count: usize,
12 pub new_start: usize,
13 pub new_count: usize,
14 pub lines: Vec<String>,
15}
16
17#[derive(Debug, Clone, Default)]
19pub struct UnifiedPatch {
20 pub old_file: String,
21 pub new_file: String,
22 pub hunks: Vec<DiffHunk>,
23}
24
25impl UnifiedPatch {
26 pub fn new(old_file: &str, new_file: &str) -> Self {
27 UnifiedPatch {
28 old_file: old_file.to_string(),
29 new_file: new_file.to_string(),
30 hunks: Vec::new(),
31 }
32 }
33
34 pub fn add_hunk(&mut self, hunk: DiffHunk) {
35 self.hunks.push(hunk);
36 }
37
38 pub fn is_empty(&self) -> bool {
39 self.hunks.is_empty()
40 }
41}
42
43pub fn generate_patch(old_file: &str, new_file: &str, old: &[&str], new: &[&str]) -> UnifiedPatch {
45 let mut patch = UnifiedPatch::new(old_file, new_file);
46 if old == new {
47 return patch;
48 }
49 let mut hunk_lines = Vec::new();
50 for line in old {
51 hunk_lines.push(format!("-{}", line));
52 }
53 for line in new {
54 hunk_lines.push(format!("+{}", line));
55 }
56 patch.add_hunk(DiffHunk {
57 old_start: 1,
58 old_count: old.len(),
59 new_start: 1,
60 new_count: new.len(),
61 lines: hunk_lines,
62 });
63 patch
64}
65
66pub fn serialize_patch(patch: &UnifiedPatch) -> String {
68 let mut out = String::new();
69 out.push_str(&format!("--- {}\n", patch.old_file));
70 out.push_str(&format!("+++ {}\n", patch.new_file));
71 for hunk in &patch.hunks {
72 out.push_str(&format!(
73 "@@ -{},{} +{},{} @@\n",
74 hunk.old_start, hunk.old_count, hunk.new_start, hunk.new_count
75 ));
76 for line in &hunk.lines {
77 out.push_str(line);
78 out.push('\n');
79 }
80 }
81 out
82}
83
84pub fn total_changed_lines(patch: &UnifiedPatch) -> usize {
86 patch
87 .hunks
88 .iter()
89 .flat_map(|h| h.lines.iter())
90 .filter(|l| l.starts_with('+') || l.starts_with('-'))
91 .count()
92}
93
94pub fn apply_patch_stub(old: &[&str], patch: &UnifiedPatch) -> Vec<String> {
96 if patch.is_empty() {
97 return old.iter().map(|s| s.to_string()).collect();
98 }
99 patch
101 .hunks
102 .iter()
103 .flat_map(|h| {
104 h.lines
105 .iter()
106 .filter(|l| l.starts_with('+'))
107 .map(|l| l[1..].to_string())
108 })
109 .collect()
110}
111
112pub fn is_identity_patch(patch: &UnifiedPatch) -> bool {
114 patch.is_empty()
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_empty_patch_for_same_files() {
123 let p = generate_patch("a.txt", "b.txt", &["x"], &["x"]);
124 assert!(p.is_empty());
125 }
126
127 #[test]
128 fn test_patch_has_hunk_for_diff() {
129 let p = generate_patch("a.txt", "b.txt", &["x"], &["y"]);
130 assert!(!p.is_empty());
131 assert_eq!(p.hunks.len(), 1);
132 }
133
134 #[test]
135 fn test_serialize_contains_header() {
136 let p = generate_patch("old.txt", "new.txt", &["a"], &["b"]);
137 let s = serialize_patch(&p);
138 assert!(s.contains("--- old.txt"));
139 assert!(s.contains("+++ new.txt"));
140 }
141
142 #[test]
143 fn test_total_changed_lines() {
144 let p = generate_patch("a", "b", &["x"], &["y"]);
145 assert_eq!(total_changed_lines(&p), 2); }
147
148 #[test]
149 fn test_apply_patch_stub() {
150 let p = generate_patch("a", "b", &["old"], &["new"]);
151 let result = apply_patch_stub(&["old"], &p);
152 assert_eq!(result, vec!["new".to_string()]);
153 }
154
155 #[test]
156 fn test_identity_patch() {
157 let p = generate_patch("a", "b", &["same"], &["same"]);
158 assert!(is_identity_patch(&p));
159 }
160
161 #[test]
162 fn test_patch_new_default() {
163 let p = UnifiedPatch::default();
164 assert!(p.is_empty());
165 }
166
167 #[test]
168 fn test_hunk_line_counts() {
169 let p = generate_patch("f", "g", &["a", "b"], &["c"]);
170 assert_eq!(p.hunks[0].old_count, 2);
171 assert_eq!(p.hunks[0].new_count, 1);
172 }
173
174 #[test]
175 fn test_serialize_empty_patch() {
176 let p = UnifiedPatch::new("a", "b");
177 let s = serialize_patch(&p);
178 assert!(s.contains("--- a"));
179 }
180
181 #[test]
182 fn test_apply_empty_patch() {
183 let p = UnifiedPatch::new("a", "b");
184 let result = apply_patch_stub(&["line1"], &p);
185 assert_eq!(result, vec!["line1".to_string()]);
186 }
187}