use std::collections::HashMap;
use std::path::PathBuf;
use std::process::Command;
use tracing::info;
use walkdir::WalkDir;
#[derive(Debug, Clone)]
pub struct Plugin {
pub name: String,
pub path: PathBuf,
pub description: String,
}
pub struct PluginManager {
plugin_dir: PathBuf,
plugins: HashMap<String, Plugin>,
}
impl PluginManager {
pub fn new(plugin_dir: PathBuf) -> Self {
Self {
plugin_dir,
plugins: HashMap::new(),
}
}
pub fn scan(&mut self) -> anyhow::Result<usize> {
if !self.plugin_dir.exists() {
return Ok(0);
}
info!("Scanning for plugins in {}", self.plugin_dir.display());
self.plugins.clear();
for entry in WalkDir::new(&self.plugin_dir)
.min_depth(1)
.max_depth(1)
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if path.is_file() {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(metadata) = path.metadata() {
if metadata.permissions().mode() & 0o111 == 0 {
continue; }
}
}
if let Some(filename) = path.file_name().and_then(|s| s.to_str()) {
if filename.starts_with("rk-") {
let name = filename.strip_prefix("rk-").unwrap().to_string();
self.plugins.insert(
name.clone(),
Plugin {
name,
path: path.to_path_buf(),
description: format!("External plugin: {}", filename),
},
);
}
}
}
}
Ok(self.plugins.len())
}
pub fn get(&self, name: &str) -> Option<&Plugin> {
self.plugins.get(name)
}
pub fn list(&self) -> Vec<&Plugin> {
self.plugins.values().collect()
}
pub fn execute(&self, name: &str, args: &[String]) -> anyhow::Result<std::process::ExitStatus> {
if let Some(plugin) = self.get(name) {
info!("Executing plugin: {} {:?}", plugin.path.display(), args);
let status = Command::new(&plugin.path).args(args).status()?;
Ok(status)
} else {
Err(anyhow::anyhow!("Plugin not found: {}", name))
}
}
}