diff --git a/README.md b/README.md index 8a25fdd04c7dbf460b38556e392281ffde06cf69..1734067266288e7d278082e4bb79595c7ec75e86 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,7 @@ The `let` keyword is utilized to create local variables within the shell. The `e a similar action, only setting the variable globally as an environment variable for the operating system. ```ion -// TODO: Ion Shell does not yet implement stderr redirection. -let git_branch = $(git rev-parse --abbrev-ref HEAD 2> /dev/null) +let git_branch = $(git rev-parse --abbrev-ref HEAD ^> /dev/null) ``` If the command is executed without any arguments, it will simply list all available variables. @@ -196,4 +195,4 @@ end for i in 1..20 fib $i end -``` \ No newline at end of file +``` diff --git a/src/parser/peg.rs b/src/parser/peg.rs index 184e4d17d89f11782bdb871407f0ae2ca0f85160..d957e804ba462e743d0f04905a25641253cbf9f4 100644 --- a/src/parser/peg.rs +++ b/src/parser/peg.rs @@ -6,7 +6,7 @@ use self::grammar::parse_; use glob::glob; -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] pub enum RedirectFrom { Stdout, Stderr, Both} #[derive(Debug, PartialEq, Clone)] diff --git a/src/parser/pipelines.rs b/src/parser/pipelines.rs index 1d17da388ae77eeb1b6496c85897d76ecaa531f1..fa5777b469dd949b6d50b848324595129c3bc6da 100644 --- a/src/parser/pipelines.rs +++ b/src/parser/pipelines.rs @@ -1,7 +1,6 @@ // TODO: // - Implement Herestrings // - Implement Heredocs -// - Implement Stderr Redirection // - Implement Stderr Piping // - Fix the cyclomatic complexity issue @@ -21,7 +20,7 @@ const PROCESS_VAL: u8 = 255 ^ (BACKSLASH + WHITESPACE + 32); const IS_VALID: u8 = 255 ^ (BACKSLASH + WHITESPACE); #[derive(PartialEq)] -enum RedirMode { False, Stdin, Stdout, StdoutAppend } +enum RedirMode { False, Stdin, Stdout(RedirectFrom), StdoutAppend(RedirectFrom) } fn get_job_kind(args: &str, index: usize, pipe_char_was_found: bool) -> (JobKind, bool) { if pipe_char_was_found { @@ -41,7 +40,7 @@ fn get_job_kind(args: &str, index: usize, pipe_char_was_found: bool) -> (JobKind /// Parses each individual pipeline, separating arguments, pipes, background tasks, and redirections. pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, args: &str) { let mut jobs: Vec<Job> = Vec::new(); - let mut args_iter = args.bytes(); + let mut args_iter = args.bytes().peekable(); let (mut index, mut arg_start) = (0, 0); let mut flags = 0u8; // (backslash, single_quote, double_quote, x, x, x, process_one, process_two) @@ -52,14 +51,14 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, let mut levels = 0; macro_rules! redir_check { - ($file:ident, $name:ident, $is_append:expr) => {{ + ($from:expr, $file:ident, $name:ident, $is_append:expr) => {{ if $file.is_none() { if $name.is_empty() { *possible_error = Some("missing standard output file argument after '>'"); } else { $file = Some(Redirection { - from: RedirectFrom::Stdout, - file: unsafe { String::from_utf8_unchecked($name) }, + from: $from, + file: unsafe { String::from_utf8_unchecked($name) }, append: $is_append }); } @@ -125,8 +124,21 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, } }, b'|' if (flags & (255 ^ BACKSLASH) == 0) => job_found!(true), - b'&' if (flags & IS_VALID == 0) => job_found!(false), - b'>' if (flags & IS_VALID == 0) => redir_found!(RedirMode::Stdout), + b'&' if (flags & (255 ^ BACKSLASH) == 0) => { + if args_iter.peek() == Some(&b'>') { + let _ = args_iter.next(); + redir_found!(RedirMode::Stdout(RedirectFrom::Both)); + } else { + job_found!(false) + } + }, + b'^' if (flags & IS_VALID == 0) => { + if args_iter.peek() == Some(&b'>') { + let _ = args_iter.next(); + redir_found!(RedirMode::Stdout(RedirectFrom::Stderr)); + } + }, + b'>' if (flags & IS_VALID == 0) => redir_found!(RedirMode::Stdout(RedirectFrom::Stdout)), b'<' if (flags & IS_VALID == 0) => redir_found!(RedirMode::Stdin), _ if (flags >> 6 != 2) => flags &= 255 ^ (PROCESS_ONE + PROCESS_TWO), _ => (), @@ -135,9 +147,9 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, } break 'outer }, - RedirMode::Stdout | RedirMode::StdoutAppend => { + RedirMode::Stdout(from) | RedirMode::StdoutAppend(from) => { match args_iter.next() { - Some(character) => if character == b'>' { mode = RedirMode::StdoutAppend; }, + Some(character) => if character == b'>' { mode = RedirMode::StdoutAppend(from); }, None => { *possible_error = Some("missing standard output file argument after '>'"); break 'outer @@ -169,7 +181,7 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, out_file = Some(Redirection { from: RedirectFrom::Stdout, file: unsafe { String::from_utf8_unchecked(stdout_file.clone()) }, - append: mode == RedirMode::StdoutAppend + append: if let RedirMode::StdoutAppend(_) = mode { true } else { false } }); }, b'<' if stdout_file.is_empty() => { @@ -180,7 +192,7 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, out_file = Some(Redirection { from: RedirectFrom::Stdout, file: unsafe { String::from_utf8_unchecked(stdout_file.clone()) }, - append: mode == RedirMode::StdoutAppend + append: if let RedirMode::StdoutAppend(_) = mode { true } else { false } }); if in_file.is_some() { @@ -195,7 +207,12 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, } } - redir_check!(out_file, stdout_file, mode == RedirMode::StdoutAppend); + redir_check!( + from, + out_file, + stdout_file, + if let RedirMode::StdoutAppend(_) = mode { true } else { false } + ); break 'outer }, @@ -209,7 +226,7 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, if out_file.is_some() { break 'outer } else { - mode = RedirMode::Stdout; + mode = RedirMode::Stdout(RedirectFrom::Stdout); continue 'outer } } @@ -252,7 +269,8 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, } } - redir_check!(in_file, stdin_file, false); + let dummy_val = RedirectFrom::Stdout; + redir_check!(dummy_val, in_file, stdin_file, false); break 'outer } @@ -273,7 +291,26 @@ pub fn collect(pipelines: &mut Vec<Pipeline>, possible_error: &mut Option<&str>, #[cfg(test)] mod tests { use flow_control::Statement; - use parser::peg::{parse, JobKind}; + use parser::peg::{parse, JobKind, RedirectFrom, Redirection}; + + #[test] + fn stderr_redirection() { + if let Statement::Pipelines(mut pipelines) = parse("git rev-parse --abbrev-ref HEAD ^> /dev/null") { + let pipeline = pipelines.remove(0); + assert_eq!("git", pipeline.jobs[0].args[0]); + assert_eq!("rev-parse", pipeline.jobs[0].args[1]); + assert_eq!("--abbrev-ref", pipeline.jobs[0].args[2]); + assert_eq!("HEAD", pipeline.jobs[0].args[3]); + + let expected = Redirection { + from: RedirectFrom::Stderr, + file: "/dev/null".to_owned(), + append: false + }; + + assert_eq!(Some(expected), pipeline.stdout); + } + } #[test] fn subshells_within_subshells() { diff --git a/src/pipe.rs b/src/pipe.rs index 1a47ce3aaef87a26d0ca8c62ddde8c5994558027..b3ce9dce9533ea4f1f7145f505dcf1579bd68dd2 100644 --- a/src/pipe.rs +++ b/src/pipe.rs @@ -5,7 +5,7 @@ use std::fs::{File, OpenOptions}; use std::thread; use status::*; -use parser::peg::{Pipeline, JobKind}; +use parser::peg::{Pipeline, JobKind, RedirectFrom}; pub fn execute_pipeline(pipeline: Pipeline) -> i32 { // Generate a list of commands from the given pipeline @@ -31,7 +31,21 @@ pub fn execute_pipeline(pipeline: Pipeline) -> i32 { File::create(&stdout.file) }; match file { - Ok(f) => unsafe { command.0.stdout(Stdio::from_raw_fd(f.into_raw_fd())); }, + Ok(f) => unsafe { + match stdout.from { + RedirectFrom::Both => { + let fd = f.into_raw_fd(); + command.0.stderr(Stdio::from_raw_fd(fd)); + command.0.stdout(Stdio::from_raw_fd(fd)); + }, + RedirectFrom::Stderr => { + command.0.stderr(Stdio::from_raw_fd(f.into_raw_fd())); + }, + RedirectFrom::Stdout => { + command.0.stdout(Stdio::from_raw_fd(f.into_raw_fd())); + }, + } + }, Err(err) => { let stderr = io::stderr(); let mut stderr = stderr.lock(); diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 5667a97cad512ba4c2a18f7ae9a8dcb6fd8a060a..3a845ce489c1339f4cb864709d3f55de91675584 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -188,7 +188,7 @@ impl Shell { self.on_command(command); // Mark the command in the context history if it was a success. - if self.previous_status == SUCCESS || self.flow_control.level > 0 { + if self.previous_status != NO_SUCH_COMMAND || self.flow_control.level > 0 { self.set_context_history_from_vars(); if let Err(err) = self.context.history.push(command.into()) { let stderr = io::stderr();