pocket_cli/plugins/
mod.rs1pub mod backup;
7
8use std::collections::HashMap;
9use std::path::Path;
10use anyhow::{Result, Context};
11use serde::{Serialize, Deserialize};
12
13pub trait Plugin: Send + Sync {
15 fn name(&self) -> &str;
17
18 fn version(&self) -> &str;
20
21 fn description(&self) -> &str;
23
24 fn initialize(&mut self, config: &PluginConfig) -> Result<()>;
26
27 fn execute(&self, command: &str, args: &[String]) -> Result<()>;
29
30 fn commands(&self) -> Vec<PluginCommand>;
32
33 fn cleanup(&mut self) -> Result<()>;
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct PluginConfig {
40 pub name: String,
42
43 pub enabled: bool,
45
46 #[serde(default)]
48 pub options: HashMap<String, serde_json::Value>,
49}
50
51#[derive(Debug, Clone)]
53pub struct PluginCommand {
54 pub name: String,
56
57 pub description: String,
59
60 pub usage: String,
62}
63
64pub struct PluginManager {
66 plugins: Vec<Box<dyn Plugin>>,
68
69 configs: HashMap<String, PluginConfig>,
71
72 plugin_dir: std::path::PathBuf,
74}
75
76impl PluginManager {
77 pub fn new(plugin_dir: impl AsRef<Path>) -> Self {
79 Self {
80 plugins: Vec::new(),
81 configs: HashMap::new(),
82 plugin_dir: plugin_dir.as_ref().to_path_buf(),
83 }
84 }
85
86 pub fn load_plugins(&mut self) -> Result<()> {
88 if !self.plugin_dir.exists() {
90 std::fs::create_dir_all(&self.plugin_dir)
91 .context("Failed to create plugin directory")?;
92 return Ok(());
93 }
94
95 self.load_configs()?;
97
98 self.register_builtin_plugins()?;
104
105 Ok(())
106 }
107
108 fn load_configs(&mut self) -> Result<()> {
110 let config_path = self.plugin_dir.join("plugins.json");
111
112 if !config_path.exists() {
113 let default_configs: HashMap<String, PluginConfig> = HashMap::new();
115 let json = serde_json::to_string_pretty(&default_configs)?;
116 std::fs::write(&config_path, json)?;
117 return Ok(());
118 }
119
120 let json = std::fs::read_to_string(&config_path)?;
122 self.configs = serde_json::from_str(&json)?;
123
124 Ok(())
125 }
126
127 pub fn save_configs(&self) -> Result<()> {
129 let config_path = self.plugin_dir.join("plugins.json");
130 let json = serde_json::to_string_pretty(&self.configs)?;
131 std::fs::write(&config_path, json)?;
132 Ok(())
133 }
134
135 fn register_builtin_plugins(&mut self) -> Result<()> {
137 use crate::plugins::backup::BackupPlugin;
139
140 let data_dir = self.plugin_dir.parent().unwrap_or(&self.plugin_dir).to_path_buf();
142
143 self.register_plugin(Box::new(BackupPlugin::new(data_dir)))?;
145
146 Ok(())
147 }
148
149 pub fn register_plugin(&mut self, mut plugin: Box<dyn Plugin>) -> Result<()> {
151 let name = plugin.name().to_string();
152
153 let config = self.configs.entry(name.clone()).or_insert_with(|| {
155 PluginConfig {
156 name: name.clone(),
157 enabled: true,
158 options: HashMap::new(),
159 }
160 });
161
162 plugin.initialize(config)?;
164
165 self.plugins.push(plugin);
167
168 Ok(())
169 }
170
171 pub fn list_plugins(&self) -> Vec<(&str, &str, bool)> {
173 self.plugins.iter()
174 .map(|p| {
175 let name = p.name();
176 let version = p.version();
177 let enabled = self.configs.get(name)
178 .map(|c| c.enabled)
179 .unwrap_or(false);
180 (name, version, enabled)
181 })
182 .collect()
183 }
184
185 pub fn enable_plugin(&mut self, name: &str) -> Result<()> {
187 if let Some(config) = self.configs.get_mut(name) {
188 config.enabled = true;
189 self.save_configs()?;
190 Ok(())
191 } else {
192 anyhow::bail!("Plugin '{}' not found", name)
193 }
194 }
195
196 pub fn disable_plugin(&mut self, name: &str) -> Result<()> {
198 if let Some(config) = self.configs.get_mut(name) {
199 config.enabled = false;
200 self.save_configs()?;
201 Ok(())
202 } else {
203 anyhow::bail!("Plugin '{}' not found", name)
204 }
205 }
206
207 pub fn execute_command(&self, plugin_name: &str, command: &str, args: &[String]) -> Result<()> {
209 let plugin = self.plugins.iter()
211 .find(|p| p.name() == plugin_name)
212 .ok_or_else(|| anyhow::anyhow!("Plugin '{}' not found", plugin_name))?;
213
214 let enabled = self.configs.get(plugin_name)
216 .map(|c| c.enabled)
217 .unwrap_or(false);
218
219 if !enabled {
220 anyhow::bail!("Plugin '{}' is disabled", plugin_name);
221 }
222
223 plugin.execute(command, args)
225 }
226
227 pub fn list_commands(&self) -> Vec<(String, Vec<PluginCommand>)> {
229 self.plugins.iter()
230 .filter(|p| {
231 self.configs.get(p.name())
232 .map(|c| c.enabled)
233 .unwrap_or(false)
234 })
235 .map(|p| (p.name().to_string(), p.commands()))
236 .collect()
237 }
238
239 pub fn cleanup(&mut self) -> Result<()> {
241 for plugin in &mut self.plugins {
242 plugin.cleanup()?;
243 }
244 Ok(())
245 }
246}
247
248impl Drop for PluginManager {
249 fn drop(&mut self) {
250 let _ = self.cleanup();
252 }
253}