git_contribution_analyzer/
export.rs1use crate::app::App;
2use std::{error::Error, fs, path::Path};
3
4pub fn export_html_report(app: &App, output_path: &Path) -> Result<(), Box<dyn Error>> {
5 let mut html = String::from(
6 r#"<!DOCTYPE html>
7<html lang="en">
8<head>
9 <meta charset="UTF-8">
10 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 <title>Git Contribution Analysis Report</title>
12 <style>
13 body {
14 font-family: Arial, sans-serif;
15 line-height: 1.6;
16 margin: 0;
17 padding: 20px;
18 color: #333;
19 }
20 h1, h2 {
21 color: #2c3e50;
22 }
23 table {
24 border-collapse: collapse;
25 width: 100%;
26 margin-bottom: 20px;
27 }
28 th, td {
29 text-align: left;
30 padding: 12px;
31 border-bottom: 1px solid #ddd;
32 }
33 th {
34 background-color: #f2f2f2;
35 font-weight: bold;
36 }
37 tr:hover {
38 background-color: #f5f5f5;
39 }
40 .report-date {
41 color: #7f8c8d;
42 font-style: italic;
43 margin-bottom: 30px;
44 }
45 .container {
46 max-width: 1200px;
47 margin: 0 auto;
48 }
49 .repo-section {
50 margin-bottom: 40px;
51 border: 1px solid #eee;
52 padding: 20px;
53 border-radius: 5px;
54 }
55 </style>
56</head>
57<body>
58 <div class="container">
59 <h1>Git Contribution Analysis Report</h1>
60 <p class="report-date">Generated on: "#,
61 )
62 .to_string();
63
64 use chrono::Local;
65 html.push_str(&Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
66
67 html.push_str(
68 r#"</p>
69
70 <div class="repo-section">
71 <h2>Summary Across All Repositories</h2>
72 <table>
73 <thead>
74 <tr>
75 <th>Author</th>
76 <th>Email</th>
77 <th>Total Commits</th>
78 <th>Lines Added</th>
79 <th>Lines Deleted</th>
80 <th>Overall %</th>
81 <th>Preferred Repo</th>
82 <th>Preferred %</th>
83 </tr>
84 </thead>
85 <tbody>
86"#,
87 );
88
89 for summary in &app.author_summaries {
90 html.push_str(&format!(
91 r#"
92 <tr>
93 <td>{}</td>
94 <td>{}</td>
95 <td>{}</td>
96 <td>{}</td>
97 <td>{}</td>
98 <td>{:.2}%</td>
99 <td>{}</td>
100 <td>{:.2}%</td>
101 </tr>
102"#,
103 summary.author,
104 summary.email,
105 summary.total_commits,
106 summary.total_lines_added,
107 summary.total_lines_deleted,
108 summary.overall_contribution_percent,
109 summary.preferred_repo,
110 summary.preferred_repo_percent
111 ));
112 }
113
114 html.push_str(
115 r#"
116 </tbody>
117 </table>
118 </div>
119"#,
120 );
121
122 for repo_name in &app.repositories {
123 html.push_str(&format!(
124 r#"
125 <div class="repo-section">
126 <h2>Repository: {}</h2>
127 <table>
128 <thead>
129 <tr>
130 <th>Author</th>
131 <th>Email</th>
132 <th>Commits</th>
133 <th>Lines Added</th>
134 <th>Lines Deleted</th>
135 <th>Contribution %</th>
136 </tr>
137 </thead>
138 <tbody>
139"#,
140 repo_name
141 ));
142
143 if let Some(contributions) = app.contributions.get(repo_name) {
144 for contrib in contributions {
145 html.push_str(&format!(
146 r#"
147 <tr>
148 <td>{}</td>
149 <td>{}</td>
150 <td>{}</td>
151 <td>{}</td>
152 <td>{}</td>
153 <td>{:.2}%</td>
154 </tr>
155"#,
156 contrib.author,
157 contrib.email,
158 contrib.commits,
159 contrib.lines_added,
160 contrib.lines_deleted,
161 contrib.contribution_percent
162 ));
163 }
164 }
165
166 html.push_str(
167 r#"
168 </tbody>
169 </table>
170 </div>
171"#,
172 );
173 }
174
175 html.push_str(
176 r#"
177 </div>
178</body>
179</html>
180"#,
181 );
182
183 fs::write(output_path, html)?;
184
185 Ok(())
186}