use std::io::{Error, ErrorKind}; use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus}; // > overwrite // >> append // | pipe #[derive(Debug)] pub(in crate::parser) enum Redirect { Std, FileOverwrite(String), FileAppend(String), } #[derive(Debug, PartialEq, Copy, Clone)] pub(in crate::parser) enum RunOn { Always, ExitSuccess, ExitFailure, } impl RunOn { pub(in crate::parser) fn can_run(&self, status: &Option) -> bool { if *self == RunOn::Always { return true; } match status { Some(s) => { (*self == RunOn::ExitSuccess && s.success()) || (*self == RunOn::ExitFailure && !s.success()) } None => true, } } } pub enum RunResult { Command(ExitStatus), Builtin, } #[derive(Debug)] pub struct CommandInfo { pub(in crate::parser) args: Vec, pub(in crate::parser) stdout: Redirect, pub(in crate::parser) when: RunOn, } impl CommandInfo { pub(in crate::parser) fn new(line: &str, when: RunOn) -> CommandInfo { let (args, stdout) = tokenize(line); CommandInfo { args, stdout, when } } pub fn run(&self, home: &PathBuf, status: &Option) -> Result { match self.args[0].as_str() { "!" => { println!("{:?}", status); Ok(RunResult::Builtin) } "cd" => cd(&self.args[1..], home), "exit" => { std::process::exit(0); } "set" => set(&self.args[1..]), "unset" => unset(&self.args[1..]), _ => { let mut child = Command::new(&self.args[0]).args(&self.args[1..]).spawn()?; Ok(RunResult::Command(child.wait().unwrap())) } } } } enum TokenType { Argument, StdoutFileOverwrite, StdoutFileAppend, } fn tokenize(line: &str) -> (Vec, Redirect) { let mut args: Vec = Vec::new(); let mut stdout = Redirect::Std; let mut iter = line.chars().peekable(); let mut token = String::new(); let mut token_type = TokenType::Argument; let mut quote = false; while let Some(i) = iter.next() { match i { ' ' => { if quote { token.push(' '); } } '\'' => match iter.peek() { Some(&'\'') | Some(&'>') | Some(&'&') => { token.push(iter.next().unwrap()); } _ => { quote = !quote; } }, '>' => { if iter.peek() == Some(&'>') { token_type = TokenType::StdoutFileAppend; iter.next(); } else { token_type = TokenType::StdoutFileOverwrite; } } _ => { token.push(i); } } if !token.is_empty() && ((iter.peek() == Some(&' ') && !quote) || iter.peek() == None) { match token_type { TokenType::Argument => args.push(token), TokenType::StdoutFileOverwrite => stdout = Redirect::FileOverwrite(token), TokenType::StdoutFileAppend => stdout = Redirect::FileAppend(token), } token = String::new(); } } (args, stdout) } /* #[test] fn test_tokenizer() { { let ls = tokenize("ls -l"); assert_eq!(ls.args, vec!("ls", "-l")); let string = tokenize("ls -l 'something else'"); assert_eq!(string.args, vec!("ls", "-l", "something else")); let escape = tokenize("ls -l 'junction jan''s'"); assert_eq!(escape.args, vec!("ls", "-l", "junction jan\'s")); } { let o = tokenize("&& ls"); assert_eq!(o.args, vec!("ls")); assert_eq!(o.when, RunOn::ExitSuccess); let f = tokenize("|| ls"); assert_eq!(f.args, vec!("ls")); assert_eq!(f.when, RunOn::ExitFailure); } println!("{:?}", tokenize("ls -l something'>")); println!("{:?}", tokenize("ls -l something'>'>")); println!("{:?}", tokenize("ls -l something >output")); println!("{:?}", tokenize("ls -l something > output")); println!("{:?}", tokenize("ls -l something >'junction jan''s'")); println!("{:?}", tokenize("ls -l something >>output")); println!("{:?}", tokenize("ls -l something >> output")); println!("{:?}", tokenize("ls -l something >>'junction jan''s'")); } */ fn cd(args: &[String], home: &PathBuf) -> Result { if args.len() > 1 { return Err(Error::new( ErrorKind::InvalidInput, "Too many arguments passed to cd", )); } let root = if args.len() == 0 { home.to_path_buf() } else { std::fs::canonicalize(Path::new(args[0].as_str()))? }; match std::env::set_current_dir(&root) { Ok(_) => Ok(RunResult::Builtin), Err(e) => Err(e), } } fn set(args: &[String]) -> Result { if args.len() != 2 { return Err(Error::new( ErrorKind::InvalidInput, format!("set requires 2 arguments, got {}", args.len()), )); } std::env::set_var(&args[0], &args[1]); Ok(RunResult::Builtin) } fn unset(args: &[String]) -> Result { if args.len() != 1 { return Err(Error::new( ErrorKind::InvalidInput, format!("unset requires 1 argument, got {}", args.len()), )); } std::env::remove_var(&args[0]); Ok(RunResult::Builtin) }