use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use crate_plugin_kit::CratePluginKit;
use pmpx_plugin::abi::PmpxPluginV1;
use pmpx_plugin::Family;
mod manifest;
use self::manifest::read_one;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstalledPlugin {
pub name: String,
pub crate_name: String,
pub version: String,
pub family: Option<Family>,
pub abi: Option<u32>,
pub dir: PathBuf,
pub strong: Vec<String>,
pub weak: Vec<String>,
}
impl InstalledPlugin {
pub fn is_usable(&self) -> bool {
self.problem().is_none()
}
pub fn problem(&self) -> Option<&'static str> {
match self.family {
None => Some("the manifest does not declare a family, so it cannot take part in family resolution"),
Some(_) if self.strong.is_empty() && self.weak.is_empty() => {
Some("the manifest's [detect] section is empty, so detection will never match it")
}
Some(_) => None,
}
}
pub fn detect_names(&self) -> impl Iterator<Item = &str> {
self.strong
.iter()
.chain(self.weak.iter())
.map(String::as_str)
}
}
#[derive(Debug, Clone, Default)]
pub struct PluginSet {
pub plugins: Vec<InstalledPlugin>,
detect_names: Vec<String>,
}
impl PluginSet {
pub fn load(kit: &CratePluginKit<PmpxPluginV1>) -> Result<Self> {
let infos = kit.list().context("failed to scan the plugin directory")?;
let mut plugins = Vec::with_capacity(infos.len());
for info in infos {
plugins.push(read_one(kit, &info)?);
}
plugins.sort_by(|a, b| a.crate_name.cmp(&b.crate_name));
let mut detect_names: Vec<String> = plugins
.iter()
.filter(|p| p.is_usable())
.flat_map(|p| p.detect_names())
.map(str::to_string)
.collect();
detect_names.sort_unstable();
detect_names.dedup();
Ok(Self {
plugins,
detect_names,
})
}
pub fn usable(&self) -> impl Iterator<Item = &InstalledPlugin> {
self.plugins.iter().filter(|p| p.is_usable())
}
pub fn by_name(&self, name: &str) -> Option<&InstalledPlugin> {
self.plugins.iter().find(|p| p.name == name)
}
pub fn by_crate_name(&self, crate_name: &str) -> Option<&InstalledPlugin> {
self.plugins.iter().find(|p| p.crate_name == crate_name)
}
pub fn detect_names(&self) -> &[String] {
&self.detect_names
}
pub fn marks_root(&self, dir: &Path) -> bool {
if dir.join(".pmpx.toml").is_file() {
return true;
}
self.detect_names()
.iter()
.any(|name| dir.join(name).exists())
}
}
#[cfg(test)]
mod tests;