This commit is contained in:
Oliver Atkinson 2024-11-25 12:17:07 -07:00
commit 50173b1ed5
5 changed files with 133 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View File

@ -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"

6
Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "custom_cli"
version = "0.1.0"
edition = "2021"
[dependencies]

15
README.md Normal file
View File

@ -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.

104
src/main.rs Normal file
View File

@ -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::<Vec<&str>>();
choice.call(words);
}
}
}
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 {
name: String,
sub_commands: Vec<Command>,
cmd: Run,
}
impl Command {
fn init(name: &str, sub_commands: Vec<Command>, 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<Command>) -> 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::<Vec<&str>>();
return Some(cmd);
}
None
}
}