split up command into own file
This commit is contained in:
parent
c71ea179ec
commit
bc844cb5fa
183
src/command.rs
Normal file
183
src/command.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
189
src/main.rs
189
src/main.rs
@ -1,11 +1,9 @@
|
|||||||
#![feature(iter_intersperse)]
|
#![feature(iter_intersperse)]
|
||||||
#![feature(trait_alias)]
|
#![feature(trait_alias)]
|
||||||
use std::{
|
use std::io::{BufRead, Write};
|
||||||
fs,
|
use command::{Command, Param};
|
||||||
io::{BufRead, Write},
|
|
||||||
path::Path,
|
mod command;
|
||||||
process::{self},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let stdin = std::io::stdin();
|
let stdin = std::io::stdin();
|
||||||
@ -14,7 +12,7 @@ fn main() {
|
|||||||
let stdout = std::io::stdout();
|
let stdout = std::io::stdout();
|
||||||
let mut out_lock = stdout.lock();
|
let mut out_lock = stdout.lock();
|
||||||
|
|
||||||
let commands = &Command::commands();
|
let commands = &Command::all_commands();
|
||||||
println!("Loaded {} commands", commands.len());
|
println!("Loaded {} commands", commands.len());
|
||||||
|
|
||||||
loop {
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user