From ac775f8ce02b0b2df4be872da523c60c14c9a708 Mon Sep 17 00:00:00 2001 From: Justine Date: Mon, 6 Feb 2023 23:19:51 +0100 Subject: [PATCH] sqishrc ok --- Cargo.lock | 16 ++++++ Cargo.toml | 1 + README.md | 13 ++--- sqishrc.yaml | 24 +++++++++ src/lib.rs | 70 ++++++++++++++----------- src/shell/config.rs | 123 ++++++++++++++++++++++++++++++++++++++++++++ src/shell/prompt.rs | 42 --------------- 7 files changed, 212 insertions(+), 77 deletions(-) create mode 100644 sqishrc.yaml create mode 100644 src/shell/config.rs delete mode 100644 src/shell/prompt.rs diff --git a/Cargo.lock b/Cargo.lock index 2ef228e..c23d06d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,12 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "log" version = "0.4.17" @@ -178,6 +184,7 @@ dependencies = [ "unicode-segmentation", "users", "which", + "yaml-rust", ] [[package]] @@ -341,3 +348,12 @@ name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index 64921d9..4bf6a74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ termion = "2.0.1" unicode-segmentation = "1.6.0" which = { version = "4.4.0", features = ["regex"] } regex = "1.7.1" +yaml-rust = "0.4.5" diff --git a/README.md b/README.md index a038ccd..aacca5b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Sqish Rust Shell. This is an attempt to create a simple shell in Rust, because why not. -!!! LOOK into the pty crate to handle pseudoterminals such as in top or ssh +TO DO: +* Add prompt RGB support +* Add hotkeys in sqishrc (albeit in the same manner as per colors, the conf Struct already has an hashmap field for it) +* Add aliases in sqishrc (same old same old) + +## sqishrc (Config) +See the included sqishrc.yaml.example file included, and copy it as ~/.sqishrc.yaml -My starting point is [this article](https://www.joshmcguigan.com/blog/build-your-own-shell-rust/) which is excellent. I will start with the final form of a shell given in the article and try to reformat it: -## To fix -* Autocomplete : when nothing found, return input -* Autocomplete : Weird permission issues sometimes (ignore unallowed files and folders) -* Bug with find_common_chars (if I type Cargo.t and press tab, we can't choose between Cargo.toml and Cargo.lock) diff --git a/sqishrc.yaml b/sqishrc.yaml new file mode 100644 index 0000000..1a9517e --- /dev/null +++ b/sqishrc.yaml @@ -0,0 +1,24 @@ +--- +#This is an example sqishrc. to be copied and modified as ~/.sqish.yaml (not .yml, +#I can't be bothered to deal with 2 different extensions). +# +#Prompt: Defines the promptline. Values are defined in all-caps, between $ and _. +#Possible values: +#HISTNUMBER: Current history number. Useful for !callback +#USER : Username. +#HOSTNAME : Hostname of the machine. +#DIR : Name of the current directory. +# +#Colors : All colors come from Termion. +#Possible values: +#RESET : Resets the color. Don't forget to add one in the end of your prompt ! +#GREEN +#BLUE +#RED +#BLACK +#WHITE +#CYAN +#LBLACK (light black, a dark gray in fact) +#Will definitely add RGB support, stay tuned. +prompt: "$COLORLBLACK_ !$HISTNUMBER_$COLORCYAN_[$USER_@$HOSTNAME_] $DIR_> $COLORRESET_" + diff --git a/src/lib.rs b/src/lib.rs index 2729126..fb8a5f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub mod shell { use termion::color; use termion; use unicode_segmentation::UnicodeSegmentation; + use std::collections::HashMap; mod history; use crate::shell::history::*; @@ -23,8 +24,8 @@ pub mod shell { mod autocomplete; use crate::shell::autocomplete::*; - mod prompt; - use crate::shell::prompt::*; + mod config; + use crate::shell::config::*; //used for home directory extern crate dirs; @@ -58,11 +59,11 @@ pub mod shell { Err(e) => eprintln!(" Err: {}", e), } }, - "ssh" => { - let ssh_args = args.peekable().peek().map_or(" ", |x| *x); - let mut child = Command::new("ssh").arg(ssh_args).spawn().unwrap(); - let _ = child.wait().unwrap(); - }, +// "ssh" => { +// let ssh_args = args.peekable().peek().map_or(" ", |x| *x); +// let mut child = Command::new("ssh").arg(ssh_args).spawn().unwrap(); +// let _ = child.wait().unwrap(); +// }, command => { if commands.peek().is_some() { let stdin = match previous_command { @@ -230,14 +231,30 @@ pub mod shell { //Used in conjunction with Up arrow to go back in history //Resetted by pressing Enter let mut current_number = get_curr_history_number(); - let mut prompt = build_prompt(¤t_number); //Used to track the location of the cursor inside the command let mut current_pos: usize = 0; let mut max_pos: usize = 0; + let mut conf = match SqishConf::from_sqishrc() { + Ok(c) => c, + Err(e) => { + let ret_line = format!("Could not build conf, got {}\r\nUsing default\r\n", e); + write!(stdout, "{}", ret_line); + let conf = SqishConf { + promptline: String::from("$COLORGREEN_ [$USER_@$HOSTNAME_] "), + promptline_base: String::from("$COLORGREEN_ [$USER_@$HOSTNAME_] "), + aliases: HashMap::new(), + hotkeys: HashMap::new(), + }; + conf + }, + }; + &conf.update_prompt(get_curr_history_number()); + + //Initialize - write!(stdout, "\r\n SquiShell (sqish)--- \r\n{}", prompt); + write!(stdout, "\r\n SquiShell (sqish)--- \r\n{}", conf.promptline); stdout.flush(); for c in stdin.keys() { @@ -248,44 +265,42 @@ pub mod shell { if *&mycommand.len() == 0 { continue; } - //Search + *&mut mycommand = replace_signs(&mycommand); let (res, list) = Search::build(&mycommand).unwrap().autocomplete(); write!(stdout, "\r\n{}\r\n", list); mycommand.clear(); mycommand.push_str(&res); max_pos = res.len(); current_pos = max_pos; - write!(stdout, "\r\n{}{}", prompt, res); + write!(stdout, "\r\n{}{}", conf.promptline, res); stdout.flush(); } Key::Char('\n') => { - current_number = get_curr_history_number(); - prompt = build_prompt(¤t_number); current_pos = 0; max_pos = 0; if (mycommand != String::from("\n")) && (mycommand != String::from("exit")) { let comm = replace_signs(&mycommand); RawTerminal::suspend_raw_mode(&stdout); - let res = handle_input(&comm); + let _res = handle_input(&comm); RawTerminal::activate_raw_mode(&stdout); mycommand.clear(); //Proper printing - for line in res.split('\n') { - if line != "\r" { - write!(stdout, "\r\n{}", line); - } - } - current_number = get_curr_history_number(); - prompt = build_prompt(¤t_number); - write!(stdout, "{}", prompt).unwrap(); + //for line in res.split('\n') { + // if line != "\r" { + // write!(stdout, "\r\n{}", line); + // } + //} + &conf.update_prompt(get_curr_history_number()); + write!(stdout, "\r\n{}", conf.promptline).unwrap(); stdout.flush(); } else if mycommand == String::from("exit") { write!(stdout, "\r\n Sayonara \r\n"); break; } else { - write!(stdout, "\r\n{}", prompt).unwrap(); + &conf.update_prompt(get_curr_history_number()); + write!(stdout, "\r\n{}", conf.promptline).unwrap(); stdout.flush(); } }, @@ -300,9 +315,7 @@ pub mod shell { max_pos = 0; mycommand.clear(); write!(stdout, "\r\n--CANCEL--\r\n"); - current_number = get_curr_history_number(); - prompt = build_prompt(¤t_number); - write!(stdout, "{}", prompt); + write!(stdout, "{}", conf.promptline); }, Key::Ctrl('t') => { @@ -327,6 +340,7 @@ pub mod shell { } }, + Key::Char(c) => { //if at the end of the command... if current_pos == max_pos { @@ -350,7 +364,7 @@ pub mod shell { }, Key::Delete => { - if current_pos >= 0 && current_pos < max_pos { + if current_pos < max_pos { mycommand.remove(current_pos); if current_pos > 0 { current_pos -= 1; @@ -388,7 +402,6 @@ pub mod shell { }; mycommand = lastcmd.trim().to_string(); (max_pos, current_pos) = get_cmd_curs_pos(&mycommand); - //let prompt = build_prompt(); write!(stdout, "{}", mycommand); }, @@ -401,7 +414,6 @@ pub mod shell { }; mycommand = lastcmd.trim().to_string(); (max_pos, current_pos) = get_cmd_curs_pos(&mycommand); - //let prompt = build_prompt(); write!(stdout, "{}", mycommand); }, diff --git a/src/shell/config.rs b/src/shell/config.rs new file mode 100644 index 0000000..355a574 --- /dev/null +++ b/src/shell/config.rs @@ -0,0 +1,123 @@ +use users::{get_user_by_uid, get_current_uid}; +use termion::color; +use std::{env, fs}; +use std::collections::HashMap; + +extern crate dirs; + +extern crate yaml_rust; +use yaml_rust::{YamlLoader}; + +#[derive(Debug)] +pub struct SqishConf { + pub promptline: String, + pub promptline_base: String, + pub aliases: HashMap, + pub hotkeys: HashMap, +} + +impl SqishConf { + pub fn from_sqishrc() -> Result { + let mut conf_path = match dirs::home_dir() { + Some(p) => p, + None => { + return Err(String::from("Home dir could not be determined.")); + }, + }; + conf_path.push(".sqishrc.yaml"); + + let sqishrc_file = match fs::read_to_string(conf_path) { + Ok(s) => s, + Err(_) => { + return Err(String::from("Could not read ~/.sqishrc.yaml")); + }, + }; + + let sqishrc = match YamlLoader::load_from_str(&sqishrc_file) { + Ok(s) => s, + Err(_) => { + return Err(String::from("sqishrc is not valid yaml")); + }, + }; + + let sqishrc = &sqishrc[0]; + let out_str = String::from(sqishrc["prompt"].as_str().unwrap()); + + let mut out_conf = SqishConf { + promptline: out_str.clone(), + promptline_base: out_str, + aliases: HashMap::new(), + hotkeys: HashMap::new(), + }; + + out_conf.handle_colors(); + + return Ok(out_conf); + } + + #[warn(unused_assignments)] + pub fn update_prompt(&mut self, hist_nbr: i32) { + + let user = String::from( + get_user_by_uid(get_current_uid()) + .unwrap() + .name() + .to_str() + .unwrap() + ); + + let host = String::from( + gethostname::gethostname() + .to_str() + .unwrap() + ); + + let current_dir = String::from( + env::current_dir() + .unwrap() + .to_str() + .unwrap() + ); + + let s = current_dir.split('/'); + let dir_last = String::from( + s.last() + .unwrap() + ); + + let hist_nbr = hist_nbr.to_string(); + + let mut prompt = self.promptline_base.replace("$USER_", &user); + prompt = prompt.replace("$HOSTNAME_", &host); + prompt = prompt.replace("$DIR_", &dir_last); + prompt = prompt.replace("$HISTNUMBER_", &hist_nbr); + prompt = prompt.to_owned(); + self.promptline = prompt; + self.handle_colors(); + } + + + fn handle_colors(&mut self) { + let reset = format!("{}", color::Fg(color::Reset)); + let green = format!("{}", color::Fg(color::Green)); + let blue = format!("{}", color::Fg(color::Blue)); + let red = format!("{}", color::Fg(color::Red)); + let black = format!("{}", color::Fg(color::Black)); + let white = format!("{}", color::Fg(color::White)); + let cyan = format!("{}", color::Fg(color::Cyan)); + let lightblack = format!("{}", color::Fg(color::LightBlack)); + + let mut prompt = self.promptline.replace("$COLORGREEN_", &green); + prompt = prompt.replace("$COLORBLUE_", &blue); + prompt = prompt.replace("$COLORRED_", &red); + prompt = prompt.replace("$COLORBLACK_", &black); + prompt = prompt.replace("$COLORWHITE_", &white); + prompt = prompt.replace("$COLORCYAN_", &cyan); + prompt = prompt.replace("$COLORLBLACK_", &lightblack); + prompt = prompt.replace("$COLORRESET_", &reset); + let promptown = prompt.to_owned(); + self.promptline = promptown; + + } + +} diff --git a/src/shell/prompt.rs b/src/shell/prompt.rs deleted file mode 100644 index 9a26b48..0000000 --- a/src/shell/prompt.rs +++ /dev/null @@ -1,42 +0,0 @@ -use users::{get_user_by_uid, get_current_uid}; -use termion::color; -use std::env; - -pub fn build_prompt(curr_number: &i32) -> String { - - let user = String::from( - get_user_by_uid(get_current_uid()) - .unwrap() - .name() - .to_str() - .unwrap() - ); - - let host = String::from( - gethostname::gethostname() - .to_str() - .unwrap() - ); - - let current_dir = String::from( - env::current_dir() - .unwrap() - .to_str() - .unwrap() - ); - - let s = current_dir.split('/'); - let dir_last = String::from( - s.last() - .unwrap() - ); - - let prompt = String::from(format!("{}{}{}[{}@{} in {}]{} ", - color::Fg(color::LightBlack), - curr_number, - color::Fg(color::LightCyan), - user, host, dir_last, - color::Fg(color::Reset))); - return prompt; -} -