rush_sync_server/commands/
registry.rs1use super::command::Command;
2use crate::core::prelude::*;
3use std::collections::HashMap;
4
5pub struct CommandRegistry {
6 commands: Vec<Box<dyn Command>>,
7 name_map: HashMap<String, usize>,
8 available_cache: std::sync::RwLock<Vec<usize>>,
9 cache_dirty: std::sync::atomic::AtomicBool,
10}
11
12impl CommandRegistry {
13 pub fn new() -> Self {
14 Self {
15 commands: Vec::new(),
16 name_map: HashMap::new(),
17 available_cache: std::sync::RwLock::new(Vec::new()),
18 cache_dirty: std::sync::atomic::AtomicBool::new(true),
19 }
20 }
21
22 pub fn register<T>(&mut self, command: T) -> &mut Self
23 where
24 T: Into<Box<dyn Command>>,
25 {
26 let boxed = command.into();
27 let name = boxed.name().to_lowercase();
28 let index = self.commands.len();
29
30 self.commands.push(boxed);
31 self.name_map.insert(name, index);
32
33 self.cache_dirty
35 .store(true, std::sync::atomic::Ordering::Release);
36
37 self
38 }
39
40 pub fn find_command(&self, input: &str) -> Option<&dyn Command> {
41 let input = input.trim().to_lowercase();
42
43 if let Some(&index) = self.name_map.get(&input) {
45 if let Some(cmd) = self.commands.get(index) {
46 if cmd.is_available() {
47 return Some(cmd.as_ref());
48 }
49 }
50 }
51
52 self.update_available_cache_if_needed();
54
55 if let Ok(cache) = self.available_cache.read() {
56 for &index in cache.iter() {
57 if let Some(cmd) = self.commands.get(index) {
58 if cmd.matches(&input) {
59 return Some(cmd.as_ref());
60 }
61 }
62 }
63 }
64
65 None
66 }
67
68 fn update_available_cache_if_needed(&self) {
69 if !self.cache_dirty.load(std::sync::atomic::Ordering::Acquire) {
70 return;
71 }
72
73 if let Ok(mut cache) = self.available_cache.write() {
74 cache.clear();
75 for (index, cmd) in self.commands.iter().enumerate() {
76 if cmd.is_available() {
77 cache.push(index);
78 }
79 }
80 self.cache_dirty
81 .store(false, std::sync::atomic::Ordering::Release);
82 }
83 }
84
85 pub fn execute_sync(&self, command: &str, args: &[&str]) -> Option<Result<String>> {
86 if args == ["?"] || args == ["--help"] || args == ["-h"] {
88 if self.find_command(command).is_some() {
89 return self
91 .find_command("help")
92 .map(|help_cmd| help_cmd.execute_sync(&[command]));
93 }
94 }
95
96 self.find_command(command).map(|cmd| cmd.execute_sync(args))
97 }
98
99 pub async fn execute_async(&self, command: &str, args: &[&str]) -> Option<Result<String>> {
100 if args == ["?"] || args == ["--help"] || args == ["-h"] {
102 if self.find_command(command).is_some() {
103 return self
104 .find_command("help")
105 .map(|help_cmd| help_cmd.execute_sync(&[command]));
106 }
107 }
108
109 match self.find_command(command) {
110 Some(cmd) => Some(cmd.execute(args).await),
111 None => None,
112 }
113 }
114
115 pub fn list_commands(&self) -> Vec<(&str, &str)> {
116 self.update_available_cache_if_needed();
117
118 if let Ok(cache) = self.available_cache.read() {
119 cache
120 .iter()
121 .filter_map(|&index| {
122 self.commands
123 .get(index)
124 .map(|cmd| (cmd.name(), cmd.description()))
125 })
126 .collect()
127 } else {
128 self.commands
130 .iter()
131 .filter(|cmd| cmd.is_available())
132 .map(|cmd| (cmd.name(), cmd.description()))
133 .collect()
134 }
135 }
136
137 pub fn debug_info(&self) -> String {
138 format!(
139 "CommandRegistry: {} commands registered",
140 self.commands.len()
141 )
142 }
143
144 pub fn len(&self) -> usize {
145 self.commands.len()
146 }
147
148 pub fn is_empty(&self) -> bool {
149 self.commands.is_empty()
150 }
151}
152
153impl Default for CommandRegistry {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159impl<T: Command> From<T> for Box<dyn Command> {
161 fn from(cmd: T) -> Self {
162 Box::new(cmd)
163 }
164}