Skip to main content

unistructgen_core/
plugin.rs

1//! Plugin system for extending functionality
2//!
3//! Plugins allow you to hook into the parsing and generation process at specific points,
4//! enabling custom processing without modifying the core library.
5
6use crate::IRModule;
7use std::error::Error as StdError;
8use thiserror::Error;
9
10/// Errors that can occur during plugin execution
11#[derive(Error, Debug)]
12pub enum PluginError {
13    /// Plugin initialization failed
14    #[error("Plugin '{plugin}' initialization failed: {message}")]
15    Initialization {
16        /// Name of the plugin
17        plugin: String,
18        /// Error message
19        message: String,
20    },
21
22    /// Plugin execution failed
23    #[error("Plugin '{plugin}' execution failed: {message}")]
24    Execution {
25        /// Name of the plugin
26        plugin: String,
27        /// Error message
28        message: String,
29        /// Optional underlying error
30        #[source]
31        source: Option<Box<dyn StdError + Send + Sync>>,
32    },
33
34    /// Plugin not found
35    #[error("Plugin '{0}' not found in registry")]
36    NotFound(String),
37
38    /// Plugin already registered
39    #[error("Plugin '{0}' is already registered")]
40    AlreadyRegistered(String),
41}
42
43impl PluginError {
44    /// Create an initialization error
45    pub fn initialization(plugin: impl Into<String>, message: impl Into<String>) -> Self {
46        Self::Initialization {
47            plugin: plugin.into(),
48            message: message.into(),
49        }
50    }
51
52    /// Create an execution error
53    pub fn execution(plugin: impl Into<String>, message: impl Into<String>) -> Self {
54        Self::Execution {
55            plugin: plugin.into(),
56            message: message.into(),
57            source: None,
58        }
59    }
60
61    /// Add a source error
62    pub fn with_source(mut self, error: Box<dyn StdError + Send + Sync>) -> Self {
63        if let Self::Execution { source, .. } = &mut self {
64            *source = Some(error);
65        }
66        self
67    }
68}
69
70/// Trait for implementing plugins
71///
72/// Plugins can hook into various stages of the processing pipeline:
73/// - **Initialization**: Setup when the plugin is loaded
74/// - **Before parsing**: Modify input before it's parsed
75/// - **After parsing**: Modify the IR after parsing
76/// - **After generation**: Modify generated code
77///
78/// # Examples
79///
80/// ```
81/// use unistructgen_core::{Plugin, PluginError, IRModule};
82///
83/// struct MyPlugin {
84///     name: String,
85/// }
86///
87/// impl Plugin for MyPlugin {
88///     fn name(&self) -> &str {
89///         &self.name
90///     }
91///
92///     fn version(&self) -> &str {
93///         "1.0.0"
94///     }
95///
96///     fn initialize(&mut self) -> Result<(), PluginError> {
97///         println!("Plugin {} initialized", self.name());
98///         Ok(())
99///     }
100///
101///     fn after_parse(&mut self, module: IRModule) -> Result<IRModule, PluginError> {
102///         // Modify the IR module here
103///         Ok(module)
104///     }
105/// }
106/// ```
107pub trait Plugin: Send + Sync {
108    /// Name of the plugin
109    fn name(&self) -> &str;
110
111    /// Version of the plugin
112    fn version(&self) -> &str;
113
114    /// Optional description
115    fn description(&self) -> Option<&str> {
116        None
117    }
118
119    /// Called during plugin initialization
120    ///
121    /// Use this to set up any resources the plugin needs.
122    fn initialize(&mut self) -> Result<(), PluginError>;
123
124    /// Called before parsing
125    ///
126    /// This allows the plugin to modify the input before it's parsed.
127    /// By default, returns the input unchanged.
128    fn before_parse(&mut self, input: &str) -> Result<String, PluginError> {
129        Ok(input.to_string())
130    }
131
132    /// Called after parsing
133    ///
134    /// This allows the plugin to modify the IR after parsing.
135    /// By default, returns the module unchanged.
136    fn after_parse(&mut self, module: IRModule) -> Result<IRModule, PluginError> {
137        Ok(module)
138    }
139
140    /// Called after code generation
141    ///
142    /// This allows the plugin to modify the generated code.
143    /// By default, returns the code unchanged.
144    fn after_generate(&mut self, code: String) -> Result<String, PluginError> {
145        Ok(code)
146    }
147
148    /// Called during plugin shutdown
149    ///
150    /// Use this to clean up any resources.
151    fn shutdown(&mut self) -> Result<(), PluginError> {
152        Ok(())
153    }
154}
155
156/// Registry for managing plugins
157///
158/// The registry allows you to register, retrieve, and execute plugins.
159///
160/// # Examples
161///
162/// ```
163/// use unistructgen_core::PluginRegistry;
164///
165/// let mut registry = PluginRegistry::new();
166///
167/// // Register plugins
168/// // registry.register(Box::new(my_plugin)).unwrap();
169///
170/// // Get a plugin
171/// // let plugin = registry.get("my_plugin");
172/// ```
173pub struct PluginRegistry {
174    plugins: Vec<Box<dyn Plugin>>,
175}
176
177impl PluginRegistry {
178    /// Create a new plugin registry
179    pub fn new() -> Self {
180        Self {
181            plugins: Vec::new(),
182        }
183    }
184
185    /// Register a plugin
186    ///
187    /// # Errors
188    ///
189    /// Returns an error if a plugin with the same name is already registered.
190    ///
191    /// # Examples
192    ///
193    /// ```
194    /// use unistructgen_core::PluginRegistry;
195    ///
196    /// let mut registry = PluginRegistry::new();
197    /// // registry.register(Box::new(my_plugin)).unwrap();
198    /// ```
199    pub fn register(&mut self, mut plugin: Box<dyn Plugin>) -> Result<(), PluginError> {
200        let name = plugin.name().to_string();
201
202        // Check if already registered
203        if self.plugins.iter().any(|p| p.name() == name) {
204            return Err(PluginError::AlreadyRegistered(name));
205        }
206
207        // Initialize the plugin
208        plugin
209            .initialize()
210            .map_err(|e| PluginError::initialization(&name, format!("{}", e)))?;
211
212        self.plugins.push(plugin);
213        Ok(())
214    }
215
216    /// Get a reference to a plugin by name
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// use unistructgen_core::PluginRegistry;
222    ///
223    /// let registry = PluginRegistry::new();
224    /// let plugin = registry.get("my_plugin");
225    /// assert!(plugin.is_none());
226    /// ```
227    pub fn get(&self, name: &str) -> Option<&dyn Plugin> {
228        self.plugins
229            .iter()
230            .map(|p| p.as_ref())
231            .find(|p| p.name() == name)
232    }
233
234    /// Get a mutable reference to a plugin by name
235    pub fn get_mut(&mut self, name: &str) -> Option<&mut dyn Plugin> {
236        for plugin in &mut self.plugins {
237            if plugin.name() == name {
238                return Some(plugin.as_mut());
239            }
240        }
241        None
242    }
243
244    /// Get all registered plugin names
245    ///
246    /// # Examples
247    ///
248    /// ```
249    /// use unistructgen_core::PluginRegistry;
250    ///
251    /// let registry = PluginRegistry::new();
252    /// let names = registry.plugin_names();
253    /// assert_eq!(names.len(), 0);
254    /// ```
255    pub fn plugin_names(&self) -> Vec<&str> {
256        self.plugins.iter().map(|p| p.name()).collect()
257    }
258
259    /// Get the number of registered plugins
260    pub fn count(&self) -> usize {
261        self.plugins.len()
262    }
263
264    /// Check if a plugin is registered
265    pub fn has_plugin(&self, name: &str) -> bool {
266        self.plugins.iter().any(|p| p.name() == name)
267    }
268
269    /// Remove a plugin by name
270    ///
271    /// # Examples
272    ///
273    /// ```
274    /// use unistructgen_core::PluginRegistry;
275    ///
276    /// let mut registry = PluginRegistry::new();
277    /// let removed = registry.remove("my_plugin");
278    /// assert!(removed.is_none());
279    /// ```
280    pub fn remove(&mut self, name: &str) -> Option<Box<dyn Plugin>> {
281        let pos = self.plugins.iter().position(|p| p.name() == name)?;
282        Some(self.plugins.remove(pos))
283    }
284
285    /// Execute `before_parse` on all plugins
286    ///
287    /// Plugins are executed in the order they were registered.
288    pub fn before_parse(&mut self, mut input: String) -> Result<String, PluginError> {
289        for plugin in &mut self.plugins {
290            input = plugin.before_parse(&input)?;
291        }
292        Ok(input)
293    }
294
295    /// Execute `after_parse` on all plugins
296    ///
297    /// Plugins are executed in the order they were registered.
298    pub fn after_parse(&mut self, mut module: IRModule) -> Result<IRModule, PluginError> {
299        for plugin in &mut self.plugins {
300            module = plugin.after_parse(module)?;
301        }
302        Ok(module)
303    }
304
305    /// Execute `after_generate` on all plugins
306    ///
307    /// Plugins are executed in the order they were registered.
308    pub fn after_generate(&mut self, mut code: String) -> Result<String, PluginError> {
309        for plugin in &mut self.plugins {
310            code = plugin.after_generate(code)?;
311        }
312        Ok(code)
313    }
314
315    /// Shutdown all plugins
316    pub fn shutdown(&mut self) -> Result<(), PluginError> {
317        for plugin in &mut self.plugins {
318            plugin.shutdown()?;
319        }
320        Ok(())
321    }
322}
323
324impl Default for PluginRegistry {
325    fn default() -> Self {
326        Self::new()
327    }
328}
329
330impl Drop for PluginRegistry {
331    fn drop(&mut self) {
332        let _ = self.shutdown();
333    }
334}
335
336/// Example plugin that adds a header comment to generated code
337///
338/// # Examples
339///
340/// ```
341/// use unistructgen_core::plugin::HeaderPlugin;
342/// use unistructgen_core::Plugin;
343///
344/// let mut plugin = HeaderPlugin::new("Generated by MyTool");
345/// plugin.initialize().unwrap();
346/// ```
347pub struct HeaderPlugin {
348    header: String,
349}
350
351impl HeaderPlugin {
352    /// Create a new header plugin
353    pub fn new(header: impl Into<String>) -> Self {
354        Self {
355            header: header.into(),
356        }
357    }
358}
359
360impl Plugin for HeaderPlugin {
361    fn name(&self) -> &str {
362        "HeaderPlugin"
363    }
364
365    fn version(&self) -> &str {
366        "1.0.0"
367    }
368
369    fn description(&self) -> Option<&str> {
370        Some("Adds a header comment to generated code")
371    }
372
373    fn initialize(&mut self) -> Result<(), PluginError> {
374        Ok(())
375    }
376
377    fn after_generate(&mut self, code: String) -> Result<String, PluginError> {
378        Ok(format!("// {}\n\n{}", self.header, code))
379    }
380}
381
382/// Example plugin that logs processing stages
383///
384/// Useful for debugging and monitoring the pipeline.
385pub struct LoggingPlugin {
386    verbose: bool,
387}
388
389impl LoggingPlugin {
390    /// Create a new logging plugin
391    pub fn new(verbose: bool) -> Self {
392        Self { verbose }
393    }
394
395    fn log(&self, stage: &str, message: &str) {
396        if self.verbose {
397            println!("[LoggingPlugin] {}: {}", stage, message);
398        }
399    }
400}
401
402impl Plugin for LoggingPlugin {
403    fn name(&self) -> &str {
404        "LoggingPlugin"
405    }
406
407    fn version(&self) -> &str {
408        "1.0.0"
409    }
410
411    fn description(&self) -> Option<&str> {
412        Some("Logs processing stages for debugging")
413    }
414
415    fn initialize(&mut self) -> Result<(), PluginError> {
416        self.log("init", "Plugin initialized");
417        Ok(())
418    }
419
420    fn before_parse(&mut self, input: &str) -> Result<String, PluginError> {
421        self.log("before_parse", &format!("Input length: {}", input.len()));
422        Ok(input.to_string())
423    }
424
425    fn after_parse(&mut self, module: IRModule) -> Result<IRModule, PluginError> {
426        self.log(
427            "after_parse",
428            &format!("Generated {} types", module.types.len()),
429        );
430        Ok(module)
431    }
432
433    fn after_generate(&mut self, code: String) -> Result<String, PluginError> {
434        self.log(
435            "after_generate",
436            &format!("Generated code: {} bytes", code.len()),
437        );
438        Ok(code)
439    }
440
441    fn shutdown(&mut self) -> Result<(), PluginError> {
442        self.log("shutdown", "Plugin shutting down");
443        Ok(())
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450
451    struct TestPlugin {
452        name: String,
453        initialized: bool,
454    }
455
456    impl TestPlugin {
457        fn new(name: impl Into<String>) -> Self {
458            Self {
459                name: name.into(),
460                initialized: false,
461            }
462        }
463    }
464
465    impl Plugin for TestPlugin {
466        fn name(&self) -> &str {
467            &self.name
468        }
469
470        fn version(&self) -> &str {
471            "1.0.0"
472        }
473
474        fn initialize(&mut self) -> Result<(), PluginError> {
475            self.initialized = true;
476            Ok(())
477        }
478
479        fn after_generate(&mut self, code: String) -> Result<String, PluginError> {
480            Ok(format!("// Modified by {}\n{}", self.name, code))
481        }
482    }
483
484    #[test]
485    fn test_plugin_registration() {
486        let mut registry = PluginRegistry::new();
487        let plugin = Box::new(TestPlugin::new("test1"));
488
489        registry.register(plugin).unwrap();
490        assert_eq!(registry.count(), 1);
491        assert!(registry.has_plugin("test1"));
492    }
493
494    #[test]
495    fn test_duplicate_registration() {
496        let mut registry = PluginRegistry::new();
497        registry
498            .register(Box::new(TestPlugin::new("test1")))
499            .unwrap();
500
501        let result = registry.register(Box::new(TestPlugin::new("test1")));
502        assert!(result.is_err());
503    }
504
505    #[test]
506    fn test_plugin_retrieval() {
507        let mut registry = PluginRegistry::new();
508        registry
509            .register(Box::new(TestPlugin::new("test1")))
510            .unwrap();
511
512        let plugin = registry.get("test1");
513        assert!(plugin.is_some());
514        assert_eq!(plugin.unwrap().name(), "test1");
515
516        let missing = registry.get("missing");
517        assert!(missing.is_none());
518    }
519
520    #[test]
521    fn test_plugin_removal() {
522        let mut registry = PluginRegistry::new();
523        registry
524            .register(Box::new(TestPlugin::new("test1")))
525            .unwrap();
526
527        assert_eq!(registry.count(), 1);
528
529        let removed = registry.remove("test1");
530        assert!(removed.is_some());
531        assert_eq!(registry.count(), 0);
532    }
533
534    #[test]
535    fn test_after_generate() {
536        let mut registry = PluginRegistry::new();
537        registry
538            .register(Box::new(TestPlugin::new("test1")))
539            .unwrap();
540
541        let code = "original code".to_string();
542        let result = registry.after_generate(code).unwrap();
543
544        assert!(result.contains("Modified by test1"));
545        assert!(result.contains("original code"));
546    }
547
548    #[test]
549    fn test_header_plugin() {
550        let mut plugin = HeaderPlugin::new("Generated by Test");
551        plugin.initialize().unwrap();
552
553        let code = "fn main() {}".to_string();
554        let result = plugin.after_generate(code).unwrap();
555
556        assert!(result.starts_with("// Generated by Test"));
557    }
558
559    #[test]
560    fn test_logging_plugin() {
561        let mut plugin = LoggingPlugin::new(false);
562        plugin.initialize().unwrap();
563
564        let input = "test input";
565        let result = plugin.before_parse(input).unwrap();
566        assert_eq!(result, input);
567    }
568}