Skip to content

Commit 17325db

Browse files
committed
feat(main): implement main process with global config, error handling, and report generation
- Initialize global configuration and make it accessible via OnceLock singleton - Add current working directory output for debugging - Implement main analysis loop: repo name resolution, git log retrieval, log filtering, formatting, and aggregation - Generate Markdown report and support graceful error handling - Use Result and ? operator for clean error propagation - Add user-friendly error messages for missing or invalid config
1 parent 5fe2763 commit 17325db

File tree

7 files changed

+120
-35
lines changed

7 files changed

+120
-35
lines changed

src/config/error.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
use crate::i18n::t;
2-
31
#[derive(Debug)]
42
pub enum ConfigError {
5-
FileNotFound(String),
6-
ParseError(String),
3+
FileNotFound,
4+
ParseError,
75
InvalidConfig(String),
86
}
97

@@ -12,14 +10,20 @@ impl std::error::Error for ConfigError {}
1210
impl std::fmt::Display for ConfigError {
1311
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1412
match self {
15-
ConfigError::FileNotFound(msg) => {
16-
write!(f, "{}", t("config_file_not_found").replace("{}", msg))
13+
ConfigError::FileNotFound => {
14+
write!(
15+
f,
16+
"\nPlease make sure there is a config.json file in the program directory."
17+
)
1718
}
18-
ConfigError::ParseError(msg) => {
19-
write!(f, "{}", t("failed_parse_config").replace("{}", msg))
19+
ConfigError::ParseError => {
20+
write!(
21+
f,
22+
"\nFailed to parse config.json, please check the file format."
23+
)
2024
}
2125
ConfigError::InvalidConfig(msg) => {
22-
write!(f, "{}", t("config_invalid").replace("{}", msg))
26+
write!(f, "{}", msg)
2327
}
2428
}
2529
}

src/config/mod.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,18 @@ use error::ConfigError;
77
use schema::ConfigFile;
88
use std::path::Path;
99
pub use store::{init, is_chinese, print_config};
10-
pub use types::{Config, Language};
10+
pub use types::Config;
1111

1212
const CONFIG_FILE_PATH: &str = "config.json";
1313

1414
/// Initialize configuration from file or use default settings
1515
pub fn init_config() -> Result<Config, ConfigError> {
1616
if !Path::new(CONFIG_FILE_PATH).exists() {
17-
return Err(ConfigError::FileNotFound(
18-
"config.json not found".to_string(),
19-
));
17+
return Err(ConfigError::FileNotFound);
2018
}
2119

2220
ConfigFile::from_file(CONFIG_FILE_PATH)
23-
.map_err(|e| ConfigError::ParseError(e.to_string()))
21+
.map_err(|_e| ConfigError::ParseError)
2422
.and_then(|file_config| {
2523
let config = Config::new_from_file(&file_config)?;
2624
init(config.clone());

src/i18n/messages/en.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ use phf::phf_map;
33
pub static MESSAGES: phf::Map<&'static str, &'static str> = phf_map! {
44
// Configuration file I/O
55
"config_loaded" => "Configuration loaded successfully",
6-
"config_not_found" => "Configuration file not found",
7-
"failed_init_config" => "Failed to initialize configuration: {}",
86
"failed_print_config" => "Failed to print configuration: {}",
9-
"config_file_not_found" => "Config file not found: {}",
107
"failed_parse_config" => "Failed to parse config: {}",
118
"global_config_not_initialized" => "Global configuration not initialized",
129

src/i18n/messages/zh.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ use phf::phf_map;
33
pub static MESSAGES: phf::Map<&'static str, &'static str> = phf_map! {
44
// Configuration file I/O
55
"config_loaded" => "配置加载成功",
6-
"config_not_found" => "未找到配置文件",
7-
"failed_init_config" => "配置初始化失败:{}",
86
"failed_print_config" => "打印配置失败:{}",
9-
"config_file_not_found" => "配置文件不存在:{}",
107
"failed_parse_config" => "解析配置失败:{}",
118
"global_config_not_initialized" => "全局配置未初始化",
129

src/main.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
mod config;
2+
mod i18n;
3+
mod utils;
4+
5+
use std::collections::HashMap;
6+
7+
use config::{init_config, print_config};
8+
use i18n::t;
9+
use utils::{
10+
filter_logs::filter_logs,
11+
format_log::{format_log, LogInfo},
12+
get_repo_logs::get_repo_logs,
13+
get_repo_name::get_repo_name,
14+
keypress::exit_on_keypress,
15+
save_report::save_report_markdown,
16+
};
17+
18+
fn run() -> Result<(), Box<dyn std::error::Error>> {
19+
// Initialize global configuration.
20+
// After initialization, the config is stored in a global OnceLock singleton,
21+
// and can be accessed from other modules via `config::store::global()`,
22+
// such as in the i18n module.
23+
let config = init_config()?;
24+
25+
print_config();
26+
27+
let mut result: HashMap<String, HashMap<String, Vec<LogInfo>>> = HashMap::new();
28+
29+
for repo_dir in &config.repos {
30+
// Get repo name
31+
let repo_name =
32+
get_repo_name(repo_dir, &config.format).unwrap_or_else(|| "undefined".to_string());
33+
34+
let logs = get_repo_logs(repo_dir)?;
35+
36+
// Append repoName to the beginning of each log line
37+
let logs_with_repo: Vec<String> = logs
38+
.into_iter()
39+
.map(|log| format!("{}|||{}", repo_name, log))
40+
.collect();
41+
42+
// Filter logs according to the rules in the configuration file
43+
let filtered_logs = match filter_logs(
44+
&logs_with_repo,
45+
&config.authors,
46+
&config.includes,
47+
&config.excludes,
48+
) {
49+
Ok(l) => l,
50+
Err(e) => {
51+
eprintln!(
52+
"{}",
53+
t("err_filter_logs_failed").replace("{}", &e.to_string())
54+
);
55+
continue;
56+
}
57+
};
58+
59+
// Formatting and aggregating logs
60+
for log in filtered_logs {
61+
let log_info = format_log(&log);
62+
let type_name = log_info.type_name.clone();
63+
result
64+
.entry(repo_name.clone())
65+
.or_default()
66+
.entry(type_name)
67+
.or_default()
68+
.push(log_info);
69+
}
70+
71+
// save_report_markdown(&result, "report.txt").expect(t("report_gen_error"));
72+
save_report_markdown(&result, "report.txt")?;
73+
74+
exit_on_keypress(Some(t("press_to_exit")));
75+
}
76+
77+
Ok(())
78+
}
79+
80+
fn main() {
81+
// Print the current working directory
82+
// When the program crashes, it can help locate the cause
83+
println!("");
84+
println!(
85+
"Current working directory: \n{}",
86+
&std::env::current_dir().unwrap().display()
87+
);
88+
89+
// Using `?` to propagate errors to `main` causes Rust's default error output
90+
// (via the `std::process::Termination` trait) to use the Debug format (`{:?}`)
91+
// for printing error types. As a result, only the enum variant name is shown
92+
// (e.g., `FileNotFound`), not the user-friendly message from the Display trait.
93+
//
94+
// Wrapping the main logic in a separate `run` function and handling errors
95+
// explicitly with `eprintln!` ensures that the Display output is used.
96+
if let Err(e) = run() {
97+
eprintln!("{}", e); // Use Display format to output the error
98+
std::process::exit(1);
99+
}
100+
}

src/utils/format_commit.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#[cfg(not(test))]
12
use crate::i18n::t;
23

34
/// Commit type and its category

src/utils/format_log.rs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::utils::format_commit::{format_commit, CommitInfo};
22
use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
33

4+
#[allow(dead_code)]
5+
#[derive(Debug)]
46
pub struct LogInfo {
57
pub repo: String,
68
pub author: String,
@@ -49,21 +51,7 @@ pub fn format_log(log: &str) -> LogInfo {
4951
let hash = arr.get(4).unwrap_or(&"").replace("'", "#");
5052
let time_str = arr.get(5).unwrap_or(&"").to_string();
5153

52-
let (time, unix) = if let Ok(dt) = DateTime::parse_from_rfc2822(&time_str) {
53-
let local_dt: DateTime<Local> = dt.with_timezone(&Local);
54-
(
55-
local_dt.format("%Y-%m-%d %H:%M:%S").to_string(),
56-
local_dt.timestamp_millis(),
57-
)
58-
} else if let Ok(naive_dt) = NaiveDateTime::parse_from_str(&time_str, "%Y-%m-%d %H:%M:%S") {
59-
let local_dt = Local.from_local_datetime(&naive_dt).unwrap();
60-
(
61-
local_dt.format("%Y-%m-%d %H:%M:%S").to_string(),
62-
local_dt.timestamp_millis(),
63-
)
64-
} else {
65-
(time_str.clone(), 0)
66-
};
54+
let (time, unix) = parse_time(&time_str);
6755

6856
let CommitInfo {
6957
type_name,

0 commit comments

Comments
 (0)