-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathmain.rs
More file actions
127 lines (107 loc) · 4.1 KB
/
main.rs
File metadata and controls
127 lines (107 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
mod config;
mod i18n;
mod utils;
use std::{
collections::{HashMap, HashSet},
path::PathBuf,
};
use config::{init_config, print_config};
use i18n::t;
use utils::{
filter_logs::filter_logs,
format_log::{format_log, LogInfo},
get_repo_logs::get_repo_logs,
get_repo_name::get_repo_name,
keypress::exit_on_keypress,
save_report::save_report_markdown,
};
fn run(root_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
// Initialize global configuration.
// After initialization, the config is stored in a global OnceLock singleton,
// and can be accessed from other modules via `config::store::global()`,
// such as in the i18n module.
let config = init_config(&root_path)?;
print_config();
let mut result: HashMap<String, HashMap<String, Vec<LogInfo>>> = HashMap::new();
// Deduplicate logs by repoName and typeName
let mut dedup_map: HashMap<String, HashMap<String, HashSet<String>>> = HashMap::new();
for repo_dir in &config.repos {
// Get repo name
let repo_name =
get_repo_name(repo_dir, &config.format).unwrap_or_else(|| "undefined".to_string());
let logs = get_repo_logs(repo_dir)?;
// Append repoName to the beginning of each log line
let logs_with_repo: Vec<String> = logs
.into_iter()
.map(|log| format!("{}|||{}", repo_name, log))
.collect();
// Filter logs according to the rules in the configuration file
let filtered_logs = match filter_logs(
&logs_with_repo,
&config.authors,
&config.includes,
&config.excludes,
) {
Ok(l) => l,
Err(e) => {
eprintln!(
"{}",
t("err_filter_logs_failed").replace("{}", &e.to_string())
);
continue;
}
};
// Formatting and aggregating logs
for log in filtered_logs {
let log_info = format_log(&log);
let type_name = log_info.type_name.clone();
// get HashSet of type_name in repo_name, if not exist, create a new one
let type_set = dedup_map
.entry(repo_name.clone())
.or_default()
.entry(type_name.clone())
.or_default();
// if message not in type_set, push to result
if type_set.insert(log_info.message.clone()) {
result
.entry(repo_name.clone())
.or_default()
.entry(type_name)
.or_default()
.push(log_info);
}
}
save_report_markdown(&result, &root_path)?;
exit_on_keypress(Some(t("press_to_exit")));
}
Ok(())
}
fn main() {
// Get the APP_ENV environment variable from the startup command (see Makefile.toml).
// Since the working directories differ between development and production,
// we need to handle them separately.
let env = std::env::var("APP_ENV").unwrap_or_else(|_| "production".to_string());
let root_path = if env == "development" {
std::env::current_dir().unwrap()
} else {
std::env::current_exe()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
};
// Print the root path to help locate issues if the program crashes.
println!("");
println!("\nProgram root directory:\n{}", root_path.display());
// Using `?` to propagate errors to `main` causes Rust's default error output
// (via the `std::process::Termination` trait) to use the Debug format (`{:?}`)
// for printing error types. As a result, only the enum variant name is shown
// (e.g., `FileNotFound`), not the user-friendly message from the Display trait.
//
// Wrapping the main logic in a separate `run` function and handling errors
// explicitly with `eprintln!` ensures that the Display output is used.
if let Err(e) = run(root_path) {
eprintln!("{}", e); // Use Display format to output the error
std::process::exit(1);
}
}