#![warn(missing_docs)]
use nargo_types::{NargoContext, NargoValue, Result};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum PluginLifecycle {
Init,
PreParse,
Parse,
PostParse,
PreTransform,
Transform,
PostTransform,
PreBundle,
Bundle,
PostBundle,
Cleanup,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginConfig {
pub name: String,
pub version: String,
pub description: String,
pub author: Option<String>,
pub homepage: Option<String>,
pub priority: i32,
pub config: HashMap<String, NargoValue>,
pub enabled: bool,
}
impl Default for PluginConfig {
fn default() -> Self {
Self { name: String::new(), version: "0.1.0".to_string(), description: String::new(), author: None, homepage: None, priority: 0, config: HashMap::new(), enabled: true }
}
}
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
fn version(&self) -> &str {
"0.1.0"
}
fn description(&self) -> &str {
""
}
fn author(&self) -> Option<&str> {
None
}
fn homepage(&self) -> Option<&str> {
None
}
fn priority(&self) -> i32 {
0
}
fn on_init(&self, _ctx: Arc<NargoContext>, _config: &PluginConfig) -> Result<()> {
Ok(())
}
fn on_pre_parse(&self, _source: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_parse(&self, _source: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_post_parse(&self, _source: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_pre_transform(&self, _code: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_transform(&self, _code: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_post_transform(&self, _code: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_pre_bundle(&self, _bundle: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_bundle(&self, _bundle: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_post_bundle(&self, _bundle: &str) -> Result<Option<String>> {
Ok(None)
}
fn on_cleanup(&self) -> Result<()> {
Ok(())
}
}
pub struct JsPlugin {
name: String,
version: String,
description: String,
}
impl JsPlugin {
pub fn new(name: String, version: String, description: String, _source: &str) -> Result<Self> {
Ok(Self { name, version, description })
}
}
impl Plugin for JsPlugin {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &str {
&self.version
}
fn description(&self) -> &str {
&self.description
}
}
pub struct PluginManager {
ctx: Arc<NargoContext>,
plugins: Vec<Box<dyn Plugin>>,
configs: HashMap<String, PluginConfig>,
}
impl PluginManager {
pub fn new(ctx: Arc<NargoContext>) -> Self {
Self { ctx, plugins: Vec::new(), configs: HashMap::new() }
}
pub fn register(&mut self, plugin: Box<dyn Plugin>) {
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() };
self.register_with_config(plugin, config);
}
pub fn register_with_config(&mut self, plugin: Box<dyn Plugin>, config: PluginConfig) {
tracing::info!("Registering plugin: {} v{}", plugin.name(), plugin.version());
self.configs.insert(plugin.name().to_string(), config);
self.plugins.push(plugin);
self.sort_plugins();
}
fn sort_plugins(&mut self) {
self.plugins.sort_by(|a, b| {
let a_priority = a.priority();
let b_priority = b.priority();
a_priority.cmp(&b_priority)
});
}
pub fn init_all(&self) -> Result<()> {
for plugin in &self.plugins {
if let Some(config) = self.configs.get(plugin.name()) {
if config.enabled {
plugin.on_init(self.ctx.clone(), config)?;
}
}
}
Ok(())
}
pub async fn parse(&self, mut source: String) -> Result<String> {
for plugin in &self.plugins {
if let Some(config) = self.configs.get(plugin.name()) {
if !config.enabled {
continue;
}
}
if let Some(new_source) = plugin.on_pre_parse(&source)? {
source = new_source;
}
if let Some(new_source) = plugin.on_parse(&source)? {
source = new_source;
}
if let Some(new_source) = plugin.on_post_parse(&source)? {
source = new_source;
}
}
Ok(source)
}
pub async fn transform(&self, mut code: String) -> Result<String> {
for plugin in &self.plugins {
if let Some(config) = self.configs.get(plugin.name()) {
if !config.enabled {
continue;
}
}
if let Some(new_code) = plugin.on_pre_transform(&code)? {
code = new_code;
}
if let Some(new_code) = plugin.on_transform(&code)? {
code = new_code;
}
if let Some(new_code) = plugin.on_post_transform(&code)? {
code = new_code;
}
}
Ok(code)
}
pub async fn bundle(&self, mut bundle: String) -> Result<String> {
for plugin in &self.plugins {
if let Some(config) = self.configs.get(plugin.name()) {
if !config.enabled {
continue;
}
}
if let Some(new_bundle) = plugin.on_pre_bundle(&bundle)? {
bundle = new_bundle;
}
if let Some(new_bundle) = plugin.on_bundle(&bundle)? {
bundle = new_bundle;
}
if let Some(new_bundle) = plugin.on_post_bundle(&bundle)? {
bundle = new_bundle;
}
}
Ok(bundle)
}
pub fn cleanup_all(&self) -> Result<()> {
for plugin in &self.plugins {
plugin.on_cleanup()?;
}
Ok(())
}
pub fn plugins(&self) -> &[Box<dyn Plugin>] {
&self.plugins
}
pub fn get_plugin(&self, name: &str) -> Option<&Box<dyn Plugin>> {
self.plugins.iter().find(|p| p.name() == name)
}
pub fn get_config(&self, name: &str) -> Option<&PluginConfig> {
self.configs.get(name)
}
pub fn set_plugin_enabled(&mut self, name: &str, enabled: bool) -> Result<()> {
if let Some(config) = self.configs.get_mut(name) {
config.enabled = enabled;
Ok(())
}
else {
Err(nargo_types::Error::external_error("PluginManager".to_string(), format!("Plugin {} not found", name), nargo_types::Span::unknown()))
}
}
pub fn get_plugins_info(&self) -> Vec<(&str, &PluginConfig)> {
self.plugins.iter().filter_map(|p| self.configs.get(p.name()).map(|c| (p.name(), c))).collect()
}
}