1#[macro_export]
70macro_rules! define_aam_loader {
71 {
73 name: $struct_name:ident,
74 dir: $dir:expr,
75 list: { $($list_field:ident: $list_ty:ty),* $(,)? },
76 opt: { $($opt_field:ident: $opt_ty:ty),* $(,)? },
77 req: { $($req_field:ident: $req_ty:ty),* $(,)? },
78 $(,)?
79 } => {
80 define_aam_loader! {
81 @impl
82 name: $struct_name,
83 list: { $($list_field: $list_ty),* },
84 opt: { $($opt_field: $opt_ty),* },
85 req: { $($req_field: $req_ty),* },
86 }
87 };
88 {
90 name: $struct_name:ident,
91 dir: $dir:expr,
92 list: { $($list_field:ident: $list_ty:ty),* $(,)? },
93 opt: { $($opt_field:ident: $opt_ty:ty),* $(,)? },
94 $(,)?
95 } => {
96 define_aam_loader! {
97 @impl
98 name: $struct_name,
99 list: { $($list_field: $list_ty),* },
100 opt: { $($opt_field: $opt_ty),* },
101 req: { },
102 }
103 };
104 {
106 name: $struct_name:ident,
107 dir: $dir:expr,
108 list: { $($list_field:ident: $list_ty:ty),* $(,)? },
109 req: { $($req_field:ident: $req_ty:ty),* $(,)? },
110 $(,)?
111 } => {
112 define_aam_loader! {
113 @impl
114 name: $struct_name,
115 list: { $($list_field: $list_ty),* },
116 opt: { },
117 req: { $($req_field: $req_ty),* },
118 }
119 };
120 {
122 name: $struct_name:ident,
123 dir: $dir:expr,
124 opt: { $($opt_field:ident: $opt_ty:ty),* $(,)? },
125 req: { $($req_field:ident: $req_ty:ty),* $(,)? },
126 $(,)?
127 } => {
128 define_aam_loader! {
129 @impl
130 name: $struct_name,
131 list: { },
132 opt: { $($opt_field: $opt_ty),* },
133 req: { $($req_field: $req_ty),* },
134 }
135 };
136 {
138 name: $struct_name:ident,
139 dir: $dir:expr,
140 list: { $($list_field:ident: $list_ty:ty),* $(,)? },
141 $(,)?
142 } => {
143 define_aam_loader! {
144 @impl
145 name: $struct_name,
146 list: { $($list_field: $list_ty),* },
147 opt: { },
148 req: { },
149 }
150 };
151 {
153 name: $struct_name:ident,
154 dir: $dir:expr,
155 opt: { $($opt_field:ident: $opt_ty:ty),* $(,)? },
156 $(,)?
157 } => {
158 define_aam_loader! {
159 @impl
160 name: $struct_name,
161 list: { },
162 opt: { $($opt_field: $opt_ty),* },
163 req: { },
164 }
165 };
166 {
168 name: $struct_name:ident,
169 dir: $dir:expr,
170 req: { $($req_field:ident: $req_ty:ty),* $(,)? },
171 $(,)?
172 } => {
173 define_aam_loader! {
174 @impl
175 name: $struct_name,
176 list: { },
177 opt: { },
178 req: { $($req_field: $req_ty),* },
179 }
180 };
181 {
183 name: $struct_name:ident,
184 dir: $dir:expr,
185 $(,)?
186 } => {
187 define_aam_loader! {
188 @impl
189 name: $struct_name,
190 list: { },
191 opt: { },
192 req: { },
193 }
194 };
195 {
197 @impl
198 name: $struct_name:ident,
199 list: { $($list_field:ident: $list_ty:ty),* $(,)? },
200 opt: { $($opt_field:ident: $opt_ty:ty),* $(,)? },
201 req: { $($req_field:ident: $req_ty:ty),* $(,)? },
202 $(,)?
203 } => {
204 pub struct $struct_name {
205 deps: ::std::collections::HashMap<String, ::std::sync::Arc<$crate::aam::AAM>>,
206 }
207
208 impl Default for $struct_name {
209 fn default() -> Self {
210 Self::new()
211 }
212 }
213
214 #[allow(dead_code)]
215 impl $struct_name {
216 pub fn new() -> Self {
217 $struct_name {
218 deps: ::std::collections::HashMap::new(),
219 }
220 }
221
222 pub fn load_dir<P: AsRef<::std::path::Path>>(&mut self, path: P) -> ::anyhow::Result<()> {
223 let files: Vec<_> = ::anyhow::Context::with_context(
224 ::std::fs::read_dir(&path),
225 || format!("Failed to read dir: {}", path.as_ref().display())
226 )?
227 .filter_map(|entry| entry.ok())
228 .map(|entry| entry.path())
229 .filter(|p| {
230 p.is_file()
231 && p.extension().and_then(|s| s.to_str()) == Some("aam")
232 && p.file_name().and_then(|s| s.to_str()) != Some("config.aam")
233 })
234 .collect();
235
236 for file in files {
237 self.load_aam(&file)?;
238 }
239
240 Ok(())
241 }
242
243 pub fn load_aam<P: AsRef<::std::path::Path>>(&mut self, path: P) -> ::anyhow::Result<()> {
244 let model = $crate::aam::AAM::load(&path)
245 .map_err(|e| ::anyhow::anyhow!("Failed to load {}: {:?}", path.as_ref().display(), e))?;
246
247 let id = model
248 .get("id")
249 .ok_or_else(|| ::anyhow::anyhow!("No 'id' in {}", path.as_ref().display()))?
250 .to_string();
251
252 self.deps.insert(id, ::std::sync::Arc::new(model));
253 Ok(())
254 }
255
256 pub fn get_dep(&self, id: &str) -> Option<::std::sync::Arc<$crate::aam::AAM>> {
257 self.deps.get(id).cloned()
258 }
259
260 pub fn get_all_ids(&self) -> Vec<String> {
261 self.deps.keys().cloned().collect()
262 }
263
264 $(
266 pub fn $list_field(&self, id: &str) -> ::anyhow::Result<Vec<$list_ty>> {
267 let target = self
268 .deps
269 .get(id)
270 .ok_or_else(|| ::anyhow::anyhow!("Package '{}' not found", id))?;
271
272 let Some(raw) = target.get(stringify!($list_field)) else {
273 return Ok(::std::vec::Vec::new());
274 };
275
276 let parsed = $crate::found_value::FoundValue::new(raw)
277 .parse_list::<$list_ty>();
278 match parsed {
279 Some(Ok(v)) => Ok(v),
280 Some(Err(_)) => Err(::anyhow::anyhow!(
281 "Failed to parse list '{}' in package '{}'. Raw: {:?}",
282 stringify!($list_field), id, raw
283 )),
284 None => Err(::anyhow::anyhow!(
285 "Expected list format for '{}' in package '{}'. Raw: {:?}",
286 stringify!($list_field), id, raw
287 )),
288 }
289 }
290 )*
291
292 $(
294 pub fn $opt_field(&self, id: &str) -> ::anyhow::Result<Option<$opt_ty>> {
295 let target = self
296 .deps
297 .get(id)
298 .ok_or_else(|| ::anyhow::anyhow!("Package '{}' not found", id))?;
299
300 let Some(raw) = target.get(stringify!($opt_field)) else {
301 return Ok(None);
302 };
303
304 let parsed = raw.parse::<$opt_ty>().map(Some);
305 ::anyhow::Context::with_context(parsed, || {
306 format!(
307 "Failed to parse '{}' as {} for package '{}'. Raw: {:?}",
308 stringify!($opt_field), stringify!($opt_ty), id, raw
309 )
310 })
311 }
312 )*
313
314 $(
316 pub fn $req_field(&self, id: &str) -> ::anyhow::Result<$req_ty> {
317 let target = self
318 .deps
319 .get(id)
320 .ok_or_else(|| ::anyhow::anyhow!("Package '{}' not found", id))?;
321
322 let raw = target
323 .get(stringify!($req_field))
324 .ok_or_else(|| ::anyhow::anyhow!(
325 "Required field '{}' not found in package '{}'",
326 stringify!($req_field), id
327 ))?;
328
329 let parsed = raw.parse::<$req_ty>();
330 ::anyhow::Context::with_context(parsed, || {
331 format!(
332 "Failed to parse '{}' as {} for package '{}'. Raw: {:?}",
333 stringify!($req_field), stringify!($req_ty), id, raw
334 )
335 })
336 }
337 )*
338 }
339 };
340}
341
342#[cfg(test)]
343mod tests {
344 use std::io::Write;
345
346 define_aam_loader! {
347 name: TestDeps,
348 dir: "does_not_matter",
349
350 list: {
351 build_deps: String,
352 },
353 opt: {
354 version: u64,
355 },
356 req: {
357 name: String,
358 },
359 }
360
361 #[test]
362 fn test_define_aam_loader_basic() {
363 let temp = tempfile::tempdir().unwrap();
364 let file_path = temp.path().join("pkg.aam");
365 let mut f = std::fs::File::create(&file_path).unwrap();
366 writeln!(
367 f,
368 "id = my-pkg\nname = hello\nbuild_deps = [dep-a, dep-b]\nversion = 42\n"
369 )
370 .unwrap();
371 drop(f);
372
373 let mut deps = TestDeps::new();
374 deps.load_aam(&file_path).unwrap();
375
376 let name = deps.name("my-pkg").unwrap();
377 assert_eq!(name, "hello");
378
379 let bdeps = deps.build_deps("my-pkg").unwrap();
380 assert_eq!(bdeps, vec!["dep-a", "dep-b"]);
381
382 let ver = deps.version("my-pkg").unwrap();
383 assert_eq!(ver, Some(42));
384
385 let ids = deps.get_all_ids();
386 assert_eq!(ids, vec!["my-pkg"]);
387 }
388
389 #[test]
390 fn test_define_aam_loader_missing_opt() {
391 let temp = tempfile::tempdir().unwrap();
392 let file_path = temp.path().join("pkg2.aam");
393 let mut f = std::fs::File::create(&file_path).unwrap();
394 writeln!(f, "id = pkg2\nname = test\nbuild_deps = []\n").unwrap();
395 drop(f);
396
397 let mut deps = TestDeps::new();
398 deps.load_aam(&file_path).unwrap();
399
400 let ver = deps.version("pkg2").unwrap();
401 assert_eq!(ver, None);
402
403 let bdeps = deps.build_deps("pkg2").unwrap();
404 assert!(bdeps.is_empty());
405 }
406
407 #[test]
408 fn test_define_aam_loader_load_dir() {
409 let temp = tempfile::tempdir().unwrap();
410 for i in 1..=3 {
411 let file_path = temp.path().join(format!("pkg{i}.aam"));
412 let mut f = std::fs::File::create(&file_path).unwrap();
413 writeln!(f, "id = pkg{i}\nname = package-{i}\nbuild_deps = []\n").unwrap();
414 drop(f);
415 }
416
417 let mut deps = TestDeps::new();
418 deps.load_dir(temp.path()).unwrap();
419
420 let ids = deps.get_all_ids();
421 assert_eq!(ids.len(), 3);
422 assert_eq!(deps.name("pkg2").unwrap(), "package-2");
423 }
424}