1use crate::utils;
2use flate2::read::GzDecoder;
3use md5;
4use mlua::{self, Lua, LuaSerdeExt, Table, Value};
5use sequoia_openpgp::{Cert, parse::Parse};
6use serde::Deserialize;
7use sha2::{Digest, Sha256, Sha512};
8use std::io::Read;
9use std::path::PathBuf;
10use std::{fs, path::Path};
11use urlencoding;
12use walkdir::WalkDir;
13use xz2::read::XzDecoder;
14use zip::ZipArchive;
15use zstd::stream::read::Decoder as ZstdDecoder;
16
17fn add_parse_util(lua: &Lua) -> Result<(), mlua::Error> {
18 let parse_table = lua.create_table()?;
19
20 let json_fn = lua.create_function(|lua, json_str: String| {
21 let value: serde_json::Value = serde_json::from_str(&json_str)
22 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
23 lua.to_value(&value)
24 })?;
25 parse_table.set("json", json_fn)?;
26
27 let yaml_fn = lua.create_function(|lua, yaml_str: String| {
28 let value: serde_yaml::Value = serde_yaml::from_str(&yaml_str)
29 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
30 lua.to_value(&value)
31 })?;
32 parse_table.set("yaml", yaml_fn)?;
33
34 let toml_fn = lua.create_function(|lua, toml_str: String| {
35 let value: toml::Value =
36 toml::from_str(&toml_str).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
37 lua.to_value(&value)
38 })?;
39 parse_table.set("toml", toml_fn)?;
40
41 let checksum_fn = lua.create_function(|_, (content, file_name): (String, String)| {
42 for line in content.lines() {
43 let parts: Vec<&str> = line.split_whitespace().collect();
44 if parts.len() == 2 && parts[1] == file_name {
45 return Ok(Some(parts[0].to_string()));
46 }
47 }
48 Ok(None)
49 })?;
50 parse_table.set("checksumFile", checksum_fn)?;
51
52 let utils_table: Table = lua.globals().get("UTILS")?;
53 utils_table.set("PARSE", parse_table)?;
54
55 Ok(())
56}
57
58fn add_fetch_util(lua: &Lua) -> Result<(), mlua::Error> {
59 let fetch_table = lua.create_table()?;
60
61 let fetch_fn = lua.create_function(|_, url: String| -> Result<String, mlua::Error> {
62 let response =
63 reqwest::blocking::get(url).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
64 let text = response
65 .text()
66 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
67 Ok(text)
68 })?;
69 fetch_table.set("url", fetch_fn)?;
70
71 let utils_table: Table = lua.globals().get("UTILS")?;
72 utils_table.set("FETCH", fetch_table)?;
73
74 Ok(())
75}
76
77#[derive(Deserialize)]
78struct GitArgs {
79 repo: String,
80 domain: Option<String>,
81 branch: Option<String>,
82}
83
84fn fetch_json(url: &str) -> Result<serde_json::Value, mlua::Error> {
85 let client = reqwest::blocking::Client::builder()
86 .user_agent("zoi")
87 .build()
88 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
89
90 let response = client
91 .get(url)
92 .send()
93 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
94
95 if !response.status().is_success() {
96 return Err(mlua::Error::RuntimeError(format!(
97 "Request to {} failed with status: {} and body: {}",
98 url,
99 response.status(),
100 response.text().unwrap_or_else(|_| "N/A".to_string())
101 )));
102 }
103
104 let text = response
105 .text()
106 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
107 serde_json::from_str(&text).map_err(|e| mlua::Error::RuntimeError(e.to_string()))
108}
109
110fn add_git_fetch_util(lua: &Lua) -> Result<(), mlua::Error> {
111 let utils_table: Table = lua.globals().get("UTILS")?;
112 let fetch_table: Table = utils_table.get("FETCH")?;
113
114 for provider in ["GITHUB", "GITLAB", "GITEA", "FORGEJO"] {
115 let provider_table = lua.create_table()?;
116 let latest_table = lua.create_table()?;
117
118 for what in ["tag", "release", "commit"] {
119 let get_latest_fn = lua.create_function(move |lua, args: Table| {
120 let git_args: GitArgs = lua
121 .from_value(Value::Table(args))
122 .map_err(|e| mlua::Error::RuntimeError(format!("Invalid arguments: {}", e)))?;
123
124 let base_url = match provider {
125 "GITHUB" => git_args
126 .domain
127 .unwrap_or_else(|| "https://api.github.com".to_string()),
128 "GITLAB" => git_args
129 .domain
130 .unwrap_or_else(|| "https://gitlab.com".to_string()),
131 "GITEA" => git_args
132 .domain
133 .unwrap_or_else(|| "https://gitea.com".to_string()),
134 "FORGEJO" => git_args
135 .domain
136 .unwrap_or_else(|| "https://codeberg.org".to_string()),
137 _ => unreachable!(),
138 };
139
140 let url = match (provider, what) {
141 ("GITHUB", "tag") => format!("{}/repos/{}/tags", base_url, git_args.repo),
142 ("GITHUB", "release") => {
143 format!("{}/repos/{}/releases/latest", base_url, git_args.repo)
144 }
145 ("GITHUB", "commit") => format!(
146 "{}/repos/{}/commits?sha={}",
147 base_url,
148 git_args.repo,
149 git_args.branch.as_deref().unwrap_or("HEAD")
150 ),
151
152 ("GITLAB", "tag") => format!(
153 "{}/api/v4/projects/{}/repository/tags",
154 base_url,
155 urlencoding::encode(&git_args.repo)
156 ),
157 ("GITLAB", "release") => format!(
158 "{}/api/v4/projects/{}/releases",
159 base_url,
160 urlencoding::encode(&git_args.repo)
161 ),
162 ("GITLAB", "commit") => format!(
163 "{}/api/v4/projects/{}/repository/commits?ref_name={}",
164 base_url,
165 urlencoding::encode(&git_args.repo),
166 git_args.branch.as_deref().unwrap_or("HEAD")
167 ),
168
169 ("GITEA" | "FORGEJO", "tag") => {
170 format!("{}/api/v1/repos/{}/tags", base_url, git_args.repo)
171 }
172 ("GITEA" | "FORGEJO", "release") => {
173 format!(
174 "{}/api/v1/repos/{}/releases/latest",
175 base_url, git_args.repo
176 )
177 }
178 ("GITEA" | "FORGEJO", "commit") => format!(
179 "{}/api/v1/repos/{}/commits?sha={}",
180 base_url,
181 git_args.repo,
182 git_args.branch.as_deref().unwrap_or("HEAD")
183 ),
184 _ => unreachable!(),
185 };
186
187 let json = fetch_json(&url)?;
188
189 let result = match (provider, what) {
190 ("GITHUB", "tag") | ("GITEA", "tag") | ("FORGEJO", "tag") => json
191 .as_array()
192 .and_then(|a| a.first())
193 .and_then(|t| t["name"].as_str()),
194 ("GITHUB", "release") | ("GITEA", "release") | ("FORGEJO", "release") => {
195 json["tag_name"].as_str()
196 }
197 ("GITHUB", "commit") | ("GITEA", "commit") | ("FORGEJO", "commit") => json
198 .as_array()
199 .and_then(|a| a.first())
200 .and_then(|c| c["sha"].as_str()),
201
202 ("GITLAB", "tag") => json
203 .as_array()
204 .and_then(|a| a.first())
205 .and_then(|t| t["name"].as_str()),
206 ("GITLAB", "release") => json
207 .as_array()
208 .and_then(|a| a.first())
209 .and_then(|r| r["tag_name"].as_str()),
210 ("GITLAB", "commit") => json
211 .as_array()
212 .and_then(|a| a.first())
213 .and_then(|c| c["id"].as_str()),
214 _ => unreachable!(),
215 };
216
217 result.map(|s| s.to_string()).ok_or_else(|| {
218 mlua::Error::RuntimeError(
219 "Could not extract value from API response".to_string(),
220 )
221 })
222 })?;
223 latest_table.set(what, get_latest_fn)?;
224 }
225
226 provider_table.set("LATEST", latest_table)?;
227 fetch_table.set(provider, provider_table)?;
228 }
229
230 Ok(())
231}
232
233fn add_file_util(lua: &Lua) -> Result<(), mlua::Error> {
234 let file_fn = lua.create_function(
235 |_, (url, path): (String, String)| -> Result<(), mlua::Error> {
236 let response = reqwest::blocking::get(url)
237 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
238 let content = response
239 .bytes()
240 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
241 fs::write(path, content).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
242 Ok(())
243 },
244 )?;
245
246 let utils_table: Table = lua.globals().get("UTILS")?;
247 utils_table.set("FILE", file_fn)?;
248
249 Ok(())
250}
251
252fn add_import_util(lua: &Lua, current_path: &Path) -> Result<(), mlua::Error> {
253 let current_path_buf = current_path.to_path_buf();
254 let import_fn = lua.create_function(move |lua, file_name: String| {
255 let path = current_path_buf.parent().unwrap().join(&file_name);
256 let content =
257 fs::read_to_string(&path).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
258
259 if let Some(extension) = path.extension().and_then(|s| s.to_str()) {
260 match extension {
261 "json" => {
262 let value: serde_json::Value = serde_json::from_str(&content)
263 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
264 return lua.to_value(&value);
265 }
266 "yaml" | "yml" => {
267 let value: serde_yaml::Value = serde_yaml::from_str(&content)
268 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
269 return lua.to_value(&value);
270 }
271 "toml" => {
272 let value: toml::Value = toml::from_str(&content)
273 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
274 return lua.to_value(&value);
275 }
276 _ => {
277 return lua.to_value(&content);
278 }
279 }
280 }
281
282 lua.to_value(&content)
283 })?;
284 lua.globals().set("IMPORT", import_fn)?;
285 Ok(())
286}
287
288fn add_include_util(lua: &Lua, current_path: &Path) -> Result<(), mlua::Error> {
289 let current_path_buf = current_path.to_path_buf();
290 let include_fn =
291 lua.create_function(move |lua, file_name: String| -> Result<(), mlua::Error> {
292 let path = current_path_buf.parent().unwrap().join(file_name);
293 let code =
294 fs::read_to_string(path).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
295 lua.load(&code).exec()?;
296 Ok(())
297 })?;
298 lua.globals().set("INCLUDE", include_fn)?;
299 Ok(())
300}
301
302fn add_zcp(lua: &Lua) -> Result<(), mlua::Error> {
303 let zcp_fn = lua.create_function(|lua, (source, destination): (String, String)| {
304 let ops_table: Table = match lua.globals().get("__ZoiBuildOperations") {
305 Ok(t) => t,
306 Err(_) => {
307 let new_t = lua.create_table()?;
308 lua.globals().set("__ZoiBuildOperations", new_t.clone())?;
309 new_t
310 }
311 };
312 let op = lua.create_table()?;
313 op.set("op", "zcp")?;
314 op.set("source", source)?;
315 op.set("destination", destination)?;
316 ops_table.push(op)?;
317 Ok(())
318 })?;
319 lua.globals().set("zcp", zcp_fn)?;
320 Ok(())
321}
322
323fn add_verify_hash(lua: &Lua) -> Result<(), mlua::Error> {
324 let verify_hash_fn = lua.create_function(|_lua, (file_path, hash_str): (String, String)| {
325 let parts: Vec<&str> = hash_str.splitn(2, '-').collect();
326 if parts.len() != 2 {
327 return Err(mlua::Error::RuntimeError(
328 "Invalid hash format. Expected 'algo-hash'".to_string(),
329 ));
330 }
331 let algo = parts[0];
332 let expected_hash = parts[1];
333
334 let mut file = fs::File::open(&file_path)
335 .map_err(|e| mlua::Error::RuntimeError(format!("Failed to open file: {}", e)))?;
336 let mut buffer = Vec::new();
337 file.read_to_end(&mut buffer)
338 .map_err(|e| mlua::Error::RuntimeError(format!("Failed to read file: {}", e)))?;
339
340 let actual_hash = match algo {
341 "md5" => {
342 let digest = md5::compute(&buffer);
343 format!("{:x}", digest)
344 }
345 "sha256" => {
346 let mut hasher = Sha256::new();
347 hasher.update(&buffer);
348 hex::encode(hasher.finalize())
349 }
350 "sha512" => {
351 let mut hasher = Sha512::new();
352 hasher.update(&buffer);
353 hex::encode(hasher.finalize())
354 }
355 _ => {
356 return Err(mlua::Error::RuntimeError(format!(
357 "Unsupported hash algorithm: {}",
358 algo
359 )));
360 }
361 };
362
363 if actual_hash.eq_ignore_ascii_case(expected_hash) {
364 Ok(true)
365 } else {
366 println!(
367 "Hash mismatch for {}: expected {}, got {}",
368 file_path, expected_hash, actual_hash
369 );
370 Ok(false)
371 }
372 })?;
373 lua.globals().set("verifyHash", verify_hash_fn)?;
374 Ok(())
375}
376
377fn add_zrm(lua: &Lua) -> Result<(), mlua::Error> {
378 let zrm_fn = lua.create_function(|lua, path: String| {
379 let ops_table: Table = match lua.globals().get("__ZoiUninstallOperations") {
380 Ok(t) => t,
381 Err(_) => {
382 let new_t = lua.create_table()?;
383 lua.globals()
384 .set("__ZoiUninstallOperations", new_t.clone())?;
385 new_t
386 }
387 };
388 let op = lua.create_table()?;
389 op.set("op", "zrm")?;
390 op.set("path", path)?;
391 ops_table.push(op)?;
392 Ok(())
393 })?;
394 lua.globals().set("zrm", zrm_fn)?;
395 Ok(())
396}
397
398fn add_cmd_util(lua: &Lua) -> Result<(), mlua::Error> {
399 let cmd_fn = lua.create_function(|_, command: String| {
400 println!("Executing: {}", command);
401 let output = if cfg!(target_os = "windows") {
402 std::process::Command::new("pwsh")
403 .arg("-Command")
404 .arg(&command)
405 .output()
406 } else {
407 std::process::Command::new("bash")
408 .arg("-c")
409 .arg(&command)
410 .output()
411 };
412
413 match output {
414 Ok(out) => {
415 if !out.status.success() {
416 eprintln!("[cmd] {}", String::from_utf8_lossy(&out.stderr));
417 }
418 Ok(out.status.success())
419 }
420 Err(e) => {
421 eprintln!("[cmd] Failed to execute command: {}", e);
422 Ok(false)
423 }
424 }
425 })?;
426 lua.globals().set("cmd", cmd_fn)?;
427 Ok(())
428}
429
430fn add_find_util(lua: &Lua) -> Result<(), mlua::Error> {
431 let find_table = lua.create_table()?;
432
433 let find_file_fn = lua.create_function(|lua, (dir, name): (String, String)| {
434 let build_dir_str: String = lua.globals().get("BUILD_DIR")?;
435 let search_dir = Path::new(&build_dir_str).join(dir);
436 for entry in WalkDir::new(search_dir) {
437 let entry = entry.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
438 if entry.file_name().to_string_lossy() == name {
439 let path = entry.path();
440 let relative_path = path.strip_prefix(Path::new(&build_dir_str)).unwrap();
441 return Ok(Some(relative_path.to_string_lossy().to_string()));
442 }
443 }
444 Ok(None)
445 })?;
446 find_table.set("file", find_file_fn)?;
447
448 let utils_table: Table = lua.globals().get("UTILS")?;
449 utils_table.set("FIND", find_table)?;
450
451 Ok(())
452}
453
454fn add_extract_util(lua: &Lua) -> Result<(), mlua::Error> {
455 let extract_fn = lua.create_function(|lua, (source, out_name): (String, Option<String>)| {
456 let build_dir_str: String = lua.globals().get("BUILD_DIR")?;
457 let build_dir = Path::new(&build_dir_str);
458
459 let archive_file = if source.starts_with("http") {
460 println!("Downloading: {}", source);
461 let file_name = source.split('/').next_back().unwrap_or("download.tmp");
462 let temp_path = build_dir.join(file_name);
463 let response = reqwest::blocking::get(&source)
464 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
465 let content = response
466 .bytes()
467 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
468 fs::write(&temp_path, content).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
469 temp_path
470 } else {
471 PathBuf::from(source)
472 };
473
474 let out_dir_name = out_name.unwrap_or_else(|| "extracted".to_string());
475 let out_dir = build_dir.join(out_dir_name);
476 fs::create_dir_all(&out_dir).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
477
478 println!(
479 "Extracting {} to {}",
480 archive_file.display(),
481 out_dir.display()
482 );
483
484 let file =
485 fs::File::open(&archive_file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
486
487 let archive_path_str = archive_file.to_string_lossy();
488
489 if archive_path_str.ends_with(".zip") {
490 let mut archive =
491 ZipArchive::new(file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
492 archive
493 .extract(&out_dir)
494 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
495 } else if archive_path_str.ends_with(".tar.gz") {
496 let tar_gz = GzDecoder::new(file);
497 let mut archive = tar::Archive::new(tar_gz);
498 archive
499 .unpack(&out_dir)
500 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
501 } else if archive_path_str.ends_with(".tar.zst") {
502 let tar_zst =
503 ZstdDecoder::new(file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
504 let mut archive = tar::Archive::new(tar_zst);
505 archive
506 .unpack(&out_dir)
507 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
508 } else if archive_path_str.ends_with(".tar.xz") {
509 let tar_xz = XzDecoder::new(file);
510 let mut archive = tar::Archive::new(tar_xz);
511 archive
512 .unpack(&out_dir)
513 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
514 } else {
515 return Err(mlua::Error::RuntimeError(format!(
516 "Unsupported archive format for file: {}",
517 archive_path_str
518 )));
519 }
520
521 Ok(())
522 })?;
523
524 let utils_table: Table = lua.globals().get("UTILS")?;
525 utils_table.set("EXTRACT", extract_fn)?;
526
527 Ok(())
528}
529
530fn add_verify_signature(lua: &Lua) -> Result<(), mlua::Error> {
531 let verify_sig_fn = lua.create_function(
532 |_, (file_path, sig_path, key_source): (String, String, String)| {
533 let key_bytes: Vec<u8> = if key_source.starts_with("http") {
534 match reqwest::blocking::get(&key_source).and_then(|r| r.bytes()) {
535 Ok(b) => b.to_vec(),
536 Err(e) => {
537 return Err(mlua::Error::RuntimeError(format!(
538 "Failed to download key: {}",
539 e
540 )));
541 }
542 }
543 } else if Path::new(&key_source).exists() {
544 match fs::read(&key_source) {
545 Ok(b) => b,
546 Err(e) => {
547 return Err(mlua::Error::RuntimeError(format!(
548 "Failed to read key file: {}",
549 e
550 )));
551 }
552 }
553 } else {
554 let pgp_dir = match crate::pkg::pgp::get_pgp_dir() {
555 Ok(dir) => dir,
556 Err(e) => {
557 return Err(mlua::Error::RuntimeError(format!(
558 "Failed to get PGP dir: {}",
559 e
560 )));
561 }
562 };
563 let key_path = pgp_dir.join(format!("{}.asc", key_source));
564 if !key_path.exists() {
565 return Err(mlua::Error::RuntimeError(format!(
566 "Key with name '{}' not found.",
567 key_source
568 )));
569 }
570 match fs::read(&key_path) {
571 Ok(b) => b,
572 Err(e) => {
573 return Err(mlua::Error::RuntimeError(format!(
574 "Failed to read key file: {}",
575 e
576 )));
577 }
578 }
579 };
580
581 let cert = match Cert::from_bytes(&key_bytes) {
582 Ok(c) => c,
583 Err(e) => return Err(mlua::Error::RuntimeError(format!("Invalid PGP key: {}", e))),
584 };
585
586 let result = crate::pkg::pgp::verify_detached_signature(
587 Path::new(&file_path),
588 Path::new(&sig_path),
589 &cert,
590 );
591
592 match result {
593 Ok(_) => Ok(true),
594 Err(e) => {
595 eprintln!("Signature verification failed: {}", e);
596 Ok(false)
597 }
598 }
599 },
600 )?;
601 lua.globals().set("verifySignature", verify_sig_fn)?;
602 Ok(())
603}
604
605fn add_add_pgp_key(lua: &Lua) -> Result<(), mlua::Error> {
606 let add_pgp_key_fn = lua.create_function(|_, (source, name): (String, String)| {
607 let result = if source.starts_with("http") {
608 crate::pkg::pgp::add_key_from_url(&source, &name)
609 } else {
610 crate::pkg::pgp::add_key_from_path(&source, Some(&name))
611 };
612
613 if let Err(e) = result {
614 eprintln!("Failed to add PGP key '{}': {}", name, e);
615 return Ok(false);
616 }
617 Ok(true)
618 })?;
619 lua.globals().set("addPgpKey", add_pgp_key_fn)?;
620 Ok(())
621}
622
623pub fn add_package_lifecycle_functions(lua: &Lua) -> Result<(), mlua::Error> {
624 let metadata_fn = lua.create_function(move |lua, pkg_def: Table| {
625 if let Ok(meta_table) = lua.globals().get::<Table>("__ZoiPackageMeta")
626 && let Ok(pkg_global) = lua.globals().get::<Table>("PKG")
627 {
628 for pair in pkg_def.pairs::<Value, Value>() {
629 let (key, value) = pair?;
630 meta_table.set(key.clone(), value.clone())?;
631 pkg_global.set(key, value)?;
632 }
633 }
634 Ok(())
635 })?;
636 lua.globals().set("metadata", metadata_fn)?;
637
638 let dependencies_fn = lua.create_function(move |lua, deps_def: Table| {
639 if let Ok(deps_table) = lua.globals().get::<Table>("__ZoiPackageDeps") {
640 for pair in deps_def.pairs::<String, Value>() {
641 let (key, value) = pair?;
642 deps_table.set(key, value)?;
643 }
644 }
645 Ok(())
646 })?;
647 lua.globals().set("dependencies", dependencies_fn)?;
648
649 let updates_fn = lua.create_function(move |lua, updates_list: Table| {
650 if let Ok(updates_table) = lua.globals().get::<Table>("__ZoiPackageUpdates") {
651 for pair in updates_list.pairs::<Value, Table>() {
652 let (_, update_info) = pair?;
653 updates_table.push(update_info)?;
654 }
655 }
656 Ok(())
657 })?;
658 lua.globals().set("updates", updates_fn)?;
659
660 let hooks_fn = lua.create_function(move |lua, hooks_def: Table| {
661 if let Ok(hooks_table) = lua.globals().get::<Table>("__ZoiPackageHooks") {
662 for pair in hooks_def.pairs::<String, Value>() {
663 let (key, value) = pair?;
664 hooks_table.set(key, value)?;
665 }
666 }
667 Ok(())
668 })?;
669 lua.globals().set("hooks", hooks_fn)?;
670
671 let prepare_fn = lua.create_function(|_, _: Table| Ok(()))?;
672 lua.globals().set("prepare", prepare_fn)?;
673 let package_fn = lua.create_function(|_, _: Table| Ok(()))?;
674 lua.globals().set("package", package_fn)?;
675 let verify_fn = lua.create_function(|_, _: Table| Ok(()))?;
676 lua.globals().set("verify", verify_fn)?;
677 let uninstall_fn = lua.create_function(|_, _: Table| Ok(()))?;
678 lua.globals().set("uninstall", uninstall_fn)?;
679
680 Ok(())
681}
682
683pub fn setup_lua_environment(
684 lua: &Lua,
685 platform: &str,
686 version_override: Option<&str>,
687 file_path: Option<&str>,
688 create_pkg_dir: Option<&str>,
689) -> Result<(), mlua::Error> {
690 let system_table = lua.create_table()?;
691 let parts: Vec<&str> = platform.split('-').collect();
692 system_table.set("OS", *parts.first().unwrap_or(&""))?;
693 system_table.set("ARCH", *parts.get(1).unwrap_or(&""))?;
694 if let Some(distro) = utils::get_linux_distribution() {
695 system_table.set("DISTRO", distro)?;
696 }
697 if let Some(manager) = utils::get_native_package_manager() {
698 system_table.set("MANAGER", manager)?;
699 }
700 lua.globals().set("SYSTEM", system_table)?;
701
702 let zoi_table = lua.create_table()?;
703 if let Some(ver) = version_override {
704 zoi_table.set("VERSION", ver)?;
705 }
706
707 if let Some(dir) = create_pkg_dir {
708 zoi_table.set("CREATE_PKG_DIR", dir)?;
709 }
710
711 let path_table = lua.create_table()?;
712 if let Some(home_dir) = home::home_dir() {
713 path_table.set("user", home_dir.join(".zoi").to_string_lossy().to_string())?;
714 }
715
716 let system_bin_path = if cfg!(target_os = "windows") {
717 "C:\\ProgramData\\zoi\\pkgs\\bin".to_string()
718 } else {
719 "/usr/local/bin".to_string()
720 };
721 path_table.set("system", system_bin_path)?;
722
723 zoi_table.set("PATH", path_table)?;
724 lua.globals().set("ZOI", zoi_table)?;
725
726 let utils_table = lua.create_table()?;
727 lua.globals().set("UTILS", utils_table)?;
728
729 add_fetch_util(lua)?;
730 add_parse_util(lua)?;
731 add_git_fetch_util(lua)?;
732 add_file_util(lua)?;
733 add_zcp(lua)?;
734 add_verify_hash(lua)?;
735 add_zrm(lua)?;
736 add_cmd_util(lua)?;
737 add_find_util(lua)?;
738 add_extract_util(lua)?;
739 add_verify_signature(lua)?;
740 add_add_pgp_key(lua)?;
741 add_package_lifecycle_functions(lua)?;
742
743 if let Some(path_str) = file_path {
744 let path = Path::new(path_str);
745 add_import_util(lua, path)?;
746 add_include_util(lua, path)?;
747 }
748
749 Ok(())
750}