From fede81b5b18a15f1240aabf0d81c43ff23b586e7 Mon Sep 17 00:00:00 2001
From: Jason Spalti <vipershniper08@gmail.com>
Date: Sun, 1 Dec 2024 23:52:12 -0600
Subject: [PATCH] Add descriptions to commands and improve REPL display
 formatting

---
 engine/src/core/mod.rs                 |  10 +-
 engine/src/core/repl.rs                | 248 -------------------------
 engine/src/core/{ => repl}/commands.rs |  20 +-
 engine/src/core/repl/mod.rs            | 135 ++++++++++++++
 engine/src/core/repl/repl.rs           | 146 +++++++++++++++
 engine/src/core/splash.rs              |  26 ---
 engine/src/main.rs                     |  30 ++-
 7 files changed, 323 insertions(+), 292 deletions(-)
 delete mode 100644 engine/src/core/repl.rs
 rename engine/src/core/{ => repl}/commands.rs (72%)
 create mode 100644 engine/src/core/repl/mod.rs
 create mode 100644 engine/src/core/repl/repl.rs
 delete mode 100644 engine/src/core/splash.rs

diff --git a/engine/src/core/mod.rs b/engine/src/core/mod.rs
index cebc91e..fe2a8cd 100644
--- a/engine/src/core/mod.rs
+++ b/engine/src/core/mod.rs
@@ -1,16 +1,14 @@
-pub mod commands;
-pub mod repl;
-pub mod splash;
 pub mod renderer;
+pub mod repl;
+
 use anyhow::Result;
 use renderer::App;
 use winit::event_loop::{ControlFlow, EventLoop};
 
