use std::process::Command as Proc;
use std::process::Stdio;
use slash_lang::parser::ast::Arg;
use crate::command::{MethodDef, SlashCommand};
use crate::executor::{CommandOutput, ExecutionError, PipeValue};
pub struct Exec;
impl SlashCommand for Exec {
fn name(&self) -> &str {
"exec"
}
fn methods(&self) -> &[MethodDef] {
static METHODS: [MethodDef; 1] = [MethodDef::flag("verbose")];
&METHODS
}
fn execute(
&self,
primary: Option<&str>,
args: &[Arg],
input: Option<&PipeValue>,
) -> Result<CommandOutput, ExecutionError> {
let cmd_str = primary.ok_or_else(|| {
ExecutionError::Runner("/exec requires a command: /exec(cargo test)".into())
})?;
let stdin_bytes: Option<&[u8]> = match input {
Some(PipeValue::Bytes(b)) => Some(b),
Some(PipeValue::Context(ctx)) => {
let _ = ctx;
None
}
None => None,
};
let context_json: Option<String> = match input {
Some(PipeValue::Context(ctx)) => Some(ctx.to_json()),
_ => None,
};
let effective_stdin = stdin_bytes.or(context_json.as_deref().map(|s| s.as_bytes()));
let stdin_cfg = if effective_stdin.is_some() {
Stdio::piped()
} else {
Stdio::null()
};
let mut child = Proc::new("sh")
.arg("-c")
.arg(cmd_str)
.stdin(stdin_cfg)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| ExecutionError::Runner(format!("/exec({}): {}", cmd_str, e)))?;
if let Some(data) = effective_stdin {
if let Some(mut stdin) = child.stdin.take() {
use std::io::Write as _;
let _ = stdin.write_all(data);
}
}
let output = child
.wait_with_output()
.map_err(|e| ExecutionError::Runner(format!("/exec({}): {}", cmd_str, e)))?;
let verbose = args.iter().any(|a| a.name == "verbose");
if verbose && !output.stderr.is_empty() {
use std::io::Write as _;
let _ = std::io::stderr().write_all(&output.stderr);
}
let success = output.status.success();
let stdout = if output.stdout.is_empty() {
None
} else {
Some(output.stdout)
};
let stderr = if output.stderr.is_empty() {
None
} else {
Some(output.stderr)
};
Ok(CommandOutput {
stdout,
stderr,
success,
})
}
}