1use crate::functions;
2use anyhow::{Result, anyhow};
3use mlua::{self, Lua, LuaSerdeExt, Table, Value};
4use std::fs;
5use std::path::Path;
6use tar::Archive;
7use walkdir::WalkDir;
8use zoi_core::{types, utils};
9use zstd::stream::read::Decoder as ZstdDecoder;
10
11pub fn parse_lua_package_from_archive(
12 archive_path: &Path,
13 version_override: Option<&str>,
14 scope: Option<types::Scope>,
15 quiet: bool,
16) -> Result<types::Package> {
17 let platform = utils::get_platform()?;
18 parse_lua_package_from_archive_for_platform(
19 archive_path,
20 &platform,
21 version_override,
22 scope,
23 quiet,
24 )
25}
26
27pub fn parse_lua_package_from_archive_for_platform(
28 archive_path: &Path,
29 platform: &str,
30 version_override: Option<&str>,
31 scope: Option<types::Scope>,
32 quiet: bool,
33) -> Result<types::Package> {
34 let file = fs::File::open(archive_path)?;
35 let decoder = ZstdDecoder::new(file)?;
36 let mut archive = Archive::new(decoder);
37 let temp_dir = tempfile::Builder::new()
38 .prefix("zoi-arch-parse-")
39 .tempdir()?;
40 archive.unpack(temp_dir.path())?;
41
42 let mut pkg_lua = None;
43 for entry in WalkDir::new(temp_dir.path())
44 .into_iter()
45 .filter_map(|e| e.ok())
46 {
47 if entry.file_name().to_string_lossy().ends_with(".pkg.lua") {
48 pkg_lua = Some(entry.path().to_path_buf());
49 break;
50 }
51 }
52
53 let pkg_lua_path = pkg_lua.ok_or_else(|| anyhow!("No .pkg.lua in archive"))?;
54 parse_lua_package_from_file_for_platform(
55 pkg_lua_path
56 .to_str()
57 .ok_or_else(|| anyhow!("Invalid path"))?,
58 platform,
59 version_override,
60 scope,
61 quiet,
62 )
63}
64
65pub fn parse_lua_package_for_platform(
66 file_path: &str,
67 platform: &str,
68 version_override: Option<&str>,
69 scope: Option<types::Scope>,
70 quiet: bool,
71) -> Result<types::Package> {
72 if file_path.ends_with(".zpa") || file_path.ends_with(".zsa") {
73 return parse_lua_package_from_archive_for_platform(
74 Path::new(file_path),
75 platform,
76 version_override,
77 scope,
78 quiet,
79 );
80 }
81 parse_lua_package_from_file_for_platform(file_path, platform, version_override, scope, quiet)
82}
83
84fn parse_lua_package_from_file_for_platform(
85 file_path: &str,
86 platform: &str,
87 version_override: Option<&str>,
88 scope: Option<types::Scope>,
89 quiet: bool,
90) -> Result<types::Package> {
91 let lua_code = fs::read_to_string(file_path)?;
92 let lua = Lua::new();
93
94 let pkg_meta_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
95 let pkg_deps_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
96 let pkg_updates_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
97 let pkg_hooks_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
98 let pkg_service_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
99 lua.globals()
100 .set("__ZoiPackageMeta", pkg_meta_table)
101 .map_err(|e| anyhow!(e.to_string()))?;
102 lua.globals()
103 .set("__ZoiPackageDeps", pkg_deps_table)
104 .map_err(|e| anyhow!(e.to_string()))?;
105 lua.globals()
106 .set("__ZoiPackageUpdates", pkg_updates_table)
107 .map_err(|e| anyhow!(e.to_string()))?;
108 lua.globals()
109 .set("__ZoiPackageHooks", pkg_hooks_table)
110 .map_err(|e| anyhow!(e.to_string()))?;
111 lua.globals()
112 .set("__ZoiPackageService", pkg_service_table)
113 .map_err(|e| anyhow!(e.to_string()))?;
114
115 let pkg_table = lua.create_table().map_err(|e| anyhow!(e.to_string()))?;
116 lua.globals()
117 .set("PKG", pkg_table)
118 .map_err(|e| anyhow!(e.to_string()))?;
119
120 functions::setup_lua_environment(
121 &lua,
122 platform,
123 version_override,
124 Some(file_path),
125 None,
126 None,
127 None,
128 None,
129 scope,
130 quiet,
131 )
132 .map_err(|e| anyhow!("Failed to setup Lua environment for '{}': {}", file_path, e))?;
133
134 lua.load(&lua_code).exec().map_err(|e| {
135 anyhow!(
136 "Failed to execute Lua package file '{}':
137{}",
138 file_path,
139 e
140 )
141 })?;
142
143 let final_pkg_meta: Table = lua
144 .globals()
145 .get("__ZoiPackageMeta")
146 .map_err(|e| anyhow!(e.to_string()))?;
147 let final_pkg_deps: Table = lua
148 .globals()
149 .get("__ZoiPackageDeps")
150 .map_err(|e| anyhow!(e.to_string()))?;
151 let final_pkg_updates: Table = lua
152 .globals()
153 .get("__ZoiPackageUpdates")
154 .map_err(|e| anyhow!(e.to_string()))?;
155 let final_pkg_hooks: Table = lua
156 .globals()
157 .get("__ZoiPackageHooks")
158 .map_err(|e| anyhow!(e.to_string()))?;
159 let final_pkg_service: Table = lua
160 .globals()
161 .get("__ZoiPackageService")
162 .map_err(|e| anyhow!(e.to_string()))?;
163
164 let mut package: types::Package =
165 lua.from_value(Value::Table(final_pkg_meta)).map_err(|e| {
166 anyhow!(
167 "Failed to parse 'metadata' block in package file '{}':
168{}",
169 file_path,
170 e
171 )
172 })?;
173
174 package.dependencies = if final_pkg_deps.is_empty() {
175 None
176 } else {
177 Some(lua.from_value(Value::Table(final_pkg_deps)).map_err(|e| {
178 anyhow!(
179 "Failed to parse 'dependencies' block in package file '{}':
180{}",
181 file_path,
182 e
183 )
184 })?)
185 };
186
187 package.updates = if final_pkg_updates.is_empty() {
188 None
189 } else {
190 Some(
191 lua.from_value(Value::Table(final_pkg_updates))
192 .map_err(|e| {
193 anyhow!(
194 "Failed to parse 'updates' block in package file '{}':
195{}",
196 file_path,
197 e
198 )
199 })?,
200 )
201 };
202
203 package.hooks = if final_pkg_hooks.is_empty() {
204 None
205 } else {
206 Some(lua.from_value(Value::Table(final_pkg_hooks)).map_err(|e| {
207 anyhow!(
208 "Failed to parse 'hooks' block in package file '{}':
209{}",
210 file_path,
211 e
212 )
213 })?)
214 };
215
216 package.service = if final_pkg_service.is_empty() {
217 None
218 } else {
219 Some(
220 lua.from_value(Value::Table(final_pkg_service))
221 .map_err(|e| {
222 anyhow!(
223 "Failed to parse 'service' block in package file '{}':
224{}",
225 file_path,
226 e
227 )
228 })?,
229 )
230 };
231
232 Ok(package)
233}
234
235pub fn parse_lua_package(
236 file_path: &str,
237 version_override: Option<&str>,
238 scope: Option<types::Scope>,
239 quiet: bool,
240) -> Result<types::Package> {
241 let platform = utils::get_platform()?;
242 parse_lua_package_for_platform(file_path, &platform, version_override, scope, quiet)
243}