Skip to main content

hx_plugins/
lib.rs

1//! Plugin system for hx using Steel Scheme.
2//!
3//! This crate provides:
4//! - Pre/post build hooks
5//! - Custom commands via Scheme scripts
6//! - A Steel API for interacting with hx
7//!
8//! # Example
9//!
10//! ```ignore
11//! use hx_plugins::{PluginManager, PluginConfig};
12//!
13//! let config = PluginConfig::default();
14//! let mut manager = PluginManager::new(config)?;
15//! manager.initialize()?;
16//!
17//! // Run pre-build hooks
18//! let ctx = PluginContext::new(project_root, project_name);
19//! manager.run_hook(HookEvent::PreBuild, &ctx)?;
20//! ```
21
22pub mod api;
23pub mod commands;
24pub mod config;
25pub mod context;
26pub mod engine;
27pub mod error;
28pub mod hooks;
29pub mod loader;
30
31// Re-exports for convenience
32pub use commands::CustomCommand;
33pub use config::{HookConfig, PluginConfig};
34pub use context::{BuildContext, PluginContext, TestContext};
35pub use engine::{PluginSystem, SteelEngine};
36pub use error::{PluginError, Result};
37pub use hooks::{HookEvent, HookResult};
38pub use loader::{DiscoveredPlugin, PluginPaths, discover_plugins, find_plugin};
39
40use std::path::Path;
41
42/// Main plugin manager that handles all plugin operations.
43pub struct PluginManager {
44    engine: SteelEngine,
45    initialized: bool,
46}
47
48impl PluginManager {
49    /// Create a new plugin manager with the given configuration.
50    pub fn new(config: PluginConfig) -> Result<Self> {
51        Ok(PluginManager {
52            engine: SteelEngine::new(config),
53            initialized: false,
54        })
55    }
56
57    /// Create a new plugin manager with default configuration.
58    pub fn with_defaults() -> Result<Self> {
59        Self::new(PluginConfig::new())
60    }
61
62    /// Initialize the plugin system.
63    ///
64    /// This registers all API functions and loads the prelude.
65    pub fn initialize(&mut self) -> Result<()> {
66        if self.initialized {
67            return Ok(());
68        }
69
70        self.engine.initialize()?;
71        self.initialized = true;
72        Ok(())
73    }
74
75    /// Load all plugins from the configured paths.
76    pub fn load_all(&mut self, project_root: &Path) -> Result<()> {
77        self.ensure_initialized()?;
78
79        let plugins = discover_plugins(self.engine.config(), project_root)?;
80
81        for plugin in plugins {
82            if let Err(e) = self.engine.load_plugin(&plugin.path) {
83                // Log but don't fail on individual plugin errors
84                tracing::warn!("Failed to load plugin {}: {}", plugin.name, e);
85            }
86        }
87
88        Ok(())
89    }
90
91    /// Load a specific plugin by name or path.
92    pub fn load_plugin(&mut self, path: &Path) -> Result<()> {
93        self.ensure_initialized()?;
94        self.engine.load_plugin(path)
95    }
96
97    /// Run hooks for an event.
98    pub fn run_hook(&mut self, event: HookEvent, ctx: &PluginContext) -> Result<HookResult> {
99        self.ensure_initialized()?;
100        self.engine.run_hook(event, ctx)
101    }
102
103    /// Run a custom command.
104    pub fn run_command(&mut self, name: &str, args: &[String]) -> Result<i32> {
105        self.ensure_initialized()?;
106        self.engine.run_command(name, args)
107    }
108
109    /// Get the list of registered custom commands.
110    pub fn commands(&self) -> &std::collections::HashMap<String, CustomCommand> {
111        self.engine.commands()
112    }
113
114    /// Get the plugin configuration.
115    pub fn config(&self) -> &PluginConfig {
116        self.engine.config()
117    }
118
119    /// Check if a command is registered.
120    pub fn has_command(&self, name: &str) -> bool {
121        self.engine.commands().contains_key(name)
122    }
123
124    /// Ensure the manager is initialized.
125    fn ensure_initialized(&mut self) -> Result<()> {
126        if !self.initialized {
127            self.initialize()?;
128        }
129        Ok(())
130    }
131}
132
133/// Check if plugins are enabled in the given configuration.
134pub fn plugins_enabled(config: &PluginConfig) -> bool {
135    config.enabled
136}
137
138/// Create a simple plugin context for testing.
139pub fn test_context(project_root: impl Into<std::path::PathBuf>, name: &str) -> PluginContext {
140    PluginContext::new(project_root.into(), name.to_string())
141}