dyn_inventory/
lib.rs

1#![allow(clippy::needless_doctest_main)]
2/* START OF README CONTENTS */
3
4//! ## dyn-inventory
5//!
6//! [![Crates.io Version](https://img.shields.io/crates/v/dyn-inventory?style=for-the-badge)](https://crates.io/crates/dyn-inventory)
7//! [![docs.rs](https://img.shields.io/docsrs/dyn-inventory?style=for-the-badge)](https://docs.rs/dyn-inventory)
8//! ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/joshua-auchincloss/dyn-inventory/test.yaml?style=for-the-badge&label=Tests)
9//! ![Crates.io License](https://img.shields.io/crates/l/dyn-inventory?style=for-the-badge)
10//!
11//! proc macro for building runtime plugin registries using dyn-compatible traits and the inventory crate.
12//!
13//! this crate generates code to:
14//!
15//! - register plugins that implement a trait object (`dyn trait`)
16//! - carry typed metadata alongside each plugin
17//! - collect and instantiate all registered plugins at runtime
18//!
19//! ```rust
20//! use dyn_inventory::dyn_inventory;
21//!
22//! pub trait MyPlugin {
23//!     fn handle(&self);
24//! }
25//!
26//! dyn_inventory! {
27//!     MyPlugin: Plugin<Handle> {
28//!         pub name: &'static str,
29//!         desc: &'static str,
30//!         handle: Handle
31//!     };
32//!     macro_name = new_plugin
33//! }
34//!
35//! mod my_plugin {
36//!     use crate::{MyPlugin, Plugin};
37//!
38//!     new_plugin! {
39//!         Handle {
40//!             name = "my plugin for abc-framework";
41//!             desc = "implements my plugin by doing xyz";
42//!         }
43//!     }
44//!
45//!     impl MyPlugin for Handle {
46//!         fn handle(&self) {
47//!             println!("MyPlugin was used");
48//!         }
49//!     }
50//! }
51//!
52//! fn main() {
53//!     let collected = PluginCollector::new();
54//!     for plugin in &collected.plugins {
55//!         plugin.handle.handle();
56//!         // >> "MyPlugin was used"
57//!     }
58//! }
59//! ```
60//!
61//! ## Why dyn-compatible traits
62//!
63//! the plugins produced by this crate are stored and used as `Box<dyn Trait>`. when used with [`inventory`](https://crates.io/crates/inventory), this allows for new plugin registries to be developed for decentralized libraries and frameworks.
64//!
65//! ## Quick Start
66//!
67//! 1. add dependencies:
68//!
69//! ```toml
70//! [dependencies]
71//! inventory = "0.3"
72//! dyn-inventory = "0.1"
73//! ```
74//!
75//! 2. define a trait that is dyn-compatible:
76//!
77//! ```rust
78//! pub trait Greeter {
79//!     fn greet(&self) -> String;
80//! }
81//! ```
82//!
83//! 3. declare your inventory using the `dyn_inventory!` proc macro:
84//!
85//! ```rust
86//! pub trait Greeter {
87//!     fn greet(&self) -> String;
88//! }
89//!
90//! dyn_inventory::dyn_inventory!(
91//!     Greeter: GreeterPlugin<T> {
92//!         name: &'static str,
93//!         version: u32,
94//!         t: T,
95//!     };
96//!     // optional extra params, see below
97//!     macro_name = register_greeter,
98//! );
99//! ```
100//!
101//!
102//!  what this generates:
103//!
104//!  - a struct `GreeterPlugin<T>` with the fields you declared
105//!  - an implementation `impl<T> GreeterPlugin<T> { pub const fn new(...) -> Self }`
106//!  - an inventory registration type `inventory::collect!(GreeterPlugin<fn() -> Box<dyn Greeter>>)`
107//!  - a macro `register_greeter!` (snake_case of the struct name by default) to register plugins
108//!  - a collector `GreeterPluginCollector` that has `plugin` of type `Vec<GreeterPlugin<Box<dyn Greeter>>>`
109//!
110//! 4. register a plugin somewhere in your code (could be another crate that depends on your trait crate):
111//!
112//! ```rust,ignore
113//! use crate::{Greeter, register_greeter};
114//!
115//! // this expands to a unit struct named `MyGreeter` and registers it into the inventory
116//! register_greeter! {
117//!     pub MyGreeter {
118//!         name = "hello";
119//!         version = 1;
120//!     }
121//! }
122//!
123//! // you implement the trait for the generated unit struct
124//! impl Greeter for MyGreeter {
125//!     fn greet(&self) -> String { "hi".to_string() }
126//! }
127//! ```
128//!
129//! 5. collect your plugins at runtime:
130//!
131//! ```rust,ignore
132//! let collected = GreeterPluginCollector::new();
133//! for plugin in collected.plugins {
134//!     // `plugin.t` is now a `Box<dyn Greeter>`; other fields are your metadata
135//!     println!("{} -> {}", plugin.name, plugin.t.greet());
136//! }
137//! ```
138//!
139//! ## Macro Syntax
140//!
141//! ```rust,ignore
142//! use dyn_inventory::dyn_inventory;
143//!
144//! dyn_inventory!(
145//!     TraitName: StructName<Handle> {
146//!         // exactly one field must have type `Handle`.
147//!         // the field whose type equals the generic parameter (`Generic`) is treated as the plugin “handle”.
148//!         // internally during registration this field is filled with a function pointer `fn() -> Box<dyn TraitName>`, and the collector converts it to `Box<dyn TraitName>` by calling it.
149//!         handle: Handle,
150//!
151//!         // optional visibity specifier
152//!         // any number of metadata fields are preserved
153//!         pub|pub(crate)? field_name: &'static str,
154//!         pub other_field: usize,
155//!     };
156//!     // optional, comma-separated extra params
157//!     macro_name = some_ident,
158//!     handle_name = SomeIdent,
159//! );
160//! ```
161//!
162//! ## Extra Parameters
163//!
164//! two extra params are currently accepted:
165//!
166//! - `macro_name = ident`
167//!   - sets the name of the generated registration macro. by default it is the snake_case of `StructName` (for example, `GreeterPlugin` -> `greeter_plugin`).
168//! - `handle_name = Ident`
169//!   - sets the name of the generated handle which implements your plugin. (for example, `handle_name = TheImpl` requires `impl GreeterPlugin for TheImpl`)
170//!
171//! ## Advanced: customizing collection
172//!
173//! the collector type is named by appending `Collector` to your struct name. it exposes:
174//!
175//! - `new()` -> builds the collection without modification
176//! - `new_with(|item: &mut StructName<fn() -> Box<dyn TraitName>>| {...})` -> allows you to mutate the raw entries before they are instantiated into `Box<dyn TraitName>`
177//!
178//! ## Constraints
179//!
180//! - your trait must be object-safe (dyn-compatible)
181//! - the `inventory` crate must be linked into the final binary; ensure your plugin crates depend on `inventory` and your main binary pulls in the crates that perform registrations
182/* END OF README CONTENTS */
183mod args;
184use proc_macro::TokenStream;
185use proc_macro2::Span;
186use syn::Ident;
187
188use crate::args::Args;
189
190#[proc_macro]
191pub fn dyn_inventory(tok: TokenStream) -> TokenStream {
192    let args: Args = match syn::parse(tok) {
193        Ok(parse) => parse,
194        Err(e) => {
195            return e.into_compile_error().into();
196        },
197    };
198
199    let trt = &args.trait_name;
200    let strct = &args.struct_name;
201    let handle_name = &args.handle_name;
202    let generic = &args.generic_param;
203    let macro_name = &args.macro_name;
204    let generic_str = generic.to_string();
205
206    let fields: proc_macro2::TokenStream = args.collect_fields(
207        |vis, name, ty| {
208            quote::quote! {
209                #vis #name: #ty,
210            }
211        },
212        &generic_str,
213    );
214
215    let fields_as_args = args.collect_fields(
216        |_, name, ty| {
217            quote::quote! {
218                #name: #ty,
219            }
220        },
221        &generic_str,
222    );
223
224    let fields_as_assign = args.collect_fields(
225        |_, name, _| {
226            quote::quote! {
227                #name: #name,
228            }
229        },
230        &generic_str,
231    );
232
233    let fields_as_expr = args.collect_fields_or_generic(
234        |_, name, _| {
235            quote::quote! {
236                #name = $#name: expr;
237            }
238        },
239        |_, _, _| Default::default(),
240        generic_str.clone(),
241    );
242
243    let fields_as_expr_call = args.collect_fields_or_generic(
244        |_, name, _| {
245            quote::quote! {
246                #name = $#name;
247            }
248        },
249        |_, _, _| Default::default(),
250        generic_str.clone(),
251    );
252
253    let fields_as_expr_assign = args.collect_fields_or_generic(
254        |_, name, _| {
255            quote::quote! {
256                $#name,
257            }
258        },
259        |_, _, _| Default::default(),
260        generic_str.clone(),
261    );
262
263    let field_as_reassign = args.collect_fields_or_generic(
264        |_, name, _| {
265            quote::quote! {
266                plugin.#name,
267            }
268        },
269        |_, name, _| {
270            quote::quote! {
271                (plugin.#name)()
272            }
273        },
274        generic_str.clone(),
275    );
276
277    let struct_def = quote::quote! {
278        #[derive(Clone)]
279        pub struct #strct<#generic> {
280            #fields
281        }
282    };
283
284    let new = quote::quote! {
285        impl<#generic> #strct<#generic> {
286            pub const fn new(
287                #fields_as_args
288            ) -> Self {
289                Self {
290                    #fields_as_assign
291                }
292            }
293        }
294    };
295
296    let collect = quote::quote! {
297        inventory::collect!{
298            #strct<fn() -> Box<dyn #trt>>
299        }
300    };
301
302    let macro_def = quote::quote! {
303        #[macro_export]
304        macro_rules! #macro_name {
305            (
306                #fields_as_expr
307            ) => {
308                #macro_name!{
309                    pub #handle_name {
310                        #fields_as_expr_call
311                    }
312                }
313            };
314            (
315                $vis: vis $plugin: ident {
316                    #fields_as_expr
317                }
318            ) => {
319                struct $plugin;
320
321                fn get() -> Box<dyn #trt> {
322                    Box::new($plugin)
323                }
324
325                inventory::submit!{
326                    #strct::<fn() -> Box<dyn #trt>>::new(
327                        #fields_as_expr_assign
328                        get
329                    )
330                }
331            }
332        }
333    };
334
335    let plugin_collector = Ident::new(&format!("{}Collector", strct), Span::call_site());
336
337    let plugin_collector = quote::quote! {
338        pub struct #plugin_collector{
339            pub plugins: Vec<#strct<Box<dyn #trt>>>
340        }
341
342        impl #plugin_collector {
343            pub fn new() -> Self {
344                Self::new_with(|_| {})
345            }
346
347            pub fn new_with<F: Fn(&mut #strct<fn() -> Box<dyn #trt>>)>(with: F) -> Self {
348                let mut plugins = vec![];
349                for plugin in inventory::iter::<#strct<fn() -> Box<dyn #trt>>> {
350                    let mut plugin = plugin.clone();
351                    with(&mut plugin);
352                    plugins.push(#strct::new(
353                        #field_as_reassign
354                    ));
355                }
356                Self { plugins }
357            }
358        }
359    };
360
361    let out = quote::quote! {
362        #struct_def
363
364        #new
365
366        #collect
367
368        #macro_def
369
370        #plugin_collector
371    };
372
373    // panic!("{}", out.to_string());
374
375    out.into()
376}