From e57e7ff90753ddb761b22836f02e8dae5e35c1c1 Mon Sep 17 00:00:00 2001 From: Oliver Atkinson Date: Mon, 25 Nov 2024 14:10:23 -0700 Subject: [PATCH 1/2] added custom directory support This allows you to specifcy things such as `/bin` to pull commands from. It even kind of works --- src/main.rs | 109 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 77 insertions(+), 32 deletions(-) diff --git a/src/main.rs b/src/main.rs index 22cafc5..2d75c2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![feature(iter_intersperse)] -use std::io::{BufRead, Write}; +#![feature(trait_alias)] +use std::{fs, io::{BufRead, Write}, path::Path, process::{self, Stdio}}; fn main() { @@ -9,6 +10,9 @@ fn main() { let stdout = std::io::stdout(); let mut out_lock = stdout.lock(); + let commands = &Command::commands(); + println!("Loaded {} commands", commands.len()); + loop { let mut buf = String::new(); @@ -18,58 +22,44 @@ fn main() { out_lock.flush().unwrap(); in_lock.read_line(&mut buf).expect("Failed to read in to buffer."); - let trim = buf.trim(); + let trimmed_input = buf.trim(); - if let Some(choice) = Command::choose(trim, &commands()) { - let words = trim.split(' ').collect::>(); + let o_choice = Command::choose(trimmed_input, commands); + if let Some(choice) = o_choice { + let words = trimmed_input.split(' ').collect::>(); choice.call(words); } } } - -fn commands() -> Vec { - - let commands = vec![ - Command::init("ls", vec![], nil), - Command::init("exit", vec![], |_| { - std::process::exit(0); - }), - Command::init("echo", vec![], |f| { - let msg = &f[1..].iter().map(|s| s.to_owned()).intersperse(" ").collect::(); - println!("{msg}"); - }), - ]; - - commands -} - type Param<'a> = Vec<&'a str>; -type Run = fn(Param) -> (); -fn nil(_: Param) {} +trait Run = Fn(Param); +type Cmd = Box; -#[derive(Debug)] +// #[derive(Debug)] struct Command { name: String, - sub_commands: Vec, - cmd: Run, + _sub_commands: Vec, + cmd: Cmd, } impl Command { - fn init(name: &str, sub_commands: Vec, f: Run) -> Self { + fn init(name: T, sub_commands: Vec, f: Cmd) -> Self where T: ToString { Self { name: name.to_string(), - sub_commands, + _sub_commands: sub_commands, cmd: f, } } fn call(&self, words: Param) { - let x = self.cmd; + let x = &self.cmd; x(words) } - fn choose<'a>(text: &str, choices: &'a Vec) -> Option<&'a Command> { + fn choose<'a>(text: &str, choices: &'a Vec) -> Option<&'a Self> { + + // TODO there seems to be duplicates in here? // See what command the user is trying to call // iter thru all the commands @@ -88,7 +78,10 @@ impl Command { if ranked.len() > 1 { // If the top two command choices are ranked the same, we don't know which to choose! - if ranked[0].1 == ranked[1].1 { + let top = ranked[0]; + let second = ranked[0]; + if top.1 == second.1 { + println!("Ambigous command '{}'.\nCould be any of {}, {}, etc...", text, top.0.name, second.0.name); return None; } } @@ -100,5 +93,57 @@ impl Command { } None } -} + fn commands() -> Vec { + let path = Path::new("/bin"); + let res = fs::read_dir(path).expect("Failed to read /bin"); + + // load all the programs from /bin + let bin = res.fold(Vec::new(), |mut v, contents| { + if let Ok(cmd) = contents { + if let Ok(t) = cmd.file_type() { + if t.is_file() { + // v.push(cmd.path()); + let cmd = Command::init( + cmd.file_name().into_string().unwrap(), + vec![], + Box::new(move |args| { + let name = cmd.path().to_str().unwrap().to_owned(); + let out = process::Command::new(name) + .args(args) + // FIXME not printing to out + .stdout(Stdio::inherit()) + .output() + .expect("Failed to call external program"); + let y = out.stdout.iter().map(|b| *b as char).collect::(); + // TODO load into env + // let status = out.status.code(); + println!("{y}"); + } + ) + ); + v.push(cmd); + } + }; + } ; + v + }); + + // load custom programs + let mut commands = vec![ + Command::init("exit", vec![], Box::new(|_| { + std::process::exit(0); + })), + Command::init("custom_echo", vec![], Box::new(|f| { + let f:Vec<&str>=f; + let msg = &f[1..].iter().map(|s| s.to_owned()).intersperse(" ").collect::(); + println!("{msg}"); + })), + ]; + + // combine with a move + commands.extend(bin); + commands + } +} + From 9af20c0424edafe533d1b5b94d09266961b36fda Mon Sep 17 00:00:00 2001 From: Oliver Atkinson Date: Mon, 25 Nov 2024 15:37:23 -0700 Subject: [PATCH 2/2] built a better processor --- src/main.rs | 181 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 137 insertions(+), 44 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2d75c2f..e0e5422 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,13 @@ #![feature(iter_intersperse)] #![feature(trait_alias)] -use std::{fs, io::{BufRead, Write}, path::Path, process::{self, Stdio}}; +use std::{ + fs, + io::{BufRead, Write}, + path::Path, + process::{self}, +}; fn main() { - let stdin = std::io::stdin(); let mut in_lock = stdin.lock(); @@ -14,40 +18,69 @@ fn main() { println!("Loaded {} commands", commands.len()); loop { - let mut buf = String::new(); let ps1 = "> ".as_bytes(); out_lock.write(ps1).unwrap(); out_lock.flush().unwrap(); - in_lock.read_line(&mut buf).expect("Failed to read in to buffer."); - let trimmed_input = buf.trim(); + in_lock + .read_line(&mut buf) + .expect("Failed to read in to buffer."); - let o_choice = Command::choose(trimmed_input, commands); - if let Some(choice) = o_choice { - let words = trimmed_input.split(' ').collect::>(); - choice.call(words); + let mut args: Param = &buf.trim().split(' ').collect::>(); + let mut options = commands; + + loop { + + match args.get(0) { + Some(cmd_arg) => { + // Try to find a subcommand to run + match Command::choose(cmd_arg, options) { + Some(choice) => { + if choice.sub_commands.is_empty() { + // No sub commands left to choose from, just pass the input + choice.call(&args); + break; + } else { + args = &args[1..]; + options = &choice.sub_commands; + + // a sort of last chance before running into hella edgecases + if args.is_empty() { + choice.call(args); + break; + } + } + } + // When no good choice presents it's self + None => break, + } + } + None => todo!("Couldn't get index 0 of the args"), + } } } } -type Param<'a> = Vec<&'a str>; +type Param<'a, 'b> = &'b [&'a str]; trait Run = Fn(Param); type Cmd = Box; -// #[derive(Debug)] struct Command { name: String, - _sub_commands: Vec, + sub_commands: Vec, cmd: Cmd, } impl Command { - fn init(name: T, sub_commands: Vec, f: Cmd) -> Self where T: ToString { + fn init(name: T, sub_commands: Vec, f: Cmd) -> Self + where + T: ToString, + { Self { name: name.to_string(), - _sub_commands: sub_commands, + sub_commands, cmd: f, } } @@ -58,37 +91,65 @@ impl Command { } fn choose<'a>(text: &str, choices: &'a Vec) -> Option<&'a Self> { - // TODO there seems to be duplicates in here? // See what command the user is trying to call // iter thru all the commands let mut ranked = choices.iter().fold(Vec::new(), |mut v, cmd| { // count how many chars match from the command to the word - let matches = text.chars().zip(cmd.name.chars()).fold(0, |mut f, (word, cmd)| { - if word == cmd { f += 1; } - f - }); + let matches = text + .chars() + .zip(cmd.name.chars()) + .fold(0, |mut f, (word, cmd)| { + if word == cmd { + f += 1; + } + f + }); v.push((cmd, matches)); v }); // sort by most matches - ranked.sort_by(|a,b| b.1.cmp(&a.1)); + ranked.sort_by(|a, b| b.1.cmp(&a.1)); + + // Don't select commands just because they are first. The command also has to have a + // confidence of at least 1 + if let Some(first) = ranked.first() { + if first.1 < 1 { + return None; + } + } + + // TODO test when 1 100% match exists if ranked.len() > 1 { // If the top two command choices are ranked the same, we don't know which to choose! let top = ranked[0]; - let second = ranked[0]; + let second = ranked[1]; if top.1 == second.1 { - println!("Ambigous command '{}'.\nCould be any of {}, {}, etc...", text, top.0.name, second.0.name); + println!("Ambigous command '{text}'. Could be any of: "); + let iter: Vec<&(&Command, usize)> = ranked + .iter() + // We only care about 100% matches + .filter(|f| f.1 >= text.len()) + .collect(); + + let max_display = if 5 < iter.len() {5} else {iter.len()}; + + iter[..max_display].iter().for_each(|matching| { + println!("\t- {}", matching.0.name); + }); + + if iter.len() > max_display { + println!("\tOmitting {} other entries.", iter.len() - max_display); + } + return None; } } if let Some((cmd, _)) = ranked.first() { - // let words = text.split(' ').collect::>(); - return Some(cmd); } None @@ -97,7 +158,7 @@ impl Command { fn commands() -> Vec { let path = Path::new("/bin"); let res = fs::read_dir(path).expect("Failed to read /bin"); - + // load all the programs from /bin let bin = res.fold(Vec::new(), |mut v, contents| { if let Ok(cmd) = contents { @@ -111,34 +172,67 @@ impl Command { let name = cmd.path().to_str().unwrap().to_owned(); let out = process::Command::new(name) .args(args) - // FIXME not printing to out - .stdout(Stdio::inherit()) .output() .expect("Failed to call external program"); + // FIXME not printing to out let y = out.stdout.iter().map(|b| *b as char).collect::(); // TODO load into env // let status = out.status.code(); - println!("{y}"); - } - ) - ); + println!("{y}"); + }), + ); v.push(cmd); - } - }; - } ; + } + }; + }; v }); - + // load custom programs let mut commands = vec![ - Command::init("exit", vec![], Box::new(|_| { - std::process::exit(0); - })), - Command::init("custom_echo", vec![], Box::new(|f| { - let f:Vec<&str>=f; - let msg = &f[1..].iter().map(|s| s.to_owned()).intersperse(" ").collect::(); - println!("{msg}"); - })), + Command::init( + "exit", + vec![], + Box::new(|_| { + std::process::exit(0); + }), + ), + Command::init( + "custom_echo", + vec![], + Box::new(|args| { + let msg = &args[1..] + .iter() + .map(|s| s.to_owned()) + .intersperse(" ") + .collect::(); + println!("{msg}"); + }), + ), + Command::init( + "random", + vec![ + Command::init( + "number", + vec![], + Box::new(|_| { + // how do you know it's not random? + println!("8"); + }), + ), + Command::init( + "string", + vec![], + Box::new(|_| { + // how do you know it's not random? + println!("hello world"); + }), + ), + ], + Box::new(|args| { + println!("random's debug: {:?}", args); + }), + ), ]; // combine with a move @@ -146,4 +240,3 @@ impl Command { commands } } -