codama_korok_plugins/plugin.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
use codama_errors::CodamaResult;
use codama_korok_visitors::KorokVisitable;
pub trait KorokPlugin {
fn run(
&self,
visitable: &mut dyn KorokVisitable,
next: &dyn Fn(&mut dyn KorokVisitable) -> CodamaResult<()>,
) -> CodamaResult<()>;
}
/// Reduce all plugins into a single function that runs them in sequence.
///
/// For instance, imagine we have a list of plugins [A, B, C] implemented as:
///
/// ```rust
/// use codama_errors::CodamaResult;
/// use codama_korok_plugins::KorokPlugin;
/// use codama_korok_visitors::KorokVisitable;
///
/// struct LoggingPluging;
/// impl KorokPlugin for LoggingPluging {
/// fn run(&self, visitable: &mut dyn KorokVisitable, next: &dyn Fn(&mut dyn KorokVisitable) -> CodamaResult<()>) -> CodamaResult<()> {
/// println!("Plugin X - before");
/// next(visitable)?;
/// println!("Plugin X - after");
/// Ok(())
/// }
/// }
/// ```
///
/// Where `X` is `A`, `B`, or `C`. The `resolve_plugins` function will return a function that
/// prints the following:
///
/// ```text
/// Plugin C - before
/// Plugin B - before
/// Plugin A - before
/// Plugin A - after
/// Plugin B - after
/// Plugin C - after
/// ```
pub fn resolve_plugins<'a>(
plugins: &'a [Box<dyn KorokPlugin + 'a>],
) -> Box<dyn Fn(&mut dyn KorokVisitable) -> CodamaResult<()> + 'a> {
// We fold from the left to ensure that any code before the
// `next` call is run before the previous plugin on the list.
plugins.iter().fold(
// Base case: a no-op `next` function.
Box::new(|_: &mut dyn KorokVisitable| Ok(()))
as Box<dyn Fn(&mut dyn KorokVisitable) -> CodamaResult<()>>,
// Wrap each plugin with a closure that calls the next plugin in the chain.
|next, plugin| {
Box::new(move |visitable: &mut dyn KorokVisitable| plugin.run(visitable, &next))
as Box<dyn Fn(&mut dyn KorokVisitable) -> CodamaResult<()>>
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use codama_korok_visitors::KorokVisitor;
use std::{cell::RefCell, sync::Arc};
struct LoggingPluging {
id: String,
logs: Arc<RefCell<Vec<String>>>,
}
impl LoggingPluging {
fn new(id: &str, logs: Arc<RefCell<Vec<String>>>) -> Self {
Self {
id: id.into(),
logs,
}
}
}
impl KorokPlugin for LoggingPluging {
fn run(
&self,
visitable: &mut dyn KorokVisitable,
next: &dyn Fn(&mut dyn KorokVisitable) -> CodamaResult<()>,
) -> CodamaResult<()> {
self.logs
.borrow_mut()
.push(format!("Plugin {} - before", self.id));
next(visitable)?;
self.logs
.borrow_mut()
.push(format!("Plugin {} - after", self.id));
Ok(())
}
}
struct MockVisitable;
impl KorokVisitable for MockVisitable {
fn accept(&mut self, _visitor: &mut dyn KorokVisitor) -> CodamaResult<()> {
Ok(())
}
fn get_children(&mut self) -> Vec<&mut dyn KorokVisitable> {
Vec::new()
}
}
#[test]
fn test_resolve_plugins() -> CodamaResult<()> {
let logs = Arc::new(RefCell::new(Vec::new()));
let plugins: Vec<Box<dyn KorokPlugin>> = vec![
Box::new(LoggingPluging::new("A", logs.clone())),
Box::new(LoggingPluging::new("B", logs.clone())),
];
let run_plugins = resolve_plugins(&plugins);
run_plugins(&mut MockVisitable)?;
assert_eq!(
logs.borrow().as_slice(),
&[
"Plugin B - before",
"Plugin A - before",
"Plugin A - after",
"Plugin B - after",
]
);
Ok(())
}
}