git_iris/changes/
releasenotes.rs1use super::common::generate_changes_content;
2use super::models::{
3 BreakingChange, ChangeMetrics, Highlight, ReleaseNotesResponse, Section, SectionItem,
4};
5use super::prompt;
6use crate::common::DetailLevel;
7use crate::config::Config;
8use crate::git::GitRepo;
9use anyhow::Result;
10use colored::Colorize;
11use std::sync::Arc;
12
13pub struct ReleaseNotesGenerator;
15
16impl ReleaseNotesGenerator {
17 pub async fn generate(
32 git_repo: Arc<GitRepo>,
33 from: &str,
34 to: &str,
35 config: &Config,
36 detail_level: DetailLevel,
37 version_name: Option<String>,
38 ) -> Result<String> {
39 let release_notes: ReleaseNotesResponse = generate_changes_content::<ReleaseNotesResponse>(
40 git_repo,
41 from,
42 to,
43 config,
44 detail_level,
45 prompt::create_release_notes_system_prompt,
46 prompt::create_release_notes_user_prompt,
47 )
48 .await?;
49
50 Ok(format_release_notes_response(
51 &release_notes,
52 version_name.as_deref(),
53 ))
54 }
55}
56
57fn format_release_notes_response(
59 response: &ReleaseNotesResponse,
60 version_name: Option<&str>,
61) -> String {
62 let mut formatted = String::new();
63
64 let version = match version_name {
66 Some(name) => name.to_string(),
67 None => response.version.clone().unwrap_or_default(),
68 };
69
70 formatted.push_str(&format!(
71 "# Release Notes - v{}\n\n",
72 version.bright_green().bold()
73 ));
74 formatted.push_str(&format!(
75 "Release Date: {}\n\n",
76 response.release_date.clone().unwrap_or_default().yellow()
77 ));
78
79 formatted.push_str(&format!("{}\n\n", response.summary.bright_cyan()));
81
82 if !response.highlights.is_empty() {
84 formatted.push_str(&"## ✨ Highlights\n\n".bright_magenta().bold().to_string());
85 for highlight in &response.highlights {
86 formatted.push_str(&format_highlight(highlight));
87 }
88 }
89
90 for section in &response.sections {
92 formatted.push_str(&format_section(section));
93 }
94
95 if !response.breaking_changes.is_empty() {
97 formatted.push_str(&"## ⚠️ Breaking Changes\n\n".bright_red().bold().to_string());
98 for breaking_change in &response.breaking_changes {
99 formatted.push_str(&format_breaking_change(breaking_change));
100 }
101 }
102
103 if !response.upgrade_notes.is_empty() {
105 formatted.push_str(&"## 🔧 Upgrade Notes\n\n".yellow().bold().to_string());
106 for note in &response.upgrade_notes {
107 formatted.push_str(&format!("- {note}\n"));
108 }
109 formatted.push('\n');
110 }
111
112 formatted.push_str(&"## 📊 Metrics\n\n".bright_blue().bold().to_string());
114 formatted.push_str(&format_metrics(&response.metrics));
115
116 formatted
117}
118
119fn format_highlight(highlight: &Highlight) -> String {
121 format!(
122 "### {}\n\n{}\n\n",
123 highlight.title.bright_yellow().bold(),
124 highlight.description
125 )
126}
127
128fn format_section(section: &Section) -> String {
130 let mut formatted = format!("## {}\n\n", section.title.bright_blue().bold());
131 for item in §ion.items {
132 formatted.push_str(&format_section_item(item));
133 }
134 formatted.push('\n');
135 formatted
136}
137
138fn format_section_item(item: &SectionItem) -> String {
140 let mut formatted = format!("- {}", item.description);
141
142 if !item.associated_issues.is_empty() {
143 formatted.push_str(&format!(
144 " ({})",
145 item.associated_issues.join(", ").yellow()
146 ));
147 }
148
149 if let Some(pr) = &item.pull_request {
150 formatted.push_str(&format!(" [{}]", pr.bright_purple()));
151 }
152
153 formatted.push('\n');
154 formatted
155}
156
157fn format_breaking_change(breaking_change: &BreakingChange) -> String {
159 format!(
160 "- {} ({})\n",
161 breaking_change.description,
162 breaking_change.commit_hash.dimmed()
163 )
164}
165
166fn format_metrics(metrics: &ChangeMetrics) -> String {
168 format!(
169 "- Total Commits: {}\n- Files Changed: {}\n- Insertions: {}\n- Deletions: {}\n",
170 metrics.total_commits.to_string().green(),
171 metrics.files_changed.to_string().yellow(),
172 metrics.insertions.to_string().green(),
173 metrics.deletions.to_string().red()
174 )
175}