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(
31 git_repo: Arc<GitRepo>,
32 from: &str,
33 to: &str,
34 config: &Config,
35 detail_level: DetailLevel,
36 ) -> Result<String> {
37 let release_notes: ReleaseNotesResponse = generate_changes_content::<ReleaseNotesResponse>(
38 git_repo,
39 from,
40 to,
41 config,
42 detail_level,
43 prompt::create_release_notes_system_prompt,
44 prompt::create_release_notes_user_prompt,
45 )
46 .await?;
47
48 Ok(format_release_notes_response(&release_notes))
49 }
50}
51
52fn format_release_notes_response(response: &ReleaseNotesResponse) -> String {
54 let mut formatted = String::new();
55
56 formatted.push_str(&format!(
58 "# Release Notes - v{}\n\n",
59 response
60 .version
61 .clone()
62 .unwrap_or_default()
63 .bright_green()
64 .bold()
65 ));
66 formatted.push_str(&format!(
67 "Release Date: {}\n\n",
68 response.release_date.clone().unwrap_or_default().yellow()
69 ));
70
71 formatted.push_str(&format!("{}\n\n", response.summary.bright_cyan()));
73
74 if !response.highlights.is_empty() {
76 formatted.push_str(&"## ✨ Highlights\n\n".bright_magenta().bold().to_string());
77 for highlight in &response.highlights {
78 formatted.push_str(&format_highlight(highlight));
79 }
80 }
81
82 for section in &response.sections {
84 formatted.push_str(&format_section(section));
85 }
86
87 if !response.breaking_changes.is_empty() {
89 formatted.push_str(&"## ⚠️ Breaking Changes\n\n".bright_red().bold().to_string());
90 for breaking_change in &response.breaking_changes {
91 formatted.push_str(&format_breaking_change(breaking_change));
92 }
93 }
94
95 if !response.upgrade_notes.is_empty() {
97 formatted.push_str(&"## 🔧 Upgrade Notes\n\n".yellow().bold().to_string());
98 for note in &response.upgrade_notes {
99 formatted.push_str(&format!("- {note}\n"));
100 }
101 formatted.push('\n');
102 }
103
104 formatted.push_str(&"## 📊 Metrics\n\n".bright_blue().bold().to_string());
106 formatted.push_str(&format_metrics(&response.metrics));
107
108 formatted
109}
110
111fn format_highlight(highlight: &Highlight) -> String {
113 format!(
114 "### {}\n\n{}\n\n",
115 highlight.title.bright_yellow().bold(),
116 highlight.description
117 )
118}
119
120fn format_section(section: &Section) -> String {
122 let mut formatted = format!("## {}\n\n", section.title.bright_blue().bold());
123 for item in §ion.items {
124 formatted.push_str(&format_section_item(item));
125 }
126 formatted.push('\n');
127 formatted
128}
129
130fn format_section_item(item: &SectionItem) -> String {
132 let mut formatted = format!("- {}", item.description);
133
134 if !item.associated_issues.is_empty() {
135 formatted.push_str(&format!(
136 " ({})",
137 item.associated_issues.join(", ").yellow()
138 ));
139 }
140
141 if let Some(pr) = &item.pull_request {
142 formatted.push_str(&format!(" [{}]", pr.bright_purple()));
143 }
144
145 formatted.push('\n');
146 formatted
147}
148
149fn format_breaking_change(breaking_change: &BreakingChange) -> String {
151 format!(
152 "- {} ({})\n",
153 breaking_change.description,
154 breaking_change.commit_hash.dimmed()
155 )
156}
157
158fn format_metrics(metrics: &ChangeMetrics) -> String {
160 format!(
161 "- Total Commits: {}\n- Files Changed: {}\n- Insertions: {}\n- Deletions: {}\n",
162 metrics.total_commits.to_string().green(),
163 metrics.files_changed.to_string().yellow(),
164 metrics.insertions.to_string().green(),
165 metrics.deletions.to_string().red()
166 )
167}