Skip to content

Commit 507020c

Browse files
committed
refactor: extract utilities and streamline code organization
- add new utils module with shared functions for env handling, output formatting, and error processing - refactor main module to eliminate duplicate imports and leverage utils for cleaner logic Signed-off-by: mingcheng <mingcheng@apache.org>
1 parent a81f159 commit 507020c

5 files changed

Lines changed: 206 additions & 127 deletions

File tree

src/cli.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
*/
1414

1515
use clap::Parser;
16-
mod built_info {
17-
include!(concat!(env!("OUT_DIR"), "/built.rs"));
18-
}
16+
use crate::built_info;
1917

2018
#[derive(Debug, Parser)]
2119
#[command(name = built_info::PKG_NAME, about = built_info::PKG_DESCRIPTION, version = built_info::PKG_VERSION, author = built_info::PKG_AUTHORS)]

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
* Last Modified: 2025-03-03 19:36:07
1313
*/
1414

15+
pub mod built_info {
16+
include!(concat!(env!("OUT_DIR"), "/built.rs"));
17+
}
18+
1519
pub mod cli;
1620
pub mod git;
1721
pub mod openai;
22+
pub mod utils;

src/main.rs

Lines changed: 27 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -12,48 +12,25 @@
1212
* Last Modified: 2025-09-26 15:45:37
1313
*/
1414

15+
use aigitcommit::built_info::{PKG_NAME, PKG_VERSION};
1516
use aigitcommit::cli::Cli;
1617
use aigitcommit::git::message::GitMessage;
1718
use aigitcommit::git::repository::Repository;
1819
use aigitcommit::openai;
1920
use aigitcommit::openai::OpenAI;
2021
use arboard::Clipboard;
21-
use async_openai::error::OpenAIError;
2222
use async_openai::types::{
2323
ChatCompletionRequestSystemMessageArgs, ChatCompletionRequestUserMessageArgs,
2424
};
2525
use clap::Parser;
2626
use std::error::Error;
27-
use std::fs::File;
27+
use std::fs;
2828
use std::io::Write;
29-
use std::{env, fs};
3029
use tracing::{Level, debug, trace};
3130

32-
use crate::built_info::{PKG_NAME, PKG_VERSION};
33-
use crate::utils::print_table;
34-
mod built_info {
35-
include!(concat!(env!("OUT_DIR"), "/built.rs"));
36-
}
37-
mod utils;
38-
39-
/// The output format for the commit message
40-
#[derive(Debug)]
41-
enum OutPutFormat {
42-
Stdout,
43-
Table,
44-
Json,
45-
}
46-
47-
/// Detect the output format based on the command line arguments
48-
fn detect_output_format(cli: &Cli) -> OutPutFormat {
49-
if cli.json {
50-
return OutPutFormat::Json;
51-
} else if cli.no_table {
52-
return OutPutFormat::Stdout;
53-
}
54-
55-
OutPutFormat::Table
56-
}
31+
use aigitcommit::utils::{
32+
OutputFormat, check_env_variables, format_openai_error, get_env, save_to_file, should_signoff,
33+
};
5734

5835
#[tokio::main]
5936
async fn main() -> std::result::Result<(), Box<dyn Error>> {
@@ -73,56 +50,31 @@ async fn main() -> std::result::Result<(), Box<dyn Error>> {
7350
);
7451
}
7552

76-
// Get the specified model name from environment variable, default to "gpt-4"
77-
let model_name = env::var("OPENAI_MODEL_NAME").unwrap_or_else(|_| String::from("gpt-5"));
53+
// Get the specified model name from environment variable, default to "gpt-5"
54+
let model_name = get_env("OPENAI_MODEL_NAME", "gpt-5");
7855

7956
// Instantiate OpenAI client, ready to send requests to the OpenAI API
8057
let client = openai::OpenAI::new();
8158

