1#![allow(clippy::cargo_common_metadata)]
2
3use std::{
4 env::{
5 self,
6 consts::{ARCH, OS},
7 },
8 path::MAIN_SEPARATOR,
9 process::Stdio,
10};
11
12use mlua::prelude::*;
13use mlua_luau_scheduler::Functions;
14
15use os_str_bytes::RawOsString;
16
17use lune_utils::{path::get_current_dir, TableBuilder};
18
19mod create;
20mod exec;
21mod options;
22
23use self::options::ProcessSpawnOptions;
24
25const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
26
27#[must_use]
31pub fn typedefs() -> String {
32 TYPEDEFS.to_string()
33}
34
35#[allow(clippy::missing_panics_doc)]
43pub fn module(lua: Lua) -> LuaResult<LuaTable> {
44 let mut cwd_str = get_current_dir()
45 .to_str()
46 .expect("cwd should be valid UTF-8")
47 .to_string();
48 if !cwd_str.ends_with(MAIN_SEPARATOR) {
49 cwd_str.push(MAIN_SEPARATOR);
50 }
51
52 let os = lua.create_string(OS.to_lowercase())?;
54 let arch = lua.create_string(ARCH.to_lowercase())?;
55 let endianness = lua.create_string(if cfg!(target_endian = "big") {
56 "big"
57 } else {
58 "little"
59 })?;
60
61 let args_vec = lua
63 .app_data_ref::<Vec<String>>()
64 .ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
65 .clone();
66 let args_tab = TableBuilder::new(lua.clone())?
67 .with_sequential_values(args_vec)?
68 .build_readonly()?;
69
70 let env_tab = TableBuilder::new(lua.clone())?
72 .with_metatable(
73 TableBuilder::new(lua.clone())?
74 .with_function(LuaMetaMethod::Index.name(), process_env_get)?
75 .with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
76 .with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
77 .build_readonly()?,
78 )?
79 .build_readonly()?;
80
81 let fns = Functions::new(lua.clone())?;
83 let process_exit = fns.exit;
84
85 TableBuilder::new(lua)?
87 .with_value("os", os)?
88 .with_value("arch", arch)?
89 .with_value("endianness", endianness)?
90 .with_value("args", args_tab)?
91 .with_value("cwd", cwd_str)?
92 .with_value("env", env_tab)?
93 .with_value("exit", process_exit)?
94 .with_async_function("exec", process_exec)?
95 .with_function("create", process_create)?
96 .build_readonly()
97}
98
99fn process_env_get(lua: &Lua, (_, key): (LuaValue, String)) -> LuaResult<LuaValue> {
100 match env::var_os(key) {
101 Some(value) => {
102 let raw_value = RawOsString::new(value);
103 Ok(LuaValue::String(
104 lua.create_string(raw_value.to_raw_bytes())?,
105 ))
106 }
107 None => Ok(LuaValue::Nil),
108 }
109}
110
111fn process_env_set(_: &Lua, (_, key, value): (LuaValue, String, Option<String>)) -> LuaResult<()> {
112 if key.is_empty() {
114 Err(LuaError::RuntimeError("Key must not be empty".to_string()))
115 } else if key.contains('=') {
116 Err(LuaError::RuntimeError(
117 "Key must not contain the equals character '='".to_string(),
118 ))
119 } else if key.contains('\0') {
120 Err(LuaError::RuntimeError(
121 "Key must not contain the NUL character".to_string(),
122 ))
123 } else if let Some(value) = value {
124 if value.contains('\0') {
126 Err(LuaError::RuntimeError(
127 "Value must not contain the NUL character".to_string(),
128 ))
129 } else {
130 env::set_var(&key, &value);
131 Ok(())
132 }
133 } else {
134 env::remove_var(&key);
135 Ok(())
136 }
137}
138
139fn process_env_iter(lua: &Lua, (_, ()): (LuaValue, ())) -> LuaResult<LuaFunction> {
140 let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
141 lua.create_function_mut(move |lua, (): ()| match vars.next() {
142 Some((key, value)) => {
143 let raw_key = RawOsString::new(key);
144 let raw_value = RawOsString::new(value);
145 Ok((
146 LuaValue::String(lua.create_string(raw_key.to_raw_bytes())?),
147 LuaValue::String(lua.create_string(raw_value.to_raw_bytes())?),
148 ))
149 }
150 None => Ok((LuaValue::Nil, LuaValue::Nil)),
151 })
152}
153
154async fn process_exec(
155 lua: Lua,
156 (program, args, mut options): (String, Option<Vec<String>>, ProcessSpawnOptions),
157) -> LuaResult<LuaTable> {
158 let stdin = options.stdio.stdin.take();
159 let stdout = options.stdio.stdout;
160 let stderr = options.stdio.stderr;
161
162 let child = options
163 .into_command(program, args)
164 .stdin(Stdio::piped())
165 .stdout(stdout.as_stdio())
166 .stderr(stderr.as_stdio())
167 .spawn()?;
168
169 exec::exec(lua, child, stdin, stdout, stderr).await
170}
171
172fn process_create(
173 lua: &Lua,
174 (program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
175) -> LuaResult<LuaValue> {
176 let child = options
177 .into_command(program, args)
178 .stdin(Stdio::piped())
179 .stdout(Stdio::piped())
180 .stderr(Stdio::piped())
181 .spawn()?;
182
183 create::Child::new(lua, child).into_lua(lua)
184}