├── .gitignore ├── src ├── customs │ ├── mod.rs │ ├── source.rs │ ├── history.rs │ ├── cd.rs │ └── touch.rs ├── shellname.rs ├── tokenizer.rs └── main.rs ├── Cargo.toml ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea -------------------------------------------------------------------------------- /src/customs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cd; 2 | pub mod touch; 3 | pub mod history; 4 | pub mod source; -------------------------------------------------------------------------------- /src/customs/source.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | pub enum NativeFn { 4 | Source, 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/customs/history.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use chrono::{DateTime, Utc}; 3 | 4 | struct HistEntry { 5 | line: String, 6 | timestamp: DateTime, 7 | 8 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cr4sh_" 3 | version = "0.0.1" 4 | authors = ["Beka Modebadze "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | signal-hook = '0.3.8' 11 | ctrlc = '3.1.9' 12 | nix = '0.20.0' 13 | crossbeam-channel = "0.5" 14 | anyhow = "1.0" 15 | log = "0.4" 16 | users = "0.11" 17 | sysinfo = "0.17" 18 | dirs = "3.0" 19 | chrono = "0.4" 20 | termion = "*" 21 | fs-set-times = "0.6" 22 | lazy_static = "1.4" -------------------------------------------------------------------------------- /src/customs/cd.rs: -------------------------------------------------------------------------------- 1 | use crate::{shellname::ShellName, tokenizer::Tokenizer}; 2 | use std::{ 3 | env::{current_dir, set_current_dir}, 4 | path::PathBuf, 5 | }; 6 | 7 | /// Implementation of a Linux's `cd` command, 8 | /// which stands for change directory. 9 | pub fn change_directory(shell_name: &mut ShellName, line: &mut Tokenizer) { 10 | assert_eq!("cd".to_string(), line.next().unwrap()); 11 | 12 | let path = line.next(); 13 | 14 | let new_path: PathBuf = if path.is_some() { 15 | let tmp = path.unwrap(); 16 | if tmp.eq("~") { 17 | dirs::home_dir().unwrap() 18 | } else { 19 | PathBuf::from(tmp) 20 | } 21 | } else { 22 | dirs::home_dir().unwrap() 23 | }; 24 | 25 | if let Err(e) = set_current_dir(new_path) { 26 | eprintln!("Error: {}", e); 27 | } else { 28 | let cur = current_dir().unwrap(); 29 | shell_name.set_current_dir(&cur.to_str().unwrap()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shellname.rs: -------------------------------------------------------------------------------- 1 | use sysinfo::SystemExt; 2 | use termion::{color, style}; 3 | 4 | pub struct ShellName { 5 | name: String, 6 | current_dir: String, 7 | pub shell_name: String, 8 | } 9 | 10 | impl ShellName { 11 | pub fn new(current_dir: &str) -> Self { 12 | let user = build_user_minishell(); 13 | ShellName { 14 | name: user.clone(), 15 | current_dir: current_dir.to_string(), 16 | shell_name: format!( 17 | "{}┌{}{}:{} {}\n{}└─> {}{}§ {}{}", 18 | color::Fg(color::Red), 19 | user, 20 | color::Fg(color::Cyan), 21 | current_dir, 22 | color::Fg(color::Reset), 23 | color::Fg(color::Red), 24 | color::Fg(color::Blue), 25 | style::Bold, 26 | color::Fg(color::Reset), 27 | style::Reset, 28 | ), 29 | } 30 | } 31 | 32 | pub fn set_current_dir(&mut self, dir: &str) { 33 | let home = dirs::home_dir().unwrap(); 34 | if let Some(h) = home.to_str() { 35 | if dir.starts_with(h) { 36 | self.current_dir = dir.replace(h, "~"); 37 | } else { 38 | self.current_dir = dir.to_string(); 39 | } 40 | } 41 | self.shell_name = format!( 42 | "{}┌{}{}:{} {}\n{}└─> {}{}§ {}{}", 43 | color::Fg(color::Red), 44 | self.name.to_string(), 45 | color::Fg(color::Cyan), 46 | &self.current_dir, 47 | color::Fg(color::Reset), 48 | color::Fg(color::Red), 49 | color::Fg(color::Blue), 50 | style::Bold, 51 | color::Fg(color::Reset), 52 | style::Reset, 53 | ) 54 | } 55 | } 56 | 57 | /// build a minishell name for the display 58 | fn build_user_minishell() -> String { 59 | let mut username = String::from("«"); 60 | 61 | // get user name 62 | let u = users::get_user_by_uid(users::get_current_uid()).unwrap(); 63 | 64 | username.push_str(&u.name().to_string_lossy()); 65 | username.push('@'); 66 | 67 | // get system name 68 | let system = sysinfo::System::new_all(); 69 | username.push_str(&system.get_name().unwrap()); 70 | username.push('»'); 71 | 72 | username 73 | } 74 | 75 | #[cfg(test)] 76 | mod test { 77 | use super::*; 78 | 79 | #[test] 80 | fn generate_shellname() { 81 | let sh = ShellName::new("home"); 82 | assert_eq!("home".to_string(), sh.current_dir); 83 | } 84 | 85 | #[test] 86 | fn test_dir_change() { 87 | let mut sh = ShellName::new("home"); 88 | sh.set_current_dir("~"); 89 | assert_eq!("~".to_string(), sh.current_dir); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cr4sh_ 0.0.1 2 | 3 | **cr4sh_** (pronounced _crash_, because it crashes all the time) is a UNIX mini-shell implemented with Rust. 4 | This shell is a great for educational purposes. You can learn about Linux system, processes, stdio, signals and much more. 5 | 6 | You can follow blog posts which I wrote while building this shell: 7 | 8 | - [Systems Programming with Rust (Part 1)](https://www.bexxmodd.com/post/systems-programming-with-rust-1) 9 | - [Systems Programming with Rust (Part 2)](https://www.bexxmodd.com/post/systems-programming-with-rust-2) 10 | - Systems Programming with Rust (Part 3) <<-- comming soon 11 | 12 |
13 |
14 | 15 | Currently, `cr4sh_` provides the following functionality: 16 | 17 | - Multiprocessing 18 | - SIGINT and SIGKILL signal handling 19 | - Execution of OS executables 20 | - Redirection of standard input & output 21 | - Appending stdout to the file 22 | - Piping commands and combining with redirection 23 | - `cd` command to change directories 24 | - `touch` for creating files and updating accessed & modified dates 25 | - `&&` calls to chain multiple commands 26 | 27 |
28 | 29 | ### Demo: 30 | 31 | [![asciicast](https://asciinema.org/a/aDG5n2136psEN4rnt6oqb9i6v.svg)](https://asciinema.org/a/aDG5n2136psEN4rnt6oqb9i6v) 32 | 33 |
34 | 35 | ## Installation 36 | 37 | To run the `cr4sh_` shell copy following code in your terminal: 38 | 39 | ```bash 40 | git clone https://github.com/bexxmodd/cr4sh_.git && \ 41 | cd cr4sh_ && \ 42 | cargo run 43 | ``` 44 | 45 |
46 | 47 | ## Plans 48 | 49 |
50 | Currently I'm looking to add other custom shell functions (which are listed in the TODO list) and if you want to contribute looking into the `customs` directory inside the `src` directory is a great start. If you want just add any of the functionality and I'll take of gluing with the rest of the program. Also, if you have design skills some 80's style logo can be a great contribution! 51 |
52 |
53 | 54 | Main priority right now is to add cursor and history function. 55 |
56 | 57 | ### TODO: 58 | 59 | - [ ] Create a logo 60 | - [x] Allow piping of the commands 61 | - [x] Add personalized color printing of the terminal user 62 | - [x] Allow execution of local executables properly 63 | - [x] Allow chain of commands when `&&` is supplied 64 | - [x] Handle append (`>>`) directive 65 | - [ ] Handle `&` symbol to send command as a background process 66 | - [ ] Expend signal handling capabilities 67 | - [ ] Implement cursor to handle arrow, home, end keyboard inputs and cursor movement 68 | - [ ] Usage of Tab to autocomplete commands and file/directory names 69 | - [x] Implement `touch` function: 70 | - [ ] Implement `history` function 71 | - [ ] Implement `dot/source` function 72 | - [ ] Implement redirection for custom functions: 73 | - [ ] add additional argument to functions for stdout file 74 | - [ ] Implement piping for custom functions 75 | - [ ] Add customization of colors and style for a shell-name 76 | -------------------------------------------------------------------------------- /src/customs/touch.rs: -------------------------------------------------------------------------------- 1 | use crate::Tokenizer; 2 | use fs_set_times::{set_atime, set_mtime, SystemTimeSpec}; 3 | use std::collections::HashSet; 4 | use std::io::{ErrorKind, Result}; 5 | use std::path::Path; 6 | use std::time::SystemTime; 7 | use std::{fs, io}; 8 | 9 | pub fn touch(tokenizer: &mut Tokenizer) -> Result<()> { 10 | let cmd = parse_command(tokenizer).unwrap(); 11 | let mut create_flag = true; 12 | 13 | let mut newfile_index = 0usize; 14 | let mut reffile_index = 0usize; 15 | let mut options_index = usize::MAX; 16 | for (i, val) in cmd.iter().enumerate() { 17 | if val.starts_with('-') { 18 | options_index = i; 19 | if cmd[options_index].contains('r') { 20 | if cmd.len() > i + 1 { 21 | reffile_index = i + 1; 22 | } else { 23 | return Err(io::Error::new( 24 | ErrorKind::InvalidInput, "Invalid argument" 25 | )); 26 | } 27 | if cmd.len() > i + 2 { 28 | newfile_index = i + 2; 29 | } else { 30 | newfile_index = i - 1 31 | } 32 | } else { 33 | newfile_index = i + 1; 34 | } 35 | } 36 | } 37 | 38 | // will need when 'r' flag is implemented 39 | let refer = if reffile_index > 0 { 40 | get_reference_timestamp(&cmd[reffile_index]) 41 | } else { 42 | None 43 | }; 44 | 45 | if options_index < cmd.len() { 46 | for op in cmd[options_index].chars().into_iter() { 47 | match op { 48 | '-' => continue, 49 | 'c' => create_flag = false, 50 | 'a' => set_time(&cmd[newfile_index..], &refer, 51 | create_flag, set_atime)?, 52 | 'm' => set_time(&cmd[newfile_index..], &refer, 53 | create_flag, set_mtime)?, 54 | 'r' => set_time(&cmd[newfile_index..], &refer, 55 | create_flag, set_mtime)?, 56 | _ => eprintln!("{} is invalid operand", op), 57 | } 58 | } 59 | } else { 60 | set_time(&cmd[newfile_index..], &refer, create_flag, set_atime)?; 61 | set_time(&cmd[newfile_index..], &refer, create_flag, set_mtime)?; 62 | } 63 | Ok(()) 64 | } 65 | 66 | fn parse_command(tokenizer: &mut Tokenizer) -> Result> { 67 | tokenizer.next(); 68 | let symbols: HashSet<_> = 69 | vec!["~", "#", "@", "<", ">", "&", "|", ">", "%", "*", "(", ")", "!"] 70 | .into_iter() 71 | .collect(); 72 | let res: Vec<_> = tokenizer.filter(|v| !symbols.contains(&v[0..1])).collect(); 73 | Ok(res) 74 | } 75 | 76 | fn set_time( 77 | src: &[String], 78 | refer: &Option, 79 | flag: bool, 80 | func: fn(path: String, atime: SystemTimeSpec) -> Result<()>, 81 | ) -> Result<()> { 82 | let time = if refer.is_some() { 83 | refer.unwrap() 84 | } else { 85 | SystemTime::now() 86 | }; 87 | 88 | for f in src { 89 | if !Path::new(f).exists() && flag { 90 | fs::File::create(f)?; 91 | } 92 | func(f.to_string(), SystemTimeSpec::from(time))?; 93 | } 94 | Ok(()) 95 | } 96 | 97 | fn get_reference_timestamp(refer: &str) -> Option { 98 | if Path::new(refer).exists() { 99 | return Some(fs::metadata(refer).unwrap().modified().unwrap()); 100 | } 101 | None 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | use core::time; 108 | use std::thread::sleep; 109 | 110 | #[test] 111 | fn test_create_file() { 112 | let filename = "test000.txt"; 113 | let mut token = Tokenizer::new("touch test000.txt"); 114 | let _ = touch(&mut token); 115 | 116 | assert!(Path::new(filename).exists()); 117 | 118 | if fs::remove_file(filename).is_err() { 119 | eprintln!("Can't remove {}", filename); 120 | } 121 | } 122 | 123 | #[test] 124 | fn test_parser() { 125 | let mut line = Tokenizer::new("trulala -a nu 'patom paidzom'"); 126 | let expected: Vec = vec![ 127 | "-a".to_string(), "nu".to_string(), "patom paidzom".to_string() 128 | ]; 129 | assert_eq!(expected, parse_command(&mut line).unwrap()); 130 | 131 | 132 | } 133 | 134 | #[test] 135 | fn test_create_with_space_in_name() { 136 | let filename1 = "test file"; 137 | let filename2 = "second file"; 138 | let mut token = Tokenizer::new("touch 'test file' \"second file\""); 139 | 140 | let _ = touch(&mut token); 141 | 142 | assert!(Path::new(filename1).exists()); 143 | assert!(Path::new(filename2).exists()); 144 | 145 | if fs::remove_file(filename1).is_err() { 146 | eprintln!("Can't remove {}", filename1); 147 | } 148 | 149 | if fs::remove_file(filename2).is_err() { 150 | eprintln!("Can't remove {}", filename2); 151 | } 152 | } 153 | 154 | #[test] 155 | fn test_no_file_creation() { 156 | let filename = "test001.txt"; 157 | let mut token = Tokenizer::new("touch -c test001.txt"); 158 | let _ = touch(&mut token); 159 | assert!(!Path::new(filename).exists()); 160 | } 161 | 162 | #[test] 163 | fn test_create_multiple_files() { 164 | let files = [ 165 | "multi01".to_string(), 166 | "multi02".to_string(), 167 | "multi03".to_string() 168 | ]; 169 | let mut token = Tokenizer::new("touch multi01 multi02 multi03"); 170 | let res = touch(&mut token); 171 | assert!(res.is_ok()); 172 | 173 | for f in files.iter() { 174 | assert!(Path::new(f).exists()); 175 | } 176 | 177 | for f in files.iter() { 178 | if fs::remove_file(f).is_err() { 179 | eprintln!("Can't remove {}", f); 180 | } 181 | } 182 | } 183 | 184 | #[test] 185 | fn test_updated_modification() { 186 | let filename = "test002.txt"; 187 | let mut token = Tokenizer::new("touch test002.txt"); 188 | let _ = touch(&mut token); 189 | let mut metadata = fs::metadata(filename).unwrap(); 190 | let init_time = metadata.modified().unwrap(); 191 | 192 | sleep(time::Duration::from_secs(1)); 193 | 194 | let _ = set_time(&[filename.to_string()], &None, false, set_mtime); 195 | metadata = fs::metadata(filename).unwrap(); 196 | let modified_time = metadata.modified().unwrap(); 197 | 198 | let diff = modified_time.duration_since(init_time).unwrap().as_secs(); 199 | assert_eq!(diff, time::Duration::from_secs(1).as_secs()); 200 | 201 | if fs::remove_file(filename).is_err() { 202 | eprintln!("Can't remove {}", filename); 203 | } 204 | } 205 | 206 | #[test] 207 | fn test_updated_access() { 208 | let filename = "test003.txt"; 209 | let mut token = Tokenizer::new("touch test003.txt"); 210 | let _ = touch(&mut token); 211 | let mut metadata = fs::metadata(filename).unwrap(); 212 | let init_time = metadata.accessed().unwrap(); 213 | 214 | sleep(time::Duration::from_secs(1)); 215 | 216 | let _ = set_time(&[filename.to_string()], &None, false, set_atime); 217 | metadata = fs::metadata(filename).unwrap(); 218 | let modified_time = metadata.accessed().unwrap(); 219 | 220 | let diff = modified_time.duration_since(init_time).unwrap().as_secs(); 221 | assert_eq!(diff, time::Duration::from_secs(1).as_secs()); 222 | 223 | if fs::remove_file(filename).is_err() { 224 | eprintln!("Can't remove {}", filename); 225 | } 226 | } 227 | 228 | #[test] 229 | fn test_set_reffile_time() { 230 | let filename = "test004.txt"; 231 | let mut del_token = Tokenizer::new("touch delme"); 232 | let _ = touch(&mut del_token); 233 | 234 | let metadata = fs::metadata("delme").unwrap(); 235 | 236 | sleep(time::Duration::from_secs(1)); 237 | 238 | let mut token = Tokenizer::new("touch -r delme test004.txt"); 239 | let _ = touch(&mut token); 240 | let result_metadata = fs::metadata(filename).unwrap(); 241 | 242 | assert_eq!( 243 | metadata.modified().unwrap(), 244 | result_metadata.modified().unwrap() 245 | ); 246 | 247 | if fs::remove_file(filename).is_err() { 248 | eprintln!("Can't remove {}", filename); 249 | } 250 | 251 | if fs::remove_file("delme").is_err() { 252 | eprintln!("Can't remove delme"); 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/tokenizer.rs: -------------------------------------------------------------------------------- 1 | 2 | /// struct that reads a line of string splits on 3 | /// white space and creates iterator of tokens 4 | #[derive(Clone, Debug)] 5 | pub struct Tokenizer { 6 | // holds string as an option and splits on 7 | // whitespace only when next() is called. 8 | current: Option, 9 | is_pipe: bool, 10 | has_redirection: bool, 11 | } 12 | 13 | impl Tokenizer { 14 | /// constructor 15 | pub fn new(line: &str) -> Self { 16 | let is_pipe = line.contains(" | "); 17 | let has_redirection = line.contains(" >") || line.contains(" < "); 18 | 19 | Tokenizer { 20 | current: Some(line.to_string()), 21 | is_pipe, 22 | has_redirection, 23 | } 24 | } 25 | 26 | /// If the `current` line contains redirection ">" or "<" 27 | /// returns all tokens before redirection as a vector of strings. 28 | /// Else is found returns all the tokens as vector of strings. 29 | /// This method call consumes tokens from `current` 30 | pub fn args_before_redirection(&mut self) -> Vec { 31 | if !self.has_redirection() { 32 | return self.get_args() 33 | } 34 | 35 | let mut args = vec![]; 36 | while self.current.is_some() { 37 | if self.peek().eq(">") || 38 | self.peek().eq("<") || 39 | self.peek().eq(">>") { 40 | break; 41 | } else { 42 | args.push(self.next().unwrap()); 43 | } 44 | } 45 | args 46 | } 47 | 48 | /// If the `current` line has a pipe symbol "|" in it 49 | /// this method will return part of string string before 50 | /// that symbol as a new Tokenizer object. This method consumes 51 | /// `current` line, so if no pipe symbol is found, it will 52 | /// reconstruct a new Tokenizer while disposing current one. 53 | pub fn commands_before_pipe(&mut self) -> Tokenizer { 54 | if !self.is_pipe() { 55 | self.clone() 56 | } else { 57 | self._split_tokenizer("|") 58 | } 59 | } 60 | 61 | /// get all the argument from the `current` line 62 | /// and return as a vector of strings. 63 | pub fn get_args(&mut self) -> Vec { 64 | let mut args = vec![]; 65 | while let Some(a) = self.next() { 66 | if a.eq("&&") { 67 | break; 68 | } 69 | args.push(a); 70 | } 71 | args 72 | } 73 | 74 | /// checks if the `current` Tokenizer has pipe directive 75 | pub fn is_pipe(&self) -> bool { 76 | self.is_pipe 77 | } 78 | 79 | /// check if the `current` Tokenizer has redirection directive 80 | pub fn has_redirection(&self) -> bool { 81 | self.has_redirection 82 | } 83 | 84 | /// checks if the `current` contains given string pattern 85 | pub fn contains(&self, pattern: &str) -> bool { 86 | if let Some(cur) = self.current.as_ref() { 87 | cur.contains(pattern) 88 | } else { 89 | false 90 | } 91 | } 92 | 93 | /// Check if the current lines starts with a given prefix 94 | pub fn starts_with(&self, prefix: &str) -> bool { 95 | if let Some(cur) = self.current.as_ref() { 96 | if prefix.len() > cur.len() { 97 | return false 98 | } 99 | if prefix.eq(&cur[..prefix.len()]) { 100 | return true 101 | } 102 | } 103 | false 104 | } 105 | 106 | /// peek what is the next token without consuming it. 107 | /// this returns a copy of the next token. 108 | pub fn peek(&self) -> String { 109 | let mut res = String::new(); 110 | if let Some(cur) = self.current.as_deref() { 111 | let mut open = 0u8; 112 | for c in cur.chars().into_iter() { 113 | if c.eq(&'"') || c.eq(&'\'') { 114 | open = open ^ 1; 115 | } else if c.eq(&' ') && open == 0 { 116 | break; 117 | } else { 118 | res.push(c); 119 | } 120 | } 121 | } 122 | res 123 | } 124 | 125 | pub fn get_multiple_tokens(&mut self, pattern: &str) -> Vec { 126 | let mut toks: Vec<_> = vec![]; 127 | while self.current.is_some() { 128 | toks.push(self._split_tokenizer(pattern)) 129 | } 130 | toks 131 | } 132 | 133 | pub fn _split_tokenizer(&mut self, pattern: &str) -> Tokenizer { 134 | let mut before = String::new(); 135 | while let Some(a) = self.next() { 136 | if a.eq(pattern) { 137 | break; 138 | } 139 | // if next value has space in it, that means user supplied 140 | // text in quotation marks, & we preserve it in a new Tokenizer 141 | if a.contains(' ') { 142 | before.push('\''); 143 | before.push_str(&a); 144 | before.push('\''); 145 | } else { 146 | before.push_str(&a); 147 | } 148 | before.push(' '); 149 | } 150 | before.pop(); 151 | Tokenizer::new(&before) 152 | } 153 | 154 | pub fn is_empty(&self) -> bool { 155 | self.current.is_none() 156 | } 157 | } 158 | 159 | impl Iterator for Tokenizer { 160 | type Item = String; 161 | 162 | fn next(&mut self) -> Option { 163 | let mut stop = usize::MAX; 164 | let mut nxt= String::new(); 165 | let mut remainder = String::new(); 166 | 167 | if let Some(s) = &mut self.current { 168 | let mut open = 0u8; 169 | for (i, c) in s.chars().into_iter().enumerate() { 170 | if c.eq(&'"') || c.eq(&'\'') { 171 | open = open ^ 1; 172 | } else if c.eq(&' ') && open == 0 { 173 | stop = i + 1; 174 | break 175 | } else { 176 | nxt.push(c); 177 | } 178 | } 179 | if stop < s.len() { 180 | remainder = s[stop..].to_string(); 181 | } 182 | } 183 | 184 | if remainder.is_empty() { 185 | self.current = None 186 | } else { 187 | self.current = Some(remainder); 188 | } 189 | if nxt.is_empty() { 190 | None 191 | } else { 192 | Some(nxt) 193 | } 194 | } 195 | } 196 | 197 | #[cfg(test)] 198 | mod tests { 199 | use super::*; 200 | 201 | #[test] 202 | fn test_empty_string() { 203 | let mut line = Tokenizer::new(&""); 204 | assert_eq!(None, line.next()); 205 | assert_eq!(None, line.current); 206 | } 207 | 208 | #[test] 209 | fn test_two_word_string() { 210 | let mut line = Tokenizer::new(&"Hello World"); 211 | assert_eq!("Hello".to_string(), line.next().unwrap()); 212 | assert_eq!("World".to_string(), line.current.unwrap()); 213 | } 214 | 215 | #[test] 216 | fn test_multiple_calls() { 217 | let mut line = Tokenizer::new(&"Hello Darkness My Old Friend"); 218 | 219 | assert_eq!("Hello".to_string(), line.next().unwrap()); 220 | assert_eq!("Darkness".to_string(), line.next().unwrap()); 221 | assert_eq!("My".to_string(), line.next().unwrap()); 222 | assert_eq!("Old".to_string(), line.next().unwrap()); 223 | assert_eq!("Friend".to_string(), line.next().unwrap()); 224 | assert_eq!(None, line.next()); 225 | } 226 | 227 | #[test] 228 | fn test_peek() { 229 | let mut line = Tokenizer::new(&"Hello Darkness > \"My Oldie\""); 230 | assert_eq!("Hello".to_string(), line.peek()); 231 | assert_eq!("Hello".to_string(), line.next().unwrap()); 232 | assert_eq!("Darkness".to_string(), line.peek()); 233 | line.next(); 234 | line.next(); 235 | assert_eq!("My Oldie".to_string(), line.peek()); 236 | } 237 | 238 | #[test] 239 | fn test_contains() { 240 | let line = Tokenizer::new("This line tests $ symbol"); 241 | assert!(line.contains(&"$")); 242 | } 243 | 244 | #[test] 245 | fn test_args_before_redirection() { 246 | let mut line = Tokenizer::new("Hello World > Bye"); 247 | let v = line.args_before_redirection(); 248 | assert_eq!(2, v.len()); 249 | assert_eq!("Hello".to_string(), *v[0]); 250 | assert_eq!("World".to_string(), *v[1]); 251 | 252 | let a = line.get_args(); 253 | assert_eq!(2, a.len()); 254 | assert_eq!("Bye".to_string(), *a[1]); 255 | } 256 | 257 | #[test] 258 | fn test_commands_before_pipe() { 259 | let mut line = Tokenizer::new("ls -a | cat"); 260 | let mut v = line.commands_before_pipe(); 261 | assert_eq!("ls".to_string(), v.next().unwrap()); 262 | assert_eq!("-a".to_string(), v.next().unwrap()); 263 | } 264 | 265 | #[test] 266 | fn test_prefix() { 267 | let line = Tokenizer::new("this line starts with this"); 268 | assert!(line.starts_with("this")); 269 | } 270 | 271 | #[test] 272 | fn test_quotation_marks() { 273 | let mut line = Tokenizer::new("echo \"Hello World\" 'Rust Lang' Yay!"); 274 | 275 | assert_eq!("echo".to_string(), line.next().unwrap()); 276 | assert_eq!("Hello World".to_string(), line.next().unwrap()); 277 | assert_eq!("Rust Lang".to_string(), line.next().unwrap()); 278 | assert_eq!("Yay!".to_string(), line.next().unwrap()); 279 | assert_eq!(None, line.next()); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod customs; 2 | mod shellname; 3 | mod tokenizer; 4 | 5 | #[macro_use] 6 | extern crate lazy_static; 7 | 8 | use crate::{shellname::*, tokenizer::*}; 9 | use crate::customs::{cd, touch}; 10 | use signal_hook::{ 11 | consts::{SIGINT, SIGQUIT}, 12 | iterator, 13 | }; 14 | use std::collections::HashSet; 15 | use std::{ 16 | env::current_dir, 17 | fs::{File, OpenOptions}, 18 | }; 19 | use std::{ 20 | error::Error, 21 | io::{self, Write}, 22 | process, thread, 23 | }; 24 | 25 | lazy_static! { 26 | /// Global HashSet that contains all the internally defined shell functions 27 | static ref CUSTOM_FN: HashSet<&'static str> = { 28 | vec!["cd", "source", "touch", "history", ">"] 29 | .into_iter() 30 | .collect() 31 | }; 32 | } 33 | 34 | fn main() { 35 | if register_signal_handlers().is_err() { 36 | println!("Signals are not handled properly"); 37 | } 38 | 39 | let cur = current_dir().unwrap(); 40 | let cur = cur.strip_prefix(dirs::home_dir().unwrap()).unwrap(); 41 | 42 | // create initial shell terminal display 43 | let mut minishell = ShellName::new(cur.to_str().unwrap()); 44 | 45 | loop { 46 | run_shell(&mut minishell); 47 | } 48 | } 49 | 50 | /// Register UNIX system signals 51 | fn register_signal_handlers() -> Result<(), Box> { 52 | let mut signals = iterator::Signals::new(&[SIGINT, SIGQUIT])?; 53 | 54 | // signal execution is passed to the child process 55 | thread::spawn(move || { 56 | for sig in signals.forever() { 57 | match sig { 58 | SIGQUIT => process::exit(0), 59 | SIGINT => assert_eq!(2, sig), // assert that the signal is sent 60 | _ => continue, 61 | } 62 | } 63 | }); 64 | 65 | Ok(()) 66 | } 67 | 68 | /// Run the minishell to execute user supplied instructions 69 | fn run_shell(shell_name: &mut ShellName) { 70 | write_to_stdout(&shell_name.shell_name) 71 | .expect("Unable to write to stdout"); 72 | 73 | let mut cmd_line = match get_user_commands() { 74 | Ok(t) => t, 75 | Err(e) => { 76 | eprintln!("Error: {}", e); 77 | return; 78 | } 79 | }; 80 | 81 | for mut token in cmd_line.get_multiple_tokens("&&") { 82 | if CUSTOM_FN.contains(&token.peek()[0..]) { 83 | if let Err(e) = execute_custom_fn(shell_name, &mut token) { 84 | eprint!("Error: {}", e); 85 | } 86 | continue; 87 | } else if token.is_pipe() { 88 | if let Err(e) = piped_cmd_execution(&mut token) { 89 | eprintln!("Error: {}", e); 90 | } 91 | continue; 92 | } else if token.has_redirection() { 93 | let mut proc = match redirect_cmd_execution(&mut token) { 94 | Ok(p) => p, 95 | Err(e) => { 96 | eprintln!("Error: {}", e); 97 | return; 98 | } 99 | }; 100 | 101 | if let Ok(mut c) = proc.spawn() { 102 | c.wait().unwrap(); 103 | } else { 104 | eprintln!("Invalid: command not found!"); 105 | } 106 | } else { 107 | // execute command that has no redirection 108 | let cmd = token.get_args(); 109 | if process::Command::new(&cmd[0]) 110 | .args(&cmd[1..]) 111 | .status().is_err() { 112 | eprintln!("{}: command not found!", &cmd[0]); 113 | } 114 | } 115 | } 116 | } 117 | 118 | // This function is used to execute shell defined functions 119 | fn execute_custom_fn(shell_name: &mut ShellName, 120 | token: &mut Tokenizer) -> Result<(), io::Error> { 121 | match &token.peek()[0..] { 122 | "cd" => cd::change_directory(shell_name, token), 123 | "touch" | ">" => touch::touch(token)?, 124 | _ => println!("Not implemented yet"), 125 | } 126 | Ok(()) 127 | } 128 | 129 | /// If user supplies piped command this function splits it into 130 | /// two processes, executes them and pipes one being input to the pipe 131 | /// and the other being output from the pipe, which ends up displayed 132 | pub fn piped_cmd_execution(cmd_line: &mut Tokenizer) -> Result<(), io::Error> { 133 | let mut tokens_before_pipe = cmd_line.commands_before_pipe(); 134 | 135 | let mut after_pipe_cmd: Vec = vec![]; 136 | let mut before_pipe_cmd: Vec = vec![]; 137 | 138 | let mut proc = if cmd_line.has_redirection() { 139 | redirect_cmd_execution(cmd_line)? 140 | } else { 141 | after_pipe_cmd = cmd_line.get_args(); 142 | // create a child process that has end of pipe open for stream 143 | process::Command::new(&after_pipe_cmd[0]) 144 | }; 145 | 146 | // check if we have any arguments otherwise execute command 147 | if after_pipe_cmd.is_empty() { 148 | proc.args(&after_pipe_cmd[1..]); 149 | } 150 | let child = proc.stdin(process::Stdio::piped()).spawn()?; 151 | 152 | let mut proc2 = if tokens_before_pipe.has_redirection() { 153 | redirect_cmd_execution(&mut tokens_before_pipe)? 154 | } else { 155 | before_pipe_cmd = tokens_before_pipe.get_args(); 156 | // create child process that redirects its output 157 | // to the stdout end of the pipe. 158 | // this will execute command and send output 159 | // to the previously created process pipe. 160 | process::Command::new(&before_pipe_cmd[0]) 161 | }; 162 | 163 | // check for arguments 164 | if before_pipe_cmd.len() > 1 { 165 | proc2.args(&before_pipe_cmd[1..]); 166 | } 167 | 168 | proc2.stdout(child.stdin.unwrap()).output()?; 169 | Ok(()) 170 | } 171 | 172 | /// If the user command has stream redirection this function is used 173 | /// to accommodate that. This is done by creating a redirection and returning 174 | /// a command which can then be spawned as a child processes 175 | pub fn redirect_cmd_execution(cmd_line: &mut Tokenizer) 176 | -> Result { 177 | let mut redirection_count = [0; 2]; 178 | let args = cmd_line.args_before_redirection(); 179 | 180 | // create process that will execute shell command 181 | let mut proc = process::Command::new(&args[0]); 182 | 183 | proc.args(&args[1..]); 184 | 185 | loop { 186 | match cmd_line.next().as_deref() { 187 | // command asked for append 188 | Some(">>") => { 189 | let f = append_stdout_file(&cmd_line.next().unwrap())?; 190 | proc.stdout(f); 191 | return Ok(proc); 192 | }, 193 | Some("<") => { 194 | redirection_count[0] += 1; 195 | 196 | // retrieve file name if file/directory doesn't 197 | // exist notify user and restart the shell 198 | if let Some(name) = cmd_line.next() { 199 | // redirect stdin from a given file 200 | match open_stdin_file(&name) { 201 | Ok(f) => proc.stdin(f), 202 | Err(e) => return Err(e), 203 | }; 204 | } 205 | }, 206 | Some(">") => { 207 | redirection_count[1] += 1; 208 | 209 | // redirect stdout to a give file 210 | if let Some(name) = cmd_line.next() { 211 | match open_stdout_file(&name) { 212 | Ok(f) => proc.stdout(f), 213 | Err(e) => return Err(e), 214 | }; 215 | } 216 | }, 217 | None => break, 218 | _ => continue, 219 | } 220 | } 221 | 222 | // check flags that we don't have excessive number of redirection 223 | if redirection_count[0] > 1 || redirection_count[1] > 1 { 224 | return Err(io::Error::new( 225 | io::ErrorKind::InvalidInput, 226 | "Invalid instructions for redirection", 227 | )); 228 | } 229 | 230 | Ok(proc) 231 | } 232 | 233 | fn append_stdout_file(file_name: &str) -> Result { 234 | let f = OpenOptions::new() 235 | .append(true) 236 | .create(true) 237 | .open(file_name)?; 238 | Ok(f) 239 | } 240 | 241 | /// Redirect a std out to a give file. 242 | /// If file doesn't exists create one 243 | fn open_stdout_file(file_name: &str) -> Result { 244 | let file = OpenOptions::new() 245 | .truncate(true) 246 | .write(true) 247 | .create(true) 248 | .open(file_name)?; 249 | Ok(file) 250 | } 251 | 252 | /// Redirect a std in from a given file to console. 253 | /// If file doesn't exist error is thrown 254 | fn open_stdin_file(file_name: &str) -> Result { 255 | let file = OpenOptions::new().read(true).open(file_name)?; 256 | Ok(file) 257 | } 258 | 259 | /// flushes text buffer to the stdout 260 | fn write_to_stdout(text: &str) -> io::Result<()> { 261 | io::stdout().write_all(text.as_ref())?; 262 | io::stdout().flush()?; // to the terminal 263 | Ok(()) 264 | } 265 | 266 | /// fetch the user inputted commands 267 | fn get_user_commands() -> Result { 268 | let mut input = String::new(); 269 | 270 | // read user input 271 | io::stdin().read_line(&mut input).unwrap(); 272 | 273 | if input.trim().len() < 2 { 274 | return Err(io::Error::new( 275 | io::ErrorKind::InvalidInput, 276 | "Invalid command", 277 | )); 278 | } 279 | if input.ends_with('\n') { 280 | input.pop(); 281 | } 282 | Ok(Tokenizer::new(input.trim())) 283 | } 284 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.40" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.0.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.2.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 22 | 23 | [[package]] 24 | name = "cc" 25 | version = "1.0.69" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 34 | 35 | [[package]] 36 | name = "chrono" 37 | version = "0.4.19" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 40 | dependencies = [ 41 | "libc", 42 | "num-integer", 43 | "num-traits", 44 | "time", 45 | "winapi", 46 | ] 47 | 48 | [[package]] 49 | name = "core-foundation-sys" 50 | version = "0.8.2" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" 53 | 54 | [[package]] 55 | name = "cr4sh_" 56 | version = "0.0.1" 57 | dependencies = [ 58 | "anyhow", 59 | "chrono", 60 | "crossbeam-channel", 61 | "ctrlc", 62 | "dirs", 63 | "fs-set-times", 64 | "lazy_static", 65 | "log", 66 | "nix", 67 | "signal-hook", 68 | "sysinfo", 69 | "termion", 70 | "users", 71 | ] 72 | 73 | [[package]] 74 | name = "crossbeam-channel" 75 | version = "0.5.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 78 | dependencies = [ 79 | "cfg-if", 80 | "crossbeam-utils", 81 | ] 82 | 83 | [[package]] 84 | name = "crossbeam-deque" 85 | version = "0.8.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" 88 | dependencies = [ 89 | "cfg-if", 90 | "crossbeam-epoch", 91 | "crossbeam-utils", 92 | ] 93 | 94 | [[package]] 95 | name = "crossbeam-epoch" 96 | version = "0.9.4" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" 99 | dependencies = [ 100 | "cfg-if", 101 | "crossbeam-utils", 102 | "lazy_static", 103 | "memoffset", 104 | "scopeguard", 105 | ] 106 | 107 | [[package]] 108 | name = "crossbeam-utils" 109 | version = "0.8.4" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" 112 | dependencies = [ 113 | "autocfg", 114 | "cfg-if", 115 | "lazy_static", 116 | ] 117 | 118 | [[package]] 119 | name = "cstr" 120 | version = "0.2.8" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "c11a39d776a3b35896711da8a04dc1835169dcd36f710878187637314e47941b" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | ] 127 | 128 | [[package]] 129 | name = "ctrlc" 130 | version = "3.1.9" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a" 133 | dependencies = [ 134 | "nix", 135 | "winapi", 136 | ] 137 | 138 | [[package]] 139 | name = "dirs" 140 | version = "3.0.2" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" 143 | dependencies = [ 144 | "dirs-sys", 145 | ] 146 | 147 | [[package]] 148 | name = "dirs-sys" 149 | version = "0.3.6" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" 152 | dependencies = [ 153 | "libc", 154 | "redox_users", 155 | "winapi", 156 | ] 157 | 158 | [[package]] 159 | name = "doc-comment" 160 | version = "0.3.3" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 163 | 164 | [[package]] 165 | name = "either" 166 | version = "1.6.1" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 169 | 170 | [[package]] 171 | name = "errno" 172 | version = "0.2.7" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe" 175 | dependencies = [ 176 | "errno-dragonfly", 177 | "libc", 178 | "winapi", 179 | ] 180 | 181 | [[package]] 182 | name = "errno-dragonfly" 183 | version = "0.1.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" 186 | dependencies = [ 187 | "gcc", 188 | "libc", 189 | ] 190 | 191 | [[package]] 192 | name = "fs-set-times" 193 | version = "0.6.0" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "56af20dae05f9fae64574ead745ced5f08ae7dc6f42b9facd93a43d4b7adf982" 196 | dependencies = [ 197 | "io-lifetimes", 198 | "posish", 199 | "winapi", 200 | ] 201 | 202 | [[package]] 203 | name = "gcc" 204 | version = "0.3.55" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 207 | 208 | [[package]] 209 | name = "getrandom" 210 | version = "0.2.3" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 213 | dependencies = [ 214 | "cfg-if", 215 | "libc", 216 | "wasi", 217 | ] 218 | 219 | [[package]] 220 | name = "hermit-abi" 221 | version = "0.1.18" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 224 | dependencies = [ 225 | "libc", 226 | ] 227 | 228 | [[package]] 229 | name = "io-lifetimes" 230 | version = "0.2.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "78d009010297118b0a443fef912b92e3482e6e6f46ab31e5d60f68b39a553ca9" 233 | dependencies = [ 234 | "libc", 235 | "rustc_version", 236 | "winapi", 237 | ] 238 | 239 | [[package]] 240 | name = "itoa" 241 | version = "0.4.7" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 244 | 245 | [[package]] 246 | name = "lazy_static" 247 | version = "1.4.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 250 | 251 | [[package]] 252 | name = "libc" 253 | version = "0.2.98" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" 256 | 257 | [[package]] 258 | name = "linux-raw-sys" 259 | version = "0.0.17" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "58ae91cec8978ea160429d0609ec47da5d65a858725701b77df198ecf985d6bd" 262 | 263 | [[package]] 264 | name = "log" 265 | version = "0.4.14" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 268 | dependencies = [ 269 | "cfg-if", 270 | ] 271 | 272 | [[package]] 273 | name = "memoffset" 274 | version = "0.6.3" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" 277 | dependencies = [ 278 | "autocfg", 279 | ] 280 | 281 | [[package]] 282 | name = "nix" 283 | version = "0.20.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" 286 | dependencies = [ 287 | "bitflags", 288 | "cc", 289 | "cfg-if", 290 | "libc", 291 | ] 292 | 293 | [[package]] 294 | name = "ntapi" 295 | version = "0.3.6" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 298 | dependencies = [ 299 | "winapi", 300 | ] 301 | 302 | [[package]] 303 | name = "num-integer" 304 | version = "0.1.44" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 307 | dependencies = [ 308 | "autocfg", 309 | "num-traits", 310 | ] 311 | 312 | [[package]] 313 | name = "num-traits" 314 | version = "0.2.14" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 317 | dependencies = [ 318 | "autocfg", 319 | ] 320 | 321 | [[package]] 322 | name = "num_cpus" 323 | version = "1.13.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 326 | dependencies = [ 327 | "hermit-abi", 328 | "libc", 329 | ] 330 | 331 | [[package]] 332 | name = "numtoa" 333 | version = "0.1.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 336 | 337 | [[package]] 338 | name = "once_cell" 339 | version = "1.7.2" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" 342 | 343 | [[package]] 344 | name = "posish" 345 | version = "0.16.4" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "694eb323d25f129d533cdff030813ccfd708170f46625c238839941f50372d2e" 348 | dependencies = [ 349 | "bitflags", 350 | "cc", 351 | "cstr", 352 | "errno", 353 | "io-lifetimes", 354 | "itoa", 355 | "libc", 356 | "linux-raw-sys", 357 | "once_cell", 358 | "rustc_version", 359 | ] 360 | 361 | [[package]] 362 | name = "proc-macro2" 363 | version = "1.0.27" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 366 | dependencies = [ 367 | "unicode-xid", 368 | ] 369 | 370 | [[package]] 371 | name = "quote" 372 | version = "1.0.9" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 375 | dependencies = [ 376 | "proc-macro2", 377 | ] 378 | 379 | [[package]] 380 | name = "rayon" 381 | version = "1.5.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" 384 | dependencies = [ 385 | "autocfg", 386 | "crossbeam-deque", 387 | "either", 388 | "rayon-core", 389 | ] 390 | 391 | [[package]] 392 | name = "rayon-core" 393 | version = "1.9.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" 396 | dependencies = [ 397 | "crossbeam-channel", 398 | "crossbeam-deque", 399 | "crossbeam-utils", 400 | "lazy_static", 401 | "num_cpus", 402 | ] 403 | 404 | [[package]] 405 | name = "redox_syscall" 406 | version = "0.2.9" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" 409 | dependencies = [ 410 | "bitflags", 411 | ] 412 | 413 | [[package]] 414 | name = "redox_termios" 415 | version = "0.1.2" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" 418 | dependencies = [ 419 | "redox_syscall", 420 | ] 421 | 422 | [[package]] 423 | name = "redox_users" 424 | version = "0.4.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 427 | dependencies = [ 428 | "getrandom", 429 | "redox_syscall", 430 | ] 431 | 432 | [[package]] 433 | name = "rustc_version" 434 | version = "0.4.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 437 | dependencies = [ 438 | "semver", 439 | ] 440 | 441 | [[package]] 442 | name = "scopeguard" 443 | version = "1.1.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 446 | 447 | [[package]] 448 | name = "semver" 449 | version = "1.0.3" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "5f3aac57ee7f3272d8395c6e4f502f434f0e289fcd62876f70daa008c20dcabe" 452 | 453 | [[package]] 454 | name = "signal-hook" 455 | version = "0.3.8" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" 458 | dependencies = [ 459 | "libc", 460 | "signal-hook-registry", 461 | ] 462 | 463 | [[package]] 464 | name = "signal-hook-registry" 465 | version = "1.3.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" 468 | dependencies = [ 469 | "libc", 470 | ] 471 | 472 | [[package]] 473 | name = "sysinfo" 474 | version = "0.17.4" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "0127b2569065e7a5b36b729d3e42b87b01b61391ff3df68f6613f4c7fa485c5d" 477 | dependencies = [ 478 | "cfg-if", 479 | "core-foundation-sys", 480 | "doc-comment", 481 | "libc", 482 | "ntapi", 483 | "once_cell", 484 | "rayon", 485 | "winapi", 486 | ] 487 | 488 | [[package]] 489 | name = "termion" 490 | version = "1.5.6" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" 493 | dependencies = [ 494 | "libc", 495 | "numtoa", 496 | "redox_syscall", 497 | "redox_termios", 498 | ] 499 | 500 | [[package]] 501 | name = "time" 502 | version = "0.1.43" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 505 | dependencies = [ 506 | "libc", 507 | "winapi", 508 | ] 509 | 510 | [[package]] 511 | name = "unicode-xid" 512 | version = "0.2.2" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 515 | 516 | [[package]] 517 | name = "users" 518 | version = "0.11.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" 521 | dependencies = [ 522 | "libc", 523 | "log", 524 | ] 525 | 526 | [[package]] 527 | name = "wasi" 528 | version = "0.10.2+wasi-snapshot-preview1" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 531 | 532 | [[package]] 533 | name = "winapi" 534 | version = "0.3.9" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 537 | dependencies = [ 538 | "winapi-i686-pc-windows-gnu", 539 | "winapi-x86_64-pc-windows-gnu", 540 | ] 541 | 542 | [[package]] 543 | name = "winapi-i686-pc-windows-gnu" 544 | version = "0.4.0" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 547 | 548 | [[package]] 549 | name = "winapi-x86_64-pc-windows-gnu" 550 | version = "0.4.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 553 | --------------------------------------------------------------------------------