-
-
 pub fn init_renderer() -> Result<()> {
     let event_loop = EventLoop::new().unwrap();
     event_loop.set_control_flow(ControlFlow::Poll);
     let mut app = App::default();
     Ok(event_loop.run_app(&mut app)?)
-}
\ No newline at end of file
+}
+
diff --git a/engine/src/core/repl.rs b/engine/src/core/repl.rs
deleted file mode 100644
index 899c606..0000000
--- a/engine/src/core/repl.rs
+++ /dev/null
@@ -1,248 +0,0 @@
-use super::commands;
-use chrono::Local;
-use lazy_static::lazy_static;
-use log2::{debug, error, info};
-use parking_lot::RwLock;
-use reedline::{Prompt, Reedline, Signal};
-use regex::Regex;
-use std::{borrow::Borrow, collections::HashMap, sync::Arc};
-
-struct ZPrompt {
-    left_text: String,
-    right_text: String,
-}
-
-#[derive(Clone, Debug)]
-enum Callable {
-    Simple(fn()),
-    WithArgs(fn(Vec<String>)),
-}
-#[derive(Debug)]
-pub struct Command {
-    pub name: &'static str,
-    pub description: Option<&'static str>,
-    function: Callable,
-    pub arg_count: u8,
-}
-
-impl Command {
-    pub fn execute(&self, args: Option<Vec<String>>) {
-        //debug!("Executing command: {}", self.name);
-        match &self.function {
-            Callable::Simple(f) => {
-                if let Some(args) = args {
-                    error!(
-                        "Command expected 0 arguments but {} args were given. Ignoring..",
-                        args.len()
-                    );
-                }
-                f()
-            }
-            Callable::WithArgs(f) => match args {
-                Some(args) => f(args),
-                None => error!("Command expected arguments but received 0"),
-            },
-        }
-    }
-}
-
-impl std::fmt::Display for Command {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "Name: {}\n\t{}",
-            self.name,
-            self.description.unwrap_or("No description")
-        )
-    }
-}
-
-lazy_static! {
-    pub static ref COMMAND_LIST: Arc<CommandList> = Arc::new(CommandList::new());
-}
-
-pub struct CommandList {
-    pub commands: RwLock<Vec<Command>>,
-    pub aliases: RwLock<HashMap<String, String>>,
-}
-
-impl CommandList {
-    fn new() -> Self {
-        CommandList {
-            commands: RwLock::new(Vec::new()),
-            aliases: RwLock::new(HashMap::new()),
-        }
-    }
-
-    fn add_command(
-        &self,
-        name: &'static str,
-        description: Option<&'static str>,
-        func: Callable,
-        arg_count: Option<u8>,
-    ) {
-        debug!("Adding command: {}", name);
-        let mut commands = self.commands.write();
-
-        commands.push(Command {
-            name,
-            description,
-            function: func,
-            arg_count: arg_count.unwrap_or(0),
-        });
-    }
-    fn add_alias(&self, name: String, alias: String) {
-        //println!("Input alias: {}", alias);
-        if self.aliases.read().contains_key(&alias) {
-            error!("Alias: '{}' already exists", alias);
-            return;
-        }
-        let mut commands = self.commands.write();
-        if let Some(command) = commands.iter_mut().find(|cmd| cmd.name == name) {
-            info!("Adding alias: {} for cmd: {}", alias, command.name);
-            self.aliases
-                .write()
-                .insert(alias.to_string(), name.to_string());
-        } else {
-            error!("Command: '{}' was not found", name);
-        }
-    }
-
-    fn execute_command(&self, mut name: String, args: Option<Vec<String>>) {
-        //info!("received input command: {}", name);
-        let commands = self.commands.borrow();
-        if self.aliases.read().contains_key(&name) {
-            name = self
-                .aliases
-                .read()
-                .get_key_value(&name)
-                .unwrap()
-                .1
-                .to_string();
-            debug!("changed to {}", name);
-        }
-        if let Some(command) = commands.read().iter().find(|cmd| cmd.name == name) { match (command.arg_count, args.as_ref()) {
-            (expected, Some(args_vec)) if args_vec.len() != expected as usize => {
-                eprintln!(
-                    "Command: '{}' expected {} arguments but received {}",
-                    name,
-                    expected,
-                    args_vec.len()
-                );
-            }
-            (_, _) => command.execute(args),
-        } }
-    }
-}
-
-impl Prompt for ZPrompt {
-    fn render_prompt_left(&self) -> std::borrow::Cow<str> {
-        std::borrow::Cow::Borrowed(&self.left_text)
-    }
-
-    fn render_prompt_right(&self) -> std::borrow::Cow<str> {
-        std::borrow::Cow::Borrowed(&self.right_text)
-    }
-
-    fn render_prompt_history_search_indicator(
-        &self,
-        _history_search: reedline::PromptHistorySearch,
-    ) -> std::borrow::Cow<str> {
-        std::borrow::Cow::Borrowed("")
-    }
-
-    fn render_prompt_indicator(
-        &self,
-        prompt_mode: reedline::PromptEditMode,
-    ) -> std::borrow::Cow<str> {
-        match prompt_mode {
-            reedline::PromptEditMode::Default => std::borrow::Cow::Borrowed(">>"),
-            reedline::PromptEditMode::Emacs => {
-                let timestamp = Local::now().format("[%H:%M:%S.%3f/SHELL] >>\t").to_string();
-                std::borrow::Cow::Owned(timestamp)
-            }
-            reedline::PromptEditMode::Vi(_) => std::borrow::Cow::Borrowed("vi>>"),
-            reedline::PromptEditMode::Custom(_) => std::borrow::Cow::Borrowed("custom>>"),
-        }
-    }
-
-    fn render_prompt_multiline_indicator(&self) -> std::borrow::Cow<str> {
-        std::borrow::Cow::Borrowed("><")
-    }
-}
-
-fn setup() {
-    COMMAND_LIST.add_command(
-        "hello",
-        Some("test"),
-        Callable::Simple(commands::say_hello),
-        None,
-    );
-    COMMAND_LIST.add_command("exit", None, Callable::Simple(commands::exit), None);
-    COMMAND_LIST.add_command("clear", None, Callable::Simple(commands::clear), None);
-    COMMAND_LIST.add_command("echo", None, Callable::WithArgs(commands::echo), Some(1));
-    COMMAND_LIST.add_command("cmds", None, Callable::Simple(commands::cmds), None);
-    COMMAND_LIST.add_alias("cmds".to_string(), "help".to_string());
-    COMMAND_LIST.add_alias("cmds".to_string(), "cmd_list".to_string());
-    COMMAND_LIST.add_alias("hello".to_string(), "exit".to_string());
-    COMMAND_LIST.add_alias("clear".to_string(), "exit".to_string());
-}
-
-pub async fn handle_repl() {
-    let mut line_editor = Reedline::create();
-    setup();
-
-    loop {
-        let sig = line_editor.read_line(&ZPrompt {
-            left_text: String::new(),
-            right_text: "<<".to_string(),
-        });
-
-        match sig {
-            Ok(Signal::Success(buffer)) => {
-                if buffer == "exit" {
-                    std::process::exit(0);
-                } else {
-                    evaluate_command(&buffer);
-                }
-            }
-            Ok(Signal::CtrlC) => {
-                println!("\nCONTROL+C RECEIVED, TERMINATING");
-                std::process::exit(0);
-            }
-            err => {
-                eprintln!("Error: {:?}", err);
-            }
-        }
-    }
-}
-
-fn evaluate_command(input: &str) {
-    if input.trim().is_empty() {
-        return;
-    }
-
-    let pattern = Regex::new(r"[;|\n]").unwrap();
-    let commands: Vec<&str> = pattern.split(input).collect();
-
-    for command in commands {
-        let command = command.trim();
-        if command.is_empty() {
-            println!("Empty command, skipping.");
-            continue;
-        }
-
-        let tokens: Vec<&str> = command.split_whitespace().collect();
-        if tokens.is_empty() {
-            return;
-        }
-
-        let cmd_name = tokens[0];
-        let args: Vec<String> = tokens[1..].iter().map(|&s| s.to_string()).collect();
-
-        COMMAND_LIST.execute_command(
-            cmd_name.to_string(),
-            if args.is_empty() { None } else { Some(args) },
-        );
-    }
-}
diff --git a/engine/src/core/commands.rs b/engine/src/core/repl/commands.rs
similarity index 72%
rename from engine/src/core/commands.rs
rename to engine/src/core/repl/commands.rs
index 45884ac..c4d2437 100644
--- a/engine/src/core/commands.rs
+++ b/engine/src/core/repl/commands.rs
@@ -1,34 +1,34 @@
+use super::COMMAND_LIST;
 use std::process::Command;
