use std::process::Command; use std::io::stdin; use std::io::stdout; use std::io::Write; use std::io::ErrorKind; use std::path::Path; use std::env; use std::process::Child; use std::process::Stdio; use std::fs::File; use std::fs::OpenOptions; use std::fs; use std::io::{self, prelude::*, BufReader}; //used for home directory extern crate dirs; fn process_line(input: String) -> bool { // must be peekable so we know when we are on the last command let mut commands = input.trim().split(" | ").peekable(); //Will be used later, in the case of pipes let mut previous_command = None; //While have commands to process... while let Some(command) = commands.next() { //First command is divided... let mut parts = command.trim().split_whitespace(); //into a command... let mut command = parts.next().unwrap(); //and args let args = parts; //builtins : exit and cd are special match command { "cd" => { //If a dir is given, we use it by dereferencing //otherwise it is / let new_dir = args.peekable().peek().map_or("/", |x| *x); let root = Path::new(new_dir); if let Err(e) = env::set_current_dir(&root) { eprintln!("{}", e); } previous_command = None; }, "exit" => return true, //not builtin: we process it command => { //If we are at the first command (aka previous_command is None)... let stdin = previous_command.map_or( //We inherit, meaning stdin gets the stream from its parent //what I guess that means is that the child (our command) //inherits the file descriptors (stdin, stdout, stderr) //from the process of this very own rust program ! Stdio::inherit(), //otherwise, our input is the output from the last command //(from converts a ChildStdOut to a Stdio) |output: Child| Stdio::from(output.stdout.unwrap()) ); let stdout = if commands.peek().is_some() { // there is another command piped behind this one // prepare to send output to the next command Stdio::piped() } else { // there are no more commands piped behind this one // send output to shell stdout // the child inherits from the parent's file descriptor Stdio::inherit() }; //Run the command let output = Command::new(command) .args(args) .stdin(stdin) .stdout(stdout) //spawn : execute the command as a child process, returning a handle to it .spawn(); //Checking for errors //If it matched, previous_command becomes the output of this to be used //next loop match output { Ok(output) => { previous_command = Some(output); }, Err(e) => { previous_command = None; eprintln!("{}", e); }, }; } } } if let Some(mut final_command) = previous_command { // block until the final command has finished final_command.wait(); } return false; } fn write_to_history(command: &str, number: &i32) -> Result<(), std::io::Error> { //Write a command to history let filepath = dirs::home_dir().unwrap().join("history.rshell"); let mut file = OpenOptions::new() .write(true) .append(true) .create(true) .open(&filepath) .unwrap(); writeln!(file, "{} {}", number, command.trim())?; return Ok(()) } fn get_history_number() -> Result { //Get the latest number found in history file let filepath = dirs::home_dir().unwrap().join("history.rshell"); let file = File::open(filepath)?; let mut reader = BufReader::new(file).lines(); let myline = String::from(reader.last().unwrap()?); let mut number = myline.split(' ').next().expect("???").parse().unwrap(); //number = number + 1; return Ok(number); } fn get_hist_from_number(number: &i32) -> Option { //Returns a command from its number in hist file let filepath = dirs::home_dir().unwrap().join("history.rshell"); if filepath.exists() { let file = File::open(filepath).expect("Error opening history file, should NOT happen"); let mut reader = BufReader::new(file).lines(); for line in reader { let current_line = line.expect("Empty history file ? Please rm it"); let mut s = current_line.split_whitespace(); let n: i32 = s.next().expect("Error reading a line in hist file").parse().unwrap(); let c = s.next().expect("No command!"); if &n == number { return Some(String::from(c)); } } return None; } else { return None; } } fn main(){ loop { print!("> "); stdout().flush(); let mut input = String::new(); stdin().read_line(&mut input).unwrap(); let number = match get_history_number() { Ok(n) => n + 1, Err(_) => 1 }; write_to_history(&input, &number); if process_line(input) { return } } } //tests should be run with a history file such as: //1 ls //TODO make a function that creates such history file when testing #[cfg(test)] mod tests { use super::*; fn inittests() { let filepath = dirs::home_dir().unwrap().join("history.rshell"); //if the file exists, new will truncate it let file = File::create(&filepath).expect("Could not create test history"); writeln!(&file, "1 ls"); } #[test] fn test_gethistnumber() { inittests(); let mynumber = get_history_number().unwrap(); assert_eq!(mynumber, 1); } #[test] fn test_gethistfromnumber() { inittests(); let mycmd = get_hist_from_number(&1).unwrap(); assert_eq!(mycmd, String::from("ls")); } }