use std::collections::HashMap; use colored::Colorize; use parking_lot::RwLock; use std::sync::LazyLock; use crate::error::{ZenyxError, ZenyxErrorKind}; pub static COMMAND_MANAGER: LazyLock> = LazyLock::new(|| { RwLock::new(CommandManager::init()) }); #[macro_export] macro_rules! commands { [$($command:ty),*] => [ $( { let mut manager = $crate::core::repl::handler::COMMAND_MANAGER.write(); manager.add_command(Box::new(<$command>::default())); } )* ]; } #[macro_export] macro_rules! alias { ($($alias:expr => $command:expr),*) => { $( { let mut manager = $crate::COMMAND_MANAGER.write(); manager.add_alias($alias, $command); } )* }; } fn hamming_distance(a: &str, b: &str) -> Option { if a.len() != b.len() { return None; } Some( a.chars() .zip(b.chars()) .filter(|(char_a, char_b)| char_a != char_b) .count(), ) } fn edit_distance(a: &str, b: &str) -> usize { let m = a.len(); let n = b.len(); let mut dp = vec![vec![0; n + 1]; m + 1]; for i in 0..=m { for j in 0..=n { if i == 0 { dp[i][j] = j; } else if j == 0 { dp[i][j] = i; } else if a.chars().nth(i - 1) == b.chars().nth(j - 1) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = 1 + dp[i - 1][j - 1].min(dp[i - 1][j]).min(dp[i][j - 1]); } } } dp[m][n] } fn check_similarity(target: &str) -> Option { let max_hamming_distance: usize = 2; let max_edit_distance: usize = 2; let mut best_match: Option = None; let mut best_distance = usize::MAX; for (cmd_name, _) in COMMAND_MANAGER.read().get_commands() { if let Some(hamming_dist) = hamming_distance(target, cmd_name) { if hamming_dist <= max_hamming_distance && hamming_dist < best_distance { best_distance = hamming_dist; best_match = Some(String::from(cmd_name)); } } else { let edit_dist = edit_distance(target, cmd_name); if edit_dist <= max_edit_distance && edit_dist < best_distance { best_distance = edit_dist; best_match = Some(String::from(cmd_name)); } } } best_match } pub struct CommandManager { pub commands: HashMap>, pub aliases: HashMap, } impl CommandManager { pub fn init() -> CommandManager { CommandManager { commands: HashMap::new(), aliases: HashMap::new(), } } pub fn get_commands(&self) -> std::collections::hash_map::Iter<'_, String, Box> { self.commands.iter() } pub fn execute_command( &self, command: &str, args: Option>, ) -> Result<(), ZenyxError> { if let Some(command) = self.commands.get(command) { command.execute(args)?; Ok(()) } else { let corrected_cmd = check_similarity(command); if let Some(corrected_cmd) = corrected_cmd { println!( "Command: {} was not found. Did you mean {}?", command.red().bold(), corrected_cmd.green().bold().italic() ); } Err(ZenyxError::builder(ZenyxErrorKind::CommandExecution) .with_message(format!("Command '{}' not found.", command)) .build()) } } pub fn execute(&self, command: &str, args: Option>) -> Result<(), ZenyxError> { match self.aliases.get(command) { Some(command) => self.execute(command, args), None => { self.execute_command(command, args)?; Ok(()) } } } pub fn add_command(&mut self, command: Box) { self.commands .insert(command.get_name().to_lowercase(), command); } pub fn add_alias(&mut self, alias: &str, command: &str) { self.aliases.insert( alias.to_string().to_lowercase(), command.to_string().to_lowercase(), ); } } pub trait Command: Send + Sync { fn execute(&self, args: Option>) -> Result<(), ZenyxError>; fn undo(&self); fn redo(&self); fn get_description(&self) -> String; fn get_name(&self) -> String; fn get_help(&self) -> String; fn get_params(&self) -> String; }