use std::{collections::VecDeque, fs, path::PathBuf, str::FromStr}; use parking_lot::RwLock; use super::{handler::Command, input::tokenize}; use crate::core::repl::handler::COMMAND_MANAGER; use crate::error::{ZenyxError, ZenyxErrorKind}; #[derive(Default)] pub struct HelpCommand; impl Command for HelpCommand { fn execute(&self, _args: Option>) -> Result { let manager = COMMAND_MANAGER.read(); let mut output = String::new(); output.push_str("Available commands:\n\n"); for (_, command) in manager.get_commands() { output.push_str(&format!( "Command: {}\n\tDescription: {}\n\tParameters: {}\n\tHelp: {}\n\n", command.get_name().to_lowercase(), command.get_description(), command.get_params(), command.get_help() )); } if !manager.aliases.is_empty() { output.push_str("Aliases:\n"); for (alias, command) in &manager.aliases { output.push_str(&format!("\t{} -> {}\n", alias, command)); } } Ok(output) } fn undo(&self) {} fn redo(&self) {} fn get_description(&self) -> String { String::from("help") } fn get_help(&self) -> String { String::from("Displays a list of available commands and their descriptions.") } fn get_params(&self) -> String { String::from("No parameters required.") } fn get_name(&self) -> String { String::from("Help") } } #[derive(Default)] pub struct ClearCommand; impl Command for ClearCommand { fn execute(&self, _args: Option>) -> Result { let _result = if cfg!(target_os = "windows") { std::process::Command::new("cmd") .args(["/c", "cls"]) .spawn() } else { std::process::Command::new("clear").spawn() }; Ok(String::from("Screen cleared.")) } fn undo(&self) {} fn redo(&self) {} fn get_description(&self) -> String { String::from("A simple command that clears the terminal") } fn get_name(&self) -> String { String::from("clear") } fn get_help(&self) -> String { String::from("Clears the terminal") } fn get_params(&self) -> String { String::from("None") } } #[derive(Default)] pub struct ExitCommand; impl Command for ExitCommand { fn execute(&self, args: Option>) -> Result { match args { Some(args) => { let exit_code = args[0].parse().map_err(|e| { ZenyxError::builder(ZenyxErrorKind::CommandParsing) .with_message("Failed to parse exit code") .with_source(e) .build() })?; std::process::exit(exit_code); } None => { std::process::exit(0); } } } fn undo(&self) { todo!() } fn redo(&self) { todo!() } fn get_description(&self) -> String { String::from("Gracefully exists the program") } fn get_name(&self) -> String { String::from("exit") } fn get_help(&self) -> String { String::from("Exits, probably") } fn get_params(&self) -> String { String::from("None") } } #[derive(Default)] pub struct ExecFile; impl Command for ExecFile { fn execute(&self, args: Option>) -> Result { match args { Some(args) => { let file_path = PathBuf::from_str(&args[0]).map_err(|e| { ZenyxError::builder(ZenyxErrorKind::CommandParsing) .with_message("Invalid file path") .with_source(e) .build() })?; if file_path.extension().is_some() && file_path.extension().unwrap() != "zensh" { Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing) .with_message("Selected file was not a zensh file") .build()) } else { let mut script_output = String::new(); match self.evaluate_script_heap_based(file_path) { Ok(commands_to_execute) => { for (cmd_name, cmd_args) in commands_to_execute { match COMMAND_MANAGER.read().execute(&cmd_name, cmd_args) { Ok(output) => script_output.push_str(&output), Err(e) => { return Err(ZenyxError::builder( ZenyxErrorKind::CommandExecution, ) .with_message(format!( "Error executing command '{}' in script: {}", cmd_name, e )) .build()); } } } Ok(script_output) } Err(e) => Err(e), } } } None => Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing) .with_message("Not enough arguments") .build()), } } fn undo(&self) {} fn redo(&self) {} fn get_description(&self) -> String { String::from("Executes a file path") } fn get_name(&self) -> String { String::from("exec") } fn get_help(&self) -> String { String::from("this will read the contents of a .zensh file, evaluate it, and run its input") } fn get_params(&self) -> String { String::from("1: File path") } } impl ExecFile { const MAX_RECURSION_DEPTH: usize = 100; // Increased for heap-based approach fn evaluate_script_heap_based( &self, initial_file_path: PathBuf, ) -> Result>)>, ZenyxError> { let mut command_queue: VecDeque<(PathBuf, usize)> = VecDeque::new(); let mut collected_commands = Vec::new(); command_queue.push_back((initial_file_path, 0)); while let Some((current_file_path, current_depth)) = command_queue.pop_front() { if current_depth > Self::MAX_RECURSION_DEPTH { return Err(ZenyxError::builder(ZenyxErrorKind::CommandExecution) .with_message(format!( "Recursion limit of {} exceeded while executing script.", Self::MAX_RECURSION_DEPTH )) .build()); } let zscript_result = fs::read_to_string(¤t_file_path).map_err(|e| { ZenyxError::builder(ZenyxErrorKind::Io) .with_message(format!("Failed to read file: {}", current_file_path.display())) .with_source(e) .build() }); match zscript_result { Ok(zscript) => { let commands: Vec<&str> = zscript.split(|c| c == ';' || c == '\n').collect(); for command_str in commands { let command_str = command_str.trim(); if command_str.is_empty() { continue; } let tokens = tokenize(command_str); if tokens.is_empty() { continue; } let cmd_name = &tokens[0]; let args: Option> = if tokens.len() > 1 { Some(tokens[1..].iter().map(|s| s.to_string()).collect()) } else { None }; if cmd_name.to_lowercase() == "exec" { if let Some(ref file_args) = args { if let Some(nested_file_path_str) = file_args.first() { let nested_file_path = PathBuf::from_str(nested_file_path_str).map_err(|e| { ZenyxError::builder(ZenyxErrorKind::CommandParsing) .with_message("Invalid file path in nested exec command") .with_source(e) .build() })?; if nested_file_path.extension().is_some() && nested_file_path.extension().unwrap() != "zensh" { return Err(ZenyxError::builder( ZenyxErrorKind::CommandParsing, ) .with_message("Nested exec file was not a zensh file") .build()); } command_queue.push_back((nested_file_path, current_depth + 1)); } else { return Err(ZenyxError::builder( ZenyxErrorKind::CommandParsing, ) .with_message("Not enough arguments for nested exec command") .build()); } } else { return Err(ZenyxError::builder( ZenyxErrorKind::CommandParsing, ) .with_message("Not enough arguments for nested exec command") .build()); } } else { collected_commands.push((cmd_name.to_owned(), args)); } } } Err(e) => return Err(e), } } Ok(collected_commands) } } #[derive(Default)] pub struct CounterCommand { counter: RwLock, } impl Command for CounterCommand { fn execute(&self, _args: Option>) -> Result { let mut count = self.counter.write(); *count += 1; Ok(format!( "CounterCommand executed. Current count: {}", *count )) } fn undo(&self) { println!("Undo CounterCommand."); } fn redo(&self) { println!("Redo CounterCommand."); } fn get_description(&self) -> String { String::from("counter") } fn get_help(&self) -> String { String::from("Increments a counter every time it's executed.") } fn get_params(&self) -> String { String::from("No parameters for CounterCommand.") } fn get_name(&self) -> String { String::from("count") } } #[derive(Default)] pub struct PanicCommmand; impl Command for PanicCommmand { fn execute(&self, args: Option>) -> Result { if let Some(args) = args { let panic_msg = &args[0]; panic!("{}", panic_msg); } else { let option: Option = None; println!("Unwrapping None: {}", option.unwrap()); panic!("Panic command was called"); } } fn undo(&self) {} fn redo(&self) {} fn get_description(&self) -> String { String::from("causes a panic with your provided message") } fn get_name(&self) -> String { String::from("panic") } fn get_help(&self) -> String { String::from("") } fn get_params(&self) -> String { String::from("optional: panic msg") } } fn eval(input: String) -> Result>)>, ZenyxError> { if input.trim().is_empty() { return Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing) .with_message("Input was empty") .build()); } let commands: Vec<&str> = input.split(|c| c == ';' || c == '\n').collect(); let mut evaluted = vec![]; for command in commands { let command = command.trim(); if command.is_empty() { println!("Empty command, skipping."); continue; } let tokens = tokenize(command); if tokens.is_empty() { println!("Empty command, skipping."); continue; } let cmd_name = &tokens[0]; let args: Option> = if tokens.len() > 1 { Some(tokens[1..].iter().map(|s| s.to_string()).collect()) } else { None }; evaluted.push((cmd_name.to_owned(), args)); } Ok(evaluted) }