using_bin #1
							
								
								
									
										222
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,62 +1,83 @@
 | 
				
			|||||||
#![feature(iter_intersperse)]
 | 
					#![feature(iter_intersperse)]
 | 
				
			||||||
use std::io::{BufRead, Write};
 | 
					#![feature(trait_alias)]
 | 
				
			||||||
 | 
					use std::{
 | 
				
			||||||
 | 
					    fs,
 | 
				
			||||||
 | 
					    io::{BufRead, Write},
 | 
				
			||||||
 | 
					    path::Path,
 | 
				
			||||||
 | 
					    process::{self},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    let stdin = std::io::stdin();
 | 
					    let stdin = std::io::stdin();
 | 
				
			||||||
    let mut in_lock = stdin.lock();
 | 
					    let mut in_lock = stdin.lock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let stdout = std::io::stdout();
 | 
					    let stdout = std::io::stdout();
 | 
				
			||||||
    let mut out_lock = stdout.lock();
 | 
					    let mut out_lock = stdout.lock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loop {
 | 
					    let commands = &Command::commands();
 | 
				
			||||||
 | 
					    println!("Loaded {} commands", commands.len());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loop {
 | 
				
			||||||
        let mut buf = String::new();
 | 
					        let mut buf = String::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let ps1 = "> ".as_bytes();
 | 
					        let ps1 = "> ".as_bytes();
 | 
				
			||||||
        out_lock.write(ps1).unwrap();
 | 
					        out_lock.write(ps1).unwrap();
 | 
				
			||||||
        out_lock.flush().unwrap();
 | 
					        out_lock.flush().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        in_lock.read_line(&mut buf).expect("Failed to read in to buffer.");
 | 
					        in_lock
 | 
				
			||||||
        let trim = buf.trim();
 | 
					            .read_line(&mut buf)
 | 
				
			||||||
 | 
					            .expect("Failed to read in to buffer.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(choice) = Command::choose(trim, &commands()) {
 | 
					        let mut args: Param = &buf.trim().split(' ').collect::<Vec<&str>>();
 | 
				
			||||||
            let words = trim.split(' ').collect::<Vec<&str>>();
 | 
					        let mut options = commands;
 | 
				
			||||||
            choice.call(words);
 | 
					
 | 
				
			||||||
 | 
					        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, 'b> = &'b [&'a str];
 | 
				
			||||||
 | 
					trait Run = Fn(Param);
 | 
				
			||||||
 | 
					type Cmd = Box<dyn Run>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn commands() -> Vec<Command> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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::<String>();
 | 
					 | 
				
			||||||
            println!("{msg}");
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
    ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    commands
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Param<'a> = Vec<&'a str>;
 | 
					 | 
				
			||||||
type Run = fn(Param) -> ();
 | 
					 | 
				
			||||||
fn nil(_: Param) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
struct Command {
 | 
					struct Command {
 | 
				
			||||||
    name: String,
 | 
					    name: String,
 | 
				
			||||||
    sub_commands: Vec<Command>,
 | 
					    sub_commands: Vec<Command>,
 | 
				
			||||||
    cmd: Run,
 | 
					    cmd: Cmd,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Command {
 | 
					impl Command {
 | 
				
			||||||
    fn init(name: &str, sub_commands: Vec<Command>, f: Run) -> Self {
 | 
					    fn init<T>(name: T, sub_commands: Vec<Self>, f: Cmd) -> Self
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        T: ToString,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            name: name.to_string(),
 | 
					            name: name.to_string(),
 | 
				
			||||||
            sub_commands,
 | 
					            sub_commands,
 | 
				
			||||||
@@ -65,40 +86,157 @@ impl Command {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn call(&self, words: Param) {
 | 
					    fn call(&self, words: Param) {
 | 
				
			||||||
        let x = self.cmd;
 | 
					        let x = &self.cmd;
 | 
				
			||||||
        x(words)
 | 
					        x(words)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn choose<'a>(text: &str, choices: &'a Vec<Command>) -> Option<&'a Command> {
 | 
					    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
 | 
					        // See what command the user is trying to call
 | 
				
			||||||
        // iter thru all the commands
 | 
					        // iter thru all the commands
 | 
				
			||||||
        let mut ranked = choices.iter().fold(Vec::new(), |mut v, cmd| {
 | 
					        let mut ranked = choices.iter().fold(Vec::new(), |mut v, cmd| {
 | 
				
			||||||
            // count how many chars match from the command to the word
 | 
					            // 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)| {
 | 
					            let matches = text
 | 
				
			||||||
                if word == cmd { f += 1; }
 | 
					                .chars()
 | 
				
			||||||
                f
 | 
					                .zip(cmd.name.chars())
 | 
				
			||||||
            });
 | 
					                .fold(0, |mut f, (word, cmd)| {
 | 
				
			||||||
 | 
					                    if word == cmd {
 | 
				
			||||||
 | 
					                        f += 1;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    f
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
            v.push((cmd, matches));
 | 
					            v.push((cmd, matches));
 | 
				
			||||||
            v
 | 
					            v
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // sort by most matches
 | 
					        // 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 ranked.len() > 1 {
 | 
				
			||||||
            // If the top two command choices are ranked the same, we don't know which to choose!
 | 
					            // 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[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;
 | 
					                return None;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some((cmd, _)) = ranked.first() {
 | 
					        if let Some((cmd, _)) = ranked.first() {
 | 
				
			||||||
            // let words = text.split(' ').collect::<Vec<&str>>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return Some(cmd);
 | 
					            return Some(cmd);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        None
 | 
					        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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user