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, quiet: bool) -> Result<(), mlua::Error> {
324 let verify_hash_fn =
325 lua.create_function(move |_, (file_path, hash_str): (String, String)| {
326 let parts: Vec<&str> = hash_str.splitn(2, '-').collect();
327 if parts.len() != 2 {
328 return Err(mlua::Error::RuntimeError(
329 "Invalid hash format. Expected 'algo-hash'".to_string(),
330 ));
331 }
332 let algo = parts[0];
333 let expected_hash = parts[1];
334
335 let mut file = fs::File::open(&file_path)
336 .map_err(|e| mlua::Error::RuntimeError(format!("Failed to open file: {}", e)))?;
337 let mut buffer = Vec::new();
338 file.read_to_end(&mut buffer)
339 .map_err(|e| mlua::Error::RuntimeError(format!("Failed to read file: {}", e)))?;
340
341 let actual_hash = match algo {
342 "md5" => {
343 let digest = md5::compute(&buffer);
344 format!("{:x}", digest)
345 }
346 "sha256" => {
347 let mut hasher = Sha256::new();
348 hasher.update(&buffer);
349 hex::encode(hasher.finalize())
350 }
351 "sha512" => {
352 let mut hasher = Sha512::new();
353 hasher.update(&buffer);
354 hex::encode(hasher.finalize())
355 }
356 _ => {
357 return Err(mlua::Error::RuntimeError(format!(
358 "Unsupported hash algorithm: {}",
359 algo
360 )));
361 }
362 };
363
364 if actual_hash.eq_ignore_ascii_case(expected_hash) {
365 Ok(true)
366 } else {
367 if !quiet {
368 println!(
369 "Hash mismatch for {}: expected {}, got {}",
370 file_path, expected_hash, actual_hash
371 );
372 }
373 Ok(false)
374 }
375 })?;
376 lua.globals().set("verifyHash", verify_hash_fn)?;
377 Ok(())
378}
379
380fn add_zrm(lua: &Lua) -> Result<(), mlua::Error> {
381 let zrm_fn = lua.create_function(|lua, path: String| {
382 let ops_table: Table = match lua.globals().get("__ZoiUninstallOperations") {
383 Ok(t) => t,
384 Err(_) => {
385 let new_t = lua.create_table()?;
386 lua.globals()
387 .set("__ZoiUninstallOperations", new_t.clone())?;
388 new_t
389 }
390 };
391 let op = lua.create_table()?;
392 op.set("op", "zrm")?;
393 op.set("path", path)?;
394 ops_table.push(op)?;
395 Ok(())
396 })?;
397 lua.globals().set("zrm", zrm_fn)?;
398 Ok(())
399}
400
401fn add_cmd_util(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
402 let cmd_fn = lua.create_function(move |lua, command: String| {
403 let build_dir: String = lua.globals().get("BUILD_DIR")?;
404
405 if !quiet {
406 println!("Executing: {}", command);
407 }
408 let output = if cfg!(target_os = "windows") {
409 std::process::Command::new("pwsh")
410 .arg("-Command")
411 .arg(&command)
412 .current_dir(&build_dir)
413 .output()
414 } else {
415 std::process::Command::new("bash")
416 .arg("-c")
417 .arg(&command)
418 .current_dir(&build_dir)
419 .output()
420 };
421
422 match output {
423 Ok(out) => {
424 if !out.status.success() {
425 if !quiet {
426 eprintln!("[cmd] {}", String::from_utf8_lossy(&out.stderr));
427 }
428 Ok(String::new())
429 } else {
430 Ok(String::from_utf8_lossy(&out.stdout).to_string())
431 }
432 }
433 Err(e) => {
434 if !quiet {
435 eprintln!("[cmd] Failed to execute command: {}", e);
436 }
437 Ok(String::new())
438 }
439 }
440 })?;
441 lua.globals().set("cmd", cmd_fn)?;
442 Ok(())
443}
444
445fn add_fs_util(lua: &Lua) -> Result<(), mlua::Error> {
446 let fs_table = lua.create_table()?;
447
448 let exists_fn = lua.create_function(|_, path: String| Ok(Path::new(&path).exists()))?;
449 fs_table.set("exists", exists_fn)?;
450
451 let utils_table: Table = lua.globals().get("UTILS")?;
452 utils_table.set("FS", fs_table)?;
453
454 Ok(())
455}
456
457fn add_find_util(lua: &Lua) -> Result<(), mlua::Error> {
458 let find_table = lua.create_table()?;
459
460 let find_file_fn = lua.create_function(|lua, (dir, name): (String, String)| {
461 let build_dir_str: String = lua.globals().get("BUILD_DIR")?;
462 let search_dir = Path::new(&build_dir_str).join(dir);
463 for entry in WalkDir::new(search_dir) {
464 let entry = entry.map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
465 if entry.file_name().to_string_lossy() == name {
466 let path = entry.path();
467 let relative_path = path.strip_prefix(Path::new(&build_dir_str)).unwrap();
468 return Ok(Some(relative_path.to_string_lossy().to_string()));
469 }
470 }
471 Ok(None)
472 })?;
473 find_table.set("file", find_file_fn)?;
474
475 let utils_table: Table = lua.globals().get("UTILS")?;
476 utils_table.set("FIND", find_table)?;
477
478 Ok(())
479}
480
481fn add_extract_util(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
482 let extract_fn =
483 lua.create_function(move |lua, (source, out_name): (String, Option<String>)| {
484 let build_dir_str: String = lua.globals().get("BUILD_DIR")?;
485 let build_dir = Path::new(&build_dir_str);
486
487 let archive_file = if source.starts_with("http") {
488 if !quiet {
489 println!("Downloading: {}", source);
490 }
491 let file_name = source.split('/').next_back().unwrap_or("download.tmp");
492 let temp_path = build_dir.join(file_name);
493 let response = reqwest::blocking::get(&source)
494 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
495 let content = response
496 .bytes()
497 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
498 fs::write(&temp_path, content)
499 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
500 temp_path
501 } else {
502 PathBuf::from(source)
503 };
504
505 let out_dir_name = out_name.unwrap_or_else(|| "extracted".to_string());
506 let out_dir = build_dir.join(out_dir_name);
507 fs::create_dir_all(&out_dir).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
508
509 if !quiet {
510 println!(
511 "Extracting {} to {}",
512 archive_file.display(),
513 out_dir.display()
514 );
515 }
516
517 let file = fs::File::open(&archive_file)
518 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
519
520 let archive_path_str = archive_file.to_string_lossy();
521
522 if archive_path_str.ends_with(".zip") {
523 let mut archive =
524 ZipArchive::new(file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
525 archive
526 .extract(&out_dir)
527 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
528 } else if archive_path_str.ends_with(".tar.gz") {
529 let tar_gz = GzDecoder::new(file);
530 let mut archive = tar::Archive::new(tar_gz);
531 archive
532 .unpack(&out_dir)
533 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
534 } else if archive_path_str.ends_with(".tar.zst") {
535 let tar_zst =
536 ZstdDecoder::new(file).map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
537 let mut archive = tar::Archive::new(tar_zst);
538 archive
539 .unpack(&out_dir)
540 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
541 } else if archive_path_str.ends_with(".tar.xz") {
542 let tar_xz = XzDecoder::new(file);
543 let mut archive = tar::Archive::new(tar_xz);
544 archive
545 .unpack(&out_dir)
546 .map_err(|e| mlua::Error::RuntimeError(e.to_string()))?;
547 } else {
548 return Err(mlua::Error::RuntimeError(format!(
549 "Unsupported archive format for file: {}",
550 archive_path_str
551 )));
552 }
553
554 Ok(())
555 })?;
556
557 let utils_table: Table = lua.globals().get("UTILS")?;
558 utils_table.set("EXTRACT", extract_fn)?;
559
560 Ok(())
561}
562
563fn add_verify_signature(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
564 let verify_sig_fn = lua.create_function(
565 move |_, (file_path, sig_path, key_source): (String, String, String)| {
566 let key_bytes: Vec<u8> = if key_source.starts_with("http") {
567 match reqwest::blocking::get(&key_source).and_then(|r| r.bytes()) {
568 Ok(b) => b.to_vec(),
569 Err(e) => {
570 return Err(mlua::Error::RuntimeError(format!(
571 "Failed to download key: {}",
572 e
573 )));
574 }
575 }
576 } else if Path::new(&key_source).exists() {
577 match fs::read(&key_source) {
578 Ok(b) => b,
579 Err(e) => {
580 return Err(mlua::Error::RuntimeError(format!(
581 "Failed to read key file: {}",
582 e
583 )));
584 }
585 }
586 } else {
587 let pgp_dir = match crate::pkg::pgp::get_pgp_dir() {
588 Ok(dir) => dir,
589 Err(e) => {
590 return Err(mlua::Error::RuntimeError(format!(
591 "Failed to get PGP dir: {}",
592 e
593 )));
594 }
595 };
596 let key_path = pgp_dir.join(format!("{}.asc", key_source));
597 if !key_path.exists() {
598 return Err(mlua::Error::RuntimeError(format!(
599 "Key with name '{}' not found.",
600 key_source
601 )));
602 }
603 match fs::read(&key_path) {
604 Ok(b) => b,
605 Err(e) => {
606 return Err(mlua::Error::RuntimeError(format!(
607 "Failed to read key file: {}",
608 e
609 )));
610 }
611 }
612 };
613
614 let cert = match Cert::from_bytes(&key_bytes) {
615 Ok(c) => c,
616 Err(e) => return Err(mlua::Error::RuntimeError(format!("Invalid PGP key: {}", e))),
617 };
618
619 let result = crate::pkg::pgp::verify_detached_signature(
620 Path::new(&file_path),
621 Path::new(&sig_path),
622 &cert,
623 );
624
625 match result {
626 Ok(_) => Ok(true),
627 Err(e) => {
628 if !quiet {
629 eprintln!("Signature verification failed: {}", e);
630 }
631 Ok(false)
632 }
633 }
634 },
635 )?;
636 lua.globals().set("verifySignature", verify_sig_fn)?;
637 Ok(())
638}
639
640fn add_add_pgp_key(lua: &Lua, quiet: bool) -> Result<(), mlua::Error> {
641 let add_pgp_key_fn = lua.create_function(move |_, (source, name): (String, String)| {
642 let result = if source.starts_with("http") {
643 crate::pkg::pgp::add_key_from_url(&source, &name)
644 } else {
645 crate::pkg::pgp::add_key_from_path(&source, Some(&name))
646 };
647
648 if let Err(e) = result {
649 if !quiet {
650 eprintln!("Failed to add PGP key '{}': {}", name, e);
651 }
652 return Ok(false);
653 }
654 Ok(true)
655 })?;
656 lua.globals().set("addPgpKey", add_pgp_key_fn)?;
657 Ok(())
658}
659
660pub fn add_package_lifecycle_functions(lua: &Lua) -> Result<(), mlua::Error> {
661 let metadata_fn = lua.create_function(move |lua, pkg_def: Table| {
662 if let Ok(meta_table) = lua.globals().get::<Table>("__ZoiPackageMeta")
663 && let Ok(pkg_global) = lua.globals().get::<Table>("PKG")
664 {
665 for pair in pkg_def.pairs::<Value, Value>() {
666 let (key, value) = pair?;
667 meta_table.set(key.clone(), value.clone())?;
668 pkg_global.set(key, value)?;
669 }
670 }
671 Ok(())
672 })?;
673 lua.globals().set("metadata", metadata_fn)?;
674
675 let dependencies_fn = lua.create_function(move |lua, deps_def: Table| {
676 if let Ok(deps_table) = lua.globals().get::<Table>("__ZoiPackageDeps") {
677 for pair in deps_def.pairs::<String, Value>() {
678 let (key, value) = pair?;
679 deps_table.set(key, value)?;
680 }
681 }
682 Ok(())
683 })?;
684 lua.globals().set("dependencies", dependencies_fn)?;
685
686 let updates_fn = lua.create_function(move |lua, updates_list: Table| {
687 if let Ok(updates_table) = lua.globals().get::<Table>("__ZoiPackageUpdates") {
688 for pair in updates_list.pairs::<Value, Table>() {
689 let (_, update_info) = pair?;
690 updates_table.push(update_info)?;
691 }
692 }
693 Ok(())
694 })?;
695 lua.globals().set("updates", updates_fn)?;
696
697 let hooks_fn = lua.create_function(move |lua, hooks_def: Table| {
698 if let Ok(hooks_table) = lua.globals().get::<Table>("__ZoiPackageHooks") {
699 for pair in hooks_def.pairs::<String, Value>() {
700 let (key, value) = pair?;
701 hooks_table.set(key, value)?;
702 }
703 }
704 Ok(())
705 })?;
706 lua.globals().set("hooks", hooks_fn)?;
707
708 let prepare_fn = lua.create_function(|_, _: Table| Ok(()))?;
709 lua.globals().set("prepare", prepare_fn)?;
710 let package_fn = lua.create_function(|_, _: Table| Ok(()))?;
711 lua.globals().set("package", package_fn)?;
712 let verify_fn = lua.create_function(|_, _: Table| Ok(()))?;
713 lua.globals().set("verify", verify_fn)?;
714 let test_fn = lua.create_function(|_, _: Table| Ok(true))?;
715 lua.globals().set("test", test_fn)?;
716 let uninstall_fn = lua.create_function(|_, _: Table| Ok(()))?;
717 lua.globals().set("uninstall", uninstall_fn)?;
718
719 Ok(())
720}
721
722pub fn setup_lua_environment(
723 lua: &Lua,
724 platform: &str,
725 version_override: Option<&str>,
726 file_path: Option<&str>,
727 create_pkg_dir: Option<&str>,
728 sub_package: Option<&str>,
729 quiet: bool,
730) -> Result<(), mlua::Error> {
731 let system_table = lua.create_table()?;
732 let parts: Vec<&str> = platform.split('-').collect();
733 system_table.set("OS", *parts.first().unwrap_or(&""))?;
734 system_table.set("ARCH", *parts.get(1).unwrap_or(&""))?;
735 if let Some(distro) = utils::get_linux_distribution() {
736 system_table.set("DISTRO", distro)?;
737 }
738 if let Some(manager) = utils::get_native_package_manager() {
739 system_table.set("MANAGER", manager)?;
740 }
741 lua.globals().set("SYSTEM", system_table)?;
742
743 let zoi_table = lua.create_table()?;
744 if let Some(ver) = version_override {
745 zoi_table.set("VERSION", ver)?;
746 }
747
748 if let Some(dir) = create_pkg_dir {
749 zoi_table.set("CREATE_PKG_DIR", dir)?;
750 }
751
752 if let Some(sub) = sub_package {
753 lua.globals().set("SUBPKG", sub)?;
754 }
755
756 let path_table = lua.create_table()?;
757 if let Some(home_dir) = home::home_dir() {
758 path_table.set("user", home_dir.join(".zoi").to_string_lossy().to_string())?;
759 }
760
761 let system_bin_path = if cfg!(target_os = "windows") {
762 "C:\\ProgramData\\zoi\\pkgs\\bin".to_string()
763 } else {
764 "/usr/local/bin".to_string()
765 };
766 path_table.set("system", system_bin_path)?;
767
768 zoi_table.set("PATH", path_table)?;
769 lua.globals().set("ZOI", zoi_table)?;
770
771 let utils_table = lua.create_table()?;
772 lua.globals().set("UTILS", utils_table)?;
773
774 add_fetch_util(lua)?;
775 add_parse_util(lua)?;
776 add_git_fetch_util(lua)?;
777 add_file_util(lua)?;
778 add_zcp(lua)?;
779 add_verify_hash(lua, quiet)?;
780 add_zrm(lua)?;
781 add_cmd_util(lua, quiet)?;
782 add_fs_util(lua)?;
783 add_find_util(lua)?;
784 add_extract_util(lua, quiet)?;
785 add_verify_signature(lua, quiet)?;
786 add_add_pgp_key(lua, quiet)?;
787 add_package_lifecycle_functions(lua)?;
788
789 if let Some(path_str) = file_path {
790 let path = Path::new(path_str);
791 add_import_util(lua, path)?;
792 add_include_util(lua, path)?;
793 }
794
795 if quiet {
796 let quiet_print = lua.create_function(|_, _: mlua::MultiValue| Ok(()))?;
797 lua.globals().set("print", quiet_print)?;
798 }
799
800 Ok(())
801}