Merge pull request #1 from eatmynerds/repl

Add descriptions to commands and improve REPL display formatting
This commit is contained in:
Chance 2024-12-02 01:05:14 -05:00 committed by GitHub
commit f76dc9cf24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 323 additions and 292 deletions

View file

@ -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)?)
}
}

View file

@ -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) },
);
}
}

View file

@ -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);

135
engine/src/core/repl/mod.rs Normal file
View file

@ -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),
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -1,26 +0,0 @@
use colored::Colorize;
pub fn print_splash() {
println!(
r#"
&&&&&&&&&&&
&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&
&& &&&&&&&&&
&& &&&&&&&&&
&&&&&&&&&&&& &&&&&&&&&&&
&&&&&&&&&&&&& &&&&&&&&&&&&
&&&&&&&&&&&&& &&&&&&&&&&&&&
&&&&&&&&&&&& &&&&&&&&&&&&&
&&&&&&&&&&& &&&&&&&&&&&&
&&&&&&&&& &&
&&&&&&&&& &&
&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&
&&&&&&&&&&&
Version: {}
"#,
env!("CARGO_PKG_VERSION").yellow().italic().underline()
);
}

View file

@ -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?;