use std::collections::BTreeMap;
use anyhow::{anyhow, Context};
use crate::plugin::PluginMetadata;
#[macro_export]
macro_rules! static_plugins {
[] => {
Vec::<$crate::plugin::PluginMetadata>::new()
};
[$( $(#[$m:meta])* $x:path ),+ $(,)?] => {
{
vec![
$(
$(#[$m])* $crate::plugin::PluginMetadata::from_static::<$x>(),
)*
] as Vec<$crate::plugin::PluginMetadata>
}
}
}
pub struct PluginInfo {
pub metadata: PluginMetadata,
pub enabled: bool,
pub config: Option<toml::Table>,
}
pub struct PluginSet(BTreeMap<String, PluginInfo>);
pub enum PluginFilter {
Enabled,
Disabled,
Any,
}
pub enum UnknownPluginInConfigPolicy {
LogWarn,
Error,
Ignore,
}
impl PluginInfo {
fn new(metadata: PluginMetadata) -> Self {
Self {
metadata,
enabled: true,
config: None,
}
}
}
impl From<Vec<PluginMetadata>> for PluginSet {
fn from(metadata: Vec<PluginMetadata>) -> Self {
let map = BTreeMap::from_iter(metadata.into_iter().map(|m| (m.name.clone(), PluginInfo::new(m))));
Self(map)
}
}
impl PluginSet {
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn enable_only(&mut self, plugin_names: &[impl AsRef<str>]) {
for p in self.0.values_mut() {
p.enabled = false;
}
for p in plugin_names {
self.set_plugin_enabled(p.as_ref(), true);
}
}
pub fn extract_config(
&mut self,
global_config: &mut toml::Table,
update_status: bool,
on_unknown: UnknownPluginInConfigPolicy,
) -> anyhow::Result<()> {
if update_status {
for plugin_info in self.0.values_mut() {
plugin_info.enabled = false;
}
}
let extracted = super::config::extract_plugins_config(global_config).context("invalid config")?;
for (plugin_name, (enabled, config)) in extracted {
if let Some(plugin_info) = self.0.get_mut(&plugin_name) {
if update_status {
plugin_info.enabled = enabled;
}
plugin_info.config = Some(config);
} else {
match on_unknown {
UnknownPluginInConfigPolicy::LogWarn => {
log::warn!("unknown plugin '{plugin_name}' in configuration")
}
UnknownPluginInConfigPolicy::Error => {
return Err(anyhow!("unknown plugin '{plugin_name}' in configuration"))
}
UnknownPluginInConfigPolicy::Ignore => {
}
}
}
}
Ok(())
}
pub fn get_plugin(&self, plugin_name: &str) -> Option<&PluginInfo> {
self.0.get(plugin_name)
}
pub fn get_plugin_mut(&mut self, plugin_name: &str) -> Option<&mut PluginInfo> {
self.0.get_mut(plugin_name)
}
pub fn is_plugin_enabled(&self, plugin_name: &str) -> bool {
self.0.get(plugin_name).map(|p| p.enabled).unwrap_or(false)
}
pub fn set_plugin_enabled(&mut self, plugin_name: &str, enabled: bool) {
if let Some(plugin) = self.0.get_mut(plugin_name) {
plugin.enabled = enabled;
}
}
pub fn add_plugin(&mut self, plugin: PluginInfo) {
self.0.insert(plugin.metadata.name.clone(), plugin);
}
pub fn add_plugins(&mut self, plugins: Vec<PluginInfo>) {
self.0.extend(plugins.into_iter().map(|p| (p.metadata.name.clone(), p)));
}
pub fn metadata(&self, filter: PluginFilter) -> impl Iterator<Item = &PluginMetadata> {
self.0
.values()
.filter_map(move |p| if filter.accept(p) { Some(&p.metadata) } else { None })
}
pub fn into_partition(self) -> (Vec<PluginInfo>, Vec<PluginInfo>) {
self.0.into_values().partition(|p| p.enabled)
}
pub fn into_metadata(self, filter: PluginFilter) -> Vec<PluginMetadata> {
self.0
.into_values()
.filter(|p| filter.accept(p))
.map(|p| p.metadata)
.collect()
}
}
impl PluginFilter {
fn accept(&self, p: &PluginInfo) -> bool {
match self {
PluginFilter::Enabled => p.enabled,
PluginFilter::Disabled => !p.enabled,
PluginFilter::Any => true,
}
}
}
#[cfg(test)]
mod tests {
use serde::Serialize;
use crate::plugin::{
rust::{serialize_config, AlumetPlugin},
AlumetPluginStart, ConfigTable,
};
mod macros {
use super::MyPlugin;
#[test]
fn static_plugins_macro() {
let a = static_plugins![MyPlugin];
let b = static_plugins![MyPlugin,];
let empty = static_plugins![];
assert_eq!(1, a.len());
assert_eq!(1, b.len());
assert_eq!(a[0].name, b[0].name);
assert_eq!(a[0].version, b[0].version);
assert!(empty.is_empty());
}
#[test]
fn static_plugins_macro_with_attributes() {
let single = static_plugins![
#[cfg(test)]
MyPlugin,
];
assert_eq!(1, single.len());
let empty = static_plugins![
#[cfg(not(test))]
MyPlugin
];
assert_eq!(0, empty.len());
let multiple = static_plugins![
#[cfg(test)]
MyPlugin,
#[cfg(not(test))]
MyPlugin,
#[cfg(test)]
MyPlugin
];
assert_eq!(2, multiple.len());
}
}
mod plugin_set {
use toml::toml;
use crate::plugin::rust::AlumetPlugin;
use super::super::{PluginInfo, PluginMetadata, PluginSet, UnknownPluginInConfigPolicy};
use super::MyPlugin;
fn plugin_set() -> PluginSet {
let mut set = PluginSet::new();
set.add_plugin(PluginInfo {
metadata: PluginMetadata::from_static::<MyPlugin>(),
enabled: false,
config: None,
});
assert_eq!(set.0.len(), 1);
assert!(set.get_plugin(MyPlugin::name()).is_some());
set
}
#[test]
fn extract_config_no_update() {
let mut set = plugin_set();
let mut global_config = toml! {
global = 0
[plugins.name]
n = 123
};
set.extract_config(&mut global_config, false, UnknownPluginInConfigPolicy::Error)
.expect("config should be valid");
let plugin_info = set.get_plugin("name").unwrap();
assert_eq!(
plugin_info
.config
.as_ref()
.expect("plugin config should be set")
.get("n"),
Some(&toml::Value::Integer(123))
);
assert!(!plugin_info.enabled);
assert!(!set.is_plugin_enabled("name"));
assert!(global_config.get("plugins").is_none());
}
#[test]
fn extract_config_update_status_implicitly_enabled() {
let mut set = plugin_set();
let mut global_config = toml! {
global = 0
[plugins.name]
n = 123
};
set.extract_config(&mut global_config, true, UnknownPluginInConfigPolicy::Error)
.expect("config should be valid");
let plugin_info = set.get_plugin("name").unwrap();
assert_eq!(
plugin_info
.config
.as_ref()
.expect("plugin config should be set")
.get("n"),
Some(&toml::Value::Integer(123))
);
assert!(plugin_info.enabled);
assert!(set.is_plugin_enabled("name"));
}
#[test]
fn extract_config_update_status_implicitly_disabled() {
let mut set = plugin_set();
let mut global_config = toml! {
global = 0
};
set.extract_config(&mut global_config, true, UnknownPluginInConfigPolicy::Error)
.expect("config should be valid");
let plugin_info = set.get_plugin("name").unwrap();
assert!(plugin_info.config.is_none(), "plugin should have no config");
assert!(!plugin_info.enabled);
assert!(!set.is_plugin_enabled("name"));
}
#[test]
fn extract_config_update_status_explicitly_disabled() {
let mut set = plugin_set();
let mut global_config = toml! {
global = 0
[plugins.name]
enabled = false
n = 123
};
set.extract_config(&mut global_config, true, UnknownPluginInConfigPolicy::Error)
.expect("config should be valid");
let plugin_info = set.get_plugin("name").unwrap();
assert_eq!(
plugin_info
.config
.as_ref()
.expect("plugin config should be set")
.get("n"),
Some(&toml::Value::Integer(123))
);
assert!(!plugin_info.enabled);
assert!(!set.is_plugin_enabled("name"));
}
#[test]
fn extract_config_update_status_explicitly_enabled() {
let mut set = plugin_set();
let mut global_config = toml! {
global = 0
[plugins.name]
enabled = true
n = 123
};
set.extract_config(&mut global_config, true, UnknownPluginInConfigPolicy::Error)
.expect("config should be valid");
let plugin_info = set.get_plugin("name").unwrap();
assert_eq!(
plugin_info
.config
.as_ref()
.expect("plugin config should be set")
.get("n"),
Some(&toml::Value::Integer(123))
);
assert!(plugin_info.enabled);
assert!(set.is_plugin_enabled("name"));
}
}
struct MyPlugin;
impl AlumetPlugin for MyPlugin {
fn name() -> &'static str {
"name"
}
fn version() -> &'static str {
"version"
}
fn init(_config: ConfigTable) -> anyhow::Result<Box<Self>> {
todo!()
}
fn start(&mut self, _alumet: &mut AlumetPluginStart) -> anyhow::Result<()> {
todo!()
}
fn stop(&mut self) -> anyhow::Result<()> {
todo!()
}
fn default_config() -> anyhow::Result<Option<ConfigTable>> {
let config = serialize_config(MyPluginConfig::default())?;
Ok(Some(config))
}
}
#[derive(Serialize)]
struct MyPluginConfig {
list: Vec<String>,
count: u32,
}
impl Default for MyPluginConfig {
fn default() -> Self {
Self {
list: vec![String::from("default-item")],
count: 42,
}
}
}
}