1use std::collections::HashMap;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum FeatureType {
17 Builtin,
18 Condition,
19 MathFunc,
20 Parameter,
21 Hook,
22}
23
24#[derive(Debug, Clone)]
26pub struct ModuleFeature {
27 pub name: String,
28 pub feature_type: FeatureType,
29 pub enabled: bool,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum ModuleState {
35 Loaded,
36 Autoloaded,
37 Unloaded,
38 Failed,
39}
40
41#[derive(Debug, Clone)]
43pub struct Module {
44 pub name: String,
45 pub state: ModuleState,
46 pub features: Vec<ModuleFeature>,
47 pub deps: Vec<String>,
48 pub autoloads: Vec<String>,
49}
50
51impl Module {
52 pub fn new(name: &str) -> Self {
53 Module {
54 name: name.to_string(),
55 state: ModuleState::Loaded,
56 features: Vec::new(),
57 deps: Vec::new(),
58 autoloads: Vec::new(),
59 }
60 }
61
62 pub fn is_loaded(&self) -> bool {
63 self.state == ModuleState::Loaded
64 }
65}
66
67#[derive(Debug, Default)]
69pub struct ModuleTable {
70 modules: HashMap<String, Module>,
71 autoload_builtins: HashMap<String, String>,
73 autoload_conditions: HashMap<String, String>,
75 autoload_params: HashMap<String, String>,
77 autoload_mathfuncs: HashMap<String, String>,
79 hooks: HashMap<String, Vec<String>>,
81 wrappers: Vec<Wrapper>,
83}
84
85#[derive(Debug, Clone)]
87pub struct Wrapper {
88 pub name: String,
89 pub flags: u32,
90 pub module: String,
91}
92
93impl ModuleTable {
94 pub fn new() -> Self {
95 let mut table = Self::default();
96 table.register_builtin_modules();
97 table
98 }
99
100 fn register_builtin_modules(&mut self) {
102 let builtin_modules = [
103 (
104 "zsh/complete",
105 &[
106 "compctl",
107 "compcall",
108 "comparguments",
109 "compdescribe",
110 "compfiles",
111 "compgroups",
112 "compquote",
113 "comptags",
114 "comptry",
115 "compvalues",
116 ][..],
117 ),
118 ("zsh/complist", &["complist"][..]),
119 ("zsh/computil", &["compadd", "compset"][..]),
120 ("zsh/datetime", &["strftime"][..]),
121 (
122 "zsh/files",
123 &[
124 "mkdir", "rmdir", "ln", "mv", "cp", "rm", "chmod", "chown", "sync",
125 ][..],
126 ),
127 ("zsh/langinfo", &[][..]),
128 ("zsh/mapfile", &[][..]),
129 ("zsh/mathfunc", &[][..]),
130 ("zsh/nearcolor", &[][..]),
131 ("zsh/net/socket", &["zsocket"][..]),
132 ("zsh/net/tcp", &["ztcp"][..]),
133 ("zsh/parameter", &[][..]),
134 (
135 "zsh/pcre",
136 &["pcre_compile", "pcre_match", "pcre_study"][..],
137 ),
138 ("zsh/regex", &[][..]),
139 ("zsh/sched", &["sched"][..]),
140 ("zsh/stat", &["zstat"][..]),
141 (
142 "zsh/system",
143 &[
144 "sysread", "syswrite", "sysopen", "sysseek", "syserror", "zsystem",
145 ][..],
146 ),
147 ("zsh/termcap", &["echotc"][..]),
148 ("zsh/terminfo", &["echoti"][..]),
149 ("zsh/watch", &["log"][..]),
150 ("zsh/zftp", &["zftp"][..]),
151 ("zsh/zleparameter", &[][..]),
152 ("zsh/zprof", &["zprof"][..]),
153 ("zsh/zpty", &["zpty"][..]),
154 ("zsh/zselect", &["zselect"][..]),
155 (
156 "zsh/zutil",
157 &["zstyle", "zformat", "zparseopts", "zregexparse"][..],
158 ),
159 (
160 "zsh/attr",
161 &["zgetattr", "zsetattr", "zdelattr", "zlistattr"][..],
162 ),
163 ("zsh/cap", &["cap", "getcap", "setcap"][..]),
164 ("zsh/clone", &["clone"][..]),
165 ("zsh/curses", &["zcurses"][..]),
166 ("zsh/db/gdbm", &["ztie", "zuntie", "zgdbmpath"][..]),
167 ("zsh/param/private", &["private"][..]),
168 ];
169
170 for (name, builtins) in &builtin_modules {
171 let mut module = Module::new(name);
172 for builtin in *builtins {
173 module.features.push(ModuleFeature {
174 name: builtin.to_string(),
175 feature_type: FeatureType::Builtin,
176 enabled: true,
177 });
178 }
179 self.modules.insert(name.to_string(), module);
180 }
181 }
182
183 pub fn load_module(&mut self, name: &str) -> bool {
185 if self.modules.contains_key(name) {
186 if let Some(m) = self.modules.get_mut(name) {
187 m.state = ModuleState::Loaded;
188 }
189 return true;
190 }
191 false
193 }
194
195 pub fn unload_module(&mut self, name: &str) -> bool {
197 if let Some(module) = self.modules.get_mut(name) {
198 module.state = ModuleState::Unloaded;
199 return true;
200 }
201 false
202 }
203
204 pub fn is_loaded(&self, name: &str) -> bool {
206 self.modules
207 .get(name)
208 .map(|m| m.is_loaded())
209 .unwrap_or(false)
210 }
211
212 pub fn list_loaded(&self) -> Vec<&str> {
214 self.modules
215 .iter()
216 .filter(|(_, m)| m.is_loaded())
217 .map(|(name, _)| name.as_str())
218 .collect()
219 }
220
221 pub fn list_all(&self) -> Vec<(&str, &ModuleState)> {
223 self.modules
224 .iter()
225 .map(|(name, m)| (name.as_str(), &m.state))
226 .collect()
227 }
228
229 pub fn addbuiltin(&mut self, name: &str, module: &str) {
233 if let Some(m) = self.modules.get_mut(module) {
234 m.features.push(ModuleFeature {
235 name: name.to_string(),
236 feature_type: FeatureType::Builtin,
237 enabled: true,
238 });
239 }
240 }
241
242 pub fn deletebuiltin(&mut self, name: &str, module: &str) {
244 if let Some(m) = self.modules.get_mut(module) {
245 m.features
246 .retain(|f| f.name != name || f.feature_type != FeatureType::Builtin);
247 }
248 }
249
250 pub fn add_autobin(&mut self, name: &str, module: &str) {
252 self.autoload_builtins
253 .insert(name.to_string(), module.to_string());
254 }
255
256 pub fn del_autobin(&mut self, name: &str) {
258 self.autoload_builtins.remove(name);
259 }
260
261 pub fn setbuiltins(&mut self, module: &str, names: &[&str]) {
263 for name in names {
264 self.addbuiltin(name, module);
265 }
266 }
267
268 pub fn addconddef(&mut self, name: &str, module: &str) {
272 if let Some(m) = self.modules.get_mut(module) {
273 m.features.push(ModuleFeature {
274 name: name.to_string(),
275 feature_type: FeatureType::Condition,
276 enabled: true,
277 });
278 }
279 }
280
281 pub fn deleteconddef(&mut self, name: &str, module: &str) {
283 if let Some(m) = self.modules.get_mut(module) {
284 m.features
285 .retain(|f| f.name != name || f.feature_type != FeatureType::Condition);
286 }
287 }
288
289 pub fn getconddef(&self, name: &str) -> Option<&str> {
291 for (mod_name, module) in &self.modules {
292 for feature in &module.features {
293 if feature.name == name && feature.feature_type == FeatureType::Condition {
294 return Some(mod_name);
295 }
296 }
297 }
298 None
299 }
300
301 pub fn add_autocond(&mut self, name: &str, module: &str) {
303 self.autoload_conditions
304 .insert(name.to_string(), module.to_string());
305 }
306
307 pub fn del_autocond(&mut self, name: &str) {
309 self.autoload_conditions.remove(name);
310 }
311
312 pub fn addhookdef(&mut self, name: &str) {
316 self.hooks.entry(name.to_string()).or_default();
317 }
318
319 pub fn addhookdefs(&mut self, names: &[&str]) {
321 for name in names {
322 self.addhookdef(name);
323 }
324 }
325
326 pub fn deletehookdef(&mut self, name: &str) {
328 self.hooks.remove(name);
329 }
330
331 pub fn deletehookdefs(&mut self, names: &[&str]) {
333 for name in names {
334 self.deletehookdef(name);
335 }
336 }
337
338 pub fn addhookfunc(&mut self, hook: &str, func: &str) {
340 self.hooks
341 .entry(hook.to_string())
342 .or_default()
343 .push(func.to_string());
344 }
345
346 pub fn deletehookfunc(&mut self, hook: &str, func: &str) {
348 if let Some(funcs) = self.hooks.get_mut(hook) {
349 funcs.retain(|f| f != func);
350 }
351 }
352
353 pub fn gethookdef(&self, name: &str) -> Option<&Vec<String>> {
355 self.hooks.get(name)
356 }
357
358 pub fn runhookdef(&self, name: &str) -> Vec<String> {
360 self.hooks.get(name).cloned().unwrap_or_default()
361 }
362
363 pub fn addparamdef(&mut self, name: &str, module: &str) {
367 if let Some(m) = self.modules.get_mut(module) {
368 m.features.push(ModuleFeature {
369 name: name.to_string(),
370 feature_type: FeatureType::Parameter,
371 enabled: true,
372 });
373 }
374 }
375
376 pub fn deleteparamdef(&mut self, name: &str, module: &str) {
378 if let Some(m) = self.modules.get_mut(module) {
379 m.features
380 .retain(|f| f.name != name || f.feature_type != FeatureType::Parameter);
381 }
382 }
383
384 pub fn setparamdefs(&mut self, module: &str, names: &[&str]) {
386 for name in names {
387 self.addparamdef(name, module);
388 }
389 }
390
391 pub fn add_autoparam(&mut self, name: &str, module: &str) {
393 self.autoload_params
394 .insert(name.to_string(), module.to_string());
395 }
396
397 pub fn del_autoparam(&mut self, name: &str) {
399 self.autoload_params.remove(name);
400 }
401
402 pub fn addwrapper(&mut self, name: &str, flags: u32, module: &str) {
406 self.wrappers.push(Wrapper {
407 name: name.to_string(),
408 flags,
409 module: module.to_string(),
410 });
411 }
412
413 pub fn deletewrapper(&mut self, module: &str, name: &str) {
415 self.wrappers
416 .retain(|w| w.module != module || w.name != name);
417 }
418
419 pub fn enable_feature(&mut self, module: &str, name: &str) -> bool {
423 if let Some(m) = self.modules.get_mut(module) {
424 for feature in &mut m.features {
425 if feature.name == name {
426 feature.enabled = true;
427 return true;
428 }
429 }
430 }
431 false
432 }
433
434 pub fn disable_feature(&mut self, module: &str, name: &str) -> bool {
436 if let Some(m) = self.modules.get_mut(module) {
437 for feature in &mut m.features {
438 if feature.name == name {
439 feature.enabled = false;
440 return true;
441 }
442 }
443 }
444 false
445 }
446
447 pub fn list_features(&self, module: &str) -> Vec<&ModuleFeature> {
449 self.modules
450 .get(module)
451 .map(|m| m.features.iter().collect())
452 .unwrap_or_default()
453 }
454
455 pub fn module_linked(&self, name: &str) -> bool {
457 self.modules.contains_key(name)
458 }
459
460 pub fn resolve_autoload_builtin(&self, name: &str) -> Option<&str> {
462 self.autoload_builtins.get(name).map(|s| s.as_str())
463 }
464
465 pub fn resolve_autoload_param(&self, name: &str) -> Option<&str> {
467 self.autoload_params.get(name).map(|s| s.as_str())
468 }
469
470 pub fn ensurefeature(&mut self, module: &str, feature: &str) -> bool {
472 if !self.is_loaded(module) {
473 self.load_module(module);
474 }
475 self.is_loaded(module)
476 }
477}
478
479pub trait ModuleLifecycle {
481 fn setup(&mut self) -> i32 {
482 0
483 }
484 fn boot(&mut self) -> i32 {
485 0
486 }
487 fn cleanup(&mut self) -> i32 {
488 0
489 }
490 fn finish(&mut self) -> i32 {
491 0
492 }
493}
494
495pub fn freemodulenode(_module: Module) {
497 }
499
500pub fn printmodulenode(name: &str, module: &Module) -> String {
502 let state = match module.state {
503 ModuleState::Loaded => "loaded",
504 ModuleState::Autoloaded => "autoloaded",
505 ModuleState::Unloaded => "unloaded",
506 ModuleState::Failed => "failed",
507 };
508 format!("{} ({})", name, state)
509}
510
511pub fn newmoduletable() -> ModuleTable {
513 ModuleTable::new()
514}
515
516pub fn register_module(table: &mut ModuleTable, name: &str) -> bool {
518 if table.modules.contains_key(name) {
519 return false;
520 }
521 table.modules.insert(name.to_string(), Module::new(name));
522 true
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_module_table_new() {
531 let table = ModuleTable::new();
532 assert!(table.is_loaded("zsh/complete"));
533 assert!(table.is_loaded("zsh/datetime"));
534 assert!(table.is_loaded("zsh/system"));
535 assert!(!table.is_loaded("nonexistent"));
536 }
537
538 #[test]
539 fn test_load_unload() {
540 let mut table = ModuleTable::new();
541 assert!(table.is_loaded("zsh/complete"));
542
543 table.unload_module("zsh/complete");
544 assert!(!table.is_loaded("zsh/complete"));
545
546 table.load_module("zsh/complete");
547 assert!(table.is_loaded("zsh/complete"));
548 }
549
550 #[test]
551 fn test_list_loaded() {
552 let table = ModuleTable::new();
553 let loaded = table.list_loaded();
554 assert!(loaded.len() > 20);
555 assert!(loaded.contains(&"zsh/complete"));
556 }
557
558 #[test]
559 fn test_hooks() {
560 let mut table = ModuleTable::new();
561 table.addhookdef("chpwd");
562 table.addhookfunc("chpwd", "my_chpwd_handler");
563
564 let funcs = table.runhookdef("chpwd");
565 assert_eq!(funcs, vec!["my_chpwd_handler"]);
566
567 table.deletehookfunc("chpwd", "my_chpwd_handler");
568 let funcs = table.runhookdef("chpwd");
569 assert!(funcs.is_empty());
570 }
571
572 #[test]
573 fn test_autoload() {
574 let mut table = ModuleTable::new();
575 table.add_autobin("my_cmd", "zsh/mymodule");
576 assert_eq!(
577 table.resolve_autoload_builtin("my_cmd"),
578 Some("zsh/mymodule")
579 );
580 assert_eq!(table.resolve_autoload_builtin("nonexistent"), None);
581 }
582
583 #[test]
584 fn test_features() {
585 let table = ModuleTable::new();
586 let features = table.list_features("zsh/complete");
587 assert!(!features.is_empty());
588 assert!(features.iter().any(|f| f.name == "compctl"));
589 }
590
591 #[test]
592 fn test_module_linked() {
593 let table = ModuleTable::new();
594 assert!(table.module_linked("zsh/complete"));
595 assert!(table.module_linked("zsh/stat"));
596 assert!(!table.module_linked("zsh/nonexistent"));
597 }
598
599 #[test]
600 fn test_wrappers() {
601 let mut table = ModuleTable::new();
602 table.addwrapper("cd", 0, "zsh/mymod");
603 assert_eq!(table.wrappers.len(), 1);
604
605 table.deletewrapper("zsh/mymod", "cd");
606 assert!(table.wrappers.is_empty());
607 }
608
609 #[test]
610 fn test_printmodulenode() {
611 let module = Module::new("zsh/test");
612 let output = printmodulenode("zsh/test", &module);
613 assert!(output.contains("zsh/test"));
614 assert!(output.contains("loaded"));
615 }
616}