410 lines
No EOL
13 KiB
Rust
410 lines
No EOL
13 KiB
Rust
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<Vec<String>>) -> Result<String, ZenyxError> {
|
|
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<Vec<String>>) -> Result<String, ZenyxError> {
|
|
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<Vec<String>>) -> Result<String, ZenyxError> {
|
|
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<Vec<String>>) -> Result<String, ZenyxError> {
|
|
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<Vec<(String, Option<Vec<String>>)>, 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<Vec<String>> = 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<u32>,
|
|
}
|
|
|
|
impl Command for CounterCommand {
|
|
fn execute(&self, _args: Option<Vec<String>>) -> Result<String, ZenyxError> {
|
|
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<Vec<String>>) -> Result<String, ZenyxError> {
|
|
if let Some(args) = args {
|
|
let panic_msg = &args[0];
|
|
panic!("{}", panic_msg);
|
|
} else {
|
|
let option: Option<i32> = 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<Vec<(String, Option<Vec<String>>)>, 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<Vec<String>> = 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)
|
|
} |