From 4c271166bbad2bb6a77ddf201b32aa6ce4e0a53c Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Thu, 22 Oct 2020 18:13:54 +0300 Subject: Properly run commands on ExitSuccess/ExitFailure --- .gitignore | 1 + man/rs.1.scd | 19 +++++-- src/main.rs | 2 +- src/parser.rs | 143 ++++++++++++++------------------------------------ src/parser/command.rs | 114 +++++++++++++++++++++++++++++++++++++--- 5 files changed, 162 insertions(+), 117 deletions(-) diff --git a/.gitignore b/.gitignore index 06aba01..35ccc21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ Cargo.lock +man/rs.1 /target diff --git a/man/rs.1.scd b/man/rs.1.scd index 6256453..2036f7a 100644 --- a/man/rs.1.scd +++ b/man/rs.1.scd @@ -8,9 +8,18 @@ rs - yet another shell This is yet another shell -# BUILTINS - -|[ *Builtin* +## Command syntax +|[ +:- Description +| ; +: Run command regardless of the previous command's exit status. +| || +:[ Run command if the previous command has failed. (todo) +| && +:[ Run command if the previous command has succeeded. (todo) + +## Builtins +|[ :- Arguments :- Description | ! @@ -29,10 +38,10 @@ This is yet another shell : key :[ Remove the 'key' environment variable. -# MOTD +## motd The motd printed on startup is read from /etc/motd. -# PROMPT +## Prompt To set the prompt, use the PROMPT environment variable. If one is not set, " {USER}@{HOST} [{PWD}]\n{$} " is used by default. diff --git a/src/main.rs b/src/main.rs index efd32c2..95c3c46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ fn main() { let cmd = parser::CommandLine::new(&line); match cmd.run(&prompt.home, status) { Ok(s) => status = s, - Err(e) => eprintln!("{}", e) + Err(e) => eprintln!("{}", e), } } Err(ReadlineError::Interrupted) => { diff --git a/src/parser.rs b/src/parser.rs index a96b679..0c771f9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,27 +2,18 @@ use std::io::Error; use std::path::PathBuf; use std::process::ExitStatus; -// > overwrite -// >> append -// | pipe - -// : on prev cmd any -// && on prev cmd okay -// || on prev cmd fail +// ; on prev cmd any +// && on prev cmd okay +// || on prev cmd fail mod command; -use command::*; +use command::{CommandInfo, RunOn, RunResult}; pub struct CommandLine(Vec); impl CommandLine { pub fn new(line: &str) -> Self { - let split = line.split(':'); - let mut v = CommandLine(Vec::new()); - for x in split { - v.0.push(tokenize(x)); - } - v + CommandLine(split(line)) } pub fn run( @@ -31,108 +22,52 @@ impl CommandLine { mut status: Option, ) -> Result, Error> { for cmd in &self.0 { - match cmd.run(&home, &status)? { - RunResult::Command(s) => status = Some(s), - RunResult::Builtin => {} + if cmd.when.can_run(&status) { + match cmd.run(&home, &status)? { + RunResult::Command(s) => status = Some(s), + RunResult::Builtin => {} + } } } Ok(status) } } -enum TokenType { - Argument, - StdoutFileOverwrite, - StdoutFileAppend, -} +fn split(line: &str) -> Vec { + let mut r: Vec = Vec::new(); -pub fn tokenize(line: &str) -> CommandInfo { - let mut r = CommandInfo::new(); + let mut next = line.chars().peekable(); + let mut iter = line.chars().enumerate(); - let mut iter = line.chars().peekable(); - let mut token = String::new(); - let mut token_type = TokenType::Argument; - let mut quote = false; + let mut start_idx = 0; + let mut state = RunOn::Always; - 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; - } - } - '&' => { - if iter.peek() == Some(&'&') { - r.when = RunOn::ExitSuccess; - iter.next(); - } - } - '|' => { - if iter.peek() == Some(&'|') { - r.when = RunOn::ExitFailure; - iter.next(); - } - } - _ => { - token.push(i); - } - } + while let Some((i, v)) = { + next.next(); + iter.next() + } { + let n = next.peek(); - if !token.is_empty() && ((iter.peek() == Some(&' ') && !quote) || iter.peek() == None) { - match token_type { - TokenType::Argument => r.args.push(token), - TokenType::StdoutFileOverwrite => r.stdout = Redirect::FileOverwrite(token), - TokenType::StdoutFileAppend => r.stdout = Redirect::FileAppend(token), - } - token = String::new(); + 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 } - -#[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/parser/command.rs b/src/parser/command.rs index 5d129c3..540dc29 100644 --- a/src/parser/command.rs +++ b/src/parser/command.rs @@ -2,6 +2,10 @@ 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, @@ -9,13 +13,28 @@ pub(in crate::parser) enum Redirect { FileAppend(String), } -#[derive(Debug, PartialEq)] +#[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, @@ -29,12 +48,9 @@ pub struct CommandInfo { } impl CommandInfo { - pub(in crate::parser) fn new() -> CommandInfo { - CommandInfo { - args: Vec::new(), - stdout: Redirect::Std, - when: RunOn::Always, - } + 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 { @@ -57,6 +73,90 @@ impl CommandInfo { } } +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( -- cgit v1.2.1