From e2df94a6c2d8686ee2e623ebe0e2760f08d4dafb Mon Sep 17 00:00:00 2001 From: Oliver Atkinson Date: Tue, 26 Nov 2024 11:45:53 -0700 Subject: [PATCH] bash-like syntax --- bash_info.md | 20 ++++++++++++++++++++ src/command.rs | 9 ++++----- src/main.rs | 42 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 bash_info.md diff --git a/bash_info.md b/bash_info.md new file mode 100644 index 0000000..9a67a22 --- /dev/null +++ b/bash_info.md @@ -0,0 +1,20 @@ +# invalid +```bash +$a="b" +a = "b" +a= "b" +a ="b" +``` + +# valid +```bash +a="b" +a=b +``` + +# examples +```bash +a="b c d" +echo $a +# output: b c d +``` diff --git a/src/command.rs b/src/command.rs index e2fd2d9..42ecf5e 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,10 +1,12 @@ use std::{ collections::HashMap, - fs::{self, ReadDir}, + fs, path::Path, process, }; +use crate::SPECIAL_CHAR; + type InnerEnv = HashMap; pub struct Env { inner: InnerEnv, @@ -30,7 +32,7 @@ impl Env { } pub fn apply_args_substitution(&self, args: Args) -> Vec { args.iter().fold(Vec::new(), |mut vec, arg| { - if arg.starts_with('$') { + if arg.starts_with(SPECIAL_CHAR) { // we take a slice to get rid of the '$' if let Some(sub) = self.get(&arg[1..]) { vec.push(sub.to_owned()); @@ -66,9 +68,6 @@ impl Command { } pub fn call(&self, words: Args, env: &mut Env) { - // apply env - let words: Args = &env.apply_args_substitution(words); - let x = &self.cmd; x(words, env) } diff --git a/src/main.rs b/src/main.rs index fed5a2f..fd83d27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ use command::{Command, Env, Args}; mod command; +pub static SPECIAL_CHAR: char = '$'; + fn main() { let stdin = std::io::stdin(); let mut in_lock = stdin.lock(); @@ -14,7 +16,6 @@ fn main() { let mut env= Env::new(); env.set("HOME", "/a/fake/dir"); - env.set("PS1", "> "); let commands = &Command::all_commands(&env); println!("Loaded {} commands", commands.len()); @@ -31,7 +32,10 @@ fn main() { .read_line(&mut buf) .expect("Failed to read in to buffer."); - let mut args: Args = &buf.trim().split(' ').map(|f| f.to_owned()).collect::>(); + // TODO split at ' ' but don't split when it's surrounded by quotes '"' + let args= &buf.trim().split(' ').map(|f| f.to_owned()).collect::>(); + let args = env.apply_args_substitution(args); + let mut args: Args = &args; // let mut args: Args = &buf.trim().split(' ').collect::>(); let mut options = commands; @@ -44,7 +48,7 @@ fn main() { Some(choice) => { // No sub commands left to choose from, just pass the input if choice.sub_commands.is_empty() { - choice.call(args, &mut env); + choice.call(&args, &mut env); break; } @@ -55,13 +59,14 @@ fn main() { if args.is_empty() || Command::choose(&args[0], options).is_none() // yes, this means that we search for a choice twice if it misses, but the search takes all of 0ms, so it's fine. { - choice.call(args, &mut env); + choice.call(&args, &mut env); break; } } // When no good choice presents it's self None => { - println!("No option..."); + // perhaps they are trying to use builtins? + parse_lang(&args, &mut env); break; }, } @@ -71,3 +76,30 @@ fn main() { } } } + +fn parse_lang(tokens: Args, env: &mut Env) { + if let Some(first) = tokens.first() { + if let Some((var, val)) = first.split_once('=') { + let var = var.replace(SPECIAL_CHAR, ""); + + // Make it so that strings don't get spliced up + let val = if val.starts_with('"') { + let mut buf = String::new(); + buf += &val; + for i in &tokens[1..] { + buf += " "; + buf += i; + if i.ends_with('"') { + break; + } + } + buf.replace('"', "") + } else { + val.to_owned() + }; + env.set(&var, &val); + } else { + println!("first token is not a command but also doesn't include '='"); + } + } +}