1mod replay;
22
23pub use replay::{Replayed, replay};
24
25const HEADERS: &[&str] = &[
26 "*** Add File:",
27 "*** Update File:",
28 "*** Delete File:",
29 "*** Move to:",
30];
31
32#[must_use]
34pub fn writes(text: &str) -> Vec<String> {
35 let mut paths: Vec<String> = Vec::new();
36 for line in text.lines() {
37 let line = line.trim();
38 let Some(path) = HEADERS
39 .iter()
40 .find_map(|h| strip_header(line, h))
41 .map(str::trim)
42 .filter(|p| !p.is_empty())
43 else {
44 continue;
45 };
46 if !paths.iter().any(|p| p == path) {
47 paths.push(path.to_owned());
48 }
49 }
50 paths
51}
52
53fn strip_header<'a>(line: &'a str, header: &str) -> Option<&'a str> {
55 let head = line.get(..header.len())?;
56 head.eq_ignore_ascii_case(header)
57 .then(|| &line[header.len()..])
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn every_header_names_a_written_file() {
66 let patch = "*** Begin Patch\n*** Add File: src/new.rs\n+fn x() {}\n\
67 *** Update File: src/lib.rs\n*** Move to: src/core.rs\n@@\n-a\n+b\n\
68 *** Delete File: old.txt\r\n*** Update File: src/lib.rs\n*** End Patch\n";
69 assert_eq!(
70 writes(patch),
71 ["src/new.rs", "src/lib.rs", "src/core.rs", "old.txt"]
72 );
73 }
74
75 #[test]
76 fn added_lines_that_look_like_headers_are_content() {
77 assert_eq!(writes("*** Add File: a\n+*** Add File: ~/.zshrc\n"), ["a"]);
79 }
80
81 #[test]
82 fn indented_and_differently_cased_headers_are_writes() {
83 let patch = "*** Begin Patch\n*** Add File: a\n+x\n *** Delete File: ~/.ssh/id_rsa\n\
86 \t*** update file: ~/.zshrc\n*** End Patch\n";
87 assert_eq!(writes(patch), ["a", "~/.ssh/id_rsa", "~/.zshrc"]);
88 }
89
90 #[test]
91 fn headers_outside_the_envelope_still_count() {
92 let patch = "*** Begin Patch\n*** Add File: a\n+x\n*** End Patch\n*** Delete File: b\n";
93 assert_eq!(writes(patch), ["a", "b"]);
94 }
95
96 #[test]
97 fn a_root_path_is_passed_on_for_the_engine_to_decide() {
98 assert_eq!(writes("*** Delete File: /\n"), ["/"]);
99 }
100
101 #[test]
102 fn no_headers_means_no_files() {
103 assert_eq!(writes("just text"), Vec::<String>::new());
104 assert_eq!(writes("*** Add File: \n"), Vec::<String>::new());
105 }
106}