From 4a88b5c888da073407431cf435527ce69b032e3a Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Tue, 24 Nov 2020 23:28:03 +0200 Subject: Refactoring Add doc/rc.pdf --- doc/rc.pdf | Bin 0 -> 79087 bytes src/commandline.rs | 70 +++++++++++++++ src/commandline/builtins.rs | 45 ++++++++++ src/commandline/command.rs | 161 ++++++++++++++++++++++++++++++++++ src/main.rs | 23 ++--- src/parser.rs | 73 ---------------- src/parser/command.rs | 204 -------------------------------------------- 7 files changed, 289 insertions(+), 287 deletions(-) create mode 100644 doc/rc.pdf create mode 100644 src/commandline.rs create mode 100644 src/commandline/builtins.rs create mode 100644 src/commandline/command.rs delete mode 100644 src/parser.rs delete mode 100644 src/parser/command.rs diff --git a/doc/rc.pdf b/doc/rc.pdf new file mode 100644 index 0000000..6feab51 Binary files /dev/null and b/doc/rc.pdf differ diff --git a/src/commandline.rs b/src/commandline.rs new file mode 100644 index 0000000..489eaa8 --- /dev/null +++ b/src/commandline.rs @@ -0,0 +1,70 @@ +use std::io::Error; +use std::path::PathBuf; +use std::process::ExitStatus; + +// ; on prev cmd any +// && on prev cmd okay +// || on prev cmd fail + +mod command; +use command::{Command, RunIf, RunResult}; +mod builtins; + +pub struct CommandLine(Vec); + +impl CommandLine { + pub fn new(line: &str) -> Self { + CommandLine(split(line)) + } + + pub fn run(&self,home: &PathBuf, mut status: Option) -> Result, Error> { + for cmd in &self.0 { + if cmd.when.can_run(&status) { + match cmd.run(&home, &status)? { + RunResult::Command(s) => status = Some(s), + RunResult::Builtin => {} + } + } + } + Ok(status) + } +} + +fn split(line: &str) -> Vec { + let mut r: Vec = Vec::new(); + + let mut next = line.chars().peekable(); + let mut iter = line.chars().enumerate(); + + let mut start_idx = 0; + let mut state = RunIf::Always; + + while let Some((i, v)) = { + next.next(); + iter.next() + } { + let n = next.peek(); + + if v == ';' { + r.push(Command::new(&line[start_idx..i], state)); + state = RunIf::Always; + start_idx = i + 1; + } else if v == '|' && n == Some(&'|') { + r.push(Command::new(&line[start_idx..i], state)); + state = RunIf::ExitFailure; + start_idx = i + 2; + next.next(); + iter.next(); + } else if v == '&' && n == Some(&'&') { + r.push(Command::new(&line[start_idx..i], state)); + state = RunIf::ExitSuccess; + start_idx = i + 2; + next.next(); + iter.next(); + } else if n == None { + r.push(Command::new(&line[start_idx..], state)); + } + } + + r +} 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 { + 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 { + 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 { + 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) -> 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, + 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, + ) -> 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 = 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, 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'")); +} +*/ diff --git a/src/main.rs b/src/main.rs index 95c3c46..ca5dc00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,21 @@ use rustyline::error::ReadlineError; use rustyline::Editor; -mod parser; +mod commandline; +use commandline::CommandLine; mod prompt; -fn main() { - match std::fs::read_to_string("/etc/motd") { - Ok(motd) => print!("{}", motd), - Err(_) => {} +fn main() -> Result<(), std::io::Error>{ + if let Ok(motd) = std::fs::read_to_string("/etc/motd") { + print!("{}", motd) } - let prompt = prompt::Prompt::new().unwrap(); - let mut status = None; - - // `()` can be used when no completer is required + // TODO: [completer] `()` can be used when no completer is required let mut rl = Editor::<()>::new(); + let prompt = prompt::Prompt::new()?; + + let mut status = None; // exit status of last command + // map of variables /*if rl.load_history("history.txt").is_err() { println!("No previous history."); @@ -25,7 +26,7 @@ fn main() { Ok(line) => { rl.add_history_entry(line.as_str()); - let cmd = parser::CommandLine::new(&line); + let cmd = CommandLine::new(&line); match cmd.run(&prompt.home, status) { Ok(s) => status = s, Err(e) => eprintln!("{}", e), @@ -47,4 +48,6 @@ fn main() { } //rl.save_history("history.txt").unwrap(); + + Ok(()) } diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 0c771f9..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::io::Error; -use std::path::PathBuf; -use std::process::ExitStatus; - -// ; on prev cmd any -// && on prev cmd okay -// || on prev cmd fail - -mod command; -use command::{CommandInfo, RunOn, RunResult}; - -pub struct CommandLine(Vec); - -impl CommandLine { - pub fn new(line: &str) -> Self { - CommandLine(split(line)) - } - - pub fn run( - &self, - home: &PathBuf, - mut status: Option, - ) -> Result, Error> { - for cmd in &self.0 { - if cmd.when.can_run(&status) { - match cmd.run(&home, &status)? { - RunResult::Command(s) => status = Some(s), - RunResult::Builtin => {} - } - } - } - Ok(status) - } -} - -fn split(line: &str) -> Vec { - let mut r: Vec = Vec::new(); - - let mut next = line.chars().peekable(); - let mut iter = line.chars().enumerate(); - - let mut start_idx = 0; - let mut state = RunOn::Always; - - while let Some((i, v)) = { - next.next(); - iter.next() - } { - let n = next.peek(); - - if v == ';' { - r.push(CommandInfo::new(&line[start_idx..i], state)); - state = RunOn::Always; - start_idx = i + 1; - } else if v == '|' && n == Some(&'|') { - r.push(CommandInfo::new(&line[start_idx..i], state)); - state = RunOn::ExitFailure; - start_idx = i + 2; - next.next(); - iter.next(); - } else if v == '&' && n == Some(&'&') { - r.push(CommandInfo::new(&line[start_idx..i], state)); - state = RunOn::ExitSuccess; - start_idx = i + 2; - next.next(); - iter.next(); - } else if n == None { - r.push(CommandInfo::new(&line[start_idx..], state)); - } - } - - r -} diff --git a/src/parser/command.rs b/src/parser/command.rs deleted file mode 100644 index 4dea8a6..0000000 --- a/src/parser/command.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::io::{Error, ErrorKind}; -use std::path::{Path, PathBuf}; -use std::process::{Command, ExitStatus}; - -// > overwrite -// >> append -// | pipe - -#[derive(Debug)] -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(in crate::parser) enum RunResult { - Command(ExitStatus), - Builtin, -} - -#[derive(Debug)] -pub struct CommandInfo { - args: Vec, - 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(in crate::parser) 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) -} -- cgit v1.2.1