improve error handling and add metadata

This commit is contained in:
Chance 2025-04-03 01:00:24 -04:00
parent 13893e96a9
commit 43fd5966b7
17 changed files with 1162 additions and 787 deletions

View file

@ -1,18 +1,17 @@
use std::{fs, path::PathBuf, str::FromStr};
use anyhow::anyhow;
use parking_lot::RwLock;
use regex::Regex;
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<(), anyhow::Error> {
fn execute(&self, _args: Option<Vec<String>>) -> Result<(), ZenyxError> {
let manager = COMMAND_MANAGER.read();
println!("Available commands:\n");
@ -55,11 +54,12 @@ impl Command for HelpCommand {
String::from("Help")
}
}
#[derive(Default)]
pub struct ClearCommand;
impl Command for ClearCommand {
fn execute(&self, _args: Option<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, _args: Option<Vec<String>>) -> Result<(), ZenyxError> {
println!("Clearing screen..., running command");
let _result = if cfg!(target_os = "windows") {
std::process::Command::new("cmd")
@ -96,12 +96,16 @@ impl Command for ClearCommand {
pub struct ExitCommand;
impl Command for ExitCommand {
fn execute(&self, args: Option<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, args: Option<Vec<String>>) -> Result<(), ZenyxError> {
match args {
Some(args) => {
let exit_code = args[0].parse()?;
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);
// Ok(())
}
None => {
std::process::exit(0);
@ -133,18 +137,31 @@ impl Command for ExitCommand {
String::from("None")
}
}
#[derive(Default)]
pub struct ExecFile;
impl Command for ExecFile {
fn execute(&self, args: Option<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, args: Option<Vec<String>>) -> Result<(), ZenyxError> {
match args {
Some(args) => {
let file_path = PathBuf::from_str(&args[0])?;
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" {
return Err(anyhow!("Selected file was not a zensh file"));
return Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Selected file was not a zensh file")
.build());
} else {
let zscript = fs::read_to_string(file_path)?;
let zscript = fs::read_to_string(file_path).map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::Io)
.with_message("Failed to read file")
.with_source(e)
.build()
})?;
if let Ok(command) = eval(zscript) {
println!("{:#?}", command);
for (cmd_name, cmd_args) in command {
@ -163,7 +180,9 @@ impl Command for ExecFile {
}
Ok(())
}
None => Err(anyhow!("Not enough argumentss")),
None => Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Not enough arguments")
.build()),
}
}
@ -194,7 +213,7 @@ pub struct CounterCommand {
}
impl Command for CounterCommand {
fn execute(&self, _args: Option<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, _args: Option<Vec<String>>) -> Result<(), ZenyxError> {
// Increment the counter
let mut count = self.counter.write();
*count += 1;
@ -226,10 +245,11 @@ impl Command for CounterCommand {
String::from("count")
}
}
#[derive(Default)]
pub struct PanicCommmand;
impl Command for PanicCommmand {
fn execute(&self, args: Option<Vec<String>>) -> Result<(), anyhow::Error> {
fn execute(&self, args: Option<Vec<String>>) -> Result<(), ZenyxError> {
if args.is_some() {
let panic_msg = &args.unwrap()[0];
panic!("{}", panic_msg)
@ -260,12 +280,19 @@ impl Command for PanicCommmand {
}
}
fn eval(input: String) -> Result<Vec<(String, Option<Vec<String>>)>, anyhow::Error> {
fn eval(input: String) -> Result<Vec<(String, Option<Vec<String>>)>, ZenyxError> {
if input.trim().is_empty() {
return Err(anyhow!("Input was empty"));
return Err(ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Input was empty")
.build());
}
let pattern = Regex::new(r"[;|\n]").unwrap();
let pattern = Regex::new(r"[;|\n]").map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Failed to compile regex")
.with_source(e)
.build()
})?;
let commands: Vec<&str> = pattern.split(&input).collect();
let mut evaluted = vec![];

View file

@ -1,11 +1,12 @@
use std::collections::HashMap;
use colored::Colorize;
use lazy_static::lazy_static;
use parking_lot::RwLock;
lazy_static! {
pub static ref COMMAND_MANAGER: RwLock<CommandManager> = RwLock::new(CommandManager::init());
}
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),*] => [
@ -110,24 +111,28 @@ impl CommandManager {
&self,
command: &str,
args: Option<Vec<String>>,
) -> Result<(), anyhow::Error> {
) -> Result<(), ZenyxError> {
if let Some(command) = self.commands.get(command) {
command.execute(args)?;
Ok(())
} else {
let corrected_cmd = check_similarity(command);
if corrected_cmd.is_some() {
println!("Command: {} was not found. Did you mean {}?",command.red().bold(),corrected_cmd
.expect("A command was editied during execution, something has gone seriously wrong").green().bold().italic());
if let Some(corrected_cmd) = corrected_cmd {
println!(
"Command: {} was not found. Did you mean {}?",
command.red().bold(),
corrected_cmd.green().bold().italic()
);
}
Err(anyhow::anyhow!("Command '{}' not found.", command))
Err(ZenyxError::builder(ZenyxErrorKind::CommandExecution)
.with_message(format!("Command '{}' not found.", command))
.build())
}
}
pub fn execute(&self, command: &str, args: Option<Vec<String>>) -> Result<(), anyhow::Error> {
pub fn execute(&self, command: &str, args: Option<Vec<String>>) -> Result<(), ZenyxError> {
match self.aliases.get(command) {
Some(command) => self.execute(command, args),
// check to see if we are using an alias or the command just doesnt exist
None => {
self.execute_command(command, args)?;
Ok(())
@ -149,7 +154,7 @@ impl CommandManager {
}
pub trait Command: Send + Sync {
fn execute(&self, args: Option<Vec<String>>) -> Result<(), anyhow::Error>;
fn execute(&self, args: Option<Vec<String>>) -> Result<(), ZenyxError>;
fn undo(&self);
fn redo(&self);
fn get_description(&self) -> String;

View file

@ -15,6 +15,7 @@ use rustyline::{
use tracing::{debug, error, info, warn};
use super::handler::COMMAND_MANAGER;
use crate::error::{Result, ZenyxError, ZenyxErrorKind};
struct CommandCompleter;
impl CommandCompleter {
@ -129,29 +130,38 @@ pub fn tokenize(command: &str) -> Vec<String> {
tokens
}
pub fn parse_command(input: &str) -> anyhow::Result<Vec<String>> {
let pattern = Regex::new(r"[;|\n]").unwrap();
pub fn parse_command(input: &str) -> Result<Vec<String>> {
let pattern = Regex::new(r"[;|\n]").map_err(|_| {
ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Failed to compile regex pattern")
.build()
})?;
let commands: Vec<String> = pattern.split(input).map(String::from).collect();
Ok(commands)
}
pub fn evaluate_command(input: &str) -> anyhow::Result<()> {
pub fn evaluate_command(input: &str) -> Result<()> {
if input.trim().is_empty() {
return Ok(());
}
let pattern = Regex::new(r"[;|\n]").unwrap();
let pattern = Regex::new(r"[;|\n]").map_err(|_| {
ZenyxError::builder(ZenyxErrorKind::CommandParsing)
.with_message("Failed to compile regex pattern")
.build()
})?;
let commands: Vec<&str> = pattern.split(input).collect();
for command in commands {
let command = command.trim();
if command.is_empty() {
println!("Empty command, skipping.");
error!("Empty command, skipping.");
continue;
}
let tokens = tokenize(command);
if tokens.is_empty() {
println!("Empty command, skipping.");
error!("Empty command, skipping.");
continue;
}
let cmd_name = &tokens[0];
@ -160,15 +170,20 @@ pub fn evaluate_command(input: &str) -> anyhow::Result<()> {
} else {
None
};
match COMMAND_MANAGER.read().execute(cmd_name, args) {
Ok(_) => continue,
Err(e) => return Err(e),
}
COMMAND_MANAGER
.read()
.execute(cmd_name, args)
.map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::CommandExecution)
.with_message(format!("Failed to execute command: {cmd_name}"))
.with_context(format!("{e}"))
.build()
})?;
}
Ok(())
}
pub async fn handle_repl() -> anyhow::Result<()> {
pub async fn handle_repl() -> Result<()> {
let mut rl = Editor::<MyHelper, DefaultHistory>::new()?;
rl.set_helper(Some(MyHelper {
hinter: HistoryHinter::new(),
@ -196,7 +211,7 @@ pub async fn handle_repl() -> anyhow::Result<()> {
rl.add_history_entry(line.as_str())?;
match evaluate_command(line.as_str()) {
Ok(_) => continue,
Err(e) => println!("{e}"),
Err(e) => error!("{e}"),
}
}
Err(ReadlineError::Interrupted) => {

View file

@ -6,6 +6,9 @@ pub mod commands;
pub mod handler;
pub mod input;
pub fn setup() {
commands!(
HelpCommand,