use crate::model_plugin::{ModelPlugin, ModelPluginRegistry};
use latexsnipper_foundation::{Result, SnipperError};
use std::path::Path;
pub fn load_plugins_from_dir(
registry: &mut ModelPluginRegistry,
dir: &Path,
) -> Result<Vec<String>> {
let mut loaded = Vec::new();
if !dir.is_dir() {
return Err(SnipperError::Model(format!(
"Plugin directory not found: {}",
dir.display()
)));
}
for entry in std::fs::read_dir(dir)
.map_err(|e| SnipperError::Model(format!("Failed to read plugin directory: {}", e)))?
{
let entry =
entry.map_err(|e| SnipperError::Model(format!("Failed to read entry: {}", e)))?;
let path = entry.path();
if path
.file_name()
.is_some_and(|n| n.to_string_lossy().starts_with('.'))
{
continue;
}
if path.extension().is_some_and(|e| e == "toml") {
match load_plugin_from_manifest(&path) {
Ok(plugin) => {
let name = plugin.name().to_string();
registry.register(plugin)?;
loaded.push(name);
}
Err(e) => {
log::warn!("Failed to load plugin from {}: {}", path.display(), e);
}
}
}
}
Ok(loaded)
}
fn load_plugin_from_manifest(path: &Path) -> Result<Box<dyn ModelPlugin>> {
let content = std::fs::read_to_string(path)
.map_err(|e| SnipperError::Model(format!("Failed to read manifest: {}", e)))?;
let manifest: PluginManifest = toml::from_str(&content)
.map_err(|e| SnipperError::Model(format!("Failed to parse manifest: {}", e)))?;
log::info!(
"Loading plugin '{}' v{} (type: {}, adapters: {:?}){}",
manifest.name,
manifest.version,
manifest.plugin_type,
manifest.adapters,
manifest
.description
.as_deref()
.map(|d| format!(" - {}", d))
.unwrap_or_default()
);
match manifest.plugin_type.as_str() {
"builtin" => {
create_builtin_plugin(&manifest.name, &manifest.adapters)
}
_ => Err(SnipperError::Model(format!(
"Unsupported plugin type: {}",
manifest.plugin_type
))),
}
}
fn create_builtin_plugin(name: &str, adapters: &[String]) -> Result<Box<dyn ModelPlugin>> {
Err(SnipperError::Model(format!(
"Built-in plugin '{}' not found (adapters: {:?})",
name, adapters
)))
}
#[derive(Debug, serde::Deserialize)]
struct PluginManifest {
name: String,
version: String,
#[serde(rename = "type")]
plugin_type: String,
#[serde(default)]
adapters: Vec<String>,
#[serde(default)]
description: Option<String>,
}
pub fn load_wasm_plugin(_path: &Path) -> Result<Box<dyn ModelPlugin>> {
Err(SnipperError::Other(
"WASM plugin loading not yet implemented".into(),
))
}
pub fn load_dynamic_plugin(_path: &Path) -> Result<Box<dyn ModelPlugin>> {
Err(SnipperError::Other(
"Dynamic library plugin loading not yet implemented".into(),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_manifest_parsing() {
let toml = r#"
name = "test-plugin"
version = "1.0.0"
type = "builtin"
adapters = ["test-adapter-v1"]
description = "A test plugin"
"#;
let manifest: PluginManifest = toml::from_str(toml).unwrap();
assert_eq!(manifest.name, "test-plugin");
assert_eq!(manifest.version, "1.0.0");
assert_eq!(manifest.plugin_type, "builtin");
assert_eq!(manifest.adapters, vec!["test-adapter-v1"]);
}
}