zenyx-engine/engine/src/core/repl/handler.rs

164 lines
4.6 KiB
Rust

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<RwLock<CommandManager>> = 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<usize> {
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<String> {
let max_hamming_distance: usize = 2;
let max_edit_distance: usize = 2;
let mut best_match: Option<String> = 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<String, Box<dyn Command>>,
pub aliases: HashMap<String, String>,
}
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<dyn Command>> {
self.commands.iter()
}
pub fn execute_command(
&self,
command: &str,
args: Option<Vec<String>>,
) -> 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<Vec<String>>) -> 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<dyn Command>) {
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<Vec<String>>) -> 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;
}