From 50173b1ed5506bf866f54e12578ed57d94213b94 Mon Sep 17 00:00:00 2001 From: Oliver Atkinson Date: Mon, 25 Nov 2024 12:17:07 -0700 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 7 ++++ Cargo.toml | 6 +++ README.md | 15 ++++++++ src/main.rs | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 133 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1aea403 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "custom_cli" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..43e4c95 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "custom_cli" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d16b041 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# rShell + +Writing a shell inspired by bash (obviously) but also inspired by Ruckus and Cisco's CLIs where you don't have to type out the full comamnd. As long as it is distinct. Simmilar to how the `ip` command words in Linux. Where + +```bash +ip address +``` + +and this + +```bash +ip a +``` + +are the same thing. diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..22cafc5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,104 @@ +#![feature(iter_intersperse)] +use std::io::{BufRead, Write}; + +fn main() { + + let stdin = std::io::stdin(); + let mut in_lock = stdin.lock(); + + let stdout = std::io::stdout(); + let mut out_lock = stdout.lock(); + + 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 trim = buf.trim(); + + if let Some(choice) = Command::choose(trim, &commands()) { + let words = trim.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) {} + +#[derive(Debug)] +struct Command { + name: String, + sub_commands: Vec, + cmd: Run, +} + +impl Command { + fn init(name: &str, sub_commands: Vec, f: Run) -> Self { + 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 Command> { + + // 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)); + + 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 { + return None; + } + } + + if let Some((cmd, _)) = ranked.first() { + // let words = text.split(' ').collect::>(); + + return Some(cmd); + } + None + } +} +