split up command into own file

This commit is contained in:
Oliver 2024-11-25 20:38:51 -07:00
parent c71ea179ec
commit bc844cb5fa
2 changed files with 188 additions and 184 deletions

183
src/command.rs Normal file
View File

@ -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<dyn Run>;
pub struct Command {
name: String,
pub sub_commands: Vec<Command>,
cmd: Cmd,
}
impl Command {
fn init<T>(name: T, sub_commands: Vec<Self>, 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<Self>) -> 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<Self> {
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::<String>();
// 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::<String>();
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
}
}

View File

@ -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<dyn Run>;
struct Command {
name: String,
sub_commands: Vec<Command>,
cmd: Cmd,
}
impl Command {
fn init<T>(name: T, sub_commands: Vec<Self>, 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<Self>) -> 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<Self> {
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::<String>();
// 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::<String>();
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
}
}