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 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 let mut table = Table::new();
256 table.set_content_arrangement(ContentArrangement::Dynamic);
257 table.set_width(100);
258
259 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 table.add_row(Row::from(vec![Cell::new(""), Cell::new("")]));
286
287 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 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 table.add_row(Row::from(vec![Cell::new(""), Cell::new("")]));
345 }
346
347 table.to_string()
349}
350
351pub 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 let output = Command::new("gh")
431 .args(&["gist", "create", "--public", "--desc", description])
432 .stdin(std::process::Stdio::piped())
433 .spawn()?
434 .stdin
435 .as_mut()
436 .unwrap()
437 .write_all(content.as_bytes());
438
439 output.map_err(|e| {
440 io::Error::new(
441 io::ErrorKind::Other,
442 format!("Failed to create gist: {}", e),
443 )
444 })
445}