1212 * Last Modified: 2025-09-26 15:45:37
1313 */
1414
15+ use aigitcommit:: built_info:: { PKG_NAME , PKG_VERSION } ;
1516use aigitcommit:: cli:: Cli ;
1617use aigitcommit:: git:: message:: GitMessage ;
1718use aigitcommit:: git:: repository:: Repository ;
1819use aigitcommit:: openai;
1920use aigitcommit:: openai:: OpenAI ;
2021use arboard:: Clipboard ;
21- use async_openai:: error:: OpenAIError ;
2222use async_openai:: types:: {
2323 ChatCompletionRequestSystemMessageArgs , ChatCompletionRequestUserMessageArgs ,
2424} ;
2525use clap:: Parser ;
2626use std:: error:: Error ;
27- use std:: fs:: File ;
27+ use std:: fs;
2828use std:: io:: Write ;
29- use std:: { env, fs} ;
3029use 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]
5936async 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 ( ( ) )
0 commit comments