use std::borrow::Cow;
use crate::app::AppBuilder;
pub trait Plugin: Sized + Send + 'static {
fn name(&self) -> Cow<'static, str> {
Cow::Borrowed(std::any::type_name::<Self>())
}
#[must_use]
fn build(self, app: AppBuilder) -> AppBuilder;
}
pub trait Plugins: Sized {
#[must_use]
fn apply(self, app: AppBuilder) -> AppBuilder;
}
impl<P: Plugin> Plugins for P {
fn apply(self, app: AppBuilder) -> AppBuilder {
app.plugin(self)
}
}
macro_rules! impl_plugins_tuple {
($($idx:tt => $ty:ident),+ $(,)?) => {
impl<$($ty: Plugin),+> Plugins for ($($ty,)+) {
#[allow(non_snake_case)]
fn apply(self, app: AppBuilder) -> AppBuilder {
let ($($ty,)+) = self;
let app = app;
$(let app = app.plugin($ty);)+
app
}
}
};
}
impl_plugins_tuple!(0 => P0);
impl_plugins_tuple!(0 => P0, 1 => P1);
impl_plugins_tuple!(0 => P0, 1 => P1, 2 => P2);
impl_plugins_tuple!(0 => P0, 1 => P1, 2 => P2, 3 => P3);
impl_plugins_tuple!(0 => P0, 1 => P1, 2 => P2, 3 => P3, 4 => P4);
impl_plugins_tuple!(0 => P0, 1 => P1, 2 => P2, 3 => P3, 4 => P4, 5 => P5);
impl_plugins_tuple!(0 => P0, 1 => P1, 2 => P2, 3 => P3, 4 => P4, 5 => P5, 6 => P6);
impl_plugins_tuple!(
0 => P0, 1 => P1, 2 => P2, 3 => P3, 4 => P4, 5 => P5, 6 => P6, 7 => P7
);
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::Mutex;
use super::*;
#[derive(Default)]
struct Recorder {
events: Arc<Mutex<Vec<&'static str>>>,
}
impl Recorder {
fn new() -> Self {
Self::default()
}
fn events(&self) -> Vec<&'static str> {
self.events.lock().unwrap().clone()
}
fn push(&self, label: &'static str) {
self.events.lock().unwrap().push(label);
}
}
struct RecordingPlugin {
label: &'static str,
recorder: Arc<Recorder>,
}
impl Plugin for RecordingPlugin {
fn name(&self) -> Cow<'static, str> {
Cow::Borrowed(self.label)
}
fn build(self, app: AppBuilder) -> AppBuilder {
self.recorder.push(self.label);
app
}
}
struct ColaPlugin {
recorder: Arc<Recorder>,
}
impl Plugin for ColaPlugin {
fn build(self, app: AppBuilder) -> AppBuilder {
self.recorder.push("cola");
app
}
}
struct PepsiPlugin {
recorder: Arc<Recorder>,
}
impl Plugin for PepsiPlugin {
fn build(self, app: AppBuilder) -> AppBuilder {
self.recorder.push("pepsi");
app
}
}
#[test]
fn single_plugin_builds_once() {
let recorder = Arc::new(Recorder::new());
let builder = crate::app::app().plugin(RecordingPlugin {
label: "only",
recorder: recorder.clone(),
});
assert_eq!(recorder.events(), vec!["only"]);
assert!(builder.has_plugin("only"));
}
#[test]
fn duplicate_named_plugin_is_skipped_with_warning() {
let recorder = Arc::new(Recorder::new());
let builder = crate::app::app()
.plugin(RecordingPlugin {
label: "dup",
recorder: recorder.clone(),
})
.plugin(RecordingPlugin {
label: "dup",
recorder: recorder.clone(),
});
assert_eq!(recorder.events(), vec!["dup"]);
assert!(builder.has_plugin("dup"));
}
#[test]
fn tuple_of_plugins_applies_in_declaration_order() {
let recorder = Arc::new(Recorder::new());
let _builder = crate::app::app().plugins((
ColaPlugin {
recorder: recorder.clone(),
},
PepsiPlugin {
recorder: recorder.clone(),
},
));
assert_eq!(recorder.events(), vec!["cola", "pepsi"]);
}
}