8259
// Check if the environment variables are set and print the configured values
8360
if cli.check_env {
84-
fn check_and_print_env(var_name: &str) {
85-
match env::var(var_name) {
86-
Ok(value) => {
87-
debug!("{} is set to {}", var_name, value);
88-
// Print the value of the environment variable
89-
println!("{:20}\t{}", var_name, value);
90-
}
91-
Err(_) => {
92-
debug!("{} is not set", var_name);
93-
}
94-
}
95-
}
96-
97-
trace!("check env option is enabled, will check the OpenAI API key and model name");
98-
debug!("the model name is `{}`", &model_name);
99-
100-
[
101-
"OPENAI_API_BASE",
102-
"OPENAI_API_TOKEN",
103-
"OPENAI_MODEL_NAME",
104-
"OPENAI_API_PROXY",
105-
"OPENAI_API_TIMEOUT",
106-
"OPENAI_API_MAX_TOKENS",
107-
"GIT_AUTO_SIGNOFF",
108-
]
109-
.iter()
110-
.for_each(|v| check_and_print_env(v));
61+
trace!("check env option is enabled");
62+
debug!("model name: `{}`", &model_name);
11163

64+
check_env_variables();
11265
return Ok(());
11366
}
11467

11568
// Check if the model name is valid
11669
if cli.check_model {
117-
trace!("check option is enabled, will check the OpenAI API key and model name");
118-
debug!("the model name is `{}`", &model_name);
70+
trace!("check model option is enabled");
71+
debug!("model name: `{}`", &model_name);
11972

12073
match client.check_model(&model_name).await {
12174
Ok(()) => {
12275
println!(
12376
"the model name `{}` is available, {} is ready for use!",
124-
model_name,
125-
built_info::PKG_NAME
77+
model_name, PKG_NAME
12678
);
12779
}
12880
Err(e) => {
@@ -133,16 +85,13 @@ async fn main() -> std::result::Result<(), Box<dyn Error>> {
13385
return Ok(());
13486
}
13587

136-
// Check if the specified path is a valid directory
88+
// Initialize repository
13789
let repo_dir = fs::canonicalize(&cli.repo_path)?;
138-
139-
// Check if the directory is empty
14090
if !repo_dir.is_dir() {
14191
return Err("the specified path is not a directory".into());
14292
}
14393

14494
trace!("specified repository directory: {:?}", repo_dir);
145-
// Check if the directory is a valid git repository
14695
let repository = Repository::new(repo_dir.to_str().unwrap_or("."))?;
14796

14897
// Get the diff and logs from the repository
@@ -181,59 +130,31 @@ async fn main() -> std::result::Result<(), Box<dyn Error>> {
181130
];
182131

183132
// Send the request to OpenAI API and get the response
184-
let result = match client.chat(&model_name.to_string(), messages).await {
133+
let result = match client.chat(&model_name, messages).await {
185134
Ok(s) => s,
186135
Err(e) => {
187-
let message = match e {
188-
OpenAIError::Reqwest(_) | OpenAIError::StreamError(_) => {
189-
"network request error".to_string()
190-
}
191-
OpenAIError::JSONDeserialize(_error, message) => {
192-
format!("json deserialization error: {message}").to_string()
193-
}
194-
OpenAIError::InvalidArgument(_) => "invalid argument".to_string(),
195-
OpenAIError::FileSaveError(_) | OpenAIError::FileReadError(_) => {
196-
"io error".to_string()
197-
}
198-
OpenAIError::ApiError(e) => format!("api error {e:?}"),
199-
};
200-
136+
let message = format_openai_error(e);
201137
return Err(message.into());
202138
}
203139
};
204140

205-
let (title, content) = result.split_once("\n\n").unwrap();
141+
let (title, content) = result
142+
.split_once("\n\n")
143+
.ok_or("Invalid response format: expected title and content separated by double newline")?;
206144

207-
// Detect auto signoff from environment variable
208-
let need_signoff = cli.signoff
209-
|| env::var("GIT_AUTO_SIGNOFF")
210-
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
211-
.unwrap_or(false);
145+
// Detect auto signoff from environment variable or CLI flag
146+
let need_signoff = should_signoff(cli.signoff);
212147

213148
let message: GitMessage = GitMessage::new(&repository, title, content, need_signoff)?;
214149

215150
// Decide the output format based on the command line arguments
216-
match detect_output_format(&cli) {
217-
OutPutFormat::Stdout => {
218-
// Write the commit message to stdout
219-
trace!("write to stdout, and finish the process");
220-
writeln!(std::io::stdout(), "{}", message)?;
221-
}
222-
OutPutFormat::Json => {
223-
// Print the commit message in JSON format
224-
let json = serde_json::to_string_pretty(&message)?;
225-
writeln!(std::io::stdout(), "{}", json)?;
226-
}
227-
OutPutFormat::Table => {
228-
// Default print message in table
229-
print_table(&message.title, &message.content);
230-
}
231-
};
151+
let output_format = OutputFormat::detect(cli.json, cli.no_table);
152+
output_format.write(&message)?;
232153

233154
// Copy the commit message to clipboard if the --copy option is enabled
234155
if cli.copy_to_clipboard {
235156
let mut clipboard = Clipboard::new()?;
236-
clipboard.set_text(format!("{}", &message))?;
157+
clipboard.set_text(message.to_string())?;
237158
writeln!(
238159
std::io::stdout(),
239160
"the commit message has been copied to clipboard."
@@ -264,14 +185,10 @@ async fn main() -> std::result::Result<(), Box<dyn Error>> {
264185
// If the --save option is enabled, save the commit message to a file
265186
if !cli.save.is_empty() {
266187
trace!("save option is enabled, will save the commit message to a file");
267-
let save_path = &cli.save;
268-
debug!("the save file path is {:?}", &save_path);
269-
270-
let mut file = File::create(save_path)?;
271-
file.write_all(result.as_bytes())?;
272-
file.flush()?;
188+
debug!("the save file path is {:?}", &cli.save);
273189

274-
writeln!(std::io::stdout(), "commit message saved to {save_path}")?;
190+
save_to_file(&cli.save, &result)?;
191+
writeln!(std::io::stdout(), "commit message saved to {}", cli.save)?;
275192
}
276193

277194
Ok(())

src/openai.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@
1212
* Last Modified: 2025-09-26 15:51:48
1313
*/
1414

15-
// Include the generated-file as a separate module
16-
mod built_info {
17-
include!(concat!(env!("OUT_DIR"), "/built.rs"));
18-
}
19-
15+
use crate::built_info;
16+
use crate::utils::get_env;
2017
use askama::Template;
2118
use async_openai::config::OPENAI_API_BASE;
2219
use async_openai::error::OpenAIError;
@@ -28,7 +25,6 @@ use async_openai::{
2825
use log::trace;
2926
use reqwest::header::{HeaderMap, HeaderValue};
3027
use reqwest::{ClientBuilder, Proxy};
31-
use std::env;
3228
use std::error::Error;
3329
use std::time::Duration;
3430
use tracing::debug;
@@ -55,10 +51,8 @@ impl OpenAI {
5551
pub fn new() -> Self {
5652
// Set up OpenAI client configuration
5753
let ai_config = OpenAIConfig::new()
58-
.with_api_key(env::var("OPENAI_API_TOKEN").unwrap_or_else(|_| String::from("")))
59-
.with_api_base(
60-
env::var("OPENAI_API_BASE").unwrap_or_else(|_| String::from(OPENAI_API_BASE)),
61-
)
54+
.with_api_key(get_env("OPENAI_API_TOKEN", ""))
55+
.with_api_base(get_env("OPENAI_API_BASE", OPENAI_API_BASE))
6256
.with_org_id(built_info::PKG_NAME);
6357

6458
// Set up HTTP client builder with default headers
@@ -80,14 +74,14 @@ impl OpenAI {
8074
});
8175

8276
// Set up proxy if specified
83-
let proxy_addr: String = env::var("OPENAI_API_PROXY").unwrap_or_else(|_| String::from(""));
77+
let proxy_addr = get_env("OPENAI_API_PROXY", "");
8478
if !proxy_addr.is_empty() {
8579
trace!("Using proxy: {proxy_addr}");
8680
http_client_builder = http_client_builder.proxy(Proxy::all(proxy_addr).unwrap());
8781
}
8882

89-
let request_timeout =
90-
env::var("OPENAI_REQUEST_TIMEOUT").unwrap_or_else(|_| String::from(""));
83+
// Set up request timeout if specified
84+
let request_timeout = get_env("OPENAI_REQUEST_TIMEOUT", "");
9185
if !request_timeout.is_empty()
9286
&& let Ok(timeout) = request_timeout.parse::<u64>()
9387
{

0 commit comments

Comments
 (0)