Skip to main content

xls_rs/
plugins.rs

1//! Plugin system for custom functions
2//!
3//! Provides a trait-based plugin system for extending xls-rs with custom operations.
4
5use anyhow::Result;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Plugin metadata
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct PluginMetadata {
12    pub name: String,
13    pub version: String,
14    pub description: String,
15    pub author: Option<String>,
16    pub functions: Vec<FunctionMetadata>,
17}
18
19/// Function metadata
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct FunctionMetadata {
22    pub name: String,
23    pub description: String,
24    pub parameters: Vec<ParameterMetadata>,
25    pub return_type: String,
26}
27
28/// Parameter metadata
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ParameterMetadata {
31    pub name: String,
32    pub param_type: String,
33    pub required: bool,
34    pub default: Option<String>,
35    pub description: Option<String>,
36}
37
38/// Trait for plugin functions
39pub trait PluginFunction: Send + Sync {
40    fn name(&self) -> &str;
41    fn execute(&self, args: &[String], data: &[Vec<String>]) -> Result<Vec<Vec<String>>>;
42    fn metadata(&self) -> FunctionMetadata;
43}
44
45/// Plugin registry
46pub struct PluginRegistry {
47    plugins: HashMap<String, Box<dyn PluginFunction>>,
48    metadata: HashMap<String, PluginMetadata>,
49}
50
51impl PluginRegistry {
52    pub fn new() -> Self {
53        Self {
54            plugins: HashMap::new(),
55            metadata: HashMap::new(),
56        }
57    }
58
59    /// Register a plugin function
60    pub fn register<F>(&mut self, function: F)
61    where
62        F: PluginFunction + 'static,
63    {
64        let name = function.name().to_string();
65        let func_metadata = function.metadata();
66
67        // Update or create plugin metadata
68        let plugin_meta = self
69            .metadata
70            .entry(name.clone())
71            .or_insert_with(|| PluginMetadata {
72                name: name.clone(),
73                version: "1.0.0".to_string(),
74                description: format!("Plugin: {}", name),
75                author: None,
76                functions: Vec::new(),
77            });
78
79        plugin_meta.functions.push(func_metadata);
80        self.plugins.insert(name, Box::new(function));
81    }
82
83    /// Execute a plugin function
84    pub fn execute(
85        &self,
86        function_name: &str,
87        args: &[String],
88        data: &[Vec<String>],
89    ) -> Result<Vec<Vec<String>>> {
90        let function = self
91            .plugins
92            .get(function_name)
93            .ok_or_else(|| anyhow::anyhow!("Plugin function '{}' not found", function_name))?;
94
95        function.execute(args, data)
96    }
97
98    /// List all registered plugins
99    pub fn list_plugins(&self) -> Vec<&PluginMetadata> {
100        self.metadata.values().collect()
101    }
102
103    /// Get plugin metadata
104    pub fn get_metadata(&self, name: &str) -> Option<&PluginMetadata> {
105        self.metadata.get(name)
106    }
107}
108
109/// Example plugin: Uppercase transformation
110pub struct UppercasePlugin;
111
112impl PluginFunction for UppercasePlugin {
113    fn name(&self) -> &str {
114        "uppercase"
115    }
116
117    fn execute(&self, args: &[String], data: &[Vec<String>]) -> Result<Vec<Vec<String>>> {
118        if args.is_empty() {
119            anyhow::bail!("Column index required");
120        }
121
122        let col_idx: usize = args[0]
123            .parse()
124            .map_err(|_| anyhow::anyhow!("Invalid column index: {}", args[0]))?;
125
126        let mut result = data.to_vec();
127
128        for row in result.iter_mut().skip(1) {
129            // Skip header
130            if let Some(cell) = row.get_mut(col_idx) {
131                *cell = cell.to_uppercase();
132            }
133        }
134
135        Ok(result)
136    }
137
138    fn metadata(&self) -> FunctionMetadata {
139        FunctionMetadata {
140            name: "uppercase".to_string(),
141            description: "Convert column values to uppercase".to_string(),
142            parameters: vec![ParameterMetadata {
143                name: "column".to_string(),
144                param_type: "usize".to_string(),
145                required: true,
146                default: None,
147                description: Some("Column index to transform".to_string()),
148            }],
149            return_type: "Vec<Vec<String>>".to_string(),
150        }
151    }
152}
153
154/// Example plugin: Add prefix
155pub struct PrefixPlugin;
156
157impl PluginFunction for PrefixPlugin {
158    fn name(&self) -> &str {
159        "prefix"
160    }
161
162    fn execute(&self, args: &[String], data: &[Vec<String>]) -> Result<Vec<Vec<String>>> {
163        if args.len() < 2 {
164            anyhow::bail!("Column index and prefix required");
165        }
166
167        let col_idx: usize = args[0]
168            .parse()
169            .map_err(|_| anyhow::anyhow!("Invalid column index: {}", args[0]))?;
170        let prefix = &args[1];
171
172        let mut result = data.to_vec();
173
174        for row in result.iter_mut().skip(1) {
175            // Skip header
176            if let Some(cell) = row.get_mut(col_idx) {
177                *cell = format!("{}{}", prefix, cell);
178            }
179        }
180
181        Ok(result)
182    }
183
184    fn metadata(&self) -> FunctionMetadata {
185        FunctionMetadata {
186            name: "prefix".to_string(),
187            description: "Add prefix to column values".to_string(),
188            parameters: vec![
189                ParameterMetadata {
190                    name: "column".to_string(),
191                    param_type: "usize".to_string(),
192                    required: true,
193                    default: None,
194                    description: Some("Column index to transform".to_string()),
195                },
196                ParameterMetadata {
197                    name: "prefix".to_string(),
198                    param_type: "String".to_string(),
199                    required: true,
200                    default: None,
201                    description: Some("Prefix to add".to_string()),
202                },
203            ],
204            return_type: "Vec<Vec<String>>".to_string(),
205        }
206    }
207}
208
209impl Default for PluginRegistry {
210    fn default() -> Self {
211        let mut registry = Self::new();
212
213        // Register built-in plugins
214        registry.register(UppercasePlugin);
215        registry.register(PrefixPlugin);
216
217        registry
218    }
219}