use crate::analyzer::{AnalysisResult, CommitType}; use crate::git::StagedChanges; pub fn generate_message(analysis: &AnalysisResult, staged: &StagedChanges) -> String { let scope = analysis.scope.clone().unwrap_or_default(); let description = &analysis.description; if !scope.is_empty() { format!("{} ({}): {}", analysis.commit_type, scope, description) } else { format!("{}: {}", analysis.commit_type, description) } } pub fn generate_alternative_messages( analysis: &AnalysisResult, staged: &StagedChanges, ) -> Vec { let mut messages = Vec::new(); let scope = analysis.scope.clone().unwrap_or_default(); let base_description = &analysis.description; let verb_alternatives = vec!["update", "modify", "change", "revise"]; if !scope.is_empty() { for verb in &verb_alternatives { let desc = verb_alternatives_description(verb, &analysis.description, &staged.files); messages.push(format!("{} ({}): {}", analysis.commit_type, scope, desc)); } } else { for verb in &verb_alternatives { let desc = verb_alternatives_description(verb, &analysis.description, &staged.files); messages.push(format!("{}: {}", analysis.commit_type, desc)); } } messages } fn verb_alternatives_description( verb: &str, base: &str, files: &[crate::git::ChangedFile], ) -> String { let file_count = files.len(); if file_count <= 3 && !files.is_empty() { let file_names: Vec = files .iter() .map(|f| f.path.split('/').last().unwrap_or(&f.path).to_string()) .collect(); let file_list = file_names.join(", "); format!("{} {}", verb, file_list) } else { format!("{} {} files", verb, file_count) } } pub fn format_message(message: &str) -> String { if message.len() <= 72 { return message.to_string(); } let words: Vec<&str> = message.split_whitespace().collect(); let mut lines = Vec::new(); let mut current_line = String::new(); for word in words { if current_line.is_empty() { current_line = word.to_string(); } else if current_line.len() + 1 + word.len() <= 72 { current_line.push(' '); current_line.push_str(word); } else { lines.push(current_line); current_line = word.to_string(); } } if !current_line.is_empty() { lines.push(current_line); } lines.join("\n") } #[cfg(test)] mod tests { use super::*; use crate::analyzer::{AnalysisResult, CommitType}; use crate::git::{ChangedFile, FileStatus, StagedChanges}; #[test] fn test_message_format_with_scope() { let analysis = AnalysisResult { commit_type: CommitType::Feat, scope: Some(String::from("auth")), confidence: 0.9, description: String::from("add login functionality"), reasons: vec![], }; let staged = StagedChanges { files: vec![], diff_text: String::new(), }; let message = generate_message(&analysis, &staged); assert_eq!(message, "feat(auth): add login functionality"); } #[test] fn test_message_format_without_scope() { let analysis = AnalysisResult { commit_type: CommitType::Fix, scope: None, confidence: 0.9, description: String::from("resolve null pointer exception"), reasons: vec![], }; let staged = StagedChanges { files: vec![], diff_text: String::new(), }; let message = generate_message(&analysis, &staged); assert_eq!(message, "fix: resolve null pointer exception"); } #[test] fn test_scope_extraction() { let analysis = AnalysisResult { commit_type: CommitType::Test, scope: Some(String::from("api")), confidence: 0.9, description: String::from("add API endpoint tests"), reasons: vec![], }; let staged = StagedChanges { files: vec![ChangedFile { path: String::from("api/users_test.rs"), status: FileStatus::Added, additions: 50, deletions: 0, is_new: true, is_deleted: false, is_renamed: false, old_path: None, }], diff_text: String::new(), }; let message = generate_message(&analysis, &staged); assert_eq!(message, "test(api): add API endpoint tests"); } #[test] fn test_format_message_short() { let message = format_message("feat: add new feature"); assert_eq!(message, "feat: add new feature"); } #[test] fn test_format_message_long() { let long_desc = "this is a very long description that should be wrapped because it exceeds seventy two characters in length"; let message = format_message(&format!("feat: {}", long_desc)); assert!(message.contains('\n')); } }