dyn_inventory/lib.rs
1#![allow(clippy::needless_doctest_main)]
2/* START OF README CONTENTS */
3
4//! ## dyn-inventory
5//!
6//! [](https://crates.io/crates/dyn-inventory)
7//! [](https://docs.rs/dyn-inventory)
8//! 
9//! 
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}