cargo_e/
e_reports.rs

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    // Get HEAD OID and shorten to 7 chars
12    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    // Look up the "origin" remote URL
24    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    // Extract the repository name from the remote URL
37    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    // Metadata Table
80    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    // Results Table
111    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            // Cell::new(format!("{} {}", result.cmd, result.args.join(" "))),
150        ]));
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        // Diagnostics Table
166        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            // Add a row with the full debug print of the diagnostic
204            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; // Disable color for detailed output
210                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    let mut system = sysinfo::System::new_all();
224    system.refresh_all();
225
226    let system_name = sysinfo::System::name().unwrap_or_else(|| "Unknown".to_string());
227    let system_version = sysinfo::System::os_version().unwrap_or_else(|| "Unknown".to_string());
228    let system_long_version =
229        sysinfo::System::long_os_version().unwrap_or_else(|| "Unknown".to_string());
230
231    let rustc_version = Command::new("rustc")
232        .arg("--version")
233        .output()
234        .ok()
235        .and_then(|output| {
236            if output.status.success() {
237                Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
238            } else {
239                None
240            }
241        })
242        .unwrap_or_else(|| "Unknown".to_string());
243
244    let (repo_remote, repo_sha, repo_name) = current_remote_and_short_sha().unwrap_or_else(|_| {
245        (
246            "Unknown".to_string(),
247            "Unknown".to_string(),
248            "Unknown".to_string(),
249        )
250    });
251
252    let current_time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
253
254    // Create the main table
255    let mut table = Table::new();
256    table.set_content_arrangement(ContentArrangement::Dynamic);
257    table.set_width(100);
258
259    // Add metadata rows
260    table.add_row(Row::from(vec![
261        Cell::new("Run Report"),
262        Cell::new(format!("{} ({}@{})", repo_name, repo_remote, repo_sha)),
263    ]));
264    table.add_row(Row::from(vec![
265        Cell::new("Generated On"),
266        Cell::new(current_time),
267    ]));
268    table.add_row(Row::from(vec![
269        Cell::new("Cargo-e Version"),
270        Cell::new(env!("CARGO_PKG_VERSION")),
271    ]));
272    table.add_row(Row::from(vec![
273        Cell::new("Rustc Version"),
274        Cell::new(rustc_version),
275    ]));
276    table.add_row(Row::from(vec![
277        Cell::new("System Info"),
278        Cell::new(format!(
279            "{} - {} - {}",
280            system_name, system_version, system_long_version
281        )),
282    ]));
283
284    // Add a separator row
285    table.add_row(Row::from(vec![Cell::new(""), Cell::new("")]));
286
287    // Add results for each target
288    for (index, result) in results.iter().enumerate() {
289        let start_time = result
290            .start_time
291            .map(|t| {
292                chrono::DateTime::<chrono::Local>::from(t)
293                    .format("%H:%M:%S")
294                    .to_string()
295            })
296            .unwrap_or_else(|| "-".to_string());
297        let end_time = result
298            .end_time
299            .map(|t| {
300                chrono::DateTime::<chrono::Local>::from(t)
301                    .format("%H:%M:%S")
302                    .to_string()
303            })
304            .unwrap_or_else(|| "-".to_string());
305        let duration = result
306            .elapsed_time
307            .map(|d| format!("{:.2?}", d))
308            .unwrap_or_else(|| "-".to_string());
309        let exit_code = result.exit_status.map_or("-".to_string(), |s| {
310            s.code().map_or("-".to_string(), |c| c.to_string())
311        });
312        let success = result
313            .exit_status
314            .map_or("No", |s| if s.success() { "Yes" } else { "No" });
315
316        table.add_row(Row::from(vec![
317            Cell::new(format!("{}. {}", index + 1, result.target_name)),
318            Cell::new(""),
319        ]));
320        table.add_row(Row::from(vec![
321            Cell::new("Start Time"),
322            Cell::new(start_time),
323        ]));
324        table.add_row(Row::from(vec![Cell::new("End Time"), Cell::new(end_time)]));
325        table.add_row(Row::from(vec![Cell::new("Duration"), Cell::new(duration)]));
326        table.add_row(Row::from(vec![
327            Cell::new("Exit Code"),
328            Cell::new(exit_code),
329        ]));
330        table.add_row(Row::from(vec![Cell::new("Success"), Cell::new(success)]));
331
332        // Add diagnostics if available
333        if !result.diagnostics.is_empty() {
334            table.add_row(Row::from(vec![Cell::new("Diagnostics"), Cell::new("")]));
335            for diagnostic in &result.diagnostics {
336                table.add_row(Row::from(vec![
337                    Cell::new(format!("Level: {}", diagnostic.level)),
338                    Cell::new(&diagnostic.message),
339                ]));
340            }
341        }
342
343        // Add a separator row
344        table.add_row(Row::from(vec![Cell::new(""), Cell::new("")]));
345    }
346
347    // Convert the table to a string
348    table.to_string()
349}
350
351// pub fn generate_markdown_report(results: &[CargoProcessResult]) -> String {
352//         let mut system = sysinfo::System::new_all();
353//     system.refresh_all();
354//     let system_name = sysinfo::System::name().unwrap_or_else(|| "Unknown".to_string());
355//     let system_long_version = sysinfo::System::long_os_version().unwrap_or_else(|| "Unknown".to_string());
356//     let system_version = sysinfo::System::os_version().unwrap_or_else(|| "Unknown".to_string());
357
358//         let rustc_version = Command::new("rustc")
359//         .arg("--version")
360//         .output()
361//         .ok()
362//         .and_then(|output| {
363//             if output.status.success() {
364//                 Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
365//             } else {
366//                 None
367//             }
368//         })
369//         .unwrap_or_else(|| "Unknown".to_string());
370//         let (repo_remote, repo_sha, repo_name) = current_remote_and_short_sha()
371//         .unwrap_or_else(|_| ("-".to_string(), "-".to_string(), "-".to_string()));
372//     let current_time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
373//         let mut report = String::from(format!(
374//         "# Run Report for {} ({}@{})\n\nGenerated on: {}\n\n",
375//         repo_name, repo_remote, repo_sha, current_time
376//     ));
377//     report.push_str(&format!("**repo info** {} @ {}\n", repo_remote, repo_sha));
378//     report.push_str(&format!("**rustc version** {}\n", rustc_version));
379//     report.push_str(&format!("**system name** {} - {} - {}\n", system_name, system_version, system_long_version));
380//     report.push_str(&format!("**cargo-e version** {}\n", env!("CARGO_PKG_VERSION")));
381//     report.push_str("\n");
382//     let mut cnt = 0;
383//     for result in results {
384//         cnt=cnt + 1;
385//     report.push_str(&format!("## {}. {}\n\n", cnt, result.target_name));
386//     report.push_str("| Target Name | Start Time | End Time | Duration | Exit Code | Success |\n");
387//     report.push_str("|-------------|------------|----------|----------|-----------|---------|\n");
388
389//         let start_time = result
390//             .start_time
391//             .map(|t| chrono::DateTime::<chrono::Local>::from(t).format("%H:%M:%S").to_string())
392//             .unwrap_or_else(|| "-".to_string());
393//         let end_time = result
394//             .end_time
395//             .map(|t| chrono::DateTime::<chrono::Local>::from(t).format("%H:%M:%S").to_string())
396//             .unwrap_or_else(|| "-".to_string());
397//         let duration = result
398//             .elapsed_time
399//             .map(|d| format!("{:.2?}", d))
400//             .unwrap_or_else(|| "-".to_string());
401//         let exit_code = result.exit_status.map_or("-".to_string(), |s| s.code().map_or("-".to_string(), |c| c.to_string()));
402//         let success = result.exit_status.map_or("No", |s| if s.success() { "Yes" } else { "No" });
403
404//         report.push_str(&format!(
405//             "| {} | {} | {} | {} | {} | {} |\n",
406//             result.target_name, start_time, end_time, duration, exit_code, success
407//         ));
408
409//         if !result.diagnostics.is_empty() {
410//             report.push_str("\n| Level | Message                            |\n");
411//             report.push_str("|----------------------------------------------|\n");
412//             for diagnostic in &result.diagnostics {
413//                 report.push_str(&format!("| **{}** | {} |\n",diagnostic.level, diagnostic.message));
414//             }
415//             report.push_str("|----------------------------------------------------------------------|\n");
416//         }
417//         report.push_str("\n");
418//     }
419
420//     report
421// }
422
423pub fn save_report_to_file(report: &str, file_path: &str) -> io::Result<()> {
424    let mut file = File::create(file_path)?;
425    file.write_all(report.as_bytes())?;
426    Ok(())
427}
428
429pub fn create_gist(content: &str, description: &str) -> io::Result<()> {
430    crate::e_installer::ensure_github_gh().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; // check for gh CLI
431    let output = Command::new("gh")
432        .args(&["gist", "create", "--public", "--desc", description])
433        .stdin(std::process::Stdio::piped())
434        .spawn()?
435        .stdin
436        .as_mut()
437        .unwrap()
438        .write_all(content.as_bytes());
439
440    output.map_err(|e| {
441        io::Error::new(
442            io::ErrorKind::Other,
443            format!("Failed to create gist: {}", e),
444        )
445    })
446}