1use std::{
2 fs, io,
3 path::{Path, PathBuf},
4};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9pub const DIFF_SIGN_LINE_ADDED: &str = "+";
10pub const DIFF_SIGN_LINE_DELETED: &str = "-";
11pub const DIFF_SIGN_LINE_DEFAULT: &str = " ";
12pub const DIFF_SIGN_HEADER_ORIGIN: &str = "---";
13pub const DIFF_SIGN_HEADER_NEW: &str = "+++";
14pub const DIFF_SIGN_HUNK: &str = "@@";
15
16#[cfg(feature = "serde")]
17#[derive(Serialize, Deserialize, Debug)]
18pub enum DiffFormat {
19 GitUdiff,
20}
21#[cfg(not(feature = "serde"))]
22#[derive(Debug)]
23pub enum DiffFormat {
24 GitUdiff,
25}
26
27#[cfg(feature = "serde")]
28#[derive(Serialize, Deserialize, Debug)]
29pub struct DiffComposition {
30 pub format: DiffFormat,
31 pub diff: Vec<Diff>,
32}
33#[cfg(not(feature = "serde"))]
34#[derive(Debug)]
35pub struct DiffComposition {
36 pub format: DiffFormat,
37 pub diff: Vec<Diff>,
38}
39#[cfg(feature = "serde")]
40#[derive(Serialize, Deserialize, Debug)]
41pub struct Diff {
42 pub command: Option<String>,
43 pub index: Option<String>, pub path: PathBuf,
45 pub hunk: Vec<DiffHunk>,
46}
47#[cfg(not(feature = "serde"))]
48#[derive(Debug)]
49pub struct Diff {
50 pub command: Option<String>,
51 pub index: Option<String>, pub path: PathBuf,
53 pub hunk: Vec<DiffHunk>,
54}
55#[cfg(feature = "serde")]
56#[derive(Serialize, Deserialize, Debug)]
57pub struct DiffHunk {
58 pub old_line: usize,
59 pub old_len: usize,
60 pub new_line: usize,
61 pub new_len: usize,
62 pub change: Vec<LineChange>,
63}
64#[cfg(not(feature = "serde"))]
65#[derive(Debug)]
66pub struct DiffHunk {
67 pub old_line: usize,
68 pub old_len: usize,
69 pub new_line: usize,
70 pub new_len: usize,
71 pub change: Vec<LineChange>,
72}
73#[cfg(feature = "serde")]
74#[derive(Serialize, Deserialize, Debug)]
75pub struct LineChange {
76 pub kind: Change,
77 pub content: String,
78}
79
80#[cfg(not(feature = "serde"))]
81#[derive(Debug)]
82pub struct LineChange {
83 pub kind: Change,
84 pub content: String,
85}
86#[cfg(feature = "serde")]
87#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
88pub enum Change {
89 Default,
90 Added,
91 Deleted,
92}
93#[cfg(not(feature = "serde"))]
94#[derive(Debug, Copy, Clone)]
95pub enum Change {
96 Default,
97 Added,
98 Deleted,
99}
100
101#[cfg(feature = "serde")]
102#[derive(Serialize, Deserialize, Debug)]
103pub enum Line {
104 Command,
105 Index,
106 OrignPath,
107 NewPath,
108 Hunk,
109 LineChange(Change),
110 Unknown,
111}
112#[cfg(not(feature = "serde"))]
113#[derive(Debug)]
114pub enum Line {
115 Command,
116 Index,
117 OrignPath,
118 NewPath,
119 Hunk,
120 LineChange(Change),
121 Unknown,
122}
123
124#[derive(Debug)]
125pub struct DiffError {
126 kind: DiffErrorKind,
127 reason: String,
128}
129#[derive(Debug)]
130pub enum DiffErrorKind {
131 IOError(io::Error),
132 InvalidIndex(usize),
133 UnmatchedContent(String, String),
134}
135
136impl From<io::Error> for DiffError {
137 fn from(e: io::Error) -> Self {
138 DiffError {
139 kind: DiffErrorKind::IOError(e),
140 reason: "cannot read or write".to_string(),
141 }
142 }
143}
144
145impl DiffComposition {
146 pub fn apply(&self, root: &Path) -> Result<(), DiffError> {
147 for diff in &self.diff {
148 let target_path = root.join(&diff.path);
149 let original = fs::read_to_string(&target_path)?;
150 let after = diff.apply(&original)?;
151 fs::write(target_path, after)?;
152 }
153 Ok(())
154 }
155 pub fn revert(&self, root: &Path) -> Result<(), DiffError> {
156 for diff in &self.diff {
157 let target_path = root.join(&diff.path);
158 let applied = fs::read_to_string(&target_path)?;
159 let after = diff.revert(&applied)?;
160 fs::write(target_path, after)?;
161 }
162 Ok(())
163 }
164}
165
166impl Diff {
167 pub fn apply(&self, original: &str) -> Result<String, DiffError> {
168 let mut buffer = String::new();
169
170 let mut oidx: usize = 0;
172 let lines: Vec<&str> = original.lines().collect();
173
174 for hunk in &self.hunk {
175 while oidx < (hunk.old_line - 1) {
176 buffer.push_str(lines.get(oidx).ok_or_else(|| DiffError {
177 kind: DiffErrorKind::InvalidIndex(oidx),
178 reason: format!("cannot get line at {oidx}"),
179 })?);
180 buffer.push('\n');
181 oidx += 1;
182 }
183 for change in &hunk.change {
184 match change.kind {
185 Change::Default => {
186 buffer.push_str(lines.get(oidx).ok_or_else(|| {
187 DiffError {
188 kind: DiffErrorKind::InvalidIndex(oidx),
189 reason: format!("cannot get line at {oidx}"),
190 }
191 })?);
192 buffer.push('\n');
193 oidx += 1;
194 }
195 Change::Deleted => {
196 oidx += 1;
197 continue;
198 }
199 Change::Added => {
200 buffer.push_str(&change.content);
201 buffer.push('\n');
202 }
203 }
204 }
205 }
206
207 while oidx < lines.len() {
208 buffer
209 .push_str(lines.get(oidx).expect("there is no line in lines"));
210 buffer.push('\n');
211 oidx += 1;
212 }
213
214 Ok(buffer)
215 }
216
217 pub fn revert(&self, applied: &str) -> Result<String, DiffError> {
218 let mut buffer = String::new();
219
220 let mut aidx: usize = 0;
221 let lines: Vec<&str> = applied.lines().collect();
222
223 for hunk in &self.hunk {
224 while aidx < (hunk.new_line - 1) {
225 buffer.push_str(lines.get(aidx).ok_or_else(|| DiffError {
226 kind: DiffErrorKind::InvalidIndex(aidx),
227 reason: format!("cannot get line at {aidx}"),
228 })?);
229 buffer.push('\n');
230 aidx += 1;
231 }
232 for change in &hunk.change {
233 match change.kind {
234 Change::Default => {
235 let content =
236 lines.get(aidx).ok_or_else(|| DiffError {
237 kind: DiffErrorKind::InvalidIndex(aidx),
238 reason: format!("cannot get line at {aidx}"),
239 })?;
240 if change.content != *content {
241 Err(DiffError {
242 kind: DiffErrorKind::UnmatchedContent(
243 change.content.to_string(),
244 content.to_string(),
245 ),
246 reason: format!(
247 "(change) : {:?}, line(content) : {}, @line_idx : {aidx}, Buffer:{}, hunk current {:#?}",
248 change, content, buffer, hunk
249 ),
250 })?;
251 }
252 buffer.push_str(content);
253 buffer.push('\n');
254 aidx += 1;
255 }
256 Change::Deleted => {
257 buffer.push_str(&change.content);
258 buffer.push('\n');
259 }
260 Change::Added => {
261 aidx += 1;
262 continue;
263 }
264 }
265 }
266 }
267
268 while aidx < lines.len() {
269 buffer
270 .push_str(lines.get(aidx).expect("there is no line in lines"));
271 buffer.push('\n');
272 aidx += 1;
273 }
274
275 Ok(buffer)
276 }
277}
278
279#[cfg(test)]
280mod test {
281 use std::str::FromStr;
282
283 use {core::panic, std::fs};
284
285 use crate::{diff::*, parser::Parser};
286
287 #[test]
288 fn test_diff_apply_simple() {
289 let original = fs::read_to_string("test_data/simple.before").unwrap();
290 println!("<<original start>>");
291 print!("{}", original);
292 println!("<<original end>>");
293
294 let diff_file = fs::read_to_string("test_data/simple.diffs").unwrap();
295
296 let com = Parser::parse_git_udiff(&diff_file).unwrap();
297 println!("{:#?}", com);
298 let diff = com.diff.first().unwrap();
299 let applied = diff.apply(&original).unwrap();
300
301 println!("<<applied start>>");
302 print!("{}", applied);
303 println!("<<applied end>>");
304
305 let expected = fs::read_to_string("test_data/simple.after").unwrap();
306 println!("<<expected start>>");
307 print!("{}", expected);
308 println!("<<expected end>>");
309
310 assert_eq!(applied.as_str(), expected.as_str())
311 }
312
313 #[test]
314 fn test_diff_apply_middle() {
315 let original = fs::read_to_string("test_data/middle.before").unwrap();
316 println!("<<original start>>");
317 print!("{}", original);
318 println!("<<original end>>");
319
320 let diff_file = fs::read_to_string("test_data/middle.diffs").unwrap();
321
322 let com = Parser::parse_git_udiff(&diff_file).unwrap();
323 println!("{:#?}", com);
324 let diff = com.diff.first().unwrap();
325 let applied = diff.apply(&original).unwrap();
326
327 println!("<<applied start>>");
328 print!("{}", applied);
329 println!("<<applied end>>");
330
331 let expected = fs::read_to_string("test_data/middle.after").unwrap();
332 println!("<<expected start>>");
333 print!("{}", expected);
334 println!("<<expected end>>");
335
336 assert_eq!(applied.as_str(), expected.as_str())
337 }
338
339 #[test]
340 fn test_diff_apply_revert_simple() {
341 let original = fs::read_to_string("test_data/simple.before").unwrap();
342 println!("<<original start>>");
343 print!("{}", original);
344 println!("<<original end>>");
345
346 let diff_file = fs::read_to_string("test_data/simple.diffs").unwrap();
347
348 let com = Parser::parse_git_udiff(&diff_file).unwrap();
349 println!("{:#?}", com);
350 let diff = com.diff.first().unwrap();
351 let applied = diff.apply(&original).unwrap();
352
353 println!("<<applied start>>");
354 print!("{}", applied);
355 println!("<<applied end>>");
356
357 let expected = fs::read_to_string("test_data/simple.after").unwrap();
358 println!("<<expected start>>");
359 print!("{}", expected);
360 println!("<<expected end>>");
361
362 assert_eq!(applied.as_str(), expected.as_str());
363 let before = diff.revert(&applied).unwrap();
364 assert_eq!(before.as_str(), original.as_str())
365 }
366
367 #[test]
368 fn test_diff_apply_revert_middle() {
369 let original = fs::read_to_string("test_data/middle.before").unwrap();
370 println!("<<original start>>");
371 print!("{}", original);
372 println!("<<original end>>");
373
374 let diff_file = fs::read_to_string("test_data/middle.diffs").unwrap();
375
376 let com = Parser::parse_git_udiff(&diff_file).unwrap();
377 println!("{:#?}", com);
378 let diff = com.diff.first().unwrap();
379 let applied = diff.apply(&original).unwrap();
380
381 println!("<<applied start>>");
382 print!("{}", applied);
383 println!("<<applied end>>");
384
385 let expected = fs::read_to_string("test_data/middle.after").unwrap();
386 println!("<<expected start>>");
387 print!("{}", expected);
388 println!("<<expected end>>");
389
390 assert_eq!(applied.as_str(), expected.as_str());
391 let before = diff.revert(&applied).unwrap();
392 assert_eq!(before.as_str(), original.as_str())
393 }
394
395 #[test]
396 fn test_comp_simple_apply() {
397 let diff_file =
398 fs::read_to_string("test_data/composition/simple_app.diffs")
399 .unwrap();
400 let com = Parser::parse_git_udiff(&diff_file).unwrap();
401 fs::copy(
402 "test_data/simple.before",
403 "test_data/composition/simple_app",
404 )
405 .expect("failed to copy");
406 let comp_root = PathBuf::from_str("test_data/composition").unwrap();
407 com.apply(&comp_root).unwrap();
408 let applied =
409 fs::read_to_string("test_data/composition/simple_app").unwrap();
410 let expected = fs::read_to_string("test_data/simple.after").unwrap();
411 assert_eq!(applied.as_str(), expected.as_str());
412 fs::remove_file("test_data/composition/simple_app")
413 .expect("failed to remove file");
414 }
415
416 #[test]
417 fn test_comp_simple_revert() {
418 let diff_file =
419 fs::read_to_string("test_data/composition/simple_rev.diffs")
420 .unwrap();
421 let com = Parser::parse_git_udiff(&diff_file).unwrap();
422 fs::copy("test_data/simple.after", "test_data/composition/simple_rev")
423 .expect("failed to copy");
424 let comp_root = PathBuf::from_str("test_data/composition").unwrap();
425 com.revert(&comp_root);
426 let reverted =
427 fs::read_to_string("test_data/composition/simple_rev").unwrap();
428 let expected = fs::read_to_string("test_data/simple.before").unwrap();
429 assert_eq!(reverted.as_str(), expected.as_str());
430 fs::remove_file("test_data/composition/simple_rev")
431 .expect("failed to remove file");
432 }
433
434 #[test]
435 fn test_comp_middle_apply() {
436 let diff_file =
437 fs::read_to_string("test_data/composition/middle_app.diffs")
438 .unwrap();
439 let com = Parser::parse_git_udiff(&diff_file).unwrap();
440 fs::copy(
441 "test_data/middle.before",
442 "test_data/composition/middle_app",
443 )
444 .expect("failed to copy");
445 let comp_root = PathBuf::from_str("test_data/composition").unwrap();
446 com.apply(&comp_root);
447 let applied =
448 fs::read_to_string("test_data/composition/middle_app").unwrap();
449 let expected = fs::read_to_string("test_data/middle.after").unwrap();
450 assert_eq!(applied.as_str(), expected.as_str());
451 fs::remove_file("test_data/composition/middle_app")
452 .expect("failed to remove file");
453 }
454
455 #[test]
456 fn test_comp_middle_revert() {
457 let diff_file =
458 fs::read_to_string("test_data/composition/middle_rev.diffs")
459 .unwrap();
460 let com = Parser::parse_git_udiff(&diff_file).unwrap();
461 fs::copy("test_data/middle.after", "test_data/composition/middle_rev")
462 .expect("failed to copy");
463 let comp_root = PathBuf::from_str("test_data/composition").unwrap();
464 com.revert(&comp_root);
465 let reverted =
466 fs::read_to_string("test_data/composition/middle_rev").unwrap();
467 let expected = fs::read_to_string("test_data/middle.before").unwrap();
468 assert_eq!(reverted.as_str(), expected.as_str());
469 fs::remove_file("test_data/composition/middle_rev")
470 .expect("failed to remove file");
471 }
472}