Skip to main content

rich_plugin_api/
linked.rs

1//! Compile-time plugins: registered with [`export_plugin!`](crate::export_plugin)
2//! and collected at link time.
3//!
4//! A plugin crate writes `rich_plugin_api::export_plugin!(MyPlugin);` once.
5//! Every such plugin in the final binary is listed by [`linked_plugins`], and
6//! a host (`rs-rich-ext`'s `ExtensionRegistry::with_linked_plugins`) adds them
7//! in a fixed order: sorted by plugin id, with a duplicate id an error.
8//!
9//! Collection uses [`inventory`], which needs no proc macro and works on every
10//! platform `rich` supports. One caveat, shared by every link-time scheme: a
11//! crate the binary depends on but never names may be dropped by the linker.
12//! Name it once (`use my_plugin as _;`) to keep its plugins.
13
14use crate::Plugin;
15
16/// One plugin submitted with [`export_plugin!`](crate::export_plugin).
17pub struct LinkedPlugin {
18    make: fn() -> Box<dyn Plugin>,
19    module: &'static str,
20}
21
22impl LinkedPlugin {
23    /// Used by [`export_plugin!`](crate::export_plugin); not called directly.
24    #[doc(hidden)]
25    pub const fn new(make: fn() -> Box<dyn Plugin>, module: &'static str) -> Self {
26        LinkedPlugin { make, module }
27    }
28
29    /// A fresh instance of the plugin.
30    pub fn plugin(&self) -> Box<dyn Plugin> {
31        (self.make)()
32    }
33
34    /// The module that exported it (`module_path!()`), for error messages.
35    pub fn module(&self) -> &'static str {
36        self.module
37    }
38}
39
40inventory::collect!(LinkedPlugin);
41
42/// Every plugin exported with [`export_plugin!`](crate::export_plugin) in this
43/// binary, in no particular order (a host sorts them by id).
44pub fn linked_plugins() -> impl Iterator<Item = &'static LinkedPlugin> {
45    inventory::iter::<LinkedPlugin>.into_iter()
46}
47
48#[doc(hidden)]
49pub mod __private {
50    pub use inventory;
51}
52
53/// Register a plugin for link-time collection.
54///
55/// The argument is an expression that makes the plugin, evaluated each time
56/// a host asks for it. It must not capture anything:
57///
58/// ```
59/// use rich_plugin_api::{Plugin, PluginError, PluginMetadata, PluginRegistrar};
60///
61/// struct Hello;
62/// impl Plugin for Hello {
63///     fn metadata(&self) -> PluginMetadata {
64///         PluginMetadata::new("hello", "Hello", "1.0.0")
65///     }
66///     fn register(&self, _: &mut dyn PluginRegistrar) -> Result<(), PluginError> {
67///         Ok(())
68///     }
69/// }
70///
71/// rich_plugin_api::export_plugin!(Hello);
72///
73/// # fn main() {
74/// assert!(rich_plugin_api::linked_plugins().any(|p| p.plugin().metadata().id == "hello"));
75/// # }
76/// ```
77#[macro_export]
78macro_rules! export_plugin {
79    ($plugin:expr) => {
80        $crate::__private::inventory::submit! {
81            $crate::LinkedPlugin::new(
82                || -> ::std::boxed::Box<dyn $crate::Plugin> { ::std::boxed::Box::new($plugin) },
83                ::core::module_path!(),
84            )
85        }
86    };
87}