diff options
author | Aqua-sama <aqua@iserlohn-fortress.net> | 2020-11-24 23:28:03 +0200 |
---|---|---|
committer | Aqua-sama <aqua@iserlohn-fortress.net> | 2020-11-29 12:52:50 +0200 |
commit | 4a88b5c888da073407431cf435527ce69b032e3a (patch) | |
tree | 64f828ac7b9085d0ee7a03c4e926a0806e2c85d5 /src/commandline | |
parent | Update manpage (diff) | |
download | rshell-4a88b5c888da073407431cf435527ce69b032e3a.tar.xz |
Refactoring
Add doc/rc.pdf
Diffstat (limited to 'src/commandline')
-rw-r--r-- | src/commandline/builtins.rs | 45 | ||||
-rw-r--r-- | src/commandline/command.rs | 161 |
2 files changed, 206 insertions, 0 deletions
diff --git a/src/commandline/builtins.rs b/src/commandline/builtins.rs new file mode 100644 index 0000000..c5999f2 --- /dev/null +++ b/src/commandline/builtins.rs @@ -0,0 +1,45 @@ +use std::io::{Error, ErrorKind::InvalidInput}; +use std::path::{Path, PathBuf}; +use super::command::RunResult; + +pub(in crate::commandline) fn cd(args: &[String], home: &PathBuf) -> Result<RunResult, Error> { + if args.len() > 1 { + return Err(Error::new( + 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), + } +} + +pub(in crate::commandline) fn set(args: &[String]) -> Result<RunResult, Error> { + if args.len() != 2 { + return Err(Error::new( + InvalidInput, + format!("set requires 2 arguments, got {}", args.len()), + )); + } + std::env::set_var(&args[0], &args[1]); + Ok(RunResult::Builtin) +} + +pub(in crate::commandline) fn unset(args: &[String]) -> Result<RunResult, Error> { + if args.len() != 1 { + return Err(Error::new( + InvalidInput, + format!("unset requires 1 argument, got {}", args.len()), + )); + } + std::env::remove_var(&args[0]); + Ok(RunResult::Builtin) +} diff --git a/src/commandline/command.rs b/src/commandline/command.rs new file mode 100644 index 0000000..d00f420 --- /dev/null +++ b/src/commandline/command.rs @@ -0,0 +1,161 @@ +use std::io::{Error}; +use std::path::{PathBuf}; +use std::process::ExitStatus; +use std::process::Command as Process; +use super::builtins::{cd, set, unset}; + +// > overwrite +// >> append +// | pipe + +#[derive(Debug)] +enum Redirect { + Std, + FileOverwrite(String), + FileAppend(String), +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub(in crate::commandline) enum RunIf { Always, ExitSuccess, ExitFailure } + +impl RunIf { + pub(in crate::commandline) fn can_run(&self, status: &Option<ExitStatus>) -> bool { + if *self == RunIf::Always { + return true; + } + match status { + Some(s) => { + (*self == RunIf::ExitSuccess && s.success()) + || (*self == RunIf::ExitFailure && !s.success()) + } + None => true, + } + } +} + +pub(in crate::commandline) enum RunResult { + Command(ExitStatus), + Builtin, +} + +#[derive(Debug)] +pub struct Command { + args: Vec<String>, + stdout: Redirect, + pub(in crate::commandline) when: RunIf, +} + +impl Command { + pub(in crate::commandline) fn new(line: &str, when: RunIf) -> Command { + let (args, stdout) = tokenize(line); + Command { args, stdout, when } + } + + pub(in crate::commandline) fn run( + &self, + home: &PathBuf, + status: &Option<ExitStatus>, + ) -> Result<RunResult, Error> { + 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 = Process::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<String>, Redirect) { + let mut args: Vec<String> = 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'")); +} +*/ |