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}