diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..e924737 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,183 @@ +use std::{ + fs, + path::Path, + process, +}; + +pub type Param<'a, 'b> = &'b [&'a str]; +trait Run = Fn(Param); +type Cmd = Box; + +pub struct Command { + name: String, + pub sub_commands: Vec, + cmd: Cmd, +} + +impl Command { + fn init(name: T, sub_commands: Vec, f: Cmd) -> Self + where + T: ToString, + { + Self { + name: name.to_string(), + sub_commands, + cmd: f, + } + } + + pub fn call(&self, words: Param) { + let x = &self.cmd; + x(words) + } + + pub 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 + }); + v.push((cmd, matches)); + v + }); + + // sort by most matches + 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[1]; + if top.1 == second.1 { + 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() { + return Some(cmd); + } + None + } + + pub fn all_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) + .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}"); + }), + ); + 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(|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 + commands.extend(bin); + commands + } +} diff --git a/src/main.rs b/src/main.rs index e0e5422..21d51db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,9 @@ #![feature(iter_intersperse)] #![feature(trait_alias)] -use std::{ - fs, - io::{BufRead, Write}, - path::Path, - process::{self}, -}; +use std::io::{BufRead, Write}; +use command::{Command, Param}; + +mod command; fn main() { let stdin = std::io::stdin(); @@ -14,7 +12,7 @@ fn main() { let stdout = std::io::stdout(); let mut out_lock = stdout.lock(); - let commands = &Command::commands(); + let commands = &Command::all_commands(); println!("Loaded {} commands", commands.len()); loop { @@ -63,180 +61,3 @@ fn main() { } } -type Param<'a, 'b> = &'b [&'a str]; -trait Run = Fn(Param); -type Cmd = Box; - -struct Command { - name: String, - sub_commands: Vec, - cmd: Cmd, -} - -impl Command { - fn init(name: T, sub_commands: Vec, f: Cmd) -> Self - where - T: ToString, - { - Self { - name: name.to_string(), - sub_commands, - cmd: f, - } - } - - fn call(&self, words: Param) { - let x = &self.cmd; - x(words) - } - - 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 - }); - v.push((cmd, matches)); - v - }); - - // sort by most matches - 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[1]; - if top.1 == second.1 { - 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() { - return Some(cmd); - } - 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) - .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}"); - }), - ); - 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(|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 - commands.extend(bin); - commands - } -}