Skip to main content

zoi_lua/api/
system.rs

1use mlua::{self, Lua};
2
3/// Exposes system command and patching utilities to the Lua environment.
4///
5/// These functions provide the bridge to the host operating system's tools:
6/// - `cmd`: Executes a shell command and captures its output and exit code.
7/// - `zpatch`: A wrapper around the `patch` command for applying diffs.
8///
9/// All commands are executed relative to the `BUILD_DIR` and respect the
10/// user's environment and Zoi's quiet/verbose settings.
11pub fn add_cmd_util(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
12    let cmd_fn = lua.create_function(move |lua, command: String| {
13        let build_dir: String = lua.globals().get("BUILD_DIR")?;
14
15        if !quiet {
16            println!("Executing: {}", command);
17        }
18        let output = if cfg!(target_os = "windows") {
19            std::process::Command::new("pwsh")
20                .arg("-Command")
21                .arg(&command)
22                .current_dir(&build_dir)
23                .output()
24        } else {
25            std::process::Command::new("bash")
26                .arg("-c")
27                .arg(&command)
28                .current_dir(&build_dir)
29                .output()
30        };
31
32        match output {
33            Ok(out) => {
34                let stdout = String::from_utf8_lossy(&out.stdout).to_string();
35                let stderr = String::from_utf8_lossy(&out.stderr).to_string();
36                let exit_code =
37                    out.status
38                        .code()
39                        .unwrap_or(if out.status.success() { 0 } else { 1 });
40
41                if !out.status.success() && !quiet {
42                    eprintln!("[cmd] {}", stderr);
43                }
44
45                Ok((stdout, stderr, exit_code))
46            }
47            Err(e) => {
48                if !quiet {
49                    eprintln!("[cmd] Failed to execute command: {}", e);
50                }
51                Ok((String::new(), e.to_string(), 1))
52            }
53        }
54    })?;
55    lua.globals().set("cmd", cmd_fn)?;
56    Ok(())
57}
58
59pub fn add_zpatch(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
60    let zpatch_fn =
61        lua.create_function(move |lua, (patch_file, strip): (String, Option<u32>)| {
62            let build_dir: String = lua.globals().get("BUILD_DIR")?;
63            let strip_level = strip.unwrap_or(1);
64
65            if !quiet {
66                println!("Applying patch: {}", patch_file);
67            }
68
69            let output = std::process::Command::new("patch")
70                .arg(format!("-p{}", strip_level))
71                .arg("-i")
72                .arg(&patch_file)
73                .current_dir(&build_dir)
74                .output();
75
76            match output {
77                Ok(out) => {
78                    if !out.status.success() {
79                        let stderr = String::from_utf8_lossy(&out.stderr);
80                        return Err(mlua::Error::RuntimeError(format!(
81                            "patch failed: {}",
82                            stderr
83                        )));
84                    }
85                    if !quiet {
86                        println!("Successfully applied patch {}", patch_file);
87                    }
88                    Ok(())
89                }
90                Err(e) => Err(mlua::Error::RuntimeError(format!(
91                    "Failed to execute patch command: {}",
92                    e
93                ))),
94            }
95        })?;
96    lua.globals().set("zpatch", zpatch_fn)?;
97    Ok(())
98}