aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--man/rs.1.scd19
-rw-r--r--src/main.rs2
-rw-r--r--src/parser.rs143
-rw-r--r--src/parser/command.rs114
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<CommandInfo>);
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<ExitStatus>,
) -> Result<Option<ExitStatus>, 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<CommandInfo> {
+ let mut r: Vec<CommandInfo> = 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<ExitStatus>) -> 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<ExitStatus>) -> Result<RunResult, Error> {
@@ -57,6 +73,90 @@ impl CommandInfo {
}
}
+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'"));
+}
+*/
fn cd(args: &[String], home: &PathBuf) -> Result<RunResult, Error> {
if args.len() > 1 {
return Err(Error::new(