slash_lib/builtins/
exec.rs1use std::process::Command as Proc;
2use std::process::Stdio;
3
4use slash_lang::parser::ast::Arg;
5
6use crate::command::{MethodDef, SlashCommand};
7use crate::executor::{CommandOutput, ExecutionError, PipeValue};
8
9pub struct Exec;
16
17impl SlashCommand for Exec {
18 fn name(&self) -> &str {
19 "exec"
20 }
21
22 fn methods(&self) -> &[MethodDef] {
23 static METHODS: [MethodDef; 1] = [MethodDef::flag("verbose")];
24 &METHODS
25 }
26
27 fn execute(
28 &self,
29 primary: Option<&str>,
30 args: &[Arg],
31 input: Option<&PipeValue>,
32 ) -> Result<CommandOutput, ExecutionError> {
33 let cmd_str = primary.ok_or_else(|| {
34 ExecutionError::Runner("/exec requires a command: /exec(cargo test)".into())
35 })?;
36
37 let stdin_bytes: Option<&[u8]> = match input {
38 Some(PipeValue::Bytes(b)) => Some(b),
39 Some(PipeValue::Context(ctx)) => {
40 let _ = ctx;
43 None
44 }
45 None => None,
46 };
47
48 let context_json: Option<String> = match input {
50 Some(PipeValue::Context(ctx)) => Some(ctx.to_json()),
51 _ => None,
52 };
53
54 let effective_stdin = stdin_bytes.or(context_json.as_deref().map(|s| s.as_bytes()));
55
56 let stdin_cfg = if effective_stdin.is_some() {
57 Stdio::piped()
58 } else {
59 Stdio::null()
60 };
61
62 let mut child = Proc::new("sh")
63 .arg("-c")
64 .arg(cmd_str)
65 .stdin(stdin_cfg)
66 .stdout(Stdio::piped())
67 .stderr(Stdio::piped())
68 .spawn()
69 .map_err(|e| ExecutionError::Runner(format!("/exec({}): {}", cmd_str, e)))?;
70
71 if let Some(data) = effective_stdin {
72 if let Some(mut stdin) = child.stdin.take() {
73 use std::io::Write as _;
74 let _ = stdin.write_all(data);
75 }
76 }
77
78 let output = child
79 .wait_with_output()
80 .map_err(|e| ExecutionError::Runner(format!("/exec({}): {}", cmd_str, e)))?;
81
82 let verbose = args.iter().any(|a| a.name == "verbose");
83 if verbose && !output.stderr.is_empty() {
84 use std::io::Write as _;
85 let _ = std::io::stderr().write_all(&output.stderr);
86 }
87
88 let success = output.status.success();
89 let stdout = if output.stdout.is_empty() {
90 None
91 } else {
92 Some(output.stdout)
93 };
94 let stderr = if output.stderr.is_empty() {
95 None
96 } else {
97 Some(output.stderr)
98 };
99
100 Ok(CommandOutput {
101 stdout,
102 stderr,
103 success,
104 })
105 }
106}