use std::collections::HashMap;
use rskit_errors::{AppError, ErrorCode};
pub trait CommandHandler: Send + Sync {
fn execute(&self, args: &str) -> Result<String, AppError>;
}
impl<F> CommandHandler for F
where
F: Fn(&str) -> Result<String, AppError> + Send + Sync,
{
fn execute(&self, args: &str) -> Result<String, AppError> {
(self)(args)
}
}
pub struct Command {
pub name: String,
pub description: String,
pub usage: String,
pub handler: Box<dyn CommandHandler>,
}
pub struct CommandRegistry {
commands: HashMap<String, Command>,
}
impl Default for CommandRegistry {
fn default() -> Self {
Self::new()
}
}
impl CommandRegistry {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
}
}
pub fn register(&mut self, cmd: Command) -> Result<(), AppError> {
if cmd.name.trim().is_empty() {
return Err(AppError::new(
ErrorCode::InvalidInput,
"command name must not be empty",
));
}
self.commands.insert(cmd.name.clone(), cmd);
Ok(())
}
pub fn get(&self, name: &str) -> Option<&Command> {
self.commands.get(name)
}
pub fn list(&self) -> Vec<&Command> {
let mut cmds: Vec<&Command> = self.commands.values().collect();
cmds.sort_by(|a, b| a.name.cmp(&b.name));
cmds
}
pub fn is_command(input: &str) -> bool {
let trimmed = input.trim();
trimmed.starts_with('/')
&& trimmed
.chars()
.nth(1)
.is_some_and(|c| c.is_ascii_alphabetic())
}
pub fn parse_command(input: &str) -> Option<(&str, &str)> {
let trimmed = input.trim();
if !Self::is_command(trimmed) {
return None;
}
let without_slash = &trimmed[1..];
let Some(pos) = without_slash.find(|c: char| c.is_whitespace()) else {
return Some((without_slash, ""));
};
let name = &without_slash[..pos];
let args = without_slash[pos..].trim_start();
Some((name, args))
}
pub fn execute(&self, input: &str) -> Result<String, AppError> {
let (name, args) = Self::parse_command(input).ok_or_else(|| {
AppError::new(ErrorCode::InvalidInput, "input is not a slash command")
})?;
let cmd = self.get(name).ok_or_else(|| {
AppError::new(ErrorCode::InvalidInput, format!("unknown command: /{name}"))
})?;
cmd.handler.execute(args)
}
}