Skip to main content

streamdown_plugin/
builtin.rs

1//! Built-in plugins and plugin discovery.
2//!
3//! This module provides:
4//! - A list of all built-in plugins
5//! - Plugin discovery from configuration directory
6//! - Plugin factory functions
7
8use crate::{latex::LatexPlugin, Plugin};
9use std::path::Path;
10
11/// Get all built-in plugins.
12///
13/// Returns a vector of boxed plugins ready for registration.
14pub fn builtin_plugins() -> Vec<Box<dyn Plugin>> {
15    vec![Box::new(LatexPlugin::new())]
16}
17
18/// Plugin metadata.
19#[derive(Debug, Clone)]
20pub struct PluginInfo {
21    /// Plugin name
22    pub name: &'static str,
23    /// Short description
24    pub description: &'static str,
25    /// Whether it's enabled by default
26    pub default_enabled: bool,
27    /// Priority (lower = higher priority)
28    pub priority: i32,
29}
30
31/// Get information about all built-in plugins.
32pub fn builtin_plugin_info() -> Vec<PluginInfo> {
33    vec![PluginInfo {
34        name: "latex",
35        description: "Converts LaTeX math expressions ($$ or $) to Unicode",
36        default_enabled: true,
37        priority: 10,
38    }]
39}
40
41/// Create a plugin by name.
42///
43/// # Returns
44/// - `Some(plugin)` if the name matches a built-in plugin
45/// - `None` if the name is not recognized
46pub fn create_plugin(name: &str) -> Option<Box<dyn Plugin>> {
47    match name {
48        "latex" => Some(Box::new(LatexPlugin::new())),
49        _ => None,
50    }
51}
52
53/// Discover plugins from a directory.
54///
55/// Currently this is a placeholder for future dynamic plugin loading.
56/// In the future, this could load:
57/// - WASM plugins
58/// - Shared library plugins
59/// - Script-based plugins
60///
61/// # Arguments
62/// * `_config_dir` - Path to the configuration directory
63///
64/// # Returns
65/// Vector of discovered plugins (currently empty)
66pub fn discover_plugins(_config_dir: &Path) -> Vec<Box<dyn Plugin>> {
67    // TODO: Implement dynamic plugin loading
68    // For now, return empty - all plugins must be built-in
69    vec![]
70}
71
72/// Plugin filter for selective loading.
73#[derive(Debug, Clone, Default)]
74pub struct PluginFilter {
75    /// Plugins to include (if empty, include all)
76    pub include: Vec<String>,
77    /// Plugins to exclude
78    pub exclude: Vec<String>,
79}
80
81impl PluginFilter {
82    /// Create a filter that includes all plugins.
83    pub fn all() -> Self {
84        Self::default()
85    }
86
87    /// Create a filter that includes no plugins.
88    pub fn none() -> Self {
89        Self {
90            include: vec![],
91            exclude: vec!["*".to_string()],
92        }
93    }
94
95    /// Create a filter that only includes specific plugins.
96    pub fn only(names: Vec<String>) -> Self {
97        Self {
98            include: names,
99            exclude: vec![],
100        }
101    }
102
103    /// Check if a plugin should be loaded.
104    pub fn should_load(&self, name: &str) -> bool {
105        // Check exclude list first
106        if self.exclude.contains(&"*".to_string()) {
107            return self.include.iter().any(|n| n == name);
108        }
109        if self.exclude.iter().any(|n| n == name) {
110            return false;
111        }
112
113        // Check include list
114        if self.include.is_empty() {
115            return true;
116        }
117        self.include.iter().any(|n| n == name)
118    }
119}
120
121/// Load built-in plugins with a filter.
122pub fn load_builtin_plugins(filter: &PluginFilter) -> Vec<Box<dyn Plugin>> {
123    builtin_plugin_info()
124        .iter()
125        .filter(|info| filter.should_load(info.name))
126        .filter_map(|info| create_plugin(info.name))
127        .collect()
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_builtin_plugins() {
136        let plugins = builtin_plugins();
137        assert!(!plugins.is_empty());
138
139        // Should have latex plugin
140        let names: Vec<_> = plugins.iter().map(|p| p.name()).collect();
141        assert!(names.contains(&"latex"));
142    }
143
144    #[test]
145    fn test_builtin_plugin_info() {
146        let info = builtin_plugin_info();
147        assert!(!info.is_empty());
148
149        let latex_info = info.iter().find(|i| i.name == "latex");
150        assert!(latex_info.is_some());
151        assert!(latex_info.unwrap().default_enabled);
152    }
153
154    #[test]
155    fn test_create_plugin() {
156        let latex = create_plugin("latex");
157        assert!(latex.is_some());
158        assert_eq!(latex.unwrap().name(), "latex");
159
160        let unknown = create_plugin("unknown");
161        assert!(unknown.is_none());
162    }
163
164    #[test]
165    fn test_discover_plugins() {
166        // Currently returns empty
167        let plugins = discover_plugins(Path::new("/tmp"));
168        assert!(plugins.is_empty());
169    }
170
171    #[test]
172    fn test_plugin_filter_all() {
173        let filter = PluginFilter::all();
174        assert!(filter.should_load("latex"));
175        assert!(filter.should_load("any"));
176    }
177
178    #[test]
179    fn test_plugin_filter_none() {
180        let filter = PluginFilter::none();
181        assert!(!filter.should_load("latex"));
182        assert!(!filter.should_load("any"));
183    }
184
185    #[test]
186    fn test_plugin_filter_only() {
187        let filter = PluginFilter::only(vec!["latex".to_string()]);
188        assert!(filter.should_load("latex"));
189        assert!(!filter.should_load("other"));
190    }
191
192    #[test]
193    fn test_plugin_filter_exclude() {
194        let mut filter = PluginFilter::all();
195        filter.exclude.push("latex".to_string());
196        assert!(!filter.should_load("latex"));
197        assert!(filter.should_load("other"));
198    }
199
200    #[test]
201    fn test_load_builtin_plugins() {
202        let filter = PluginFilter::all();
203        let plugins = load_builtin_plugins(&filter);
204        assert!(!plugins.is_empty());
205
206        let filter = PluginFilter::none();
207        let plugins = load_builtin_plugins(&filter);
208        assert!(plugins.is_empty());
209    }
210}