null_e/plugins/
registry.rs1use super::Plugin;
4use crate::core::{ProjectKind, ProjectMarker};
5use parking_lot::RwLock;
6use std::collections::HashMap;
7use std::path::Path;
8use std::sync::Arc;
9
10pub struct PluginRegistry {
12 plugins: RwLock<Vec<Arc<dyn Plugin>>>,
14 markers: RwLock<Vec<(ProjectMarker, Arc<dyn Plugin>)>>,
16 by_kind: RwLock<HashMap<ProjectKind, Vec<Arc<dyn Plugin>>>>,
18 cleanable_dirs: RwLock<HashMap<&'static str, Vec<Arc<dyn Plugin>>>>,
20}
21
22impl PluginRegistry {
23 pub fn new() -> Self {
25 Self {
26 plugins: RwLock::new(Vec::new()),
27 markers: RwLock::new(Vec::new()),
28 by_kind: RwLock::new(HashMap::new()),
29 cleanable_dirs: RwLock::new(HashMap::new()),
30 }
31 }
32
33 pub fn with_builtins() -> Self {
35 let registry = Self::new();
36
37 for plugin in super::builtin_plugins() {
38 registry.register(Arc::from(plugin));
39 }
40
41 registry
42 }
43
44 pub fn register(&self, plugin: Arc<dyn Plugin>) {
46 for marker in plugin.markers() {
48 self.markers.write().push((marker, Arc::clone(&plugin)));
49 }
50
51 for kind in plugin.supported_kinds() {
53 self.by_kind
54 .write()
55 .entry(*kind)
56 .or_default()
57 .push(Arc::clone(&plugin));
58 }
59
60 for dir in plugin.cleanable_dirs() {
62 self.cleanable_dirs
63 .write()
64 .entry(dir)
65 .or_default()
66 .push(Arc::clone(&plugin));
67 }
68
69 self.plugins.write().push(plugin);
70 }
71
72 pub fn all(&self) -> Vec<Arc<dyn Plugin>> {
74 self.plugins.read().clone()
75 }
76
77 pub fn all_markers(&self) -> Vec<ProjectMarker> {
79 self.markers.read().iter().map(|(m, _)| m.clone()).collect()
80 }
81
82 pub fn plugins_for_kind(&self, kind: ProjectKind) -> Vec<Arc<dyn Plugin>> {
84 self.by_kind.read().get(&kind).cloned().unwrap_or_default()
85 }
86
87 pub fn is_cleanable_dir(&self, name: &str) -> bool {
89 self.cleanable_dirs.read().contains_key(name)
90 }
91
92 pub fn plugins_for_cleanable_dir(&self, name: &str) -> Vec<Arc<dyn Plugin>> {
94 self.cleanable_dirs
95 .read()
96 .get(name)
97 .cloned()
98 .unwrap_or_default()
99 }
100
101 pub fn detect_project(&self, path: &Path) -> Option<(ProjectKind, Arc<dyn Plugin>)> {
103 let plugins = self.plugins.read();
104
105 let mut candidates: Vec<_> = plugins
107 .iter()
108 .filter_map(|p| p.detect(path).map(|k| (k, Arc::clone(p), p.priority())))
109 .collect();
110
111 candidates.sort_by(|a, b| b.2.cmp(&a.2));
113
114 candidates.into_iter().next().map(|(k, p, _)| (k, p))
115 }
116
117 pub fn all_cleanable_dir_names(&self) -> Vec<&'static str> {
119 self.cleanable_dirs.read().keys().copied().collect()
120 }
121
122 pub fn get_by_id(&self, id: &str) -> Option<Arc<dyn Plugin>> {
124 self.plugins.read().iter().find(|p| p.id() == id).cloned()
125 }
126
127 pub fn len(&self) -> usize {
129 self.plugins.read().len()
130 }
131
132 pub fn is_empty(&self) -> bool {
134 self.plugins.read().is_empty()
135 }
136}
137
138impl Default for PluginRegistry {
139 fn default() -> Self {
140 Self::with_builtins()
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_registry_with_builtins() {
150 let registry = PluginRegistry::with_builtins();
151 assert!(!registry.is_empty());
152 assert!(registry.len() >= 5); }
154
155 #[test]
156 fn test_cleanable_dirs() {
157 let registry = PluginRegistry::with_builtins();
158 assert!(registry.is_cleanable_dir("node_modules"));
159 assert!(registry.is_cleanable_dir("target"));
160 assert!(registry.is_cleanable_dir("__pycache__"));
161 assert!(!registry.is_cleanable_dir("src"));
162 }
163
164 #[test]
165 fn test_get_by_id() {
166 let registry = PluginRegistry::with_builtins();
167 let node = registry.get_by_id("node");
168 assert!(node.is_some());
169 assert_eq!(node.unwrap().id(), "node");
170 }
171}