steckrs/lib.rs
1//! # steckrs
2//!
3//! A lightweight, trait-based plugin system for Rust applications and libraries.
4//!
5//! ## What is steckrs?
6//!
7//! "steckrs" is a wordplay combining the German word "Stecker" (meaning "plug" or "connector") and
8//! the Rust file extension (.rs). The library provides a flexible, type-safe plugin architecture
9//! for Rust applications, allowing developers to:
10//!
11//! - Define extension points in their applications
12//! - Create plugins that integrate with these extension points
13//! - Dynamically manage plugins (loading, enabling, disabling, unloading)
14//! - Register and invoke hooks with proper type safety
15//!
16//! ## Core Concepts
17//!
18//! ### Extension Points
19//!
20//! [Extension points](crate::hook::ExtensionPoint) define interfaces where plugins can add functionality. Each extension point:
21//! - Is defined as a trait that plugins implement
22//! - Specifies the contract that plugins must fulfill
23//! - Provides type-safe interaction between the core application and plugins
24//!
25//! ### Plugins
26//!
27//! [Plugins](Plugin) are self-contained modules that implement functionality for extension points.
28//! Each plugin:
29//! - Has a unique identifier
30//! - Can be enabled or disabled at runtime
31//! - Can register multiple hooks to different extension points
32//! - Has lifecycle methods ([`on_load`](Plugin::on_load), [`on_unload`](Plugin::on_unload))
33//!
34//! ### Hooks
35//!
36//! [Hooks](crate::hook::Hook) are implementations of extension points that plugins register. They:
37//! - Implement the trait defined by an extension point
38//! - Are invoked when the application calls that extension point
39//! - Can be uniquely identified by their plugin ID, extension point, and optional discriminator
40//!
41//! ## Logs
42//!
43//! This library logs certain events with the [`tracing`] library.
44//!
45//! ## Usage Example
46//!
47//! Here's a simple example of how to use steckrs to create a plugin-enabled application:
48//!
49//! ```rust
50//! use steckrs::{extension_point, simple_plugin, PluginManager};
51//!
52//! // Define an extension point
53//! extension_point!(
54//! GreeterExtension: GreeterTrait;
55//! fn greet(&self, name: &str) -> String;
56//! );
57//!
58//! // Create a plugin
59//! simple_plugin!(
60//! HelloPlugin,
61//! "hello_plugin",
62//! "A simple greeting plugin",
63//! hooks: [(GreeterExtension, EnglishGreeter)]
64//! );
65//!
66//! // Implement a hook
67//! struct EnglishGreeter;
68//! impl GreeterTrait for EnglishGreeter {
69//! fn greet(&self, name: &str) -> String {
70//! format!("Hello, {}!", name)
71//! }
72//! }
73//!
74//! // Create plugin manager
75//! let mut plugin_manager = PluginManager::new();
76//!
77//! // Load and enable the plugin
78//! plugin_manager.load_plugin(Box::new(HelloPlugin::new())).unwrap();
79//! plugin_manager.enable_plugin(HelloPlugin::ID).unwrap();
80//!
81//! // Get all enabled hooks (plugins could be disabled)
82//! let hooks = plugin_manager.get_enabled_hooks_by_ep::<GreeterExtension>();
83//!
84//! // execute all hooks relevant for this extension point
85//! for (_id, hook) in hooks {
86//! println!("{}", hook.inner().greet("World"));
87//! }
88//! ```
89//!
90//! ## Macros
91//!
92//! steckrs provides several convenience macros to reduce boilerplate:
93//!
94//! - [`extension_point!`] - Defines an extension point and its associated trait
95//! - [`simple_plugin!`] - Creates a simple plugin with minimal boilerplate
96//! - [`register_hook!`] - Registers a hook with the hook registry
97//!
98//! Note that [`register_hook!`] is not needed if you generate your plugin with [`simple_plugin!`].
99//!
100//! ## Advanced Usage
101//!
102//! For more complex scenarios, you can implement the [`Plugin`] trait directly,
103//! allowing for more customized plugin behavior and state management.
104
105#![warn(missing_docs)]
106#![warn(clippy::missing_errors_doc)]
107#![warn(clippy::missing_panics_doc)]
108#![warn(clippy::missing_safety_doc)]
109#![warn(clippy::panic)]
110#![warn(clippy::todo)]
111#![warn(clippy::pedantic)]
112#![warn(clippy::all)]
113#![warn(clippy::empty_docs)]
114
115use std::any::Any;
116use std::collections::HashMap;
117use std::fmt::Debug;
118
119pub mod error;
120pub mod hook;
121pub mod macros;
122
123use tracing::{error, warn};
124
125use self::error::{PluginError, PluginResult};
126use self::hook::{ExtensionPoint, HookRegistry};
127
128/// Plugin identifier type.
129///
130/// Every plugin must have a unique identifier. This type is used to identify plugins within
131/// the [steckrs](crate) system. It's implemented as a static string reference for efficiency and
132/// simplicity.
133///
134/// See also [`PluginIDOwned`], which can be owned and provides serialization support, if you need
135/// that.
136///
137/// # Examples
138///
139/// ```
140/// let id: steckrs::PluginID = "hello_world_plugin";
141/// ```
142pub type PluginID = &'static str;
143
144/// An owned version of [`PluginID`] that can be owned and has optional serialization support with
145/// [serde].
146///
147/// This type wraps a static string reference and provides implementations for
148/// conversion to/from [`PluginID`], as well as serialization support when the
149/// [`serde`] feature is enabled.
150///
151/// This type is particularly useful when working with serialization frameworks,
152/// as it allows plugin identifiers to be properly serialized and deserialized.
153///
154/// If you deserialize a string into this datastructure, please note that this uses an internal
155/// leak mechanism ([`String::leak`]) to make sure that the actual data of the plugin id will always exist (making it
156/// `'static`). That means that you may take up more memory than expected if you deserialize huge
157/// amoungs of plugin ids.
158///
159/// # Examples
160///
161/// ```
162/// use steckrs::{PluginIDOwned, PluginID};
163///
164/// let plugin_id: PluginID = "my_plugin";
165///
166/// // Create from owned id
167/// let id = PluginIDOwned::from(plugin_id);
168///
169/// // Convert back to a PluginID
170/// let plugin_id2: PluginID = id.into();
171///
172/// assert_eq!(plugin_id, plugin_id2);
173/// ```
174///
175/// # Serialization
176///
177/// When the `serde` feature is enabled, this type implements [`Serialize`](serde::Serialize)
178/// and [`Deserialize`](serde::Deserialize). Note that deserialization involves a memory leak,
179/// as the string is converted to a `&'static str` by leaking memory (to make sure it is always
180/// existing in memory).
181///
182/// The leaking is using safe rust with [`String::leak`].
183#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize))]
185pub struct PluginIDOwned {
186 inner: &'static str,
187}
188
189impl From<PluginID> for PluginIDOwned {
190 fn from(value: PluginID) -> Self {
191 Self { inner: value }
192 }
193}
194
195impl From<PluginIDOwned> for PluginID {
196 fn from(value: PluginIDOwned) -> Self {
197 value.inner
198 }
199}
200
201impl std::fmt::Display for PluginIDOwned {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 std::fmt::Display::fmt(&self.inner, f)
204 }
205}
206
207#[cfg(feature = "serde")]
208impl<'de> serde::Deserialize<'de> for PluginIDOwned {
209 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210 where
211 D: serde::Deserializer<'de>,
212 {
213 struct PluginIDVisitor;
214
215 impl serde::de::Visitor<'_> for PluginIDVisitor {
216 type Value = PluginIDOwned;
217
218 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
219 formatter.write_str("a string")
220 }
221
222 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
223 where
224 E: serde::de::Error,
225 {
226 Ok(PluginIDOwned {
227 // Convert the string to a 'static str by leaking memory
228 inner: value.to_string().leak(),
229 })
230 }
231 }
232
233 deserializer.deserialize_str(PluginIDVisitor)
234 }
235}
236
237/// Plugin trait that must be implemented by all plugins.
238///
239/// This trait defines the interface for plugin lifecycle management,
240/// including loading, enabling, disabling, and unloading operations.
241///
242/// # Macros
243///
244/// Most users will find the [`simple_plugin!`] macro sufficient.
245///
246///
247/// ```
248/// # use steckrs::{extension_point, simple_plugin, PluginManager};
249/// # extension_point!(
250/// # GreeterExtension: GreeterTrait;
251/// # fn greet(&self, name: &str) -> String;
252/// # );
253/// #
254/// # struct EnglishGreeter;
255/// # impl GreeterTrait for EnglishGreeter {
256/// # fn greet(&self, name: &str) -> String {
257/// # format!("Hello, {}!", name)
258/// # }
259/// # }
260/// #
261/// simple_plugin!(
262/// HelloPlugin,
263/// "hello_plugin",
264/// "A simple greeting plugin",
265/// hooks: [(GreeterExtension, EnglishGreeter)]
266/// );
267/// ```
268///
269/// # Examples
270///
271/// ```
272/// use steckrs::{Plugin, error::PluginResult, hook::HookRegistry};
273///
274/// #[derive(Debug)]
275/// struct MyPlugin {
276/// enabled: bool,
277/// }
278///
279/// impl Plugin for MyPlugin {
280/// fn id(&self) -> steckrs::PluginID {
281/// // recommendation: add an associated constant for the ID
282/// "my_plugin"
283/// }
284///
285/// fn description(&self) -> &str {
286/// // recommendation: add an associated constant for the DESCRIPTION
287/// "A custom plugin implementation"
288/// }
289///
290/// fn is_enabled(&self) -> bool {
291/// self.enabled
292/// }
293///
294/// fn enable(&mut self) {
295/// self.enabled = true;
296/// }
297///
298/// fn disable(&mut self) {
299/// self.enabled = false;
300/// }
301///
302/// fn register_hooks(&self, registry: &mut HookRegistry) -> PluginResult<()> {
303/// // Register hooks here
304/// Ok(())
305/// }
306///
307/// // optionally define on_load and on_unload
308/// }
309/// ```
310pub trait Plugin: Any + Send + Sync + Debug {
311 /// Returns the unique identifier for this plugin.
312 ///
313 /// The ID must be unique across all loaded plugins.
314 fn id(&self) -> PluginID;
315
316 /// Returns a human-readable description of the plugin.
317 fn description(&self) -> &str;
318
319 /// Returns whether the plugin is currently enabled.
320 fn is_enabled(&self) -> bool;
321
322 /// Enables the plugin, allowing its hooks to be used.
323 fn enable(&mut self);
324
325 /// Disables the plugin, preventing its hooks from being used.
326 fn disable(&mut self);
327
328 /// Registers this plugin's [Hooks](crate::hook::Hook) with the [`HookRegistry`].
329 ///
330 /// This method is called during plugin loading, and should register
331 /// all hooks that the plugin provides.
332 ///
333 /// # Errors
334 ///
335 /// Returns a `PluginError` if hook registration fails./
336 fn register_hooks(&self, registry: &mut HookRegistry) -> PluginResult<()>;
337
338 /// Called when the plugin is loaded.
339 ///
340 /// Provides an opportunity to perform initialization that should happen
341 /// when the plugin is first loaded, before hooks are used.
342 ///
343 /// This function is always called after [`register_hooks`](Plugin::register_hooks).
344 ///
345 /// # Errors
346 ///
347 /// Returns a [`PluginError`] if loading fails.
348 fn on_load(&mut self) -> PluginResult<()> {
349 Ok(())
350 }
351
352 /// Called when the plugin is unloaded.
353 ///
354 /// Provides an opportunity to perform cleanup before the plugin is removed.
355 ///
356 /// # Errors
357 ///
358 /// Returns a [`PluginError`] if the unloading cleanup fails.
359 fn on_unload(&mut self) -> PluginResult<()> {
360 Ok(())
361 }
362}
363
364/// Manages plugin loading, execution, and lifecycle.
365///
366/// The [`PluginManager`] is the core component of the steckrs plugin system,
367/// responsible for:
368/// - Loading and unloading plugins
369/// - Enabling and disabling plugins
370/// - Maintaining the hook registry
371/// - Tracking loaded plugins
372///
373/// # Examples
374///
375/// ```
376/// use steckrs::{PluginManager, simple_plugin, extension_point};
377///
378/// // Define extension point
379/// extension_point!(
380/// ExampleExt: ExampleTrait;
381/// fn do_something(&self) -> &'static str;
382/// );
383///
384/// // Define plugin
385/// simple_plugin!(
386/// ExamplePlugin,
387/// "example_plugin",
388/// "An example plugin",
389/// hooks: [(ExampleExt, ExampleHook)]
390/// );
391///
392/// // Hook implementation
393/// struct ExampleHook;
394/// impl ExampleTrait for ExampleHook {
395/// fn do_something(&self) -> &'static str {
396/// "I did something!"
397/// }
398/// }
399///
400/// // Plugin management
401/// let mut manager = PluginManager::new();
402/// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
403/// manager.enable_plugin(ExamplePlugin::ID).unwrap();
404///
405/// // Use plugin hooks
406/// // Get all enabled hooks (plugins could be disabled)
407/// let hooks = manager.get_enabled_hooks_by_ep::<ExampleExt>();
408/// for (_id, hook) in hooks {
409/// assert_eq!(hook.inner().do_something(), "I did something!");
410/// }
411/// ```
412#[derive(Debug, Default)]
413pub struct PluginManager {
414 plugins: HashMap<PluginID, Box<dyn Plugin>>,
415 hook_registry: HookRegistry,
416}
417
418impl PluginManager {
419 /// Creates a new empty plugin manager.
420 ///
421 /// # Examples
422 ///
423 /// ```
424 /// use steckrs::PluginManager;
425 ///
426 /// let manager = PluginManager::new();
427 /// assert_eq!(manager.plugin_ids().len(), 0);
428 /// ```
429 #[must_use]
430 pub fn new() -> Self {
431 Self {
432 plugins: HashMap::new(),
433 hook_registry: HookRegistry::new(),
434 }
435 }
436
437 /// Creates a new plugin manager with an existing hook registry.
438 ///
439 /// This allows sharing a hook registry between multiple plugin managers,
440 /// which can be useful for complex applications.
441 ///
442 /// # Examples
443 ///
444 /// ```
445 /// use steckrs::{PluginManager, hook::HookRegistry};
446 ///
447 /// let registry = HookRegistry::new();
448 /// let manager = PluginManager::with_registry(registry);
449 /// ```
450 #[must_use]
451 pub fn with_registry(hook_registry: HookRegistry) -> Self {
452 Self {
453 plugins: HashMap::new(),
454 hook_registry,
455 }
456 }
457
458 /// Returns a reference to the hook registry.
459 ///
460 /// The hook registry contains all registered hooks from loaded plugins.
461 #[must_use]
462 pub fn hook_registry(&self) -> &HookRegistry {
463 &self.hook_registry
464 }
465
466 /// Returns a mutable reference to the hook registry.
467 ///
468 /// This can be used to directly manipulate the hook registry if needed.
469 #[must_use]
470 pub fn hook_registry_mut(&mut self) -> &mut HookRegistry {
471 &mut self.hook_registry
472 }
473
474 /// Loads a plugin into the plugin manager.
475 ///
476 /// This will:
477 /// 1. Register the plugin's hooks in the hook registry
478 /// 2. Call the plugin's `on_load` method
479 /// 3. Store the plugin in the manager
480 ///
481 /// # Errors
482 ///
483 /// Returns a `PluginError` if:
484 /// - A plugin with the same ID is already loaded
485 /// - The plugin's [`on_load`](Plugin::register_hooks) method fails
486 /// - The plugin's [`on_load`](Plugin::on_load) method fails
487 ///
488 /// If any of the steps fail, this function will try to unload the half-loaded plugin again,
489 /// using [`unload_plugin`](Self::unload_plugin).
490 ///
491 /// # Panics
492 ///
493 /// If loading of the plugin and then unloading the half-loaded plugin both fail, this function
494 /// will panic.
495 ///
496 /// # Examples
497 ///
498 /// ```
499 /// use steckrs::{PluginManager, simple_plugin};
500 ///
501 /// simple_plugin!(
502 /// ExamplePlugin,
503 /// "example_plugin",
504 /// "An example plugin",
505 /// hooks: []
506 /// );
507 ///
508 /// let mut manager = PluginManager::new();
509 /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
510 /// assert!(manager.get_plugin("example_plugin").is_some());
511 /// ```
512 pub fn load_plugin(&mut self, mut plugin: Box<dyn Plugin>) -> PluginResult<()> {
513 let id = plugin.id();
514 if self.plugins.contains_key(id) {
515 return Err(error::PluginError::AlreadyLoaded(id.into()));
516 }
517
518 // register the hooks
519 if let Err(e) = plugin.register_hooks(self.hook_registry_mut()) {
520 self.handle_error_during_load(&e, id);
521 return Err(e);
522 }
523 // Load the plugin
524 if let Err(e) = plugin.on_load() {
525 self.handle_error_during_load(&e, id);
526 return Err(e);
527 }
528
529 // Store the plugin
530 self.plugins.insert(id, plugin);
531
532 Ok(())
533 }
534
535 /// Internal helper to handle errors during plugin loading.
536 ///
537 /// If a plugin fails during loading, this will attempt to clean up
538 /// by unloading the plugin.
539 fn handle_error_during_load(&mut self, e: &PluginError, plugin_id: PluginID) {
540 error!("Could not register hooks of plugin {plugin_id}: {e}");
541 warn!("Trying to unload the plugin again... Will crash if this fails");
542 self.unload_plugin(plugin_id)
543 .expect("Could not unload bad plugin again");
544 }
545
546 /// Unloads a plugin by ID.
547 ///
548 /// This will:
549 /// 1. Call the plugin's `on_unload` method for cleanup
550 /// 2. Remove all hooks registered by the plugin
551 /// 3. Remove the plugin from the manager
552 ///
553 /// # Errors
554 ///
555 /// Returns a [`PluginError`] if:
556 /// - The plugin's [`on_unload`](Plugin::on_unload) method fails
557 ///
558 /// # Examples
559 ///
560 /// ```
561 /// use steckrs::{PluginManager, simple_plugin};
562 ///
563 /// simple_plugin!(
564 /// ExamplePlugin,
565 /// "example_plugin",
566 /// "An example plugin",
567 /// hooks: []
568 /// );
569 ///
570 /// let mut manager = PluginManager::new();
571 /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
572 /// manager.unload_plugin("example_plugin").unwrap();
573 /// assert!(manager.get_plugin("example_plugin").is_none());
574 /// ```
575 pub fn unload_plugin(&mut self, id: PluginID) -> PluginResult<()> {
576 if let Some(mut plugin) = self.plugins.remove(id) {
577 // Call on_unload for cleanup
578 plugin.on_unload()?;
579
580 // Remove all hooks registered by this plugin
581 self.hook_registry.deregister_hooks_for_plugin(id);
582 }
583 Ok(())
584 }
585
586 /// Gets a reference to a plugin by ID.
587 ///
588 /// # Examples
589 ///
590 /// ```
591 /// use steckrs::{PluginManager, simple_plugin};
592 ///
593 /// simple_plugin!(
594 /// ExamplePlugin,
595 /// "example_plugin",
596 /// "An example plugin",
597 /// hooks: []
598 /// );
599 ///
600 /// let mut manager = PluginManager::new();
601 /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
602 ///
603 /// let plugin = manager.get_plugin("example_plugin");
604 /// assert!(plugin.is_some());
605 /// assert_eq!(plugin.unwrap().id(), "example_plugin");
606 /// ```
607 #[must_use]
608 pub fn get_plugin(&self, id: PluginID) -> Option<&dyn Plugin> {
609 self.plugins.get(id).map(std::convert::AsRef::as_ref)
610 }
611
612 /// Gets a mutable reference to a plugin by ID.
613 ///
614 /// This can be used to modify a plugin's state after it's been loaded.
615 ///
616 /// # Examples
617 ///
618 /// ```
619 /// use steckrs::{PluginManager, simple_plugin};
620 ///
621 /// simple_plugin!(
622 /// ExamplePlugin,
623 /// "example_plugin",
624 /// "An example plugin",
625 /// hooks: []
626 /// );
627 ///
628 /// let mut manager = PluginManager::new();
629 /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
630 ///
631 /// let plugin = manager.get_plugin_mut("example_plugin");
632 /// assert!(plugin.is_some());
633 /// ```
634 #[must_use]
635 pub fn get_plugin_mut(&mut self, id: PluginID) -> Option<&mut dyn Plugin> {
636 self.plugins.get_mut(id).map(std::convert::AsMut::as_mut)
637 }
638
639 /// Gets all plugin IDs.
640 ///
641 /// # Examples
642 ///
643 /// ```
644 /// use steckrs::{PluginManager, simple_plugin};
645 ///
646 /// simple_plugin!(
647 /// Plugin1,
648 /// "plugin1",
649 /// "First plugin",
650 /// hooks: []
651 /// );
652 ///
653 /// simple_plugin!(
654 /// Plugin2,
655 /// "plugin2",
656 /// "Second plugin",
657 /// hooks: []
658 /// );
659 ///
660 /// let mut manager = PluginManager::new();
661 /// manager.load_plugin(Box::new(Plugin1::new())).unwrap();
662 /// manager.load_plugin(Box::new(Plugin2::new())).unwrap();
663 ///
664 /// let ids = manager.plugin_ids();
665 /// assert_eq!(ids.len(), 2);
666 /// assert!(ids.contains(&"plugin1"));
667 /// assert!(ids.contains(&"plugin2"));
668 /// ```
669 #[must_use]
670 pub fn plugin_ids(&self) -> Vec<PluginID> {
671 self.plugins.keys().copied().collect()
672 }
673
674 /// Gets all plugins.
675 ///
676 /// # Examples
677 ///
678 /// ```
679 /// use steckrs::{PluginManager, simple_plugin};
680 ///
681 /// simple_plugin!(
682 /// Plugin1,
683 /// "plugin1",
684 /// "First plugin",
685 /// hooks: []
686 /// );
687 ///
688 /// let mut manager = PluginManager::new();
689 /// manager.load_plugin(Box::new(Plugin1::new())).unwrap();
690 ///
691 /// let plugins = manager.plugins();
692 /// assert_eq!(plugins.len(), 1);
693 /// assert_eq!(plugins[0].id(), "plugin1");
694 /// ```
695 #[must_use]
696 pub fn plugins(&self) -> Vec<&dyn Plugin> {
697 self.plugins
698 .values()
699 .map(std::convert::AsRef::as_ref)
700 .collect()
701 }
702
703 /// Gets all enabled plugins.
704 ///
705 /// # Examples
706 ///
707 /// ```
708 /// use steckrs::{PluginManager, simple_plugin};
709 ///
710 /// simple_plugin!(
711 /// Plugin1,
712 /// "plugin1",
713 /// "First plugin",
714 /// hooks: []
715 /// );
716 ///
717 /// simple_plugin!(
718 /// Plugin2,
719 /// "plugin2",
720 /// "Second plugin",
721 /// hooks: []
722 /// );
723 ///
724 /// let mut manager = PluginManager::new();
725 /// manager.load_plugin(Box::new(Plugin1::new())).unwrap();
726 /// manager.load_plugin(Box::new(Plugin2::new())).unwrap();
727 /// manager.enable_plugin("plugin1").unwrap();
728 ///
729 /// let enabled = manager.enabled_plugins();
730 /// assert_eq!(enabled.len(), 1);
731 /// assert_eq!(enabled[0].id(), "plugin1");
732 /// ```
733 #[must_use]
734 pub fn enabled_plugins(&self) -> Vec<&dyn Plugin> {
735 self.plugins
736 .values()
737 .filter(|p| p.is_enabled())
738 .map(std::convert::AsRef::as_ref)
739 .collect()
740 }
741
742 /// Quickly check if a [`Plugin`] with a specific [`PluginID`] is enabled.
743 ///
744 /// This will return [`None`] if the [`Plugin`] with that [`PluginID`] was not found, otherwise
745 /// `Some(enabled)`, where `enabled` is gotten with [`Plugin::is_enabled`].
746 ///
747 /// # Examples
748 ///
749 /// ```
750 /// use steckrs::{PluginManager, simple_plugin};
751 ///
752 /// simple_plugin!(
753 /// Plugin,
754 /// "plugin",
755 /// "Some plugin",
756 /// hooks: []
757 /// );
758 ///
759 /// let mut manager = PluginManager::new();
760 /// manager.load_plugin(Box::new(Plugin::new())).unwrap();
761 /// manager.enable_plugin("plugin").unwrap();
762 ///
763 /// assert_eq!(manager.plugin_is_enabled("plugin"), Some(true));
764 /// assert_eq!(manager.plugin_is_enabled("nope"), None);
765 ///
766 /// manager.disable_plugin("plugin").unwrap();
767 ///
768 /// assert_eq!(manager.plugin_is_enabled("plugin"), Some(false));
769 /// ```
770 #[inline]
771 #[must_use]
772 pub fn plugin_is_enabled(&self, id: PluginID) -> Option<bool> {
773 Some(self.plugins.get(id)?.is_enabled())
774 }
775
776 /// Enables a plugin by ID.
777 ///
778 /// Note that plugins are disabled by default
779 ///
780 /// # Errors
781 ///
782 /// Returns a [`PluginError::NotFound`] if no plugin with the given ID is loaded.
783 ///
784 /// # Examples
785 ///
786 /// ```
787 /// use steckrs::{PluginManager, simple_plugin};
788 ///
789 /// simple_plugin!(
790 /// ExamplePlugin,
791 /// "example_plugin",
792 /// "An example plugin",
793 /// hooks: []
794 /// );
795 ///
796 /// let mut manager = PluginManager::new();
797 /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
798 /// manager.enable_plugin("example_plugin").unwrap();
799 ///
800 /// let plugin = manager.get_plugin("example_plugin").unwrap();
801 /// assert!(plugin.is_enabled());
802 /// ```
803 pub fn enable_plugin(&mut self, id: PluginID) -> PluginResult<()> {
804 match self.plugins.get_mut(id) {
805 Some(plugin) => {
806 plugin.enable();
807 Ok(())
808 }
809 None => Err(error::PluginError::NotFound(id.into())),
810 }
811 }
812
813 /// Disables a plugin by ID.
814 ///
815 /// # Errors
816 ///
817 /// Returns a [`PluginError::NotFound`] if no plugin with the given ID is loaded.
818 ///
819 /// # Examples
820 ///
821 /// ```
822 /// use steckrs::{PluginManager, simple_plugin};
823 ///
824 /// simple_plugin!(
825 /// ExamplePlugin,
826 /// "example_plugin",
827 /// "An example plugin",
828 /// hooks: []
829 /// );
830 ///
831 /// let mut manager = PluginManager::new();
832 /// manager.load_plugin(Box::new(ExamplePlugin::new())).unwrap();
833 /// manager.enable_plugin("example_plugin").unwrap();
834 /// manager.disable_plugin("example_plugin").unwrap();
835 ///
836 /// let plugin = manager.get_plugin("example_plugin").unwrap();
837 /// assert!(!plugin.is_enabled());
838 /// ```
839 pub fn disable_plugin(&mut self, id: PluginID) -> PluginResult<()> {
840 match self.plugins.get_mut(id) {
841 Some(plugin) => {
842 plugin.disable();
843 Ok(())
844 }
845 None => Err(error::PluginError::NotFound(id.into())),
846 }
847 }
848
849 /// Gets all hooks of enabled [Plugins](Plugin) for a specific [`ExtensionPoint`] type.
850 ///
851 /// This method filters hooks by both extension point type and plugin enabled status,
852 /// returning only hooks from enabled plugins.
853 ///
854 /// # Type Parameters
855 ///
856 /// - `E`: The [`ExtensionPoint`] type
857 ///
858 /// # Returns
859 ///
860 /// A vector of tuples containing references to [`HookID`](crate::hook::HookID)s and hooks registered for the [`ExtensionPoint`]
861 /// from enabled plugins.
862 ///
863 /// # Examples
864 ///
865 /// ```
866 /// use steckrs::{extension_point, simple_plugin, PluginManager};
867 ///
868 /// extension_point!(
869 /// Logger: LoggerTrait;
870 /// fn log(&self, message: &str);
871 /// );
872 ///
873 /// struct ConsoleLogger;
874 /// impl LoggerTrait for ConsoleLogger {
875 /// fn log(&self, message: &str) {
876 /// // In a real implementation, this would print to console
877 /// }
878 /// }
879 ///
880 /// simple_plugin!(
881 /// LoggerPlugin,
882 /// "logger_plugin",
883 /// "Basic logging plugin",
884 /// hooks: [(Logger, ConsoleLogger)]
885 /// );
886 ///
887 /// let mut manager = PluginManager::new();
888 /// manager.load_plugin(Box::new(LoggerPlugin::new())).unwrap();
889 /// manager.enable_plugin(LoggerPlugin::ID).unwrap();
890 ///
891 /// // Get all enabled hooks for the Logger extension point
892 /// let hooks = manager.get_enabled_hooks_by_ep::<Logger>();
893 /// assert_eq!(hooks.len(), 1);
894 ///
895 /// // Use the hook
896 /// for (id, hook) in hooks {
897 /// assert_eq!(id.plugin_id, "logger_plugin");
898 /// hook.inner().log("Hello from logger!");
899 /// }
900 /// ```
901 #[must_use]
902 pub fn get_enabled_hooks_by_ep<E: ExtensionPoint>(
903 &self,
904 ) -> Vec<(&hook::HookID, &hook::Hook<E>)> {
905 self.hook_registry()
906 .get_by_extension_point()
907 .into_iter()
908 .filter(|(id, _hook)| {
909 if let Some(plugin) = self.plugins.get(id.plugin_id) {
910 plugin.is_enabled()
911 } else {
912 false
913 }
914 })
915 .collect()
916 }
917
918 /// Gets all mutable hooks of enabled [Plugins](Plugin) for a specific [`ExtensionPoint`] type.
919 ///
920 /// This method filters hooks by both extension point type and plugin enabled status,
921 /// returning only hooks from enabled plugins.
922 ///
923 /// # Type Parameters
924 ///
925 /// - `E`: The [`ExtensionPoint`] type
926 ///
927 /// # Returns
928 ///
929 /// A vector of tuples containing mutable references to [`HookID`](crate::hook::HookID)s and hooks registered for the [`ExtensionPoint`]
930 /// from enabled plugins.
931 ///
932 /// # Examples
933 ///
934 /// ```
935 /// use steckrs::{extension_point, simple_plugin, PluginManager};
936 ///
937 /// extension_point!(
938 /// Logger: LoggerTrait;
939 /// fn log(&self, message: &str);
940 /// );
941 ///
942 /// struct ConsoleLogger;
943 /// impl LoggerTrait for ConsoleLogger {
944 /// fn log(&self, message: &str) {
945 /// // In a real implementation, this would print to console
946 /// }
947 /// }
948 ///
949 /// simple_plugin!(
950 /// LoggerPlugin,
951 /// "logger_plugin",
952 /// "Basic logging plugin",
953 /// hooks: [(Logger, ConsoleLogger)]
954 /// );
955 ///
956 /// let mut manager = PluginManager::new();
957 /// manager.load_plugin(Box::new(LoggerPlugin::new())).unwrap();
958 /// manager.enable_plugin(LoggerPlugin::ID).unwrap();
959 ///
960 /// // Get all enabled hooks for the Logger extension point
961 /// let hooks = manager.get_enabled_hooks_by_ep::<Logger>();
962 /// assert_eq!(hooks.len(), 1);
963 ///
964 /// // Use the hook
965 /// for (id, hook) in hooks {
966 /// assert_eq!(id.plugin_id, "logger_plugin");
967 /// hook.inner().log("Hello from logger!");
968 /// }
969 /// ```
970 #[must_use]
971 pub fn get_enabled_hooks_by_ep_mut<E: ExtensionPoint>(
972 &mut self,
973 ) -> Vec<(&hook::HookID, &mut hook::Hook<E>)> {
974 let enabled_ids: Vec<PluginID> = self
975 .plugins
976 .iter()
977 .filter_map(|(id, plug)| if plug.is_enabled() { Some(*id) } else { None })
978 .collect();
979 self.hook_registry_mut()
980 .get_by_extension_point_mut()
981 .into_iter()
982 .filter(|(id, _hook)| enabled_ids.contains(&id.plugin_id))
983 .collect()
984 }
985}