Skip to main content

zoi_lua/api/
lifecycle.rs

1use std::fs;
2use std::path::Path;
3
4use mlua::{self, Lua, LuaSerdeExt, Table, Value};
5
6/// Exposes the core Package DSL and lifecycle functions to the Lua environment.
7///
8/// This module defines the primary entry points for a `.pkg.lua` script:
9/// - `metadata`: Defines the static `Package` struct fields.
10/// - `dependencies`: Defines the runtime and build dependency graph.
11/// - `prepare`/`build`/`package`: Placeholder functions that the maintainer
12///   overrides to define the build logic.
13/// - `IMPORT`/`INCLUDE`: Helpers for modular package definitions.
14///
15/// These functions bridge the declarative metadata and the imperative build
16/// logic. Adds the `IMPORT` function to the Lua environment for importing data
17/// from other files.
18///
19/// # Errors
20///
21/// Returns an error if the `IMPORT` function cannot be set in the global
22/// environment.
23pub fn add_import_util(
24    lua: &Lua,
25    current_path: &Path
26) -> Result<(), mlua::Error> {
27    let current_path_buf = current_path.to_path_buf();
28    let import_fn = lua.create_function(move |lua, file_name: String| {
29        if let Ok(refs) = lua.globals().get::<Table>("__ZoiReferencedFiles") {
30            let _ = refs.push(file_name.clone());
31        }
32        let parent = current_path_buf.parent().ok_or_else(|| {
33            mlua::Error::RuntimeError(
34                "Could not determine parent directory of package file"
35                    .to_string()
36            )
37        })?;
38        let path = parent.join(&file_name);
39        let content = fs::read_to_string(&path)
40            .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
41
42        if let Some(extension) = path.extension().and_then(|s| s.to_str()) {
43            match extension {
44                "json" => {
45                    let value: serde_json::Value =
46                        serde_json::from_str(&content).map_err(|e| {
47                            mlua::Error::RuntimeError(e.to_string())
48                        })?;
49                    return lua.to_value(&value);
50                }
51                "yaml" | "yml" => {
52                    let value: serde_yaml::Value =
53                        serde_yaml::from_str(&content).map_err(|e| {
54                            mlua::Error::RuntimeError(e.to_string())
55                        })?;
56                    return lua.to_value(&value);
57                }
58                "toml" => {
59                    let value: toml::Value =
60                        toml::from_str(&content).map_err(|e| {
61                            mlua::Error::RuntimeError(e.to_string())
62                        })?;
63                    return lua.to_value(&value);
64                }
65                _ => {
66                    return lua.to_value(&content);
67                }
68            }
69        }
70
71        lua.to_value(&content)
72    })?;
73    lua.globals().set("IMPORT", import_fn)?;
74    Ok(())
75}
76
77/// Adds the `INCLUDE` function to the Lua environment for executing other Lua
78/// files.
79///
80/// # Errors
81///
82/// Returns an error if the `INCLUDE` function cannot be set in the global
83/// environment.
84pub fn add_include_util(
85    lua: &Lua,
86    current_path: &Path
87) -> Result<(), mlua::Error> {
88    let current_path_buf = current_path.to_path_buf();
89    let include_fn = lua.create_function(
90        move |lua, file_name: String| -> Result<(), mlua::Error> {
91            if let Ok(refs) = lua.globals().get::<Table>("__ZoiReferencedFiles")
92            {
93                let _ = refs.push(file_name.clone());
94            }
95            let parent = current_path_buf.parent().ok_or_else(|| {
96                mlua::Error::RuntimeError(
97                    "Could not determine parent directory of package file"
98                        .to_string()
99                )
100            })?;
101            let path = parent.join(file_name);
102            let code = fs::read_to_string(path)
103                .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
104            lua.load(&code).exec()?;
105            Ok(())
106        }
107    )?;
108    lua.globals().set("INCLUDE", include_fn)?;
109    Ok(())
110}
111
112/// Adds core lifecycle functions (`metadata`, `dependencies`, `hooks`, etc.) to
113/// the Lua environment.
114///
115/// # Errors
116///
117/// Returns an error if any of the lifecycle functions cannot be set in the
118/// global environment.
119pub fn add_package_lifecycle_functions(lua: &Lua) -> Result<(), mlua::Error> {
120    let metadata_fn = lua.create_function(move |lua, pkg_def: Table| {
121        if let Ok(meta_table) = lua.globals().get::<Table>("__ZoiPackageMeta")
122            && let Ok(pkg_global) = lua.globals().get::<Table>("PKG")
123        {
124            for pair in pkg_def.pairs::<Value, Value>() {
125                let (key, value) = pair?;
126                meta_table.set(key.clone(), value.clone())?;
127                pkg_global.set(key, value)?;
128            }
129        }
130        Ok(())
131    })?;
132    lua.globals().set("metadata", metadata_fn)?;
133
134    let dependencies_fn =
135        lua.create_function(move |lua, deps_def: Table| {
136            if let Ok(deps_table) =
137                lua.globals().get::<Table>("__ZoiPackageDeps")
138            {
139                for pair in deps_def.pairs::<String, Value>() {
140                    let (key, value) = pair?;
141                    deps_table.set(key, value)?;
142                }
143            }
144            Ok(())
145        })?;
146    lua.globals().set("dependencies", dependencies_fn)?;
147
148    let updates_fn = lua.create_function(move |lua, updates_list: Table| {
149        if let Ok(updates_table) =
150            lua.globals().get::<Table>("__ZoiPackageUpdates")
151        {
152            for pair in updates_list.pairs::<Value, Table>() {
153                let (_, update_info) = pair?;
154                updates_table.push(update_info)?;
155            }
156        }
157        Ok(())
158    })?;
159    lua.globals().set("updates", updates_fn)?;
160
161    let hooks_fn = lua.create_function(move |lua, hooks_def: Table| {
162        if let Ok(hooks_table) = lua.globals().get::<Table>("__ZoiPackageHooks")
163        {
164            for pair in hooks_def.pairs::<String, Value>() {
165                let (key, value) = pair?;
166                hooks_table.set(key, value)?;
167            }
168        }
169        Ok(())
170    })?;
171    lua.globals().set("hooks", hooks_fn)?;
172
173    let service_fn = lua.create_function(move |lua, service_def: Table| {
174        if let Ok(service_table) =
175            lua.globals().get::<Table>("__ZoiPackageService")
176        {
177            for pair in service_def.pairs::<String, Value>() {
178                let (key, value) = pair?;
179                service_table.set(key, value)?;
180            }
181        }
182        Ok(())
183    })?;
184    lua.globals().set("service", service_fn)?;
185
186    let prepare_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(()))?;
187    lua.globals().set("prepare", prepare_fn)?;
188    let package_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(()))?;
189    lua.globals().set("package", package_fn)?;
190    let verify_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(true))?;
191    lua.globals().set("verify", verify_fn)?;
192    let test_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(true))?;
193    lua.globals().set("test", test_fn)?;
194    let uninstall_fn = lua.create_function(|_, _: mlua::MultiValue| Ok(()))?;
195    lua.globals().set("uninstall", uninstall_fn)?;
196
197    Ok(())
198}