Skip to main content

rush_sync_server/commands/
registry.rs

1use 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        // Invalidate cache
34        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        // Exact match (fast path)
44        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        // Cache-based pattern matching
53        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        // Support "command ?" syntax → redirect to help
87        if args == ["?"] || args == ["--help"] || args == ["-h"] {
88            if self.find_command(command).is_some() {
89                // Execute help with the command name as argument
90                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        // Support "command ?" syntax → redirect to help
101        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            // Fallback on lock failure
129            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
159// Enables both register(MyCommand::new()) and register(Box::new(MyCommand::new()))
160impl<T: Command> From<T> for Box<dyn Command> {
161    fn from(cmd: T) -> Self {
162        Box::new(cmd)
163    }
164}