1use anyhow::{Context, Result};
2use lazy_static::lazy_static;
3use regex::Regex;
4use std::collections::HashMap;
5
6pub type FileChanges = HashMap<String, Vec<(usize, usize)>>;
7
8pub fn parse_diff(diff: &str) -> Result<FileChanges> {
10 lazy_static! {
11 static ref RE: Regex = Regex::new(
12 r"^\+\+\+ .?/(?P<filePath>.*)\s*$|^@@ -[0-9]+(,[0-9]+)? \+(?P<linesFrom>[0-9]+)(,(?P<linesLen>[0-9]+))? @@"
13 ).expect("Failed to parse regex");
14 }
15
16 let mut file_changes: FileChanges = HashMap::new();
17 let mut curr_file_path = None;
18 for line in diff.lines() {
19 if let Some(cap) = RE.captures(line) {
20 if let Some(file_path_match) = cap.name("filePath") {
21 let file_path = file_path_match.as_str().to_string();
22 file_changes.insert(file_path.clone(), vec![]);
23 curr_file_path = Some(file_path);
24 }
25 if let Some(lines_from_match) = cap.name("linesFrom") {
26 let from = lines_from_match
27 .as_str()
28 .parse::<usize>()
29 .with_context(|| {
30 format!("Failed to parse start of lines range (line: {:?})", line)
31 })?;
32 let len = if let Some(lines_len_match) = cap.name("linesLen") {
33 lines_len_match.as_str().parse::<usize>().with_context(|| {
34 format!("Failed to parse length of lines range (line: {:?})", line)
35 })?
36 } else {
37 1
38 };
39 let curr_file_path_ref = curr_file_path
40 .as_ref()
41 .with_context(|| "Failed to retrieve current file path")?;
42 file_changes
43 .get_mut(curr_file_path_ref)
44 .with_context(|| {
45 format!(
46 "Failed to retrieve ranges of file path {:?}",
47 curr_file_path_ref
48 )
49 })?
50 .push((from, len));
51 }
52 }
53 }
54
55 Ok(file_changes)
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use indoc::indoc;
62
63 #[test]
64 fn test_parse_diff_1() {
65 let diff = indoc! {"
66 +++ b/Cargo.lock
67 @@ -3,0 +4,9 @@
68 @@ -5,0 +15,26 @@
69 +++ b/Cargo.toml
70 @@ -9,0 +10 @@
71 +++ b/src/main.rs
72 @@ -2,0 +3,3 @@
73 @@ -8 +11 @@
74 @@ -13,2 +16,15 @@
75 "};
76 let file_changes = parse_diff(diff).unwrap();
77 eprintln!("{:?}", file_changes);
78 assert_eq!(file_changes.len(), 3);
79 assert_eq!(&file_changes["Cargo.lock"], &[(4, 9), (15, 26)]);
80 assert_eq!(&file_changes["Cargo.toml"], &[(10, 1)]);
81 assert_eq!(&file_changes["src/main.rs"], &[(3, 3), (11, 1), (16, 15)]);
82 }
83
84 #[test]
85 fn test_parse_diff_2() {
86 let diff = indoc! {"
87 +++ b/prusti-viper/src/encoder/mir_encoder/mod.rs
88 @@ -98,5 +98,5 @@
89 "};
90 let file_changes = parse_diff(diff).unwrap();
91 eprintln!("{:?}", file_changes);
92 assert_eq!(file_changes.len(), 1);
93 assert_eq!(&file_changes["prusti-viper/src/encoder/mir_encoder/mod.rs"], &[(98, 5)]);
94 }
95}