use std::path::Path;
use anyhow::{Context, Result};
use crate_plugin_kit::{CratePluginKit, PluginInfo};
use pmpx_plugin::abi::PmpxPlugin;
use pmpx_plugin::Family;
mod manifest;
use self::manifest::read_one;
#[derive(Debug, Clone)]
pub struct InstalledPlugin {
pub info: PluginInfo,
pub family: Option<Family>,
pub strong: Vec<String>,
pub weak: Vec<String>,
pub wanted: Vec<String>,
}
impl InstalledPlugin {
pub fn name(&self) -> &str {
&self.info.name
}
pub fn crate_name(&self) -> &str {
&self.info.crate_name
}
pub fn version(&self) -> &str {
&self.info.version
}
pub fn abi(&self) -> Option<u32> {
self.info.abi
}
pub fn dir(&self) -> &std::path::Path {
&self.info.dir
}
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<PmpxPlugin>) -> 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(&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())
}
}
pub use crate_plugin_kit::cache::InstallSource;
pub use crate_plugin_kit::install::Installed;
pub use crate_plugin_kit::{CrateInfo, CrateSummary};
use crate::EngineError;
#[derive(Debug, Clone)]
pub struct VetReport {
pub abi: Option<u32>,
pub family: Option<String>,
pub markers: Vec<String>,
}
pub fn vet_checkout(dir: &Path, manifest_name: &str) -> Result<VetReport, EngineError> {
let path = dir.join(manifest_name);
if !path.is_file() {
return Err(EngineError::Usage(format!(
"{} has no {manifest_name}, so it is not a plugin checkout",
dir.display()
)));
}
let manifest = crate_plugin_kit::PluginManifest::read(&path)
.map_err(|error| EngineError::Usage(format!("cannot read {}: {error}", path.display())))?;
Ok(VetReport {
abi: manifest.plugin.abi,
family: manifest.plugin.family.clone(),
markers: markers(&manifest),
})
}
fn markers(manifest: &crate_plugin_kit::PluginManifest) -> Vec<String> {
let mut out = str_array(manifest, "detect", "strong");
out.extend(str_array(manifest, "detect", "weak"));
out
}
fn str_array(manifest: &crate_plugin_kit::PluginManifest, section: &str, key: &str) -> Vec<String> {
manifest
.extra
.get(section)
.and_then(|section| section.get(key))
.and_then(|value| value.as_array())
.map(|items| {
items
.iter()
.filter_map(|item| item.as_str())
.map(str::to_string)
.collect()
})
.unwrap_or_default()
}
#[cfg(test)]
mod tests;