-
 use log2::{debug, info};
 
-use crate::core::repl::COMMAND_LIST;
-
-pub fn say_hello() {
-    println!("Hello from your new command!");
+pub(crate) fn say_hello() {
+    println!("Hello, World!");
 }
 
-pub fn echo(args: Vec<String>) {
+pub(crate) fn echo(args: Vec<String>) {
     debug!("{}", args.join(" "));
     println!("{}", args.join(" "))
 }
 
-pub fn exit() {
+pub(crate) fn exit() {
     debug!("Exiting...");
     std::process::exit(0)
 }
-pub fn clear() {
+
+pub(crate) fn clear() {
     info!("Clearing screen..., running command");
     let _result = if cfg!(target_os = "windows") {
         debug!("target_os is windows");
         Command::new("cmd").args(["/c", "cls"]).spawn()
     } else {
-        debug!("target_os was unix");
+        debug!("target_os is unix");
         // "clear" or "tput reset"
         Command::new("tput").arg("reset").spawn()
     };
 }
-pub fn cmds() {
+
+pub(crate) fn help() {
     println!("Commands:");
     for cmd in COMMAND_LIST.commands.read().iter() {
         println!("{:#}", cmd);
diff --git a/engine/src/core/repl/mod.rs b/engine/src/core/repl/mod.rs
new file mode 100644
index 0000000..6a0ad4c
--- /dev/null
+++ b/engine/src/core/repl/mod.rs
@@ -0,0 +1,135 @@
+pub mod commands;
+pub mod repl;
+
+use lazy_static::lazy_static;
+use log2::{debug, error, info};
+use parking_lot::RwLock;
+use std::{borrow::Borrow, collections::HashMap, sync::Arc};
+
+lazy_static! {
+    pub static ref COMMAND_LIST: Arc<CommandList> = Arc::new(CommandList::new());
+}
+
+#[derive(Clone, Debug)]
+enum Callable {
+    Simple(fn()),
+    WithArgs(fn(Vec<String>)),
+}
+
+#[derive(Debug)]
+pub struct Command {
+    pub name: &'static str,
+    pub description: Option<&'static str>,
+    function: Callable,
+    pub arg_count: u8,
+}
+
+impl Command {
+    pub fn execute(&self, args: Option<Vec<String>>) {
+        //debug!("Executing command: {}", self.name);
+        match &self.function {
+            Callable::Simple(f) => {
+                if let Some(args) = args {
+                    error!(
+                        "Command expected 0 arguments but {} args were given. Ignoring..",
+                        args.len()
+                    );
+                }
+                f()
+            }
+            Callable::WithArgs(f) => match args {
+                Some(args) => f(args),
+                None => error!("Command expected arguments but received 0"),
+            },
+        }
+    }
+}
+
+impl std::fmt::Display for Command {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "  {:<10} {}",
+            self.name,
+            self.description.unwrap_or("No description available")
+        )
+    }
+}
+
+pub struct CommandList {
+    pub commands: RwLock<Vec<Command>>,
+    pub aliases: RwLock<HashMap<String, String>>,
+}
+
+impl CommandList {
+    fn new() -> Self {
+        CommandList {
+            commands: RwLock::new(Vec::new()),
+            aliases: RwLock::new(HashMap::new()),
+        }
+    }
+
+    fn add_command(
+        &self,
+        name: &'static str,
+        description: Option<&'static str>,
+        func: Callable,
+        arg_count: Option<u8>,
+    ) {
+        debug!("Adding command: {}", name);
+        let mut commands = self.commands.write();
+
+        commands.push(Command {
+            name,
+            description,
+            function: func,
+            arg_count: arg_count.unwrap_or(0),
+        });
+    }
+
+    fn add_alias(&self, name: String, alias: String) {
+        //println!("Input alias: {}", alias);
+        if self.aliases.read().contains_key(&alias) {
+            error!("Alias: '{}' already exists", alias);
+            return;
+        }
+        let mut commands = self.commands.write();
+        if let Some(command) = commands.iter_mut().find(|cmd| cmd.name == name) {
+            info!("Adding alias: {} for cmd: {}", alias, command.name);
+            self.aliases
+                .write()
+                .insert(alias.to_string(), name.to_string());
+        } else {
+            error!("Command: '{}' was not found", name);
+        }
+    }
+
+    fn execute_command(&self, mut name: String, args: Option<Vec<String>>) {
+        //info!("received input command: {}", name);
+        let commands = self.commands.borrow();
+        if self.aliases.read().contains_key(&name) {
+            name = self
+                .aliases
+                .read()
+                .get_key_value(&name)
+                .unwrap()
+                .1
+                .to_string();
+
+            debug!("changed to {}", name);
+        }
+        if let Some(command) = commands.read().iter().find(|cmd| cmd.name == name) {
+            match (command.arg_count, args.as_ref()) {
+                (expected, Some(args_vec)) if args_vec.len() != expected as usize => {
+                    eprintln!(
+                        "Command: '{}' expected {} arguments but received {}",
+                        name,
+                        expected,
+                        args_vec.len()
+                    );
+                }
+                (_, _) => command.execute(args),
+            }
+        }
+    }
+}
diff --git a/engine/src/core/repl/repl.rs b/engine/src/core/repl/repl.rs
new file mode 100644
index 0000000..12c5667
--- /dev/null
+++ b/engine/src/core/repl/repl.rs
@@ -0,0 +1,146 @@
+use super::{commands, Callable, COMMAND_LIST};
+use chrono::Local;
+use reedline::{Prompt, Reedline, Signal};
+use regex::Regex;
+use std::{borrow::Borrow, collections::HashMap, sync::Arc};
+
+fn register_commands() {
+    COMMAND_LIST.add_command(
+        "hello",
+        Some("Displays \"Hello World\"!"),
+        Callable::Simple(commands::say_hello),
+        None,
+    );
+
+    COMMAND_LIST.add_command(
+        "exit",
+        Some("Exits the application gracefully."),
+        Callable::Simple(commands::exit),
+        None,
+    );
+
+    COMMAND_LIST.add_command(
+        "clear",
+        Some("Clears the terminal screen."),
+        Callable::Simple(commands::clear),
+        None,
+    );
+
+    COMMAND_LIST.add_command(
+        "echo",
+        Some("Prints the provided arguments back to the terminal."),
+        Callable::WithArgs(commands::echo),
+        Some(1), // Requires at least one argument
+    );
+
+    COMMAND_LIST.add_command(
+        "help",
+        Some("Displays a list of all available commands."),
+        Callable::Simple(commands::help),
+        None,
+    );
+
+    // EXAMPLE
+    // Adding aliases for commands
+    COMMAND_LIST.add_alias("cls".to_string(), "clear".to_string()); // Likely unintended; consider removing or renaming.
+}
+
+struct ZPrompt {
+    left_text: String,
+    right_text: String,
+}
+
+impl Prompt for ZPrompt {
+    fn render_prompt_left(&self) -> std::borrow::Cow<str> {
+        std::borrow::Cow::Borrowed(&self.left_text)
+    }
+
+    fn render_prompt_right(&self) -> std::borrow::Cow<str> {
+        std::borrow::Cow::Borrowed(&self.right_text)
+    }
+
+    fn render_prompt_history_search_indicator(
+        &self,
+        _history_search: reedline::PromptHistorySearch,
+    ) -> std::borrow::Cow<str> {
+        std::borrow::Cow::Borrowed("")
+    }
+
+    fn render_prompt_indicator(
+        &self,
+        prompt_mode: reedline::PromptEditMode,
+    ) -> std::borrow::Cow<str> {
+        match prompt_mode {
+            reedline::PromptEditMode::Default => std::borrow::Cow::Borrowed(">>"),
+            reedline::PromptEditMode::Emacs => {
+                let timestamp = Local::now().format("[%H:%M:%S.%3f/SHELL] >>\t").to_string();
+                std::borrow::Cow::Owned(timestamp)
+            }
+            reedline::PromptEditMode::Vi(_) => std::borrow::Cow::Borrowed("vi>>"),
+            reedline::PromptEditMode::Custom(_) => std::borrow::Cow::Borrowed("custom>>"),
+        }
+    }
+
+    fn render_prompt_multiline_indicator(&self) -> std::borrow::Cow<str> {
+        std::borrow::Cow::Borrowed("><")
+    }
+}
+
+fn evaluate_command(input: &str) {
+    if input.trim().is_empty() {
+        return;
+    }
+
+    let pattern = Regex::new(r"[;|\n]").unwrap();
+    let commands: Vec<&str> = pattern.split(input).collect();
+
+    for command in commands {
+        let command = command.trim();
+        if command.is_empty() {
+            println!("Empty command, skipping.");
+            continue;
+        }
+
+        let tokens: Vec<&str> = command.split_whitespace().collect();
+        if tokens.is_empty() {
+            return;
+        }
+
+        let cmd_name = tokens[0];
+        let args: Vec<String> = tokens[1..].iter().map(|&s| s.to_string()).collect();
+
+        COMMAND_LIST.execute_command(
+            cmd_name.to_string(),
+            if args.is_empty() { None } else { Some(args) },
+        );
+    }
+}
+
+pub async fn handle_repl() {
+    let mut line_editor = Reedline::create();
+    register_commands();
+
+    loop {
+        let sig = line_editor.read_line(&ZPrompt {
+            left_text: String::new(),
+            right_text: "<<".to_string(),
+        });
+
+        match sig {
+            Ok(Signal::Success(buffer)) => {
+                if buffer == "exit" {
+                    std::process::exit(0);
+                } else {
+                    evaluate_command(&buffer);
+                }
+            }
+            Ok(Signal::CtrlC) => {
+                println!("\nCONTROL+C RECEIVED, TERMINATING");
+                std::process::exit(0);
+            }
+            err => {
+                eprintln!("Error: {:?}", err);
+            }
+        }
+    }
+}
diff --git a/engine/src/core/splash.rs b/engine/src/core/splash.rs
deleted file mode 100644
index f35c870..0000000
--- a/engine/src/core/splash.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-use colored::Colorize;
-
-pub fn print_splash() {
-    println!(
-        r#"
-         &&&&&&&&&&&         
-      &&&&&&&&&&&&&&&&&      
-    &&&&&&&&&&&&&&&&&&&&&    
-  &&              &&&&&&&&&  
- &&                &&&&&&&&& 
-&&&&&&&&&&&&      &&&&&&&&&&&
-&&&&&&&&&&&&&    &&&&&&&&&&&&
-&&&&&&&&&&&&&   &&&&&&&&&&&&&
-&&&&&&&&&&&&    &&&&&&&&&&&&&
-&&&&&&&&&&&      &&&&&&&&&&&&
- &&&&&&&&&                && 
-  &&&&&&&&&              &&  
-    &&&&&&&&&&&&&&&&&&&&&    
-      &&&&&&&&&&&&&&&&&      
-         &&&&&&&&&&&
-
-        Version: {}
-    "#,
-        env!("CARGO_PKG_VERSION").yellow().italic().underline()
-    );
-}
diff --git a/engine/src/main.rs b/engine/src/main.rs
index 5baa920..ec45bad 100644
--- a/engine/src/main.rs
+++ b/engine/src/main.rs
@@ -1,18 +1,44 @@
 use anyhow::Result;
+use colored::Colorize;
 use log2::info;
 
 pub mod core;
 
+pub fn print_splash() {
+    println!(
+        r#"
+         &&&&&&&&&&&         
+      &&&&&&&&&&&&&&&&&      
+    &&&&&&&&&&&&&&&&&&&&&    
+  &&              &&&&&&&&&  
+ &&                &&&&&&&&& 
+&&&&&&&&&&&&      &&&&&&&&&&&
+&&&&&&&&&&&&&    &&&&&&&&&&&&
+&&&&&&&&&&&&&   &&&&&&&&&&&&&
+&&&&&&&&&&&&    &&&&&&&&&&&&&
+&&&&&&&&&&&      &&&&&&&&&&&&
+ &&&&&&&&&                && 
+  &&&&&&&&&              &&  
+    &&&&&&&&&&&&&&&&&&&&&    
+      &&&&&&&&&&&&&&&&&      
+         &&&&&&&&&&&
+
+        Version: {}
+    "#,
+        env!("CARGO_PKG_VERSION").yellow().italic().underline()
+    );
+}
+
 #[tokio::main]
 async fn main() -> Result<()> {
     let _log2 = log2::open("z.log").tee(true).level("debug").start();
     info!("Initalizing Engine");
     let shell_thread = tokio::task::spawn(async {
         info!("Shell thread started");
-        core::repl::handle_repl().await;
+        core::repl::repl::handle_repl().await;
     });
 
-    core::splash::print_splash();
+    print_splash();
     info!("Engine Initalized");
     core::init_renderer()?;
     shell_thread.await?;