Skip to main content

nargo_plugin/
lib.rs

1#![warn(missing_docs)]
2
3use nargo_types::{NargoContext, NargoValue, Result};
4use serde::{Deserialize, Serialize};
5use std::{collections::HashMap, sync::Arc};
6
7/// 插件生命周期阶段
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
9pub enum PluginLifecycle {
10    /// 初始化阶段,插件首次加载时执行
11    Init,
12    /// 解析前阶段
13    PreParse,
14    /// 解析阶段
15    Parse,
16    /// 解析后阶段
17    PostParse,
18    /// 变换前阶段
19    PreTransform,
20    /// 变换阶段
21    Transform,
22    /// 变换后阶段
23    PostTransform,
24    /// 打包前阶段
25    PreBundle,
26    /// 打包阶段
27    Bundle,
28    /// 打包后阶段
29    PostBundle,
30    /// 清理阶段
31    Cleanup,
32}
33
34/// 插件配置项
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct PluginConfig {
37    /// 插件名称
38    pub name: String,
39    /// 插件版本
40    pub version: String,
41    /// 插件描述
42    pub description: String,
43    /// 插件作者
44    pub author: Option<String>,
45    /// 插件主页
46    pub homepage: Option<String>,
47    /// 插件优先级(数值越小,优先级越高)
48    pub priority: i32,
49    /// 插件配置参数
50    pub config: HashMap<String, NargoValue>,
51    /// 插件启用状态
52    pub enabled: bool,
53}
54
55impl Default for PluginConfig {
56    fn default() -> Self {
57        Self { name: String::new(), version: "0.1.0".to_string(), description: String::new(), author: None, homepage: None, priority: 0, config: HashMap::new(), enabled: true }
58    }
59}
60
61/// Plugin trait for extending Nargo functionality.
62pub trait Plugin: Send + Sync {
63    /// Returns the name of the plugin.
64    fn name(&self) -> &str;
65
66    /// Returns the version of the plugin.
67    fn version(&self) -> &str {
68        "0.1.0"
69    }
70
71    /// Returns the description of the plugin.
72    fn description(&self) -> &str {
73        ""
74    }
75
76    /// Returns the author of the plugin.
77    fn author(&self) -> Option<&str> {
78        None
79    }
80
81    /// Returns the homepage of the plugin.
82    fn homepage(&self) -> Option<&str> {
83        None
84    }
85
86    /// Returns the priority of the plugin.
87    fn priority(&self) -> i32 {
88        0
89    }
90
91    /// Called when the plugin is initialized.
92    fn on_init(&self, _ctx: Arc<NargoContext>, _config: &PluginConfig) -> Result<()> {
93        Ok(())
94    }
95
96    /// Called before parsing phase.
97    fn on_pre_parse(&self, _source: &str) -> Result<Option<String>> {
98        Ok(None)
99    }
100
101    /// Called during parsing phase.
102    fn on_parse(&self, _source: &str) -> Result<Option<String>> {
103        Ok(None)
104    }
105
106    /// Called after parsing phase.
107    fn on_post_parse(&self, _source: &str) -> Result<Option<String>> {
108        Ok(None)
109    }
110
111    /// Called before transform phase.
112    fn on_pre_transform(&self, _code: &str) -> Result<Option<String>> {
113        Ok(None)
114    }
115
116    /// Called during transform phase.
117    fn on_transform(&self, _code: &str) -> Result<Option<String>> {
118        Ok(None)
119    }
120
121    /// Called after transform phase.
122    fn on_post_transform(&self, _code: &str) -> Result<Option<String>> {
123        Ok(None)
124    }
125
126    /// Called before bundle phase.
127    fn on_pre_bundle(&self, _bundle: &str) -> Result<Option<String>> {
128        Ok(None)
129    }
130
131    /// Called during bundle phase.
132    fn on_bundle(&self, _bundle: &str) -> Result<Option<String>> {
133        Ok(None)
134    }
135
136    /// Called after bundle phase.
137    fn on_post_bundle(&self, _bundle: &str) -> Result<Option<String>> {
138        Ok(None)
139    }
140
141    /// Called during cleanup phase.
142    fn on_cleanup(&self) -> Result<()> {
143        Ok(())
144    }
145}
146
147/// JavaScript-based plugin implementation.
148pub struct JsPlugin {
149    name: String,
150    version: String,
151    description: String,
152}
153
154impl JsPlugin {
155    /// Creates a new JavaScript plugin.
156    pub fn new(name: String, version: String, description: String, _source: &str) -> Result<Self> {
157        Ok(Self { name, version, description })
158    }
159}
160
161impl Plugin for JsPlugin {
162    fn name(&self) -> &str {
163        &self.name
164    }
165
166    fn version(&self) -> &str {
167        &self.version
168    }
169
170    fn description(&self) -> &str {
171        &self.description
172    }
173}
174
175/// Manager for plugins.
176pub struct PluginManager {
177    ctx: Arc<NargoContext>,
178    plugins: Vec<Box<dyn Plugin>>,
179    configs: HashMap<String, PluginConfig>,
180}
181
182impl PluginManager {
183    /// Creates a new plugin manager.
184    pub fn new(ctx: Arc<NargoContext>) -> Self {
185        Self { ctx, plugins: Vec::new(), configs: HashMap::new() }
186    }
187
188    /// Registers a plugin with default config.
189    pub fn register(&mut self, plugin: Box<dyn Plugin>) {
190        let config = PluginConfig { name: plugin.name().to_string(), version: plugin.version().to_string(), description: plugin.description().to_string(), author: plugin.author().map(|s| s.to_string()), homepage: plugin.homepage().map(|s| s.to_string()), priority: plugin.priority(), enabled: true, ..Default::default() };
191        self.register_with_config(plugin, config);
192    }
193
194    /// Registers a plugin with custom config.
195    pub fn register_with_config(&mut self, plugin: Box<dyn Plugin>, config: PluginConfig) {
196        tracing::info!("Registering plugin: {} v{}", plugin.name(), plugin.version());
197        self.configs.insert(plugin.name().to_string(), config);
198        self.plugins.push(plugin);
199        self.sort_plugins();
200    }
201
202    /// Sorts plugins by priority.
203    fn sort_plugins(&mut self) {
204        self.plugins.sort_by(|a, b| {
205            let a_priority = a.priority();
206            let b_priority = b.priority();
207            a_priority.cmp(&b_priority)
208        });
209    }
210
211    /// Initializes all registered plugins.
212    pub fn init_all(&self) -> Result<()> {
213        for plugin in &self.plugins {
214            if let Some(config) = self.configs.get(plugin.name()) {
215                if config.enabled {
216                    plugin.on_init(self.ctx.clone(), config)?;
217                }
218            }
219        }
220        Ok(())
221    }
222
223    /// Processes source code through all registered plugins at parse phase.
224    pub async fn parse(&self, mut source: String) -> Result<String> {
225        for plugin in &self.plugins {
226            if let Some(config) = self.configs.get(plugin.name()) {
227                if !config.enabled {
228                    continue;
229                }
230            }
231            if let Some(new_source) = plugin.on_pre_parse(&source)? {
232                source = new_source;
233            }
234            if let Some(new_source) = plugin.on_parse(&source)? {
235                source = new_source;
236            }
237            if let Some(new_source) = plugin.on_post_parse(&source)? {
238                source = new_source;
239            }
240        }
241        Ok(source)
242    }
243
244    /// Transforms code through all registered plugins.
245    pub async fn transform(&self, mut code: String) -> Result<String> {
246        for plugin in &self.plugins {
247            if let Some(config) = self.configs.get(plugin.name()) {
248                if !config.enabled {
249                    continue;
250                }
251            }
252            if let Some(new_code) = plugin.on_pre_transform(&code)? {
253                code = new_code;
254            }
255            if let Some(new_code) = plugin.on_transform(&code)? {
256                code = new_code;
257            }
258            if let Some(new_code) = plugin.on_post_transform(&code)? {
259                code = new_code;
260            }
261        }
262        Ok(code)
263    }
264
265    /// Bundles code through all registered plugins.
266    pub async fn bundle(&self, mut bundle: String) -> Result<String> {
267        for plugin in &self.plugins {
268            if let Some(config) = self.configs.get(plugin.name()) {
269                if !config.enabled {
270                    continue;
271                }
272            }
273            if let Some(new_bundle) = plugin.on_pre_bundle(&bundle)? {
274                bundle = new_bundle;
275            }
276            if let Some(new_bundle) = plugin.on_bundle(&bundle)? {
277                bundle = new_bundle;
278            }
279            if let Some(new_bundle) = plugin.on_post_bundle(&bundle)? {
280                bundle = new_bundle;
281            }
282        }
283        Ok(bundle)
284    }
285
286    /// Cleans up all registered plugins.
287    pub fn cleanup_all(&self) -> Result<()> {
288        for plugin in &self.plugins {
289            plugin.on_cleanup()?;
290        }
291        Ok(())
292    }
293
294    /// Gets all registered plugins.
295    pub fn plugins(&self) -> &[Box<dyn Plugin>] {
296        &self.plugins
297    }
298
299    /// Gets a plugin by name.
300    pub fn get_plugin(&self, name: &str) -> Option<&Box<dyn Plugin>> {
301        self.plugins.iter().find(|p| p.name() == name)
302    }
303
304    /// Gets a plugin config by name.
305    pub fn get_config(&self, name: &str) -> Option<&PluginConfig> {
306        self.configs.get(name)
307    }
308
309    /// Enables or disables a plugin.
310    pub fn set_plugin_enabled(&mut self, name: &str, enabled: bool) -> Result<()> {
311        if let Some(config) = self.configs.get_mut(name) {
312            config.enabled = enabled;
313            Ok(())
314        }
315        else {
316            Err(nargo_types::Error::external_error("PluginManager".to_string(), format!("Plugin {} not found", name), nargo_types::Span::unknown()))
317        }
318    }
319
320    /// Gets all plugins info.
321    pub fn get_plugins_info(&self) -> Vec<(&str, &PluginConfig)> {
322        self.plugins.iter().filter_map(|p| self.configs.get(p.name()).map(|c| (p.name(), c))).collect()
323    }
324}