diff --git a/Cargo.lock b/Cargo.lock index 012e635..9aec610 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -908,8 +908,11 @@ version = "25.1.0" dependencies = [ "asimov-config", "asimov-core", + "asimov-module", "camino", "derive_more 2.1.1", + "serde_json", + "serde_yaml_ng", "tokio", ] diff --git a/lib/asimov-directory/Cargo.toml b/lib/asimov-directory/Cargo.toml index c4ad5d1..a5b40e2 100644 --- a/lib/asimov-directory/Cargo.toml +++ b/lib/asimov-directory/Cargo.toml @@ -36,7 +36,10 @@ tokio = ["dep:tokio"] [dependencies] asimov-config.workspace = true asimov-core.workspace = true +asimov-module = { workspace = true, features = ["serde"] } derive_more = { workspace = true, features = ["display"] } +serde_json = { workspace = true } +serde_yaml_ng = { workspace = true } # Integrations: camino = { workspace = true, optional = true } diff --git a/lib/asimov-directory/src/fs/module_directory.rs b/lib/asimov-directory/src/fs/module_directory.rs index cb63a6d..3cb2d1c 100644 --- a/lib/asimov-directory/src/fs/module_directory.rs +++ b/lib/asimov-directory/src/fs/module_directory.rs @@ -1,6 +1,6 @@ // This is free and unencumbered software released into the public domain. -use super::{ModuleNameIterator, StateDirectory}; +use super::{ModuleManifestIterator, ModuleNameIterator, StateDirectory}; use alloc::format; use camino::Utf8PathBuf; use derive_more::Display; @@ -47,6 +47,10 @@ impl ModuleDirectory { ModuleNameIterator::new(self.path.join("installed")).await } + pub async fn iter_manifests(&self) -> Result { + ModuleManifestIterator::new(self.path.join("installed")).await + } + pub fn as_str(&self) -> &str { self.path.as_str() } diff --git a/lib/asimov-directory/src/fs/module_iterators.rs b/lib/asimov-directory/src/fs/module_iterators.rs index dcd2b5f..25b1382 100644 --- a/lib/asimov-directory/src/fs/module_iterators.rs +++ b/lib/asimov-directory/src/fs/module_iterators.rs @@ -1,6 +1,7 @@ // This is free and unencumbered software released into the public domain. use asimov_core::ModuleName; +use asimov_module::InstalledModuleManifest; use camino::Utf8PathBuf; use std::io::Result; use tokio::fs::ReadDir; @@ -38,3 +39,43 @@ impl crate::ModuleNameIterator for ModuleNameIterator { None } } + +/// An iterator over module manifests in a module directory. +#[derive(Debug)] +pub struct ModuleManifestIterator { + dir: ReadDir, +} + +impl ModuleManifestIterator { + pub async fn new(path: Utf8PathBuf) -> Result { + Ok(ModuleManifestIterator { + dir: tokio::fs::read_dir(path).await?, + }) + } +} + +impl crate::ModuleManifestIterator for ModuleManifestIterator { + async fn next(&mut self) -> Option { + while let Some(entry) = self.dir.next_entry().await.transpose() { + if let Ok(entry) = entry + && let Some(entry_name) = entry.file_name().to_str() + && !entry_name.starts_with(".") + && let Ok(entry_type) = entry.file_type().await + && (entry_type.is_file() || entry_type.is_symlink()) + && let Some(ext) = std::path::Path::new(entry_name) + .extension() + .and_then(|ext| ext.to_str()) + && (ext == "json" || ext == "yaml" || ext == "yml") + && let Ok(content) = tokio::fs::read(entry.path()).await + && let Some(manifest) = match ext { + "json" => serde_json::from_slice(&content).ok(), + "yaml" | "yml" => serde_yaml_ng::from_slice(&content).ok(), + _ => None, + } + { + return Some(manifest); + } + } + None + } +} diff --git a/lib/asimov-directory/src/module_iterators.rs b/lib/asimov-directory/src/module_iterators.rs index 8f521a3..92e0ceb 100644 --- a/lib/asimov-directory/src/module_iterators.rs +++ b/lib/asimov-directory/src/module_iterators.rs @@ -1,12 +1,18 @@ // This is free and unencumbered software released into the public domain. use asimov_core::ModuleName; +use asimov_module::InstalledModuleManifest; /// An iterator over module names in a module directory. pub trait ModuleNameIterator { fn next(&mut self) -> impl Future> + Send; } +/// An iterator over module manifests in a module directory. +pub trait ModuleManifestIterator { + fn next(&mut self) -> impl Future> + Send; +} + /// An iterator over module names in a module directory. pub trait BlockingModuleNameIterator { fn next(&mut self) -> Option;