diff --git a/README.md b/README.md index e91b0d8..8101784 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,18 @@ Sqish is now my default shell on my home computer, and I'll keep adding features Also keep in mind I'm a beginner in Rust, so the code is pretty dirty; I will probably come around to reformating it once I have all the features I want and ironed out all the bugs. -TODO: +# TODO + +TODO (Reformat): +* Creating a struct "shell" that contains builder method taking an input and an output as parameters as well as a conf could be cool +* Maybe subdivide lib.src to have more modularity 'cause it getting kinda long... 500 lines it a bit much ? + +TODO(Features): * Allow redirecting >> to the end of a file ? * Allow && in commands ? * Improve word jumping * Add arguments : run a file (sqish myexec) which I believe would make it compatible with gdb ? -* Add export command +* Add export command => Reuse the function set_envvars ## sqishrc (Config) See the included sqishrc file included for comments, and copy it as ~/.sqishrc for use (optionnal). diff --git a/sqishrc b/sqishrc index dfe0cc9..65fd264 100644 --- a/sqishrc +++ b/sqishrc @@ -44,3 +44,8 @@ hotkeys: #Init : A command to run on startup. Can be used for many things... init: "echo ---Initialization over---" + +#Env : to set environment variables at launch +env: + CAT: "meow" + PATH: /home/cat/bin:$PATH diff --git a/src/lib.rs b/src/lib.rs index babd18c..57150ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #[allow(unused_must_use)] pub mod shell { + use regex::Regex; use std::io::Stdout; use std::process::Command; use std::io::stdin; @@ -14,7 +15,7 @@ pub mod shell { use termion::input::TermRead; use termion::raw::IntoRawMode; use termion::raw::RawTerminal; - use termion::cursor; + use termion::{color, cursor}; use termion; use unicode_segmentation::UnicodeSegmentation; use std::collections::HashMap; @@ -33,7 +34,46 @@ pub mod shell { extern crate shell_words; extern crate ctrlc; - fn change_dir(args: &Vec, previous_command: &mut Option) { + ///Stores essential terminal variables + struct TermElements { + //Stores currently typed command + command: String, + //Current cursor position in the line + cur_pos: usize, + //Max position of cursor in the line + max_pos: usize, + stdout: RawTerminal, + //Current history number + current_number: i32, + //Configuration + conf: SqishConf, + } + + ///Used to unify command output and error handling + ///across functions + struct CmdOutput { + outp: String, + rc: i16, + } + + impl CmdOutput { + fn from_values(output: String, rc: i16) -> CmdOutput { + let myoutp = CmdOutput { + outp: output, + rc: rc, + }; + return myoutp; + } + fn new_empty() -> CmdOutput { + let outp = CmdOutput { + outp: String::new(), + rc: 0, + }; + return outp; + } + } + + fn change_dir(args: &Vec, previous_command: &mut Option) -> Result { let homedir = dirs::home_dir().unwrap().into_os_string().into_string().unwrap(); //let new_dir = args.peekable().peek().map_or(homedir.as_str(), |x| *x); let mut new_dir = String::new(); @@ -45,14 +85,15 @@ pub mod shell { let root = Path::new(&new_dir); if let Err(e) = env::set_current_dir(&root) { - eprintln!(" Err: {}", e); - } else { - println!(""); - } + return Err(CmdOutput::from_values( + format!("Error setting directory to {}, got {:?}", + new_dir, e), -1)); + }; *previous_command = None; + return Ok(CmdOutput::new_empty()); } - fn print_hist(previous_command: &mut Option, command_next: bool) { + fn print_hist(previous_command: &mut Option, command_next: bool) -> Result { //let stdout = Stdio::inherit(); let res = get_history(); match res { @@ -65,30 +106,37 @@ pub mod shell { print!("{}", r); } }, - Err(e) => eprintln!(" Err reading history: {}", e), + Err(e) => { + return Err(CmdOutput::from_values(format!("Could not print history, got {:?}", e), 255)); + }, } + return Ok(CmdOutput::new_empty()); } - fn process_line(input: &str) -> String { + + fn process_line(input: &str) -> Result { let mut commands = input.trim().split("|").peekable(); let mut previous_command = None; let mut resultat = String::new(); + let mut rc: i16 = 0; while let Some(command) = commands.next() { let parts = match shell_words::split(&command.trim()) { Ok(w) => w, - Err(e) => { return format!("Error parsing the command : {:?}", e); }, + Err(e) => { + return Err(CmdOutput::from_values(format!("Could not parse command {:?}", e), -1)); + }, }; let command = parts[0].as_str(); let args = Vec::from(&parts[1..]); match command { "cd" => { - change_dir(&args, &mut previous_command); + change_dir(&args, &mut previous_command)?; }, "history" => { let next = commands.peek().is_some(); - print_hist(&mut previous_command, next); + print_hist(&mut previous_command, next)?; }, command => { if commands.peek().is_some() { @@ -126,7 +174,7 @@ pub mod shell { Ok(h) => h, Err(_) => { let err_string = format!("Not found : {}", command); - return String::from(err_string); + return Err(CmdOutput::from_values(err_string, -1)); }, }; @@ -140,12 +188,15 @@ pub mod shell { previous_command = None; let format_res = format!("{}", status); - let _ = &mut resultat.push_str(&format_res); + *&mut rc = format_res.parse::().unwrap(); + *&mut resultat = String::from_utf8(output.stdout).unwrap(); + //let _ = &mut resultat.push_str(&format_res); } }, }; } - return resultat; + //return resultat; + return Ok(CmdOutput::from_values(resultat, rc)); } fn replace_signs(line: &String) -> String { @@ -165,17 +216,18 @@ pub mod shell { return ayo; } - fn handle_input(line: &str) -> String { + fn handle_input(line: &str) -> Result { //history callback if (line.len() > 0) && (line.starts_with('!')) { let command_found = treat_history_callback(line); match command_found { Some(c) => { write_to_history(&c); - let outp = process_line(&c); - return outp; + return process_line(&c); }, - None => return String::from(" "), + None => return Err(CmdOutput::from_values( + String::from("No such value"), + 255)), }; //Regular command } else if line.len() > 0 { @@ -184,7 +236,7 @@ pub mod shell { //Nothing } else { //Command was empty, new line - return String::from(""); + return Ok(CmdOutput::new_empty()); } } @@ -262,25 +314,36 @@ pub mod shell { current_number: &mut i32, conf: &mut SqishConf, stdout: &mut RawTerminal) { + //NORMAL CMD if (mycommand != &String::from("")) && (mycommand != &String::from("exit")) { + //Run the command let comm = replace_signs(&mycommand); RawTerminal::suspend_raw_mode(&stdout); let res = handle_input(&comm); + //Display the results + match res { + Ok(_) => (), + Err(e) => { + println!("\r\n{}Got error {}, RC : {:?}{}", + color::Fg(color::Red), + e.outp, + e.rc, + color::Fg(color::Reset)); + }, + }; + //Prepare for a new command RawTerminal::activate_raw_mode(&stdout); mycommand.clear(); - for line in res.split("\r\n") { - if line != "" && line.starts_with('N'){ - write!(stdout, "{}\r\n", line); - } - } conf.update_prompt(get_curr_history_number()); write!(stdout, "{}", conf.promptline).unwrap(); stdout.flush(); *current_number += 1; + //EXITTING } else if mycommand == &String::from("exit") { write!(stdout, "\r\nThanks for using Sqish!\r\nSayonara \r\n"); RawTerminal::suspend_raw_mode(&stdout); std::process::exit(0); + //EMPTY LINE } else { conf.update_prompt(get_curr_history_number()); write!(stdout, "\r\n{}", conf.promptline).unwrap(); @@ -350,6 +413,7 @@ pub mod shell { aliases: HashMap::new(), hotkeys: HashMap::new(), init: String::new(), + env: HashMap::new(), }; let ret_line = format!("Could not build conf, got {}\ \r\nUsing default promptline", e); @@ -516,20 +580,6 @@ pub mod shell { write!(elems.stdout, "{}{}", elems.conf.promptline, elems.command); } - struct TermElements { - //Stores currently typed command - command: String, - //Current cursor position in the line - cur_pos: usize, - //Max position of cursor in the line - max_pos: usize, - stdout: RawTerminal, - //Current history number - current_number: i32, - //Configuration - conf: SqishConf, - } - fn jmp_nxt_word(elems: &mut TermElements) { let steps = find_next_space(&elems.cur_pos, &elems.max_pos, &elems.command); //println!("steps: {:?} cur {:?} max {:?}", steps, elems.cur_pos, elems.max_pos); @@ -588,6 +638,38 @@ pub mod shell { }; } + //I smell horrible code in here + fn set_envvars(conf: &SqishConf) { + let vars = conf.env.clone(); + let re = Regex::new(r"\$[A-Za-z_]+").unwrap(); + + for (key, value) in vars { + let mut after = value.clone(); + let mut nbr_of_vars = *&value.matches('$').count(); + while nbr_of_vars >= 1 { + let temp = after.clone(); + let caps = re.captures(&temp).unwrap(); + for cap in caps.iter() { + match cap { + Some(c) => { + let myvar = String::from(c.as_str()).replace('$', ""); + match env::var(myvar) { + Ok(r) => { + *&mut after = after.replace(c.as_str(), &r); + }, + Err(_) => continue, + }; + }, + None => continue, + }; + } + nbr_of_vars -= 1; + } + env::set_var(&key, &after); + } + } + + //THE ENTRYPOINT FUNCTION pub fn run_raw() { @@ -610,6 +692,7 @@ pub mod shell { //Initializing write!(elems.stdout, "\r\n ---Sqish initializing--- \r\n{}", elems.conf.promptline); elems.stdout.flush(); + set_envvars(&elems.conf); if elems.conf.init != String::from("") { run_cmd(&mut String::from(&elems.conf.init), &mut elems.current_number, &mut elems.conf, &mut elems.stdout); diff --git a/src/shell/config.rs b/src/shell/config.rs index 526adb9..89ab380 100644 --- a/src/shell/config.rs +++ b/src/shell/config.rs @@ -16,6 +16,7 @@ pub struct SqishConf { pub aliases: HashMap, pub hotkeys: HashMap, pub init: String, + pub env: HashMap, } impl SqishConf { @@ -54,12 +55,16 @@ impl SqishConf { let hotkeys_yaml = &sqishrc["hotkeys"]; let hotkeys = Self::yaml_dict2hashmap(hotkeys_yaml); + let env_yaml = &sqishrc["env"]; + let env = Self::yaml_dict2hashmap(env_yaml); + let mut out_conf = SqishConf { promptline: out_str.clone(), promptline_base: out_str, aliases: aliases, hotkeys: hotkeys, init: startup, + env: env, }; out_conf.handle_rgb();