use crate::app::App;
use std::cmp::Ordering;
use std::fmt;
pub const PLUGIN_SDK_VERSION: PluginSdkVersion = PluginSdkVersion::new(1, 0, 0);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PluginSdkVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl PluginSdkVersion {
pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major,
minor,
patch,
}
}
pub fn parse(s: &str) -> Option<Self> {
let mut parts = s.trim().split('.');
let major = parts.next()?.parse().ok()?;
let minor = parts.next().unwrap_or("0").parse().ok()?;
let patch = parts.next().unwrap_or("0").parse().ok()?;
if parts.next().is_some() {
return None;
}
Some(Self::new(major, minor, patch))
}
}
impl fmt::Display for PluginSdkVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SdkCompat {
Ok,
Warn { core: PluginSdkVersion, plugin: PluginSdkVersion },
Error(String),
}
pub fn check_plugin_sdk(plugin: PluginSdkVersion, core: PluginSdkVersion) -> SdkCompat {
if plugin.major != core.major {
return SdkCompat::Error(format!(
"plugin SDK {plugin} is incompatible with core SDK {core} (major version mismatch)"
));
}
match plugin.cmp(&core) {
Ordering::Greater => SdkCompat::Error(format!(
"plugin SDK {plugin} requires core SDK >= {plugin} (running {core})"
)),
Ordering::Less => SdkCompat::Warn { core, plugin },
Ordering::Equal => SdkCompat::Ok,
}
}
#[derive(Debug, Clone)]
pub struct PluginMeta {
pub name: &'static str,
pub description: &'static str,
pub version: &'static str,
pub author: &'static str,
pub sdk: PluginSdkVersion,
}
impl PluginMeta {
pub fn new(name: &'static str) -> Self {
Self {
name,
description: "",
version: "",
author: "",
sdk: PLUGIN_SDK_VERSION,
}
}
pub fn for_id(id: &'static str) -> Self {
Self::new(id)
}
pub fn description(mut self, description: &'static str) -> Self {
self.description = description;
self
}
pub fn version(mut self, version: &'static str) -> Self {
self.version = version;
self
}
pub fn author(mut self, author: &'static str) -> Self {
self.author = author;
self
}
pub fn sdk(mut self, sdk: PluginSdkVersion) -> Self {
self.sdk = sdk;
self
}
}
#[derive(Debug, Clone)]
pub struct InstalledPlugin {
pub id: &'static str,
pub meta: PluginMeta,
}
pub trait Plugin {
fn id(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn requires(&self) -> &'static [&'static str] {
&[]
}
fn meta(&self) -> PluginMeta {
PluginMeta::for_id(self.id())
}
fn install(self, app: &mut App);
}
impl<F> Plugin for F
where
F: FnOnce(&mut App),
{
fn install(self, app: &mut App) {
self(app);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_sdk_version() {
assert_eq!(
PluginSdkVersion::parse("1.2.3"),
Some(PluginSdkVersion::new(1, 2, 3))
);
assert_eq!(
PluginSdkVersion::parse("2"),
Some(PluginSdkVersion::new(2, 0, 0))
);
assert!(PluginSdkVersion::parse("x").is_none());
}
#[test]
fn sdk_compat_rules() {
let core = PluginSdkVersion::new(1, 1, 0);
assert_eq!(
check_plugin_sdk(PluginSdkVersion::new(1, 1, 0), core),
SdkCompat::Ok
);
assert!(matches!(
check_plugin_sdk(PluginSdkVersion::new(1, 0, 0), core),
SdkCompat::Warn { .. }
));
assert!(matches!(
check_plugin_sdk(PluginSdkVersion::new(1, 2, 0), core),
SdkCompat::Error(_)
));
assert!(matches!(
check_plugin_sdk(PluginSdkVersion::new(0, 9, 0), core),
SdkCompat::Error(_)
));
}
}