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