pyro_artifacts/
command.rs1use std::path::Path;
4use thiserror::Error;
5
6use tokio::process::Command;
7
8#[derive(Error, Debug)]
9pub enum CommandError {
10 #[error("IO error: {0}")]
11 Io(#[from] std::io::Error),
12 #[error(
13 "Cargo command failed with status {status}. Args: {args:?}\nStdout: {stdout}\nStderr: {stderr}"
14 )]
15 Cargo {
16 status: std::process::ExitStatus,
17 args: Vec<String>,
18 stdout: String,
19 stderr: String,
20 },
21}
22
23#[tracing::instrument(skip(root), fields(root = %root.display(), tool_args = ?tool_args))]
25pub async fn run_command(
26 root: &Path,
27 tool_args: &[&str],
28 capture: bool,
29) -> Result<String, CommandError> {
30 tracing::debug!("Executing cargo command: cargo {:?}", tool_args);
31 let mut cmd = Command::new("cargo");
32 cmd.args(tool_args).current_dir(root);
33
34 if capture {
35 let output = match cmd.output().await {
36 Ok(o) => o,
37 Err(e) => {
38 tracing::error!(error = ?e, "Failed to launch cargo command");
39 return Err(CommandError::Io(e));
40 }
41 };
42
43 if !output.status.success() {
44 let err = CommandError::Cargo {
45 status: output.status,
46 args: tool_args.iter().map(|s| s.to_string()).collect(),
47 stdout: String::from_utf8_lossy(&output.stdout).to_string(),
48 stderr: String::from_utf8_lossy(&output.stderr).to_string(),
49 };
50 tracing::error!(status = ?output.status, "Cargo command failed with exit status");
51 return Err(err);
52 }
53 tracing::debug!("Cargo command completed successfully (captured output)");
54 Ok(String::from_utf8_lossy(&output.stdout).to_string())
55 } else {
56 let status = match cmd.status().await {
57 Ok(s) => s,
58 Err(e) => {
59 tracing::error!(error = ?e, "Failed to launch cargo command");
60 return Err(CommandError::Io(e));
61 }
62 };
63
64 if !status.success() {
65 let err = CommandError::Cargo {
66 status,
67 args: tool_args.iter().map(|s| s.to_string()).collect(),
68 stdout: String::from("Not captured"),
69 stderr: String::from("Not captured"),
70 };
71 tracing::error!(status = ?status, "Cargo command failed with exit status");
72 return Err(err);
73 }
74 tracing::debug!("Cargo command completed successfully");
75 Ok(String::new())
76 }
77}
78
79pub fn format_syn_error(source: &str, err: syn::Error) -> String {
81 let span = err.span();
82 let start = span.start();
83 let msg = err.to_string();
84
85 let lines: Vec<&str> = source.lines().collect();
86 let line_num = start.line;
87 let col = start.column;
88
89 let mut output = String::new();
90 output.push_str(&format!("error: {}\n", msg));
91 output.push_str(&format!(" --> src/lib.rs:{}:{}\n", line_num, col + 1));
92 output.push_str(" |\n");
93
94 let start_line = line_num.saturating_sub(2);
96 let end_line = (line_num + 1).min(lines.len());
97
98 for i in start_line..end_line {
99 let line_content = lines.get(i).unwrap_or(&"");
100 let display_num = i + 1;
101
102 if display_num == line_num {
103 output.push_str(&format!("{:3} | {}\n", display_num, line_content));
104 output.push_str(&format!(" | {}^\n", " ".repeat(col)));
106 } else {
107 output.push_str(&format!("{:3} | {}\n", display_num, line_content));
108 }
109 }
110 output.push_str(" |\n");
111
112 output
113}