1use crate::context::with_context;
6use crate::error::Result;
7use std::process::Command;
8use steel::SteelVal;
9use steel::steel_vm::engine::Engine;
10use steel::steel_vm::register_fn::RegisterFn;
11
12pub fn register(engine: &mut Engine) -> Result<()> {
14 engine.register_fn("hx/run", run_command);
15 engine.register_fn("hx/run-checked", run_checked);
16 engine.register_fn("hx/run-silent", run_silent);
17 Ok(())
18}
19
20fn run_command(cmd: String, args: Vec<SteelVal>) -> SteelVal {
23 let args: Vec<String> = args
24 .into_iter()
25 .filter_map(|v| match v {
26 SteelVal::StringV(s) => Some(s.to_string()),
27 _ => None,
28 })
29 .collect();
30
31 let env_vars = with_context(|ctx| ctx.env_vars.clone()).unwrap_or_default();
33
34 let result = Command::new(&cmd).args(&args).envs(&env_vars).output();
35
36 match result {
37 Ok(output) => {
38 let exit_code = SteelVal::IntV(output.status.code().unwrap_or(-1) as isize);
40 let stdout =
41 SteelVal::StringV(String::from_utf8_lossy(&output.stdout).to_string().into());
42 let stderr =
43 SteelVal::StringV(String::from_utf8_lossy(&output.stderr).to_string().into());
44
45 SteelVal::ListV(
46 vec![
47 SteelVal::ListV(vec![SteelVal::SymbolV("exit-code".into()), exit_code].into()),
48 SteelVal::ListV(vec![SteelVal::SymbolV("stdout".into()), stdout].into()),
49 SteelVal::ListV(vec![SteelVal::SymbolV("stderr".into()), stderr].into()),
50 ]
51 .into(),
52 )
53 }
54 Err(e) => SteelVal::ListV(
55 vec![
56 SteelVal::ListV(
57 vec![SteelVal::SymbolV("exit-code".into()), SteelVal::IntV(-1)].into(),
58 ),
59 SteelVal::ListV(
60 vec![
61 SteelVal::SymbolV("stdout".into()),
62 SteelVal::StringV("".into()),
63 ]
64 .into(),
65 ),
66 SteelVal::ListV(
67 vec![
68 SteelVal::SymbolV("stderr".into()),
69 SteelVal::StringV(e.to_string().into()),
70 ]
71 .into(),
72 ),
73 ]
74 .into(),
75 ),
76 }
77}
78
79fn run_checked(cmd: String, args: Vec<SteelVal>) -> std::result::Result<SteelVal, String> {
81 let args: Vec<String> = args
82 .into_iter()
83 .filter_map(|v| match v {
84 SteelVal::StringV(s) => Some(s.to_string()),
85 _ => None,
86 })
87 .collect();
88
89 let env_vars = with_context(|ctx| ctx.env_vars.clone()).unwrap_or_default();
90
91 let output = Command::new(&cmd)
92 .args(&args)
93 .envs(&env_vars)
94 .output()
95 .map_err(|e| format!("Failed to execute {}: {}", cmd, e))?;
96
97 if output.status.success() {
98 Ok(SteelVal::StringV(
99 String::from_utf8_lossy(&output.stdout).to_string().into(),
100 ))
101 } else {
102 let stderr = String::from_utf8_lossy(&output.stderr);
103 Err(format!(
104 "Command '{}' failed with exit code {:?}: {}",
105 cmd,
106 output.status.code(),
107 stderr
108 ))
109 }
110}
111
112fn run_silent(cmd: String, args: Vec<SteelVal>) -> SteelVal {
114 let args: Vec<String> = args
115 .into_iter()
116 .filter_map(|v| match v {
117 SteelVal::StringV(s) => Some(s.to_string()),
118 _ => None,
119 })
120 .collect();
121
122 let env_vars = with_context(|ctx| ctx.env_vars.clone()).unwrap_or_default();
123
124 let result = Command::new(&cmd).args(&args).envs(&env_vars).output();
125
126 match result {
127 Ok(output) => SteelVal::IntV(output.status.code().unwrap_or(-1) as isize),
128 Err(_) => SteelVal::IntV(-1),
129 }
130}