1use crate::e_cargocommand_ext::CargoProcessResult;
2use comfy_table::{Cell, ContentArrangement, Row, Table};
3use git2::{Error, Repository};
4use std::fs::File;
5use std::io::{self, Write};
6use std::process::Command;
7
8fn current_remote_and_short_sha() -> Result<(String, String, String), Error> {
9 let repo = Repository::discover(".")?;
10
11 let oid = repo
13 .head()?
14 .target()
15 .ok_or_else(|| Error::from_str("no HEAD target"))?;
16 let short = repo
17 .find_object(oid, None)?
18 .short_id()?
19 .as_str()
20 .ok_or_else(|| Error::from_str("invalid short id"))?
21 .to_string();
22
23 let remote = repo
25 .find_remote("origin")
26 .or_else(|_| {
27 repo.remotes()?
28 .get(0)
29 .and_then(|name| repo.find_remote(name).ok())
30 .ok_or_else(|| Error::from_str("no remotes configured"))
31 })?
32 .url()
33 .unwrap_or_default()
34 .to_string();
35
36 let repo_name = remote
38 .split('/')
39 .last()
40 .and_then(|name| name.strip_suffix(".git"))
41 .unwrap_or("Unknown")
42 .to_string();
43
44 Ok((remote, short, repo_name))
45}
46
47pub fn generate_comfy_report(results: &[CargoProcessResult]) -> String {
48 let mut system = sysinfo::System::new_all();
49 system.refresh_all();
50
51 let system_name = sysinfo::System::name().unwrap_or_else(|| "Unknown".to_string());
52 let system_version = sysinfo::System::os_version().unwrap_or_else(|| "Unknown".to_string());
53 let system_long_version =
54 sysinfo::System::long_os_version().unwrap_or_else(|| "Unknown".to_string());
55
56 let rustc_version = Command::new("rustc")
57 .arg("--version")
58 .output()
59 .ok()
60 .and_then(|output| {
61 if output.status.success() {
62 Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
63 } else {
64 None
65 }
66 })
67 .unwrap_or_else(|| "Unknown".to_string());
68
69 let (repo_remote, repo_sha, repo_name) = current_remote_and_short_sha().unwrap_or_else(|_| {
70 (
71 "Unknown".to_string(),
72 "Unknown".to_string(),
73 "Unknown".to_string(),
74 )
75 });
76
77 let current_time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
78
79 let mut metadata_table = Table::new();
81 metadata_table.set_content_arrangement(ContentArrangement::Dynamic);
82 metadata_table.set_width(80);
83 metadata_table.add_row(Row::from(vec![
84 Cell::new("Run Report"),
85 Cell::new(format!("{} ({} {})", repo_name, repo_remote, repo_sha)),
86 ]));
87 metadata_table.add_row(Row::from(vec![
88 Cell::new("generated on"),
89 Cell::new(current_time),
90 ]));
91 metadata_table.add_row(Row::from(vec![
92 Cell::new("cargo-e version"),
93 Cell::new(env!("CARGO_PKG_VERSION")),
94 ]));
95 metadata_table.add_row(Row::from(vec![
96 Cell::new("rustc version"),
97 Cell::new(rustc_version),
98 ]));
99 metadata_table.add_row(Row::from(vec![
100 Cell::new("system info"),
101 Cell::new(format!(
102 "{} - {} - {}",
103 system_name, system_version, system_long_version
104 )),
105 ]));
106
107 let mut report = metadata_table.to_string();
108 report.push_str("\n\n");
109
110 let mut cnt = 0;
112 for result in results {
113 cnt += 1;
114 let mut result_table = Table::new();
115 result_table.set_content_arrangement(ContentArrangement::Dynamic);
116 result_table.set_width(100);
117
118 let start_time = result
119 .start_time
120 .map(|t| {
121 chrono::DateTime::<chrono::Local>::from(t)
122 .format("%H:%M:%S")
123 .to_string()
124 })
125 .unwrap_or_else(|| "-".to_string());
126 let end_time = result
127 .end_time
128 .map(|t| {
129 chrono::DateTime::<chrono::Local>::from(t)
130 .format("%H:%M:%S")
131 .to_string()
132 })
133 .unwrap_or_else(|| "-".to_string());
134 let duration = result
135 .elapsed_time
136 .map(|d| format!("{:.2?}", d))
137 .unwrap_or_else(|| "-".to_string());
138 let exit_code = result.exit_status.map_or("-".to_string(), |s| {
139 s.code().map_or("-".to_string(), |c| c.to_string())
140 });
141 let success = result
142 .exit_status
143 .map_or("No", |s| if s.success() { "Yes" } else { "No" });
144
145 report.push_str(&format!("## {}. {}\n\n", cnt, result.target_name));
146 report.push_str(&format!("{} {}\n", result.cmd, result.args.join(" ")));
147 result_table.add_row(Row::from(vec![
148 Cell::new(result.target_name.clone()),
149 ]));
151 result_table.add_row(Row::from(vec![
152 Cell::new("Start Time"),
153 Cell::new(start_time),
154 ]));
155 result_table.add_row(Row::from(vec![Cell::new("End Time"), Cell::new(end_time)]));
156 result_table.add_row(Row::from(vec![Cell::new("Duration"), Cell::new(duration)]));
157 result_table.add_row(Row::from(vec![
158 Cell::new("Exit Code"),
159 Cell::new(exit_code),
160 ]));
161 result_table.add_row(Row::from(vec![Cell::new("Success"), Cell::new(success)]));
162 report.push_str(&result_table.to_string());
163 report.push_str("\n\n");
164
165 if !result.diagnostics.is_empty() {
167 let mut diagnostics_table = Table::new();
168 diagnostics_table.set_content_arrangement(ContentArrangement::Dynamic);
169 diagnostics_table.set_width(100);
170
171 diagnostics_table.add_row(Row::from(vec![
172 Cell::new("Level"),
173 Cell::new("Lineref"),
174 Cell::new("Error Code"),
175 Cell::new("Message"),
176 ]));
177
178 for diagnostic in &result.diagnostics {
179 diagnostics_table.add_row(Row::from(vec![
180 Cell::new(format!(
181 "{}{}",
182 diagnostic
183 .level
184 .chars()
185 .next()
186 .unwrap_or_default()
187 .to_uppercase(),
188 diagnostic.diag_number.unwrap_or_default()
189 )),
190 Cell::new(&diagnostic.lineref),
191 Cell::new(
192 diagnostic
193 .error_code
194 .clone()
195 .unwrap_or_else(|| "-".to_string()),
196 ),
197 Cell::new(&diagnostic.message),
198 ]));
199 }
200 report.push_str(&diagnostics_table.to_string());
201 report.push_str("\n");
202
203 let mut detail_table = Table::new();
205 detail_table.set_content_arrangement(ContentArrangement::Dynamic);
206 detail_table.set_width(100);
207 for diagnostic in &result.diagnostics {
208 let mut nocolor = diagnostic.clone();
209 nocolor.uses_color = false; detail_table.add_row(Row::from(vec![Cell::new(format!("{:?}", nocolor))]));
211 }
212
213 report.push_str(&detail_table.to_string());
214 report.push_str("\n\n");
215 }
216 }
217
218 report
219}
220
221pub fn generate_markdown_report(results: &[CargoProcessResult]) -> String {
222 return generate_comfy_report(results);
223 #[allow(unreachable_code)]
224 let mut system = sysinfo::System::new_all();
225 system.refresh_all();
226
227 let system_name = sysinfo::System::name().unwrap_or_else(|| "Unknown".to_string());
228 let system_version = sysinfo::System::os_version().unwrap_or_else(|| "Unknown".to_string());
229 let system_long_version =
230 sysinfo::System::long_os_version().unwrap_or_else(|| "Unknown".to_string());
231
232 let rustc_version = Command::new("rustc")
233 .arg("--version")
234 .output()
235 .ok()
236 .and_then(|output| {
237 if output.status.success() {
238 Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
239 } else {
240 None
241 }
242 })
243 .unwrap_or_else(|| "Unknown".to_string());
244
245 let (repo_remote, repo_sha, repo_name) = current_remote_and_short_sha().unwrap_or_else(|_| {
246 (
247 "Unknown".to_string(),
248 "Unknown".to_string(),
249 "Unknown".to_string(),
250 )
251 });
252
253 let current_time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
254
255 let mut table = Table::new();
257 table.set_content_arrangement(ContentArrangement::Dynamic);
258 table.set_width(100);
259
260 table.add_row(Row::from(vec![
262 Cell::new("Run Report"),
263 Cell::new(format!("{} ({}@{})", repo_name, repo_remote, repo_sha)),
264 ]));
265 table.add_row(Row::from(vec![
266 Cell::new("Generated On"),
267 Cell::new(current_time),
268 ]));
269 table.add_row(Row::from(vec![
270 Cell::new("Cargo-e Version"),
271 Cell::new(env!("CARGO_PKG_VERSION")),
272 ]));
273 table.add_row(Row::from(vec![
274 Cell::new("Rustc Version"),
275 Cell::new(rustc_version),
276 ]));
277 table.add_row(Row::from(vec![
278 Cell::new("System Info"),
279 Cell::new(format!(
280 "{} - {} - {}",
281 system_name, system_version, system_long_version
282 )),
283 ]));
284
285 table.add_row(Row::from(vec![Cell::new(""), Cell::new("")]));
287
288 for (index, result) in results.iter().enumerate() {
290 let start_time = result
291 .start_time
292 .map(|t| {
293 chrono::DateTime::<chrono::Local>::from(t)
294 .format("%H:%M:%S")
295 .to_string()
296 })
297 .unwrap_or_else(|| "-".to_string());
298 let end_time = result
299 .end_time
300 .map(|t| {
301 chrono::DateTime::<chrono::Local>::from(t)
302 .format("%H:%M:%S")
303 .to_string()
304 })
305 .unwrap_or_else(|| "-".to_string());
306 let duration = result
307 .elapsed_time
308 .map(|d| format!("{:.2?}", d))
309 .unwrap_or_else(|| "-".to_string());
310 let exit_code = result.exit_status.map_or("-".to_string(), |s| {
311 s.code().map_or("-".to_string(), |c| c.to_string())
312 });
313 let success = result
314 .exit_status
315 .map_or("No", |s| if s.success() { "Yes" } else { "No" });
316
317 table.add_row(Row::from(vec![
318 Cell::new(format!("{}. {}", index + 1, result.target_name)),
319 Cell::new(""),
320 ]));
321 table.add_row(Row::from(vec![
322 Cell::new("Start Time"),
323 Cell::new(start_time),
324 ]));
325 table.add_row(Row::from(vec![Cell::new("End Time"), Cell::new(end_time)]));
326 table.add_row(Row::from(vec![Cell::new("Duration"), Cell::new(duration)]));
327 table.add_row(Row::from(vec![
328 Cell::new("Exit Code"),
329 Cell::new(exit_code),
330 ]));
331 table.add_row(Row::from(vec![Cell::new("Success"), Cell::new(success)]));
332
333 if !result.diagnostics.is_empty() {
335 table.add_row(Row::from(vec![Cell::new("Diagnostics"), Cell::new("")]));
336 for diagnostic in &result.diagnostics {
337 table.add_row(Row::from(vec![
338 Cell::new(format!("Level: {}", diagnostic.level)),
339 Cell::new(&diagnostic.message),
340 ]));
341 }
342 }
343
344 table.add_row(Row::from(vec![Cell::new(""), Cell::new("")]));
346 }
347
348 table.to_string()
350}
351
352pub fn save_report_to_file(report: &str, file_path: &str) -> io::Result<()> {
425 let mut file = File::create(file_path)?;
426 file.write_all(report.as_bytes())?;
427 Ok(())
428}
429
430pub fn create_gist(content: &str, description: &str) -> io::Result<()> {
431 crate::e_installer::ensure_github_gh().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let output = Command::new("gh")
433 .args(&["gist", "create", "--public", "--desc", description])
434 .stdin(std::process::Stdio::piped())
435 .spawn()?
436 .stdin
437 .as_mut()
438 .unwrap()
439 .write_all(content.as_bytes());
440
441 output.map_err(|e| {
442 io::Error::new(
443 io::ErrorKind::Other,
444 format!("Failed to create gist: {}", e),
445 )
446 })
447}