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    #[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    // Create the main table
256    let mut table = Table::new();
257    table.set_content_arrangement(ContentArrangement::Dynamic);
258    table.set_width(100);
259
260    // Add metadata rows
261    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    // Add a separator row
286    table.add_row(Row::from(vec![Cell::new(""), Cell::new("")]));
287
288    // Add results for each target
289    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        // Add diagnostics if available
334        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        // Add a separator row
345        table.add_row(Row::from(vec![Cell::new(""), Cell::new("")]));
346    }
347
348    // Convert the table to a string
349    table.to_string()
350}
351
352// pub fn generate_markdown_report(results: &[CargoProcessResult]) -> String {
353//         let mut system = sysinfo::System::new_all();
354//     system.refresh_all();
355//     let system_name = sysinfo::System::name().unwrap_or_else(|| "Unknown".to_string());
356//     let system_long_version = sysinfo::System::long_os_version().unwrap_or_else(|| "Unknown".to_string());
357//     let system_version = sysinfo::System::os_version().unwrap_or_else(|| "Unknown".to_string());
358
359//         let rustc_version = Command::new("rustc")
360//         .arg("--version")
361//         .output()
362//         .ok()
363//         .and_then(|output| {
364//             if output.status.success() {
365//                 Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
366//             } else {
367//                 None
368//             }
369//         })
370//         .unwrap_or_else(|| "Unknown".to_string());
371//         let (repo_remote, repo_sha, repo_name) = current_remote_and_short_sha()
372//         .unwrap_or_else(|_| ("-".to_string(), "-".to_string(), "-".to_string()));
373//     let current_time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
374//         let mut report = String::from(format!(
375//         "# Run Report for {} ({}@{})\n\nGenerated on: {}\n\n",
376//         repo_name, repo_remote, repo_sha, current_time
377//     ));
378//     report.push_str(&format!("**repo info** {} @ {}\n", repo_remote, repo_sha));
379//     report.push_str(&format!("**rustc version** {}\n", rustc_version));
380//     report.push_str(&format!("**system name** {} - {} - {}\n", system_name, system_version, system_long_version));
381//     report.push_str(&format!("**cargo-e version** {}\n", env!("CARGO_PKG_VERSION")));
382//     report.push_str("\n");
383//     let mut cnt = 0;
384//     for result in results {
385//         cnt=cnt + 1;
386//     report.push_str(&format!("## {}. {}\n\n", cnt, result.target_name));
387//     report.push_str("| Target Name | Start Time | End Time | Duration | Exit Code | Success |\n");
388//     report.push_str("|-------------|------------|----------|----------|-----------|---------|\n");
389
390//         let start_time = result
391//             .start_time
392//             .map(|t| chrono::DateTime::<chrono::Local>::from(t).format("%H:%M:%S").to_string())
393//             .unwrap_or_else(|| "-".to_string());
394//         let end_time = result
395//             .end_time
396//             .map(|t| chrono::DateTime::<chrono::Local>::from(t).format("%H:%M:%S").to_string())
397//             .unwrap_or_else(|| "-".to_string());
398//         let duration = result
399//             .elapsed_time
400//             .map(|d| format!("{:.2?}", d))
401//             .unwrap_or_else(|| "-".to_string());
402//         let exit_code = result.exit_status.map_or("-".to_string(), |s| s.code().map_or("-".to_string(), |c| c.to_string()));
403//         let success = result.exit_status.map_or("No", |s| if s.success() { "Yes" } else { "No" });
404
405//         report.push_str(&format!(
406//             "| {} | {} | {} | {} | {} | {} |\n",
407//             result.target_name, start_time, end_time, duration, exit_code, success
408//         ));
409
410//         if !result.diagnostics.is_empty() {
411//             report.push_str("\n| Level | Message                            |\n");
412//             report.push_str("|----------------------------------------------|\n");
413//             for diagnostic in &result.diagnostics {
414//                 report.push_str(&format!("| **{}** | {} |\n",diagnostic.level, diagnostic.message));
415//             }
416//             report.push_str("|----------------------------------------------------------------------|\n");
417//         }
418//         report.push_str("\n");
419//     }
420
421//     report
422// }
423
424pub 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))?; // check for gh CLI
432    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}