lune_std_process/
lib.rs

1#![allow(clippy::cargo_common_metadata)]
2
3use std::{
4    env::consts::{ARCH, OS},
5    path::MAIN_SEPARATOR,
6    process::Stdio,
7};
8
9use mlua::prelude::*;
10use mlua_luau_scheduler::Functions;
11
12use lune_utils::{
13    TableBuilder,
14    path::get_current_dir,
15    process::{ProcessArgs, ProcessEnv},
16};
17
18mod create;
19mod exec;
20mod options;
21
22use self::options::ProcessSpawnOptions;
23
24const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
25
26/**
27    Returns a string containing type definitions for the `process` standard library.
28*/
29#[must_use]
30pub fn typedefs() -> String {
31    TYPEDEFS.to_string()
32}
33
34/**
35    Creates the `process` standard library module.
36
37    # Errors
38
39    Errors when out of memory.
40*/
41#[allow(clippy::missing_panics_doc)]
42pub fn module(lua: Lua) -> LuaResult<LuaTable> {
43    let mut cwd_str = get_current_dir()
44        .to_str()
45        .expect("cwd should be valid UTF-8")
46        .to_string();
47    if !cwd_str.ends_with(MAIN_SEPARATOR) {
48        cwd_str.push(MAIN_SEPARATOR);
49    }
50
51    // Create constants for OS & processor architecture
52    let os = lua.create_string(OS.to_lowercase())?;
53    let arch = lua.create_string(ARCH.to_lowercase())?;
54    let endianness = lua.create_string(if cfg!(target_endian = "big") {
55        "big"
56    } else {
57        "little"
58    })?;
59
60    // Extract stored userdatas for args + env, the runtime struct
61    // should always contain and then provide through lua app data
62    let process_args = lua
63        .app_data_ref::<ProcessArgs>()
64        .ok_or_else(|| LuaError::runtime("Missing process args in Lua app data"))?
65        .into_plain_lua_table(lua.clone())?;
66    let process_env = lua
67        .app_data_ref::<ProcessEnv>()
68        .ok_or_else(|| LuaError::runtime("Missing process env in Lua app data"))?
69        .into_plain_lua_table(lua.clone())?;
70
71    process_args.set_readonly(true);
72
73    // Create our process exit function, the scheduler crate provides this
74    let fns = Functions::new(lua.clone())?;
75    let process_exit = fns.exit;
76
77    // Create the full process table
78    TableBuilder::new(lua)?
79        .with_value("os", os)?
80        .with_value("arch", arch)?
81        .with_value("endianness", endianness)?
82        .with_value("args", process_args)?
83        .with_value("cwd", cwd_str)?
84        .with_value("env", process_env)?
85        .with_value("exit", process_exit)?
86        .with_async_function("exec", process_exec)?
87        .with_function("create", process_create)?
88        .build_readonly()
89}
90
91async fn process_exec(
92    lua: Lua,
93    (program, args, mut options): (String, ProcessArgs, ProcessSpawnOptions),
94) -> LuaResult<LuaTable> {
95    let stdin = options.stdio.stdin.take();
96    let stdout = options.stdio.stdout;
97    let stderr = options.stdio.stderr;
98
99    let stdin_stdio = if stdin.is_some() {
100        Stdio::piped()
101    } else {
102        Stdio::null()
103    };
104
105    let child = options
106        .into_command(program, args)
107        .stdin(stdin_stdio)
108        .stdout(stdout.as_stdio())
109        .stderr(stderr.as_stdio())
110        .spawn()?;
111
112    exec::exec(lua, child, stdin, stdout, stderr).await
113}
114
115fn process_create(
116    lua: &Lua,
117    (program, args, options): (String, ProcessArgs, ProcessSpawnOptions),
118) -> LuaResult<LuaValue> {
119    let child = options
120        .into_command(program, args)
121        .stdin(Stdio::piped())
122        .stdout(Stdio::piped())
123        .stderr(Stdio::piped())
124        .spawn()?;
125
126    create::Child::new(lua, child).into_lua(lua)
127}