dyn_inventory/
lib.rs

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