1use crate::common::LogFile;
2use crate::log_entry::LogEntry;
3use crate::log_ops;
4
5#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
6pub struct ChangeRootSuccess
7{
8 pub info_lines: Vec<String>,
10 pub warning_lines: Vec<String>,
12 pub file_warning_lines: Option<Vec<String>>,
14}
15
16fn info_lines(entries_matched: usize, entries_omitted: usize) -> Vec<String> {
17 let mut v = vec![];
18
19 let total_entries = entries_matched.checked_add(entries_omitted)
20 .expect("entry stats should not cause arithmetic overflow");
21 v.push(format!("Input file contains {total_entries} entries:"));
22
23 match entries_matched {
24 0 => {},
25 x if x == total_entries => v.push(format!(" All {x} entries matched the prefix")),
26 x => {
27 v.push(format!(" {x} entries matched the prefix"));
28 v.push(format!(" {entries_omitted} entries did not match the prefix and were omitted"));
29 },
30 }
31 v
32}
33
34fn warning_lines(entries_matched: usize, entries_omitted: usize, root_prefix: &str) -> Vec<String> {
35 let mut v = vec![];
36
37 if entries_matched == 0 && entries_omitted == 0 {
38 v.push("Warning: No entries were loaded from the input file".to_string());
39 }
40 else if entries_matched == 0 {
41 v.push("Warning: No entries matched the prefix (All entries were omitted)".to_string());
42 }
43
44 if root_prefix.is_empty() {
45 v.push("Warning: Prefix is empty (operation will have no effect)".to_string());
46 }
47 v
48}
49
50pub fn change_root(filename: &str, out_filename: &str, root_prefix: &str)
61 -> Result<ChangeRootSuccess, Box<dyn std::error::Error>> {
62
63 let mut entry_count_before= 0;
64 let mut entry_count_after= 0;
65
66 let f = |log_file: &mut LogFile<Vec<LogEntry>>| {
67 entry_count_before = log_file.entries.len();
68
69 log_file.entries =
70 log_file.entries.iter().filter_map(|log_entry| {
71 log_entry.filename.strip_prefix(root_prefix).map(|new_path| {
72 LogEntry{
73 filename: new_path.to_string(),
74 hashes: log_entry.hashes.clone(),
75 }
76 })
77 }).collect();
78
79 entry_count_after = log_file.entries.len();
80 };
81
82 let file_warning_lines = log_ops::process_log(filename, out_filename, f)?;
83 let entries_matched = entry_count_after;
84 let entries_omitted = entry_count_before.checked_sub(entry_count_after)
86 .expect("filter should not increase entry count");
87
88 let info_lines = info_lines(entries_matched, entries_omitted);
89 let warning_lines = warning_lines(entries_matched, entries_omitted, root_prefix);
90
91 Ok(ChangeRootSuccess{file_warning_lines, info_lines, warning_lines})
92}
93
94#[cfg(test)]
95mod test {
96 use super::*;
97
98 use predicates::prelude::*;
99
100 #[test]
101 fn change_root_test() {
102 {
103 let temp_dir = tempfile::tempdir().unwrap();
104 let temp_file = temp_dir.path().join("test.txt");
105 let temp_file_path_str = temp_file.to_str().unwrap();
106
107 change_root("tests/test1.txt", temp_file_path_str, "hashdeepComp/").unwrap();
108
109 let p = predicates::path::eq_file("tests/test1_root_changed.txt");
110 assert!(p.eval(temp_file.as_path()));
111 }
112 }
113}