1#[allow(dead_code)]
4#[derive(Clone, PartialEq, Debug)]
5pub enum PluginState {
6 Unloaded,
7 Loaded,
8 Active,
9 Error(String),
10}
11
12#[allow(dead_code)]
13pub struct PluginMetadata {
14 pub id: String,
15 pub name: String,
16 pub version: [u32; 3], pub author: String,
18 pub description: String,
19 pub dependencies: Vec<String>,
20}
21
22#[allow(dead_code)]
23pub struct Plugin {
24 pub metadata: PluginMetadata,
25 pub state: PluginState,
26 pub load_order: u32,
27}
28
29#[allow(dead_code)]
30pub struct PluginApiRegistry {
31 pub plugins: Vec<Plugin>,
32 pub next_order: u32,
33}
34
35#[allow(dead_code)]
36pub fn new_registry() -> PluginApiRegistry {
37 PluginApiRegistry {
38 plugins: Vec::new(),
39 next_order: 0,
40 }
41}
42
43#[allow(dead_code)]
44pub fn register_plugin(registry: &mut PluginApiRegistry, meta: PluginMetadata) -> usize {
45 let order = registry.next_order;
46 registry.next_order += 1;
47 let plugin = Plugin {
48 metadata: meta,
49 state: PluginState::Loaded,
50 load_order: order,
51 };
52 registry.plugins.push(plugin);
53 registry.plugins.len() - 1
54}
55
56#[allow(dead_code)]
57pub fn get_plugin<'a>(registry: &'a PluginApiRegistry, id: &str) -> Option<&'a Plugin> {
58 registry.plugins.iter().find(|p| p.metadata.id == id)
59}
60
61#[allow(dead_code)]
62pub fn activate_plugin(registry: &mut PluginApiRegistry, id: &str) -> bool {
63 if let Some(p) = registry.plugins.iter_mut().find(|p| p.metadata.id == id) {
64 match p.state {
65 PluginState::Loaded | PluginState::Unloaded => {
66 p.state = PluginState::Active;
67 true
68 }
69 _ => false,
70 }
71 } else {
72 false
73 }
74}
75
76#[allow(dead_code)]
77pub fn deactivate_plugin(registry: &mut PluginApiRegistry, id: &str) -> bool {
78 if let Some(p) = registry.plugins.iter_mut().find(|p| p.metadata.id == id) {
79 if p.state == PluginState::Active {
80 p.state = PluginState::Loaded;
81 true
82 } else {
83 false
84 }
85 } else {
86 false
87 }
88}
89
90#[allow(dead_code)]
91pub fn unload_plugin(registry: &mut PluginApiRegistry, id: &str) -> bool {
92 if let Some(p) = registry.plugins.iter_mut().find(|p| p.metadata.id == id) {
93 p.state = PluginState::Unloaded;
94 true
95 } else {
96 false
97 }
98}
99
100#[allow(dead_code)]
101pub fn set_plugin_error(registry: &mut PluginApiRegistry, id: &str, msg: &str) {
102 if let Some(p) = registry.plugins.iter_mut().find(|p| p.metadata.id == id) {
103 p.state = PluginState::Error(msg.to_string());
104 }
105}
106
107#[allow(dead_code)]
108pub fn active_plugins(registry: &PluginApiRegistry) -> Vec<&Plugin> {
109 registry
110 .plugins
111 .iter()
112 .filter(|p| p.state == PluginState::Active)
113 .collect()
114}
115
116#[allow(dead_code)]
117pub fn plugin_count(registry: &PluginApiRegistry) -> usize {
118 registry.plugins.len()
119}
120
121#[allow(dead_code)]
122pub fn has_dependency(registry: &PluginApiRegistry, plugin_id: &str, dep_id: &str) -> bool {
123 if let Some(p) = get_plugin(registry, plugin_id) {
124 p.metadata.dependencies.iter().any(|d| d == dep_id)
125 } else {
126 false
127 }
128}
129
130#[allow(dead_code)]
132pub fn dependency_order(registry: &PluginApiRegistry) -> Vec<&str> {
133 let n = registry.plugins.len();
134 let mut in_degree = vec![0usize; n];
136 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
137
138 for (i, plugin) in registry.plugins.iter().enumerate() {
139 for dep in &plugin.metadata.dependencies {
140 if let Some(j) = registry.plugins.iter().position(|p| &p.metadata.id == dep) {
141 adj[j].push(i);
142 in_degree[i] += 1;
143 }
144 }
145 }
146
147 let mut queue: Vec<usize> = (0..n).filter(|&i| in_degree[i] == 0).collect();
148 let mut result = Vec::new();
149
150 while !queue.is_empty() {
151 let node = queue.remove(0);
152 result.push(registry.plugins[node].metadata.id.as_str());
153 for &next in &adj[node] {
154 in_degree[next] -= 1;
155 if in_degree[next] == 0 {
156 queue.push(next);
157 }
158 }
159 }
160
161 result
162}
163
164#[allow(dead_code)]
165pub fn plugin_version_string(plugin: &Plugin) -> String {
166 let [major, minor, patch] = plugin.metadata.version;
167 format!("{}.{}.{}", major, minor, patch)
168}
169
170#[allow(dead_code)]
171pub fn check_dependencies_met(registry: &PluginApiRegistry, plugin_id: &str) -> bool {
172 if let Some(plugin) = get_plugin(registry, plugin_id) {
173 plugin.metadata.dependencies.iter().all(|dep| {
174 if let Some(dep_plugin) = get_plugin(registry, dep) {
175 dep_plugin.state == PluginState::Active || dep_plugin.state == PluginState::Loaded
176 } else {
177 false
178 }
179 })
180 } else {
181 false
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 fn make_meta(id: &str, deps: Vec<&str>) -> PluginMetadata {
190 PluginMetadata {
191 id: id.to_string(),
192 name: format!("Plugin {}", id),
193 version: [1, 0, 0],
194 author: "test".to_string(),
195 description: "test plugin".to_string(),
196 dependencies: deps.into_iter().map(|s| s.to_string()).collect(),
197 }
198 }
199
200 #[test]
201 fn test_new_registry_empty() {
202 let reg = new_registry();
203 assert_eq!(reg.plugins.len(), 0);
204 assert_eq!(reg.next_order, 0);
205 }
206
207 #[test]
208 fn test_register_plugin() {
209 let mut reg = new_registry();
210 let idx = register_plugin(&mut reg, make_meta("foo", vec![]));
211 assert_eq!(idx, 0);
212 assert_eq!(reg.plugins.len(), 1);
213 assert_eq!(reg.plugins[0].metadata.id, "foo");
214 assert_eq!(reg.plugins[0].state, PluginState::Loaded);
215 }
216
217 #[test]
218 fn test_get_plugin_found() {
219 let mut reg = new_registry();
220 register_plugin(&mut reg, make_meta("bar", vec![]));
221 let p = get_plugin(®, "bar");
222 assert!(p.is_some());
223 assert_eq!(p.expect("should succeed").metadata.id, "bar");
224 }
225
226 #[test]
227 fn test_get_plugin_not_found() {
228 let reg = new_registry();
229 assert!(get_plugin(®, "missing").is_none());
230 }
231
232 #[test]
233 fn test_activate_plugin() {
234 let mut reg = new_registry();
235 register_plugin(&mut reg, make_meta("baz", vec![]));
236 assert!(activate_plugin(&mut reg, "baz"));
237 assert_eq!(
238 get_plugin(®, "baz").expect("should succeed").state,
239 PluginState::Active
240 );
241 }
242
243 #[test]
244 fn test_activate_plugin_missing() {
245 let mut reg = new_registry();
246 assert!(!activate_plugin(&mut reg, "ghost"));
247 }
248
249 #[test]
250 fn test_deactivate_plugin() {
251 let mut reg = new_registry();
252 register_plugin(&mut reg, make_meta("qux", vec![]));
253 activate_plugin(&mut reg, "qux");
254 assert!(deactivate_plugin(&mut reg, "qux"));
255 assert_eq!(
256 get_plugin(®, "qux").expect("should succeed").state,
257 PluginState::Loaded
258 );
259 }
260
261 #[test]
262 fn test_active_plugins_list() {
263 let mut reg = new_registry();
264 register_plugin(&mut reg, make_meta("a", vec![]));
265 register_plugin(&mut reg, make_meta("b", vec![]));
266 activate_plugin(&mut reg, "a");
267 let active = active_plugins(®);
268 assert_eq!(active.len(), 1);
269 assert_eq!(active[0].metadata.id, "a");
270 }
271
272 #[test]
273 fn test_set_plugin_error() {
274 let mut reg = new_registry();
275 register_plugin(&mut reg, make_meta("err_plugin", vec![]));
276 set_plugin_error(&mut reg, "err_plugin", "init failed");
277 let p = get_plugin(®, "err_plugin").expect("should succeed");
278 assert!(matches!(&p.state, PluginState::Error(msg) if msg == "init failed"));
279 }
280
281 #[test]
282 fn test_plugin_version_string() {
283 let mut reg = new_registry();
284 register_plugin(
285 &mut reg,
286 PluginMetadata {
287 id: "ver".to_string(),
288 name: "Ver".to_string(),
289 version: [2, 3, 4],
290 author: "test".to_string(),
291 description: "".to_string(),
292 dependencies: vec![],
293 },
294 );
295 let p = get_plugin(®, "ver").expect("should succeed");
296 assert_eq!(plugin_version_string(p), "2.3.4");
297 }
298
299 #[test]
300 fn test_has_dependency_true() {
301 let mut reg = new_registry();
302 register_plugin(&mut reg, make_meta("dep_a", vec![]));
303 register_plugin(&mut reg, make_meta("dep_b", vec!["dep_a"]));
304 assert!(has_dependency(®, "dep_b", "dep_a"));
305 }
306
307 #[test]
308 fn test_has_dependency_false() {
309 let mut reg = new_registry();
310 register_plugin(&mut reg, make_meta("solo", vec![]));
311 assert!(!has_dependency(®, "solo", "nonexistent"));
312 }
313
314 #[test]
315 fn test_dependency_order() {
316 let mut reg = new_registry();
317 register_plugin(&mut reg, make_meta("base", vec![]));
318 register_plugin(&mut reg, make_meta("mid", vec!["base"]));
319 register_plugin(&mut reg, make_meta("top", vec!["mid"]));
320 let order = dependency_order(®);
321 assert_eq!(order.len(), 3);
322 let base_pos = order
323 .iter()
324 .position(|&s| s == "base")
325 .expect("should succeed");
326 let mid_pos = order
327 .iter()
328 .position(|&s| s == "mid")
329 .expect("should succeed");
330 let top_pos = order
331 .iter()
332 .position(|&s| s == "top")
333 .expect("should succeed");
334 assert!(base_pos < mid_pos);
335 assert!(mid_pos < top_pos);
336 }
337
338 #[test]
339 fn test_check_dependencies_met() {
340 let mut reg = new_registry();
341 register_plugin(&mut reg, make_meta("lib", vec![]));
342 register_plugin(&mut reg, make_meta("app", vec!["lib"]));
343 assert!(check_dependencies_met(®, "app"));
345 }
346
347 #[test]
348 fn test_plugin_count() {
349 let mut reg = new_registry();
350 register_plugin(&mut reg, make_meta("x", vec![]));
351 register_plugin(&mut reg, make_meta("y", vec![]));
352 assert_eq!(plugin_count(®), 2);
353 }
354}