use std::borrow::Cow;
use std::collections::HashMap;
use crate::execute::{ExecCtx, ExecError, ExecStep};
use crate::pack::Action;
pub mod pack_type;
#[cfg(feature = "plugin-inventory")]
pub use pack_type::PackTypePluginSubmission;
pub use pack_type::{PackTypePlugin, PackTypeRegistry};
pub trait ActionPlugin: Send + Sync {
fn name(&self) -> &str;
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError>;
}
#[derive(Default)]
pub struct Registry {
actions: HashMap<Cow<'static, str>, Box<dyn ActionPlugin>>,
}
impl std::fmt::Debug for Registry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Registry")
.field("actions", &self.actions.keys().collect::<Vec<_>>())
.finish()
}
}
impl Registry {
#[must_use]
pub fn new() -> Self {
Self { actions: HashMap::new() }
}
pub fn register<P: ActionPlugin + 'static>(&mut self, plugin: P) {
let name: Cow<'static, str> = Cow::Owned(plugin.name().to_owned());
self.actions.insert(name, Box::new(plugin));
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&dyn ActionPlugin> {
self.actions.get(name).map(std::convert::AsRef::as_ref)
}
#[must_use]
pub fn len(&self) -> usize {
self.actions.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.actions.is_empty()
}
#[must_use]
pub fn bootstrap() -> Self {
let mut reg = Self::new();
register_builtins(&mut reg);
reg
}
#[cfg(feature = "plugin-inventory")]
pub fn register_from_inventory(&mut self) {
for sub in inventory::iter::<PluginSubmission> {
let plugin = (sub.factory)();
let name: Cow<'static, str> = Cow::Owned(plugin.name().to_owned());
self.actions.insert(name, plugin);
}
}
#[cfg(feature = "plugin-inventory")]
#[must_use]
pub fn bootstrap_from_inventory() -> Self {
let mut reg = Self::new();
reg.register_from_inventory();
reg
}
}
#[cfg(feature = "plugin-inventory")]
#[non_exhaustive]
pub struct PluginSubmission {
pub factory: fn() -> Box<dyn ActionPlugin>,
}
#[cfg(feature = "plugin-inventory")]
impl PluginSubmission {
#[must_use]
pub const fn new(factory: fn() -> Box<dyn ActionPlugin>) -> Self {
Self { factory }
}
}
#[cfg(feature = "plugin-inventory")]
inventory::collect!(PluginSubmission);
pub fn register_builtins(reg: &mut Registry) {
reg.register(SymlinkPlugin);
reg.register(UnlinkPlugin);
reg.register(EnvPlugin);
reg.register(MkdirPlugin);
reg.register(RmdirPlugin);
reg.register(RequirePlugin);
reg.register(WhenPlugin);
reg.register(ExecPlugin);
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SymlinkPlugin;
impl ActionPlugin for SymlinkPlugin {
fn name(&self) -> &str {
"symlink"
}
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError> {
match action {
Action::Symlink(s) => crate::execute::fs_executor::fs_symlink(s, ctx),
_ => Err(ExecError::ExecInvalid(format!(
"symlink plugin dispatched with non-symlink action `{}`",
action.name()
))),
}
}
}
#[cfg(feature = "plugin-inventory")]
inventory::submit!(PluginSubmission::new(|| Box::new(SymlinkPlugin)));
#[derive(Debug, Default, Clone, Copy)]
pub struct UnlinkPlugin;
impl ActionPlugin for UnlinkPlugin {
fn name(&self) -> &str {
"unlink"
}
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError> {
match action {
Action::Unlink(u) => crate::execute::fs_executor::fs_unlink(u, ctx),
_ => Err(ExecError::ExecInvalid(format!(
"unlink plugin dispatched with non-unlink action `{}`",
action.name()
))),
}
}
}
#[cfg(feature = "plugin-inventory")]
inventory::submit!(PluginSubmission::new(|| Box::new(UnlinkPlugin)));
#[derive(Debug, Default, Clone, Copy)]
pub struct EnvPlugin;
impl ActionPlugin for EnvPlugin {
fn name(&self) -> &str {
"env"
}
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError> {
match action {
Action::Env(e) => crate::execute::fs_executor::fs_env(e, ctx),
_ => Err(ExecError::ExecInvalid(format!(
"env plugin dispatched with non-env action `{}`",
action.name()
))),
}
}
}
#[cfg(feature = "plugin-inventory")]
inventory::submit!(PluginSubmission::new(|| Box::new(EnvPlugin)));
#[derive(Debug, Default, Clone, Copy)]
pub struct MkdirPlugin;
impl ActionPlugin for MkdirPlugin {
fn name(&self) -> &str {
"mkdir"
}
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError> {
match action {
Action::Mkdir(m) => crate::execute::fs_executor::fs_mkdir(m, ctx),
_ => Err(ExecError::ExecInvalid(format!(
"mkdir plugin dispatched with non-mkdir action `{}`",
action.name()
))),
}
}
}
#[cfg(feature = "plugin-inventory")]
inventory::submit!(PluginSubmission::new(|| Box::new(MkdirPlugin)));
#[derive(Debug, Default, Clone, Copy)]
pub struct RmdirPlugin;
impl ActionPlugin for RmdirPlugin {
fn name(&self) -> &str {
"rmdir"
}
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError> {
match action {
Action::Rmdir(r) => crate::execute::fs_executor::fs_rmdir(r, ctx),
_ => Err(ExecError::ExecInvalid(format!(
"rmdir plugin dispatched with non-rmdir action `{}`",
action.name()
))),
}
}
}
#[cfg(feature = "plugin-inventory")]
inventory::submit!(PluginSubmission::new(|| Box::new(RmdirPlugin)));
#[derive(Debug, Default, Clone, Copy)]
pub struct RequirePlugin;
impl ActionPlugin for RequirePlugin {
fn name(&self) -> &str {
"require"
}
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError> {
match action {
Action::Require(r) => crate::execute::fs_executor::fs_require(r, ctx),
_ => Err(ExecError::ExecInvalid(format!(
"require plugin dispatched with non-require action `{}`",
action.name()
))),
}
}
}
#[cfg(feature = "plugin-inventory")]
inventory::submit!(PluginSubmission::new(|| Box::new(RequirePlugin)));
#[derive(Debug, Default, Clone, Copy)]
pub struct WhenPlugin;
impl ActionPlugin for WhenPlugin {
fn name(&self) -> &str {
"when"
}
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError> {
match action {
Action::When(w) => {
crate::execute::fs_executor::fs_when(w, ctx)
}
_ => Err(ExecError::ExecInvalid(format!(
"when plugin dispatched with non-when action `{}`",
action.name()
))),
}
}
}
#[cfg(feature = "plugin-inventory")]
inventory::submit!(PluginSubmission::new(|| Box::new(WhenPlugin)));
#[derive(Debug, Default, Clone, Copy)]
pub struct ExecPlugin;
impl ActionPlugin for ExecPlugin {
fn name(&self) -> &str {
"exec"
}
fn execute(&self, action: &Action, ctx: &ExecCtx<'_>) -> Result<ExecStep, ExecError> {
match action {
Action::Exec(x) => crate::execute::fs_executor::fs_exec(x, ctx),
_ => Err(ExecError::ExecInvalid(format!(
"exec plugin dispatched with non-exec action `{}`",
action.name()
))),
}
}
}
#[cfg(feature = "plugin-inventory")]
inventory::submit!(PluginSubmission::new(|| Box::new(ExecPlugin)));
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_new_is_empty() {
let reg = Registry::new();
assert!(reg.is_empty());
assert_eq!(reg.len(), 0);
assert!(reg.get("symlink").is_none());
}
#[test]
fn registry_register_is_last_writer_wins() {
let mut reg = Registry::new();
reg.register(SymlinkPlugin);
reg.register(SymlinkPlugin);
assert_eq!(reg.len(), 1);
assert!(reg.get("symlink").is_some());
}
#[test]
fn bootstrap_registers_all_eight_builtins() {
let reg = Registry::bootstrap();
assert_eq!(reg.len(), 8);
for name in ["symlink", "unlink", "env", "mkdir", "rmdir", "require", "when", "exec"] {
let plugin = reg.get(name).unwrap_or_else(|| panic!("missing built-in `{name}`"));
assert_eq!(plugin.name(), name);
}
assert!(reg.get("unknown").is_none());
}
#[cfg(feature = "plugin-inventory")]
#[test]
fn bootstrap_from_inventory_registers_all_eight_builtins() {
let reg = Registry::bootstrap_from_inventory();
assert_eq!(reg.len(), 8);
for name in ["symlink", "unlink", "env", "mkdir", "rmdir", "require", "when", "exec"] {
let plugin = reg.get(name).unwrap_or_else(|| panic!("missing built-in `{name}`"));
assert_eq!(plugin.name(), name);
}
}
#[cfg(feature = "plugin-inventory")]
#[test]
fn register_from_inventory_on_empty_registry_produces_eight_entries() {
let mut reg = Registry::new();
assert!(reg.is_empty());
reg.register_from_inventory();
assert_eq!(reg.len(), 8);
for name in ["symlink", "unlink", "env", "mkdir", "rmdir", "require", "when", "exec"] {
assert!(reg.get(name).is_some(), "missing built-in `{name}`");
}
}
#[cfg(feature = "plugin-inventory")]
#[test]
fn register_from_inventory_twice_dedups_to_eight() {
let mut reg = Registry::new();
reg.register_from_inventory();
reg.register_from_inventory();
assert_eq!(reg.len(), 8);
}
}