1use std::collections::HashMap;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ParamType {
12 Scalar,
13 Integer,
14 Float,
15 Array,
16 Associative,
17 Nameref,
18}
19
20impl ParamType {
21 pub fn name(&self) -> &'static str {
22 match self {
23 ParamType::Scalar => "scalar",
24 ParamType::Integer => "integer",
25 ParamType::Float => "float",
26 ParamType::Array => "array",
27 ParamType::Associative => "association",
28 ParamType::Nameref => "nameref",
29 }
30 }
31}
32
33#[derive(Debug, Clone, Default)]
35pub struct ParamFlags {
36 pub local: bool,
37 pub left_justify: bool,
38 pub right_blanks: bool,
39 pub right_zeros: bool,
40 pub lower: bool,
41 pub upper: bool,
42 pub readonly: bool,
43 pub tagged: bool,
44 pub tied: bool,
45 pub exported: bool,
46 pub unique: bool,
47 pub hide: bool,
48 pub hideval: bool,
49 pub special: bool,
50}
51
52pub fn param_type_str(ptype: ParamType, flags: &ParamFlags) -> String {
54 let mut parts = vec![ptype.name().to_string()];
55
56 if flags.local {
57 parts.push("local".to_string());
58 }
59 if flags.left_justify {
60 parts.push("left".to_string());
61 }
62 if flags.right_blanks {
63 parts.push("right_blanks".to_string());
64 }
65 if flags.right_zeros {
66 parts.push("right_zeros".to_string());
67 }
68 if flags.lower {
69 parts.push("lower".to_string());
70 }
71 if flags.upper {
72 parts.push("upper".to_string());
73 }
74 if flags.readonly {
75 parts.push("readonly".to_string());
76 }
77 if flags.tagged {
78 parts.push("tag".to_string());
79 }
80 if flags.tied {
81 parts.push("tied".to_string());
82 }
83 if flags.exported {
84 parts.push("export".to_string());
85 }
86 if flags.unique {
87 parts.push("unique".to_string());
88 }
89 if flags.hide {
90 parts.push("hide".to_string());
91 }
92 if flags.hideval {
93 parts.push("hideval".to_string());
94 }
95 if flags.special {
96 parts.push("special".to_string());
97 }
98
99 parts.join("-")
100}
101
102#[derive(Debug, Default)]
104pub struct CommandsTable {
105 hashed: HashMap<String, PathBuf>,
106}
107
108impl CommandsTable {
109 pub fn new() -> Self {
110 Self::default()
111 }
112
113 pub fn get(&self, name: &str) -> Option<&PathBuf> {
114 self.hashed.get(name)
115 }
116
117 pub fn set(&mut self, name: &str, path: PathBuf) {
118 self.hashed.insert(name.to_string(), path);
119 }
120
121 pub fn unset(&mut self, name: &str) {
122 self.hashed.remove(name);
123 }
124
125 pub fn clear(&mut self) {
126 self.hashed.clear();
127 }
128
129 pub fn iter(&self) -> impl Iterator<Item = (&String, &PathBuf)> {
130 self.hashed.iter()
131 }
132
133 pub fn len(&self) -> usize {
134 self.hashed.len()
135 }
136
137 pub fn is_empty(&self) -> bool {
138 self.hashed.is_empty()
139 }
140
141 pub fn rehash(&mut self, path_dirs: &[PathBuf]) {
142 self.hashed.clear();
143 for dir in path_dirs {
144 if let Ok(entries) = std::fs::read_dir(dir) {
145 for entry in entries.flatten() {
146 if let Ok(ft) = entry.file_type() {
147 if ft.is_file() || ft.is_symlink() {
148 if let Some(name) = entry.file_name().to_str() {
149 self.hashed.insert(name.to_string(), entry.path());
150 }
151 }
152 }
153 }
154 }
155 }
156 }
157}
158
159#[derive(Debug, Clone)]
161pub struct FunctionDef {
162 pub body: String,
163 pub flags: u32,
164 pub autoload: bool,
165}
166
167#[derive(Debug, Default)]
168pub struct FunctionsTable {
169 functions: HashMap<String, FunctionDef>,
170 disabled: HashMap<String, FunctionDef>,
171}
172
173impl FunctionsTable {
174 pub fn new() -> Self {
175 Self::default()
176 }
177
178 pub fn get(&self, name: &str) -> Option<&FunctionDef> {
179 self.functions.get(name)
180 }
181
182 pub fn get_disabled(&self, name: &str) -> Option<&FunctionDef> {
183 self.disabled.get(name)
184 }
185
186 pub fn set(&mut self, name: &str, def: FunctionDef) {
187 self.functions.insert(name.to_string(), def);
188 }
189
190 pub fn unset(&mut self, name: &str) {
191 self.functions.remove(name);
192 }
193
194 pub fn disable(&mut self, name: &str) {
195 if let Some(def) = self.functions.remove(name) {
196 self.disabled.insert(name.to_string(), def);
197 }
198 }
199
200 pub fn enable(&mut self, name: &str) {
201 if let Some(def) = self.disabled.remove(name) {
202 self.functions.insert(name.to_string(), def);
203 }
204 }
205
206 pub fn iter(&self) -> impl Iterator<Item = (&String, &FunctionDef)> {
207 self.functions.iter()
208 }
209
210 pub fn iter_disabled(&self) -> impl Iterator<Item = (&String, &FunctionDef)> {
211 self.disabled.iter()
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct AliasDef {
218 pub value: String,
219 pub global: bool,
220 pub suffix: bool,
221}
222
223#[derive(Debug, Default)]
224pub struct AliasesTable {
225 aliases: HashMap<String, AliasDef>,
226 disabled: HashMap<String, AliasDef>,
227 global_aliases: HashMap<String, AliasDef>,
228 suffix_aliases: HashMap<String, AliasDef>,
229}
230
231impl AliasesTable {
232 pub fn new() -> Self {
233 Self::default()
234 }
235
236 pub fn get(&self, name: &str) -> Option<&AliasDef> {
237 self.aliases.get(name)
238 }
239
240 pub fn get_global(&self, name: &str) -> Option<&AliasDef> {
241 self.global_aliases.get(name)
242 }
243
244 pub fn get_suffix(&self, suffix: &str) -> Option<&AliasDef> {
245 self.suffix_aliases.get(suffix)
246 }
247
248 pub fn set(&mut self, name: &str, def: AliasDef) {
249 if def.global {
250 self.global_aliases.insert(name.to_string(), def);
251 } else if def.suffix {
252 self.suffix_aliases.insert(name.to_string(), def);
253 } else {
254 self.aliases.insert(name.to_string(), def);
255 }
256 }
257
258 pub fn unset(&mut self, name: &str) {
259 self.aliases.remove(name);
260 self.global_aliases.remove(name);
261 self.suffix_aliases.remove(name);
262 }
263
264 pub fn disable(&mut self, name: &str) {
265 if let Some(def) = self.aliases.remove(name) {
266 self.disabled.insert(name.to_string(), def);
267 }
268 }
269
270 pub fn enable(&mut self, name: &str) {
271 if let Some(def) = self.disabled.remove(name) {
272 self.aliases.insert(name.to_string(), def);
273 }
274 }
275
276 pub fn iter(&self) -> impl Iterator<Item = (&String, &AliasDef)> {
277 self.aliases.iter()
278 }
279
280 pub fn iter_global(&self) -> impl Iterator<Item = (&String, &AliasDef)> {
281 self.global_aliases.iter()
282 }
283
284 pub fn iter_suffix(&self) -> impl Iterator<Item = (&String, &AliasDef)> {
285 self.suffix_aliases.iter()
286 }
287}
288
289#[derive(Debug, Default)]
291pub struct BuiltinsTable {
292 builtins: HashMap<String, bool>,
293 disabled: HashMap<String, bool>,
294}
295
296impl BuiltinsTable {
297 pub fn new() -> Self {
298 Self::default()
299 }
300
301 pub fn register(&mut self, name: &str) {
302 self.builtins.insert(name.to_string(), true);
303 }
304
305 pub fn is_builtin(&self, name: &str) -> bool {
306 self.builtins.contains_key(name)
307 }
308
309 pub fn disable(&mut self, name: &str) {
310 if self.builtins.remove(name).is_some() {
311 self.disabled.insert(name.to_string(), true);
312 }
313 }
314
315 pub fn enable(&mut self, name: &str) {
316 if self.disabled.remove(name).is_some() {
317 self.builtins.insert(name.to_string(), true);
318 }
319 }
320
321 pub fn list(&self) -> Vec<&str> {
322 self.builtins.keys().map(|s| s.as_str()).collect()
323 }
324
325 pub fn list_disabled(&self) -> Vec<&str> {
326 self.disabled.keys().map(|s| s.as_str()).collect()
327 }
328}
329
330#[derive(Debug, Default)]
332pub struct DirStack {
333 stack: Vec<PathBuf>,
334}
335
336impl DirStack {
337 pub fn new() -> Self {
338 Self::default()
339 }
340
341 pub fn push(&mut self, dir: PathBuf) {
342 self.stack.push(dir);
343 }
344
345 pub fn pop(&mut self) -> Option<PathBuf> {
346 self.stack.pop()
347 }
348
349 pub fn get(&self, index: usize) -> Option<&PathBuf> {
350 self.stack.get(index)
351 }
352
353 pub fn set(&mut self, stack: Vec<PathBuf>) {
354 self.stack = stack;
355 }
356
357 pub fn len(&self) -> usize {
358 self.stack.len()
359 }
360
361 pub fn is_empty(&self) -> bool {
362 self.stack.is_empty()
363 }
364
365 pub fn iter(&self) -> impl Iterator<Item = &PathBuf> {
366 self.stack.iter()
367 }
368
369 pub fn to_array(&self) -> Vec<String> {
370 self.stack
371 .iter()
372 .map(|p| p.to_string_lossy().to_string())
373 .collect()
374 }
375}
376
377#[derive(Debug, Default)]
379pub struct OptionsTable {
380 options: HashMap<String, bool>,
381}
382
383impl OptionsTable {
384 pub fn new() -> Self {
385 Self::default()
386 }
387
388 pub fn set(&mut self, name: &str, value: bool) {
389 self.options.insert(name.to_lowercase(), value);
390 }
391
392 pub fn get(&self, name: &str) -> Option<bool> {
393 self.options.get(&name.to_lowercase()).copied()
394 }
395
396 pub fn is_set(&self, name: &str) -> bool {
397 self.options
398 .get(&name.to_lowercase())
399 .copied()
400 .unwrap_or(false)
401 }
402
403 pub fn iter(&self) -> impl Iterator<Item = (&String, &bool)> {
404 self.options.iter()
405 }
406
407 pub fn to_hash(&self) -> HashMap<String, String> {
408 self.options
409 .iter()
410 .map(|(k, v)| {
411 (
412 k.clone(),
413 if *v {
414 "on".to_string()
415 } else {
416 "off".to_string()
417 },
418 )
419 })
420 .collect()
421 }
422}
423
424#[derive(Debug, Default)]
426pub struct NamedDirsTable {
427 dirs: HashMap<String, PathBuf>,
428}
429
430impl NamedDirsTable {
431 pub fn new() -> Self {
432 Self::default()
433 }
434
435 pub fn set(&mut self, name: &str, path: PathBuf) {
436 self.dirs.insert(name.to_string(), path);
437 }
438
439 pub fn get(&self, name: &str) -> Option<&PathBuf> {
440 self.dirs.get(name)
441 }
442
443 pub fn unset(&mut self, name: &str) {
444 self.dirs.remove(name);
445 }
446
447 pub fn find_name(&self, path: &PathBuf) -> Option<&str> {
448 self.dirs
449 .iter()
450 .find(|(_, p)| *p == path)
451 .map(|(n, _)| n.as_str())
452 }
453
454 pub fn iter(&self) -> impl Iterator<Item = (&String, &PathBuf)> {
455 self.dirs.iter()
456 }
457}
458
459#[derive(Debug, Clone)]
461pub struct JobState {
462 pub running: bool,
463 pub suspended: bool,
464 pub done: bool,
465}
466
467impl JobState {
468 pub fn as_str(&self) -> &'static str {
469 if self.done {
470 "done"
471 } else if self.suspended {
472 "suspended"
473 } else if self.running {
474 "running"
475 } else {
476 "unknown"
477 }
478 }
479}
480
481#[derive(Debug, Default)]
483pub struct JobsTable {
484 jobs: HashMap<i32, (JobState, String)>,
485}
486
487impl JobsTable {
488 pub fn new() -> Self {
489 Self::default()
490 }
491
492 pub fn add(&mut self, id: i32, state: JobState, text: String) {
493 self.jobs.insert(id, (state, text));
494 }
495
496 pub fn remove(&mut self, id: i32) {
497 self.jobs.remove(&id);
498 }
499
500 pub fn get_state(&self, id: i32) -> Option<&JobState> {
501 self.jobs.get(&id).map(|(s, _)| s)
502 }
503
504 pub fn get_text(&self, id: i32) -> Option<&str> {
505 self.jobs.get(&id).map(|(_, t)| t.as_str())
506 }
507
508 pub fn states(&self) -> HashMap<String, String> {
509 self.jobs
510 .iter()
511 .map(|(id, (state, _))| (id.to_string(), state.as_str().to_string()))
512 .collect()
513 }
514
515 pub fn texts(&self) -> HashMap<String, String> {
516 self.jobs
517 .iter()
518 .map(|(id, (_, text))| (id.to_string(), text.clone()))
519 .collect()
520 }
521}
522
523#[derive(Debug, Clone)]
525pub struct ModuleInfo {
526 pub loaded: bool,
527 pub autoload: bool,
528}
529
530#[derive(Debug, Default)]
531pub struct ModulesTable {
532 modules: HashMap<String, ModuleInfo>,
533}
534
535impl ModulesTable {
536 pub fn new() -> Self {
537 Self::default()
538 }
539
540 pub fn register(&mut self, name: &str, info: ModuleInfo) {
541 self.modules.insert(name.to_string(), info);
542 }
543
544 pub fn get(&self, name: &str) -> Option<&ModuleInfo> {
545 self.modules.get(name)
546 }
547
548 pub fn is_loaded(&self, name: &str) -> bool {
549 self.modules.get(name).map(|m| m.loaded).unwrap_or(false)
550 }
551
552 pub fn iter(&self) -> impl Iterator<Item = (&String, &ModuleInfo)> {
553 self.modules.iter()
554 }
555
556 pub fn to_hash(&self) -> HashMap<String, String> {
557 self.modules
558 .iter()
559 .map(|(k, v)| {
560 let status = if v.loaded {
561 "loaded"
562 } else if v.autoload {
563 "autoload"
564 } else {
565 "unloaded"
566 };
567 (k.clone(), status.to_string())
568 })
569 .collect()
570 }
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576
577 #[test]
578 fn test_param_type_str() {
579 let flags = ParamFlags::default();
580 assert_eq!(param_type_str(ParamType::Scalar, &flags), "scalar");
581
582 let flags = ParamFlags {
583 local: true,
584 exported: true,
585 ..Default::default()
586 };
587 assert_eq!(
588 param_type_str(ParamType::Array, &flags),
589 "array-local-export"
590 );
591 }
592
593 #[test]
594 fn test_commands_table() {
595 let mut table = CommandsTable::new();
596 table.set("ls", PathBuf::from("/bin/ls"));
597
598 assert_eq!(table.get("ls"), Some(&PathBuf::from("/bin/ls")));
599 assert!(table.get("nonexistent").is_none());
600
601 table.unset("ls");
602 assert!(table.get("ls").is_none());
603 }
604
605 #[test]
606 fn test_functions_table() {
607 let mut table = FunctionsTable::new();
608 table.set(
609 "myfunc",
610 FunctionDef {
611 body: "echo hello".to_string(),
612 flags: 0,
613 autoload: false,
614 },
615 );
616
617 assert!(table.get("myfunc").is_some());
618
619 table.disable("myfunc");
620 assert!(table.get("myfunc").is_none());
621 assert!(table.get_disabled("myfunc").is_some());
622
623 table.enable("myfunc");
624 assert!(table.get("myfunc").is_some());
625 }
626
627 #[test]
628 fn test_aliases_table() {
629 let mut table = AliasesTable::new();
630 table.set(
631 "ll",
632 AliasDef {
633 value: "ls -l".to_string(),
634 global: false,
635 suffix: false,
636 },
637 );
638
639 assert!(table.get("ll").is_some());
640 assert_eq!(table.get("ll").unwrap().value, "ls -l");
641 }
642
643 #[test]
644 fn test_builtins_table() {
645 let mut table = BuiltinsTable::new();
646 table.register("echo");
647 table.register("cd");
648
649 assert!(table.is_builtin("echo"));
650 assert!(!table.is_builtin("nonexistent"));
651
652 table.disable("echo");
653 assert!(!table.is_builtin("echo"));
654 }
655
656 #[test]
657 fn test_dir_stack() {
658 let mut stack = DirStack::new();
659 stack.push(PathBuf::from("/home"));
660 stack.push(PathBuf::from("/tmp"));
661
662 assert_eq!(stack.len(), 2);
663 assert_eq!(stack.pop(), Some(PathBuf::from("/tmp")));
664 assert_eq!(stack.len(), 1);
665 }
666
667 #[test]
668 fn test_options_table() {
669 let mut table = OptionsTable::new();
670 table.set("autocd", true);
671 table.set("EXTENDEDGLOB", true);
672
673 assert!(table.is_set("autocd"));
674 assert!(table.is_set("extendedglob")); }
676
677 #[test]
678 fn test_named_dirs() {
679 let mut table = NamedDirsTable::new();
680 table.set("proj", PathBuf::from("/home/user/projects"));
681
682 assert_eq!(
683 table.get("proj"),
684 Some(&PathBuf::from("/home/user/projects"))
685 );
686 assert_eq!(
687 table.find_name(&PathBuf::from("/home/user/projects")),
688 Some("proj")
689 );
690 }
691
692 #[test]
693 fn test_jobs_table() {
694 let mut table = JobsTable::new();
695 table.add(
696 1,
697 JobState {
698 running: true,
699 suspended: false,
700 done: false,
701 },
702 "vim file.txt".to_string(),
703 );
704
705 assert_eq!(table.get_state(1).unwrap().as_str(), "running");
706 assert_eq!(table.get_text(1), Some("vim file.txt"));
707 }
708
709 #[test]
710 fn test_modules_table() {
711 let mut table = ModulesTable::new();
712 table.register(
713 "zsh/datetime",
714 ModuleInfo {
715 loaded: true,
716 autoload: false,
717 },
718 );
719
720 assert!(table.is_loaded("zsh/datetime"));
721 assert!(!table.is_loaded("nonexistent"));
722 }
723}