1use mlua::{self, Lua, Table};
2use std::path::Path;
3use zoi_core::utils;
4
5use std::fs;
6use walkdir::WalkDir;
7pub fn add_file_util(lua: &Lua) -> Result<(), mlua::Error> {
19 let file_fn = lua.create_function(
20 |_, (url, path): (String, String)| -> Result<(), mlua::Error> {
21 let client =
22 utils::get_http_client().map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
23 let mut attempt = 0u32;
24 let response = loop {
25 attempt += 1;
26 match client.get(&url).send() {
27 Ok(resp) => break resp,
28 Err(e) => {
29 if attempt < 3 {
30 eprintln!("Download failed ({}). Retrying...", e);
31 zoi_core::utils::retry_backoff_sleep(attempt);
32 continue;
33 } else {
34 return Err(mlua::Error::RuntimeError(e.to_string()));
35 }
36 }
37 }
38 };
39 let content = response
40 .bytes()
41 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
42 fs::write(path, content).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
43 Ok(())
44 },
45 )?;
46
47 let utils_table: Table = lua.globals().get("UTILS")?;
48 utils_table.set("FILE", file_fn)?;
49
50 Ok(())
51}
52
53pub fn add_zcp(lua: &Lua) -> Result<(), mlua::Error> {
54 let zcp_fn = lua.create_function(|lua, (source, destination): (String, String)| {
55 let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
56 Ok(t) => t,
57 Err(_) => {
58 let new_t = lua.create_table()?;
59 lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
60 new_t
61 }
62 };
63 let op = lua.create_table()?;
64 op.set("op", "zcp")?;
65 op.set("source", source)?;
66 op.set("destination", destination)?;
67 ops_table.push(op)?;
68 Ok(())
69 })?;
70 lua.globals().set("zcp", zcp_fn)?;
71 Ok(())
72}
73
74pub fn add_zlicense(lua: &Lua) -> Result<(), mlua::Error> {
75 let zlicense_fn = lua.create_function(|lua, source: String| {
76 let destination = "${pkgstore}/LICENSE".to_string();
77 let zcp: mlua::Function = lua.globals().get("zcp")?;
78 zcp.call::<()>((source, destination))?;
79 Ok(())
80 })?;
81 lua.globals().set("zlicense", zlicense_fn)?;
82 Ok(())
83}
84
85pub fn add_zdoc(lua: &Lua) -> Result<(), mlua::Error> {
86 let zdoc_fn = lua.create_function(|lua, source: String| {
87 let filename = Path::new(&source)
88 .file_name()
89 .and_then(|n| n.to_str())
90 .ok_or_else(|| mlua::Error::RuntimeError("Invalid source path".to_string()))?;
91 let destination = format!("${{pkgstore}}/doc/{}", filename);
92 let zcp: mlua::Function = lua.globals().get("zcp")?;
93 zcp.call::<()>((source, destination))?;
94 Ok(())
95 })?;
96 lua.globals().set("zdoc", zdoc_fn)?;
97 Ok(())
98}
99
100pub fn add_zshell(lua: &Lua) -> Result<(), mlua::Error> {
101 let zshell_fn = lua.create_function(|lua, (source, shell): (String, String)| {
102 let filename = Path::new(&source)
103 .file_name()
104 .and_then(|n| n.to_str())
105 .ok_or_else(|| mlua::Error::RuntimeError("Invalid source path for zshell".to_string()))?
106 .to_string();
107
108 let destination = format!("${{pkgstore}}/shell/{}/{}", shell, filename);
109 let zcp: mlua::Function = lua.globals().get("zcp")?;
110 zcp.call::<()>((source, destination.clone()))?;
111
112 let shells_table: Table = match lua.globals().get("__ZoiPackageShells") {
113 Ok(t) => t,
114 Err(_) => {
115 let new_t = lua.create_table()?;
116 lua.globals().set("__ZoiPackageShells", new_t.clone())?;
117 new_t
118 }
119 };
120
121 let shell_files: Vec<String> = shells_table.get(&*shell).unwrap_or_default();
122 let mut shell_files = shell_files;
123 shell_files.push(filename);
124 shells_table.set(shell, shell_files)?;
125
126 Ok(())
127 })?;
128 lua.globals().set("zshell", zshell_fn)?;
129 Ok(())
130}
131
132pub fn add_zsed(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
133 let zsed_fn = lua.create_function(
134 move |lua, (pattern, replacement, file): (String, String, String)| {
135 let build_dir_str: String = lua.globals().get("BUILD_DIR")?;
136 let path = Path::new(&build_dir_str).join(&file);
137
138 let content = std::fs::read_to_string(&path).map_err(|e| {
139 mlua::Error::RuntimeError(format!("Failed to read {}: {}", file, e))
140 })?;
141
142 let re = regex::Regex::new(&pattern).map_err(|e| {
143 mlua::Error::RuntimeError(format!("Invalid regex '{}': {}", pattern, e))
144 })?;
145
146 let new_content = re.replace_all(&content, replacement.as_str());
147
148 std::fs::write(&path, new_content.as_bytes()).map_err(|e| {
149 mlua::Error::RuntimeError(format!("Failed to write {}: {}", file, e))
150 })?;
151
152 if !quiet {
153 println!("Applied sed replacement to {}", file);
154 }
155
156 Ok(())
157 },
158 )?;
159 lua.globals().set("zsed", zsed_fn)?;
160 Ok(())
161}
162
163pub fn add_zln(lua: &Lua) -> Result<(), mlua::Error> {
164 let zln_fn = lua.create_function(|lua, (target, link): (String, String)| {
165 let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
166 Ok(t) => t,
167 Err(_) => {
168 let new_t = lua.create_table()?;
169 lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
170 new_t
171 }
172 };
173 let op = lua.create_table()?;
174 op.set("op", "zln")?;
175 op.set("target", target)?;
176 op.set("link", link)?;
177 ops_table.push(op)?;
178 Ok(())
179 })?;
180 lua.globals().set("zln", zln_fn)?;
181 Ok(())
182}
183
184pub fn add_zchmod(lua: &Lua) -> Result<(), mlua::Error> {
185 let zchmod_fn = lua.create_function(|lua, (path, mode): (String, u32)| {
186 let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
187 Ok(t) => t,
188 Err(_) => {
189 let new_t = lua.create_table()?;
190 lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
191 new_t
192 }
193 };
194 let op = lua.create_table()?;
195 op.set("op", "zchmod")?;
196 op.set("path", path)?;
197 op.set("mode", mode)?;
198 ops_table.push(op)?;
199 Ok(())
200 })?;
201 lua.globals().set("zchmod", zchmod_fn)?;
202 Ok(())
203}
204
205pub fn add_zchown(lua: &Lua) -> Result<(), mlua::Error> {
206 let zchown_fn =
207 lua.create_function(|lua, (path, owner, group): (String, String, String)| {
208 let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
209 Ok(t) => t,
210 Err(_) => {
211 let new_t = lua.create_table()?;
212 lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
213 new_t
214 }
215 };
216 let op = lua.create_table()?;
217 op.set("op", "zchown")?;
218 op.set("path", path)?;
219 op.set("owner", owner)?;
220 op.set("group", group)?;
221 ops_table.push(op)?;
222 Ok(())
223 })?;
224 lua.globals().set("zchown", zchown_fn)?;
225 Ok(())
226}
227
228pub fn add_zmkdir(lua: &Lua) -> Result<(), mlua::Error> {
229 let zmkdir_fn = lua.create_function(|lua, path: String| {
230 let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
231 Ok(t) => t,
232 Err(_) => {
233 let new_t = lua.create_table()?;
234 lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
235 new_t
236 }
237 };
238 let op = lua.create_table()?;
239 op.set("op", "zmkdir")?;
240 op.set("path", path)?;
241 ops_table.push(op)?;
242 Ok(())
243 })?;
244 lua.globals().set("zmkdir", zmkdir_fn)?;
245 Ok(())
246}
247
248pub fn add_zrm(lua: &Lua) -> Result<(), mlua::Error> {
249 let zrm_fn = lua.create_function(|lua, path: String| {
250 let ops_table: Table = match lua.globals().get("__ZoiUninstallOperations") {
251 Ok(t) => t,
252 Err(_) => {
253 let new_t = lua.create_table()?;
254 lua.globals()
255 .set("__ZoiUninstallOperations", new_t.clone())?;
256 new_t
257 }
258 };
259 let op = lua.create_table()?;
260 op.set("op", "zrm")?;
261 op.set("path", path)?;
262 ops_table.push(op)?;
263 Ok(())
264 })?;
265 lua.globals().set("zrm", zrm_fn)?;
266 Ok(())
267}
268
269pub fn add_fs_util(lua: &Lua) -> Result<(), mlua::Error> {
270 let fs_table = lua.create_table()?;
271
272 let exists_fn = lua.create_function(|lua, path: String| {
273 let p = Path::new(&path);
274 if p.exists() {
275 return Ok(true);
276 }
277 if let Ok(build_dir) = lua.globals().get::<String>("BUILD_DIR")
278 && Path::new(&build_dir).join(p).exists()
279 {
280 return Ok(true);
281 }
282 Ok(false)
283 })?;
284 fs_table.set("exists", exists_fn)?;
285
286 let copy_fn = lua.create_function(|_, (src, dest): (String, String)| {
287 let src_path = Path::new(&src);
288 let dest_path = Path::new(&dest);
289 if src_path.is_dir() {
290 utils::copy_dir_all(src_path, dest_path)
291 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
292 } else {
293 fs::copy(src_path, dest_path).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
294 }
295 Ok(true)
296 })?;
297 fs_table.set("copy", copy_fn)?;
298
299 let move_fn = lua.create_function(|_, (src, dest): (String, String)| {
300 fs::rename(src, dest).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
301 Ok(true)
302 })?;
303 fs_table.set("move", move_fn)?;
304
305 let chmod_fn = lua.create_function(|_, (path, mode): (String, u32)| {
306 #[cfg(unix)]
307 {
308 use std::os::unix::fs::PermissionsExt;
309 fs::set_permissions(path, fs::Permissions::from_mode(mode))
310 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
311 }
312 #[cfg(windows)]
313 {
314 let _ = (path, mode);
315 }
316 Ok(true)
317 })?;
318 fs_table.set("chmod", chmod_fn)?;
319
320 let utils_table: Table = lua.globals().get("UTILS")?;
321 utils_table.set("FS", fs_table)?;
322
323 Ok(())
324}
325
326pub fn add_find_util(lua: &Lua) -> Result<(), mlua::Error> {
327 let find_table = lua.create_table()?;
328
329 let find_file_fn = lua.create_function(|lua, (dir, name): (String, String)| {
330 let build_dir_str: String = lua.globals().get("BUILD_DIR")?;
331 let search_dir = Path::new(&build_dir_str).join(dir);
332 for entry in WalkDir::new(search_dir) {
333 let entry = entry.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
334 if entry.file_name().to_string_lossy() == name {
335 let path = entry.path();
336 let relative_path = path.strip_prefix(Path::new(&build_dir_str)).map_err(|e| {
337 mlua::Error::RuntimeError(format!(
338 "Failed to determine relative path for {:?}: {}",
339 path, e
340 ))
341 })?;
342 return Ok(Some(relative_path.to_string_lossy().to_string()));
343 }
344 }
345 Ok(None)
346 })?;
347 find_table.set("file", find_file_fn)?;
348
349 let utils_table: Table = lua.globals().get("UTILS")?;
350 utils_table.set("FIND", find_table)?;
351
352 Ok(())
353}