Skip to main content

pyro_artifacts/
command.rs

1// ! The cargo and macro expand tools
2
3use 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/// Run a cargo command within this environment
24pub async fn run_command(
25    root: &Path,
26    tool_args: &[&str],
27    capture: bool,
28) -> Result<String, CommandError> {
29    let mut cmd = Command::new("cargo");
30    cmd.args(tool_args).current_dir(&root);
31
32    if capture {
33        let output = cmd.output().await?;
34
35        if !output.status.success() {
36            return Err(CommandError::Cargo {
37                status: output.status,
38                args: tool_args.iter().map(|s| s.to_string()).collect(),
39                stdout: String::from_utf8_lossy(&output.stdout).to_string(),
40                stderr: String::from_utf8_lossy(&output.stderr).to_string(),
41            });
42        }
43        Ok(String::from_utf8_lossy(&output.stdout).to_string())
44    } else {
45        let status = cmd.status().await?;
46
47        if !status.success() {
48            return Err(CommandError::Cargo {
49                status,
50                args: tool_args.iter().map(|s| s.to_string()).collect(),
51                stdout: String::from("Not captured"),
52                stderr: String::from("Not captured"),
53            });
54        }
55        Ok(String::new())
56    }
57}
58
59/// Format a syn::Error with source context
60pub fn format_syn_error(source: &str, err: syn::Error) -> String {
61    let span = err.span();
62    let start = span.start();
63    let msg = err.to_string();
64
65    let lines: Vec<&str> = source.lines().collect();
66    let line_num = start.line;
67    let col = start.column;
68
69    let mut output = String::new();
70    output.push_str(&format!("error: {}\n", msg));
71    output.push_str(&format!("  --> src/lib.rs:{}:{}\n", line_num, col + 1));
72    output.push_str("   |\n");
73
74    // Show context: line before, error line, line after
75    let start_line = line_num.saturating_sub(2);
76    let end_line = (line_num + 1).min(lines.len());
77
78    for i in start_line..end_line {
79        let line_content = lines.get(i).unwrap_or(&"");
80        let display_num = i + 1;
81
82        if display_num == line_num {
83            output.push_str(&format!("{:3} | {}\n", display_num, line_content));
84            // Add caret pointing to the column
85            output.push_str(&format!("    | {}^\n", " ".repeat(col)));
86        } else {
87            output.push_str(&format!("{:3} | {}\n", display_num, line_content));
88        }
89    }
90    output.push_str("   |\n");
91
92    output
93}