diff --git a/src/shell/assignments.rs b/src/shell/assignments.rs index a5ead96050d9e47554c17059b8606c9bdd011d86..ad6fd1c0117a2dcc443b3109e7c1916b50d1c241 100644 --- a/src/shell/assignments.rs +++ b/src/shell/assignments.rs @@ -1,31 +1,19 @@ -use std::io::{self, Write}; + use std::env; +use std::io::{self, Write}; -use parser::assignments::{ - Binding, Operator, Value -}; -use parser::{ - Expander, - ArgumentSplitter, - expand_string, -}; -use types::{ - Identifier, - Value as VString, - Array as VArray, - Key, - ArrayVariableContext, - VariableContext, -}; -use super::status::*; use super::Shell; +use super::status::*; +use parser::{ArgumentSplitter, Expander, expand_string}; +use parser::assignments::{Binding, Operator, Value}; +use types::{Array as VArray, ArrayVariableContext, Identifier, Key, Value as VString, VariableContext}; enum Action { UpdateString(Identifier, VString), UpdateStrings(Vec<Identifier>, VArray), UpdateHashMap(Identifier, Key, VString), UpdateArray(Identifier, VArray), - List + List, } fn print_vars(list: &VariableContext) { @@ -34,7 +22,8 @@ fn print_vars(list: &VariableContext) { let _ = stdout.write(b"# Variables\n"); for (key, value) in list { - let _ = stdout.write(key.as_bytes()) + let _ = stdout + .write(key.as_bytes()) .and_then(|_| stdout.write_all(b" = ")) .and_then(|_| stdout.write_all(value.as_bytes())) .and_then(|_| stdout.write_all(b"\n")); @@ -47,7 +36,9 @@ fn print_arrays(list: &ArrayVariableContext) { let _ = stdout.write(b"\n# Arrays\n"); for (key, value) in list { - let _ = stdout.write(key.as_bytes()).and_then(|_| stdout.write_all(b" = [ \"")); + let _ = stdout.write(key.as_bytes()).and_then( + |_| stdout.write_all(b" = [ \""), + ); let mut elements = value.iter(); @@ -56,76 +47,79 @@ fn print_arrays(list: &ArrayVariableContext) { } for element in elements { - let _ = stdout.write_all(b"\" \"").and_then(|_| stdout.write_all(element.as_bytes())); + let _ = stdout.write_all(b"\" \"").and_then(|_| { + stdout.write_all(element.as_bytes()) + }); } let _ = stdout.write(b"\" ]\n"); } } -fn parse_assignment<E: Expander>( - binding: Binding, - expanders: &E, -) -> Result<Action, i32> { +fn parse_assignment<E: Expander>(binding: Binding, expanders: &E) -> Result<Action, i32> { match binding { Binding::InvalidKey(key) => { let stderr = io::stderr(); let _ = writeln!(&mut stderr.lock(), "ion: variable name, '{}', is invalid", key); Err(FAILURE) - }, - Binding::KeyValue(key, value) => match parse_expression(&value, expanders) { - Value::String(value) => Ok(Action::UpdateString(key, value)), - Value::Array(array) => Ok(Action::UpdateArray(key, array)), - }, - Binding::MapKeyValue(key, inner_key, value) => { - Ok(Action::UpdateHashMap(key, inner_key, value)) - }, - Binding::MultipleKeys(keys, value) => match parse_expression(&value, expanders) { - Value::String(value) => { - let array = value.split_whitespace().map(String::from) - .collect::<VArray>(); - Ok(Action::UpdateStrings(keys, array)) - }, - Value::Array(array) => Ok(Action::UpdateStrings(keys, array)), - }, + } + Binding::KeyValue(key, value) => { + match parse_expression(&value, expanders) { + Value::String(value) => Ok(Action::UpdateString(key, value)), + Value::Array(array) => Ok(Action::UpdateArray(key, array)), + } + } + Binding::MapKeyValue(key, inner_key, value) => Ok(Action::UpdateHashMap(key, inner_key, value)), + Binding::MultipleKeys(keys, value) => { + match parse_expression(&value, expanders) { + Value::String(value) => { + let array = value + .split_whitespace() + .map(String::from) + .collect::<VArray>(); + Ok(Action::UpdateStrings(keys, array)) + } + Value::Array(array) => Ok(Action::UpdateStrings(keys, array)), + } + } Binding::KeyOnly(key) => { let stderr = io::stderr(); let _ = writeln!(&mut stderr.lock(), "ion: please provide value for variable '{}'", key); Err(FAILURE) - }, + } Binding::ListEntries => Ok(Action::List), Binding::Math(key, operator, value) => { match parse_expression(&value, expanders) { Value::String(ref value) => { - let left = match expanders.variable(&key, false).and_then(|x| { - x.parse::<f32>().ok() - }) { + let left = match expanders.variable(&key, false).and_then( + |x| x.parse::<f32>().ok(), + ) { Some(left) => left, None => return Err(FAILURE), }; let right = match value.parse::<f32>().ok() { Some(right) => right, - None => return Err(FAILURE) + None => return Err(FAILURE), }; let result = match operator { - Operator::Add => left + right, + Operator::Add => left + right, Operator::Subtract => left - right, - Operator::Divide => left / right, + Operator::Divide => left / right, Operator::Multiply => left * right, - Operator::Exponent => f32::powf(left, right) + Operator::Exponent => f32::powf(left, right), }; Ok(Action::UpdateString(key, result.to_string())) - }, + } Value::Array(_) => { let stderr = io::stderr(); let _ = writeln!(stderr.lock(), "ion: array math not supported yet"); Err(FAILURE) } } - }, + } } } @@ -139,7 +133,6 @@ pub trait VariableStore { } impl<'a> VariableStore for Shell<'a> { - fn local(&mut self, binding: Binding) -> i32 { match parse_assignment(binding, self) { Ok(Action::UpdateArray(key, array)) => self.variables.set_array(&key, array), @@ -148,10 +141,10 @@ impl<'a> VariableStore for Shell<'a> { for (key, value) in keys.iter().zip(array.iter()) { self.variables.set_var(key, value); } - }, + } Ok(Action::UpdateHashMap(key, inner_key, value)) => { self.variables.set_hashmap_value(&key, &inner_key, &value) - }, + } Ok(Action::List) => { print_vars(&self.variables.variables); print_arrays(&self.variables.arrays); @@ -173,29 +166,26 @@ impl<'a> VariableStore for Shell<'a> { } Ok(Action::UpdateHashMap(key, inner_key, value)) => { self.variables.set_hashmap_value(&key, &inner_key, &value) - }, + } Ok(Action::List) => { let stdout = io::stdout(); let stdout = &mut stdout.lock(); for (key, value) in env::vars() { - let _ = stdout.write(key.as_bytes()) + let _ = stdout + .write(key.as_bytes()) .and_then(|_| stdout.write_all(b"=")) .and_then(|_| stdout.write_all(value.as_bytes())) .and_then(|_| stdout.write_all(b"\n")); } } - Err(code) => return code + Err(code) => return code, }; SUCCESS } - } -fn parse_expression<E: Expander>( - expression: &str, - shell_funcs: &E -) -> Value { +fn parse_expression<E: Expander>(expression: &str, shell_funcs: &E) -> Value { let arguments: Vec<&str> = ArgumentSplitter::new(expression).collect(); if arguments.len() == 1 { @@ -208,9 +198,10 @@ fn parse_expression<E: Expander>( } else { // If multiple arguments have been passed, they will be collapsed into a single string. // IE: `[ one two three ] four` is equivalent to `one two three four` - let arguments: Vec<String> = arguments.iter() - .flat_map(|expression| expand_string(expression, shell_funcs, false)) - .collect(); + let arguments: Vec<String> = arguments + .iter() + .flat_map(|expression| expand_string(expression, shell_funcs, false)) + .collect(); Value::String(arguments.join(" ")) } diff --git a/src/shell/binary.rs b/src/shell/binary.rs index 27f07e5e155f8403447ed596b71e09ca59b18f60..5a7f289cd75fc7f1dd5813787b03c6bae321c7c6 100644 --- a/src/shell/binary.rs +++ b/src/shell/binary.rs @@ -1,21 +1,21 @@ //! Contains the binary logic of Ion. -use liner::{BasicCompleter, Buffer, Context, Event, EventKind, CursorPosition}; +use super::{DirectoryStack, FlowLogic, JobControl, Shell, ShellHistory, Variables}; +use super::completer::*; +use super::flow_control::Statement; +use super::status::*; +use liner::{BasicCompleter, Buffer, Context, CursorPosition, Event, EventKind}; use parser::*; use parser::QuoteTerminator; use smallstring::SmallString; use smallvec::SmallVec; use std::env; use std::fs::File; -use std::io::{self, Write, Read, ErrorKind}; +use std::io::{self, ErrorKind, Read, Write}; use std::iter::{self, FromIterator}; use std::mem; use std::path::{Path, PathBuf}; use sys; -use super::completer::*; -use super::flow_control::Statement; -use super::status::*; -use super::{Shell, FlowLogic, JobControl, ShellHistory, Variables, DirectoryStack}; use types::*; pub trait Binary { @@ -65,37 +65,42 @@ impl<'a> Binary for Shell<'a> { loop { let prompt = self.prompt(); - let line = self.context.as_mut().unwrap().read_line(prompt, &mut move |Event { editor, kind }| { - if let EventKind::BeforeComplete = kind { - let (words, pos) = editor.get_words_and_cursor_position(); + let line = self.context.as_mut().unwrap().read_line( + prompt, + &mut move |Event { + editor, + kind, + }| { + if let EventKind::BeforeComplete = kind { + let (words, pos) = editor.get_words_and_cursor_position(); - let filename = match pos { - CursorPosition::InWord(index) => index > 0, - CursorPosition::InSpace(Some(_), _) => true, - CursorPosition::InSpace(None, _) => false, - CursorPosition::OnWordLeftEdge(index) => index >= 1, - CursorPosition::OnWordRightEdge(index) => { - match (words.into_iter().nth(index), env::current_dir()) { - (Some((start, end)), Ok(file)) => { - let filename = editor.current_buffer().range(start, end); - complete_as_file(file, filename, index) - }, - _ => false, + let filename = match pos { + CursorPosition::InWord(index) => index > 0, + CursorPosition::InSpace(Some(_), _) => true, + CursorPosition::InSpace(None, _) => false, + CursorPosition::OnWordLeftEdge(index) => index >= 1, + CursorPosition::OnWordRightEdge(index) => { + match (words.into_iter().nth(index), env::current_dir()) { + (Some((start, end)), Ok(file)) => { + let filename = editor.current_buffer().range(start, end); + complete_as_file(file, filename, index) + } + _ => false, + } } - } - }; + }; - if filename { - if let Ok(current_dir) = env::current_dir() { - if let Some(url) = current_dir.to_str() { - let completer = IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr); - mem::replace(&mut editor.context().completer, Some(Box::new(completer))); + if filename { + if let Ok(current_dir) = env::current_dir() { + if let Some(url) = current_dir.to_str() { + let completer = IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr); + mem::replace(&mut editor.context().completer, Some(Box::new(completer))); + } } - } - } else { - // Creates a list of definitions from the shell environment that will be used - // in the creation of a custom completer. - let words = builtins.iter() + } else { + // Creates a list of definitions from the shell environment that will be used + // in the creation of a custom completer. + let words = builtins.iter() // Add built-in commands to the completer's definitions. .map(|(&s, _)| Identifier::from(s)) // Add the history list to the completer's definitions. @@ -110,34 +115,35 @@ impl<'a> Binary for Shell<'a> { .chain(vars.get_vars().into_iter().map(|s| ["$", &s].concat().into())) .collect(); - // Initialize a new completer from the definitions collected. - let custom_completer = BasicCompleter::new(words); + // Initialize a new completer from the definitions collected. + let custom_completer = BasicCompleter::new(words); - // Creates completers containing definitions from all directories listed - // in the environment's **$PATH** variable. - let mut file_completers = if let Ok(val) = env::var("PATH") { - val.split(sys::PATH_SEPARATOR) - .map(|s| IonFileCompleter::new(Some(s), dirs_ptr, vars_ptr)) - .collect() - } else { - vec![IonFileCompleter::new(Some("/bin/"), dirs_ptr, vars_ptr)] - }; + // Creates completers containing definitions from all directories listed + // in the environment's **$PATH** variable. + let mut file_completers = if let Ok(val) = env::var("PATH") { + val.split(sys::PATH_SEPARATOR) + .map(|s| IonFileCompleter::new(Some(s), dirs_ptr, vars_ptr)) + .collect() + } else { + vec![IonFileCompleter::new(Some("/bin/"), dirs_ptr, vars_ptr)] + }; - // Also add files/directories in the current directory to the completion list. - if let Ok(current_dir) = env::current_dir() { - if let Some(url) = current_dir.to_str() { - file_completers.push(IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr)); + // Also add files/directories in the current directory to the completion list. + if let Ok(current_dir) = env::current_dir() { + if let Some(url) = current_dir.to_str() { + file_completers.push(IonFileCompleter::new(Some(url), dirs_ptr, vars_ptr)); + } } - } - // Merge the collected definitions with the file path definitions. - let completer = MultiCompleter::new(file_completers, custom_completer); + // Merge the collected definitions with the file path definitions. + let completer = MultiCompleter::new(file_completers, custom_completer); - // Replace the shell's current completer with the newly-created completer. - mem::replace(&mut editor.context().completer, Some(Box::new(completer))); + // Replace the shell's current completer with the newly-created completer. + mem::replace(&mut editor.context().completer, Some(Box::new(completer))); + } } - } - }); + }, + ); match line { Ok(line) => return Some(line), @@ -149,7 +155,7 @@ impl<'a> Binary for Shell<'a> { let stderr = io::stderr(); let mut stderr = stderr.lock(); let _ = writeln!(stderr, "ion: liner: {}", err); - return None + return None; } } } @@ -166,7 +172,7 @@ impl<'a> Binary for Shell<'a> { loop { if let Some(command) = lines.next() { buffer.append(command); - break + break; } else { let stderr = io::stderr(); let _ = writeln!(stderr.lock(), "ion: unterminated quote in script"); @@ -179,8 +185,10 @@ impl<'a> Binary for Shell<'a> { // The flow control level being non zero means that we have a statement that has // only been partially parsed. if self.flow_control.level != 0 { - eprintln!("ion: unexpected end of script: expected end block for `{}`", - self.flow_control.current_statement.short()); + eprintln!( + "ion: unexpected end of script: expected end block for `{}`", + self.flow_control.current_statement.short() + ); } } @@ -191,7 +199,7 @@ impl<'a> Binary for Shell<'a> { loop { if let Some(command) = self.readln() { buffer.append(command); - break + break; } else { return Err(()); } @@ -225,7 +233,9 @@ impl<'a> Binary for Shell<'a> { let mut context = Context::new(); context.word_divider_fn = Box::new(word_divide); if "1" == self.variables.get_var_or_empty("HISTFILE_ENABLED") { - let path = self.variables.get_var("HISTFILE").expect("shell didn't set HISTFILE"); + let path = self.variables.get_var("HISTFILE").expect( + "shell didn't set HISTFILE", + ); context.history.set_file_name(Some(path.clone())); if !Path::new(path.as_str()).exists() { eprintln!("ion: creating history file at \"{}\"", path); @@ -240,7 +250,7 @@ impl<'a> Binary for Shell<'a> { Err(ref err) if err.kind() == ErrorKind::NotFound => { let history_filename = self.variables.get_var_or_empty("HISTFILE"); eprintln!("ion: failed to find history file {}: {}", history_filename, err); - }, + } Err(err) => { eprintln!("ion: failed to load history: {}", err); } @@ -251,14 +261,15 @@ impl<'a> Binary for Shell<'a> { self.evaluate_init_file(); - self.variables.set_array ( + self.variables.set_array( "args", - iter::once(env::args().next().unwrap()).collect(), + iter::once(env::args().next().unwrap()) + .collect(), ); loop { if let Some(command) = self.readln() { - if ! command.is_empty() { + if !command.is_empty() { if let Ok(command) = self.terminate_quotes(command) { // Parse and potentially execute the command. self.on_command(command.trim()); @@ -293,10 +304,10 @@ impl<'a> Binary for Shell<'a> { if path == "-c" { self.execute_arguments(args); } else { - let mut array = SmallVec::from_iter( - Some(path.clone().into()) - ); - for arg in args { array.push(arg.into()); } + let mut array = SmallVec::from_iter(Some(path.clone().into())); + for arg in args { + array.push(arg.into()); + } self.variables.set_array("args", array); self.execute_script(&path); } @@ -323,7 +334,7 @@ impl<'a> Binary for Shell<'a> { let _ = writeln!(stderr, "ion: failed to read {:?}: {}", path, err); } } - }, + } Err(err) => { let stderr = io::stderr(); let mut stderr = stderr.lock(); @@ -372,22 +383,32 @@ fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> { } /// Infer if the given filename is actually a partial filename -fn complete_as_file(current_dir : PathBuf, filename : String, index : usize) -> bool { +fn complete_as_file(current_dir: PathBuf, filename: String, index: usize) -> bool { let filename = filename.trim(); let mut file = current_dir.clone(); file.push(&filename); // If the user explicitly requests a file through this syntax then complete as a file - if filename.trim().starts_with(".") { return true; } + if filename.trim().starts_with(".") { + return true; + } // If the file starts with a dollar sign, it's a variable, not a file - if filename.trim().starts_with("$") { return false; } + if filename.trim().starts_with("$") { + return false; + } // Once we are beyond the first string, assume its a file - if index > 0 { return true; } + if index > 0 { + return true; + } // If we are referencing a file that exists then just complete to that file - if file.exists() { return true; } + if file.exists() { + return true; + } // If we have a partial file inside an existing directory, e.g. /foo/b when /foo/bar // exists, then treat it as file as long as `foo` isn't the current directory, otherwise // this would apply to any string `foo` - if let Some(parent) = file.parent() { return parent.exists() && parent != current_dir; } + if let Some(parent) = file.parent() { + return parent.exists() && parent != current_dir; + } // By default assume its not a file false } diff --git a/src/shell/completer.rs b/src/shell/completer.rs index a5fd63991b8832103d5a0fc37abbcf9ad0678695..17f452cfde3a0fe7637e146e5a22d88457f81f50 100644 --- a/src/shell/completer.rs +++ b/src/shell/completer.rs @@ -1,6 +1,7 @@ -use liner::{Completer, FilenameCompleter}; + use super::directory_stack::DirectoryStack; use super::variables::Variables; +use liner::{Completer, FilenameCompleter}; /// Performs escaping to an inner `FilenameCompleter` to enable a handful of special cases /// needed by the shell, such as expanding '~' to a home directory, or adding a backslash @@ -15,15 +16,11 @@ pub struct IonFileCompleter { } impl IonFileCompleter { - pub fn new ( - path: Option<&str>, - dir_stack: *const DirectoryStack, - vars: *const Variables - ) -> IonFileCompleter { + pub fn new(path: Option<&str>, dir_stack: *const DirectoryStack, vars: *const Variables) -> IonFileCompleter { IonFileCompleter { inner: FilenameCompleter::new(path), dir_stack: dir_stack, - vars: vars + vars: vars, } } } @@ -41,7 +38,7 @@ impl Completer for IonFileCompleter { // Dereferencing the raw pointers here should be entirely safe, theoretically, // because no changes will occur to either of the underlying references in the // duration between creation of the completers and execution of their completions. - if let Some(expanded) = unsafe{ (*self.vars).tilde_expansion(start, &*self.dir_stack) } { + if let Some(expanded) = unsafe { (*self.vars).tilde_expansion(start, &*self.dir_stack) } { // Now we obtain completions for the `expanded` form of the `start` value. let completions = self.inner.completions(&expanded); let mut iterator = completions.iter(); @@ -82,11 +79,15 @@ impl Completer for IonFileCompleter { } } - return completions + return completions; } } - self.inner.completions(&unescape(start)).iter().map(|x| escape(x.as_str())).collect() + self.inner + .completions(&unescape(start)) + .iter() + .map(|x| escape(x.as_str())) + .collect() } } @@ -98,10 +99,10 @@ fn escape(input: &str) -> String { let mut output = Vec::with_capacity(input.len()); for character in input.bytes() { match character { - b'(' | b')' | b'[' | b']' | b'&' | b'$' | - b'@' | b'{' | b'}' | b'<' | b'>' | b';' | - b'"' | b'\'' => output.push(b'\\'), - _ => () + b'(' | b')' | b'[' | b']' | b'&' | b'$' | b'@' | b'{' | b'}' | b'<' | b'>' | b';' | b'"' | b'\'' => { + output.push(b'\\') + } + _ => (), } output.push(character); } @@ -129,21 +130,25 @@ fn unescape(input: &str) -> String { /// A completer that combines suggestions from multiple completers. #[derive(Clone, Eq, PartialEq)] -pub struct MultiCompleter<A, B> where A: Completer, B: Completer { +pub struct MultiCompleter<A, B> + where A: Completer, + B: Completer +{ a: Vec<A>, - b: B + b: B, } -impl<A, B> MultiCompleter<A, B> where A: Completer, B: Completer { - pub fn new(a: Vec<A>, b: B) -> MultiCompleter<A, B> { - MultiCompleter { - a: a, - b: b - } - } +impl<A, B> MultiCompleter<A, B> + where A: Completer, + B: Completer +{ + pub fn new(a: Vec<A>, b: B) -> MultiCompleter<A, B> { MultiCompleter { a: a, b: b } } } -impl<A, B> Completer for MultiCompleter<A, B> where A: Completer, B: Completer { +impl<A, B> Completer for MultiCompleter<A, B> + where A: Completer, + B: Completer +{ fn completions(&self, start: &str) -> Vec<String> { let mut completions = self.b.completions(start); for x in &self.a { diff --git a/src/shell/directory_stack.rs b/src/shell/directory_stack.rs index c4332478ee38509752be181a27ae62f05b003878..00df7e2d21fd05d008434ea66874a3f232f5ceae 100644 --- a/src/shell/directory_stack.rs +++ b/src/shell/directory_stack.rs @@ -1,9 +1,10 @@ + +use super::status::{FAILURE, SUCCESS}; +use super::variables::Variables; use std::borrow::Cow; use std::collections::VecDeque; -use std::env::{set_current_dir, current_dir, home_dir}; +use std::env::{current_dir, home_dir, set_current_dir}; use std::path::PathBuf; -use super::variables::Variables; -use super::status::{SUCCESS, FAILURE}; pub struct DirectoryStack { dirs: VecDeque<PathBuf>, // The top is always the current directory @@ -17,10 +18,10 @@ impl DirectoryStack { Ok(curr_dir) => { dirs.push_front(curr_dir); DirectoryStack { dirs: dirs } - }, + } Err(_) => { eprintln!("ion: failed to get current directory when building directory stack"); - DirectoryStack { dirs: dirs} + DirectoryStack { dirs: dirs } } } } @@ -29,7 +30,10 @@ impl DirectoryStack { /// directory stack size variable. If it succeeds, it will return the value of that variable, /// else it will return a default value of 1000. fn get_size(variables: &Variables) -> usize { - variables.get_var_or_empty("DIRECTORY_STACK_SIZE").parse::<usize>().unwrap_or(1000) + variables + .get_var_or_empty("DIRECTORY_STACK_SIZE") + .parse::<usize>() + .unwrap_or(1000) } /// Attempts to set the current directory to the directory stack's previous directory, @@ -37,153 +41,166 @@ impl DirectoryStack { pub fn popd<I: IntoIterator>(&mut self, args: I) -> Result<(), Cow<'static, str>> where I::Item: AsRef<str> { - let mut keep_front = false; // whether the -n option is present - let mut count_from_front = true; // <=> input number is positive - let mut num: usize = 0; - - for arg in args.into_iter().skip(1) { - let arg = arg.as_ref(); - if arg == "-n" { - keep_front = true; - } else { - match parse_numeric_arg(arg) { - Some((x, y)) => { count_from_front = x; num = y; } - None => return Err(Cow::Owned(format!("ion: popd: {}: invalid argument\n", arg))) - }; - } - } - - let len: usize = self.dirs.len(); - if len <= 1 { - return Err(Cow::Borrowed("ion: popd: directory stack empty\n")); - } - - let mut index: usize = if count_from_front { num } else { - (len - 1).checked_sub(num) - .ok_or_else(|| Cow::Owned(format!("ion: popd: negative directory stack index out of range\n")))? - }; - - // apply -n - if index == 0 && keep_front { index = 1; } - - // change to new directory, return if not possible - if index == 0 { - self.set_current_dir_by_index(1, "popd")?; - } - - // pop element - if self.dirs.remove(index).is_none() { - return Err(Cow::Owned(format!("ion: popd: {}: directory stack index out of range\n", index))); - } - - self.print_dirs(); - Ok(()) + let mut keep_front = false; // whether the -n option is present + let mut count_from_front = true; // <=> input number is positive + let mut num: usize = 0; + + for arg in args.into_iter().skip(1) { + let arg = arg.as_ref(); + if arg == "-n" { + keep_front = true; + } else { + match parse_numeric_arg(arg) { + Some((x, y)) => { + count_from_front = x; + num = y; + } + None => return Err(Cow::Owned(format!("ion: popd: {}: invalid argument\n", arg))), + }; + } + } + + let len: usize = self.dirs.len(); + if len <= 1 { + return Err(Cow::Borrowed("ion: popd: directory stack empty\n")); + } + + let mut index: usize = if count_from_front { + num + } else { + (len - 1).checked_sub(num).ok_or_else(|| { + Cow::Owned(format!("ion: popd: negative directory stack index out of range\n")) + })? + }; + + // apply -n + if index == 0 && keep_front { + index = 1; + } + + // change to new directory, return if not possible + if index == 0 { + self.set_current_dir_by_index(1, "popd")?; + } + + // pop element + if self.dirs.remove(index).is_none() { + return Err(Cow::Owned( + format!("ion: popd: {}: directory stack index out of range\n", index), + )); + } + + self.print_dirs(); + Ok(()) } pub fn pushd<I: IntoIterator>(&mut self, args: I, variables: &Variables) -> Result<(), Cow<'static, str>> where I::Item: AsRef<str> { - enum Action { - Switch, // <no arguments> - RotLeft(usize), // +[num] - RotRight(usize), // -[num] - Push(PathBuf) // [dir] - } - - let mut keep_front = false; // whether the -n option is present - let mut action : Action = Action::Switch; - - for arg in args.into_iter().skip(1) { - let arg = arg.as_ref(); - if arg == "-n" { - keep_front = true; - } else if let Action::Switch = action { // if action is not yet defined - action = match parse_numeric_arg(arg) { - Some((true, num)) => Action::RotLeft(num), - Some((false, num)) => Action::RotRight(num), - None => Action::Push(PathBuf::from(arg)) // no numeric arg => `dir`-parameter - }; - } else { - return Err(Cow::Borrowed("ion: pushd: too many arguments\n")); - } - } - - let len = self.dirs.len(); - match action { - Action::Switch => { - if len < 2 { - return Err(Cow::Borrowed("ion: pushd: no other directory\n")); - } - if !keep_front { - self.set_current_dir_by_index(1, "pushd")?; - self.dirs.swap(0, 1); - } - }, - Action::RotLeft(num) => { - if !keep_front { - self.set_current_dir_by_index(num, "pushd")?; - self.rotate_left(num); - } - }, - Action::RotRight(num) => { - if !keep_front { - self.set_current_dir_by_index(len - (num % len), "pushd")?; - self.rotate_right(num); - } - }, - Action::Push(dir) => { - let index = if keep_front { 1 } else { 0 }; - self.insert_dir(index, dir, variables); - } - }; - - self.print_dirs(); - - Ok(()) + enum Action { + Switch, // <no arguments> + RotLeft(usize), // +[num] + RotRight(usize), // -[num] + Push(PathBuf), // [dir] + } + + let mut keep_front = false; // whether the -n option is present + let mut action: Action = Action::Switch; + + for arg in args.into_iter().skip(1) { + let arg = arg.as_ref(); + if arg == "-n" { + keep_front = true; + } else if let Action::Switch = action { + // if action is not yet defined + action = match parse_numeric_arg(arg) { + Some((true, num)) => Action::RotLeft(num), + Some((false, num)) => Action::RotRight(num), + None => Action::Push(PathBuf::from(arg)), // no numeric arg => `dir`-parameter + }; + } else { + return Err(Cow::Borrowed("ion: pushd: too many arguments\n")); + } + } + + let len = self.dirs.len(); + match action { + Action::Switch => { + if len < 2 { + return Err(Cow::Borrowed("ion: pushd: no other directory\n")); + } + if !keep_front { + self.set_current_dir_by_index(1, "pushd")?; + self.dirs.swap(0, 1); + } + } + Action::RotLeft(num) => { + if !keep_front { + self.set_current_dir_by_index(num, "pushd")?; + self.rotate_left(num); + } + } + Action::RotRight(num) => { + if !keep_front { + self.set_current_dir_by_index(len - (num % len), "pushd")?; + self.rotate_right(num); + } + } + Action::Push(dir) => { + let index = if keep_front { 1 } else { 0 }; + self.insert_dir(index, dir, variables); + } + }; + + self.print_dirs(); + + Ok(()) } pub fn cd<I: IntoIterator>(&mut self, args: I, variables: &Variables) -> Result<(), Cow<'static, str>> where I::Item: AsRef<str> { - match args.into_iter().nth(1) { - Some(dir) => { - let dir = dir.as_ref(); - if dir == "-" { - self.switch_to_previous_directory(variables) - } else { - self.change_and_push_dir(dir, variables) - } + match args.into_iter().nth(1) { + Some(dir) => { + let dir = dir.as_ref(); + if dir == "-" { + self.switch_to_previous_directory(variables) + } else { + self.change_and_push_dir(dir, variables) } - None => self.switch_to_home_directory(variables) } + None => self.switch_to_home_directory(variables), + } } fn switch_to_home_directory(&mut self, variables: &Variables) -> Result<(), Cow<'static, str>> { - home_dir() - .map_or(Err(Cow::Borrowed("ion: failed to get home directory")), |home| { - home.to_str().map_or(Err(Cow::Borrowed("ion: failed to convert home directory to str")), |home| { + home_dir().map_or(Err(Cow::Borrowed("ion: failed to get home directory")), |home| { + home.to_str().map_or( + Err(Cow::Borrowed( + "ion: failed to convert home directory to str", + )), + |home| { self.change_and_push_dir(home, variables) - }) - }) + }, + ) + }) } fn switch_to_previous_directory(&mut self, variables: &Variables) -> Result<(), Cow<'static, str>> { - self.get_previous_dir().cloned() - .map_or_else(|| Err(Cow::Borrowed("ion: no previous directory to switch to")), |prev| { + self.get_previous_dir().cloned().map_or_else( + || { + Err(Cow::Borrowed("ion: no previous directory to switch to")) + }, + |prev| { self.dirs.remove(1); let prev = prev.to_string_lossy().to_string(); println!("{}", prev); self.change_and_push_dir(&prev, variables) - }) + }, + ) } - fn get_previous_dir(&self) -> Option<&PathBuf> { - if self.dirs.len() < 2 { - None - } else { - self.dirs.get(1) - } - } + fn get_previous_dir(&self) -> Option<&PathBuf> { if self.dirs.len() < 2 { None } else { self.dirs.get(1) } } pub fn change_and_push_dir(&mut self, dir: &str, variables: &Variables) -> Result<(), Cow<'static, str>> { match (set_current_dir(dir), current_dir()) { @@ -191,10 +208,10 @@ impl DirectoryStack { self.push_dir(cur_dir, variables); Ok(()) } - (Err(err), _) => { - Err(Cow::Owned(format!("ion: failed to set current dir to {}: {}\n", dir, err))) - } - (_, _) => Err(Cow::Borrowed("ion: change_and_push_dir(): error occurred that should never happen\n")), // This should not happen + (Err(err), _) => Err(Cow::Owned(format!("ion: failed to set current dir to {}: {}\n", dir, err))), + (..) => Err(Cow::Borrowed( + "ion: change_and_push_dir(): error occurred that should never happen\n", + )), // This should not happen } } @@ -212,78 +229,70 @@ impl DirectoryStack { pub fn dirs<I: IntoIterator>(&mut self, args: I) -> i32 where I::Item: AsRef<str> { - const CLEAR: u8 = 1; // -c - const ABS_PATHNAMES: u8 = 2; // -l - const MULTILINE: u8 = 4; // -p | -v - const INDEX: u8 = 8; // -v - - let mut dirs_args: u8 = 0; - let mut num_arg: Option<usize> = None; - - for arg in args.into_iter().skip(1) { - let arg = arg.as_ref(); - match arg { - "-c" => dirs_args |= CLEAR, - "-l" => dirs_args |= ABS_PATHNAMES, - "-p" => dirs_args |= MULTILINE, - "-v" => dirs_args |= INDEX | MULTILINE, - arg => { - num_arg = match parse_numeric_arg(arg) { - Some((true, num)) => Some(num), - Some((false, num)) if self.dirs.len() > num => Some(self.dirs.len() - num - 1), - _ => return FAILURE // Err(Cow::Owned(format!("ion: dirs: {}: invalid argument\n", arg))) - }; - } - } - } - - if dirs_args & CLEAR > 0 { - self.dirs.truncate(1); - } - - let mapper: fn((usize, &PathBuf)) -> Cow<str> = - match (dirs_args & ABS_PATHNAMES > 0, dirs_args & INDEX > 0) { - // ABS, INDEX - (true, true) => |(num, x)| Cow::Owned(format!(" {} {}", num, try_abs_path(x))), - (true, false) => |(_, x)| try_abs_path(x), - (false, true) => |(num, x)| Cow::Owned(format!(" {} {}", num, x.to_string_lossy())), - (false, false) => |(_, x)| x.to_string_lossy() - }; - - let mut iter = self.dirs.iter() - .enumerate() - .map(mapper); - - if let Some(num) = num_arg { - match iter.nth(num) { - Some(x) => println!("{}", x), - None => return FAILURE - }; - } else { - let folder: fn(String, Cow<str>) -> String = - match dirs_args & MULTILINE > 0 { - true => |x, y| x + "\n" + &y, - false => |x, y| x + " " + &y, - }; - - let first = match iter.next() { - Some(x) => x.to_string(), - None => return SUCCESS - }; - - println!("{}", iter.fold(first, folder)); - } - SUCCESS - } + const CLEAR: u8 = 1; // -c + const ABS_PATHNAMES: u8 = 2; // -l + const MULTILINE: u8 = 4; // -p | -v + const INDEX: u8 = 8; // -v + + let mut dirs_args: u8 = 0; + let mut num_arg: Option<usize> = None; + + for arg in args.into_iter().skip(1) { + let arg = arg.as_ref(); + match arg { + "-c" => dirs_args |= CLEAR, + "-l" => dirs_args |= ABS_PATHNAMES, + "-p" => dirs_args |= MULTILINE, + "-v" => dirs_args |= INDEX | MULTILINE, + arg => { + num_arg = match parse_numeric_arg(arg) { + Some((true, num)) => Some(num), + Some((false, num)) if self.dirs.len() > num => Some(self.dirs.len() - num - 1), + _ => return FAILURE, // Err(Cow::Owned(format!("ion: dirs: {}: invalid argument\n", arg))) + }; + } + } + } - pub fn dir_from_top(&self, num: usize) -> Option<&PathBuf> { - self.dirs.get(num) - } + if dirs_args & CLEAR > 0 { + self.dirs.truncate(1); + } - pub fn dir_from_bottom(&self, num: usize) -> Option<&PathBuf> { - self.dirs.iter().rev().nth(num) + let mapper: fn((usize, &PathBuf)) -> Cow<str> = match (dirs_args & ABS_PATHNAMES > 0, dirs_args & INDEX > 0) { + // ABS, INDEX + (true, true) => |(num, x)| Cow::Owned(format!(" {} {}", num, try_abs_path(x))), + (true, false) => |(_, x)| try_abs_path(x), + (false, true) => |(num, x)| Cow::Owned(format!(" {} {}", num, x.to_string_lossy())), + (false, false) => |(_, x)| x.to_string_lossy(), + }; + + let mut iter = self.dirs.iter().enumerate().map(mapper); + + if let Some(num) = num_arg { + match iter.nth(num) { + Some(x) => println!("{}", x), + None => return FAILURE, + }; + } else { + let folder: fn(String, Cow<str>) -> String = match dirs_args & MULTILINE > 0 { + true => |x, y| x + "\n" + &y, + false => |x, y| x + " " + &y, + }; + + let first = match iter.next() { + Some(x) => x.to_string(), + None => return SUCCESS, + }; + + println!("{}", iter.fold(first, folder)); + } + SUCCESS } + pub fn dir_from_top(&self, num: usize) -> Option<&PathBuf> { self.dirs.get(num) } + + pub fn dir_from_bottom(&self, num: usize) -> Option<&PathBuf> { self.dirs.iter().rev().nth(num) } + fn print_dirs(&self) { let dir = self.dirs.iter().fold(String::new(), |acc, dir| { acc + " " + dir.to_str().unwrap_or("ion: no directory found") @@ -293,50 +302,44 @@ impl DirectoryStack { // sets current_dir to the element referred by index fn set_current_dir_by_index(&self, index: usize, caller: &str) -> Result<(), Cow<'static, str>> { - let dir = self.dirs.iter().nth(index) - .ok_or_else(|| Cow::Owned(format!("ion: {}: {}: directory stack out of range\n", caller, index)))?; + let dir = self.dirs.iter().nth(index).ok_or_else(|| { + Cow::Owned(format!("ion: {}: {}: directory stack out of range\n", caller, index)) + })?; - set_current_dir(dir) - .map_err(|_| Cow::Owned(format!("ion: {}: Failed setting current dir\n", caller))) + set_current_dir(dir).map_err(|_| Cow::Owned(format!("ion: {}: Failed setting current dir\n", caller))) } // pushd +<num> fn rotate_left(&mut self, num: usize) { - let cloned = self.dirs.clone(); - for (dest, src) in self.dirs.iter_mut().zip( - cloned.iter().cycle().skip(num) - ) { - *dest = src.clone(); - } + let cloned = self.dirs.clone(); + for (dest, src) in self.dirs.iter_mut().zip(cloned.iter().cycle().skip(num)) { + *dest = src.clone(); + } } // pushd -<num> fn rotate_right(&mut self, num: usize) { - let len = self.dirs.len(); - self.rotate_left(len - (num % len)); + let len = self.dirs.len(); + self.rotate_left(len - (num % len)); } } // parses -N or +N patterns // required for popd, pushd, dirs fn parse_numeric_arg(arg: &str) -> Option<(bool, usize)> { - match arg.chars().nth(0) { - Some('+') => Some(true), - Some('-') => Some(false), - _ => None - }.and_then(|b| { - arg[1..].parse::<usize>() - .ok() - .map(|num| (b, num)) - }) + match arg.chars().nth(0) { + Some('+') => Some(true), + Some('-') => Some(false), + _ => None, + }.and_then(|b| arg[1..].parse::<usize>().ok().map(|num| (b, num))) } // converts pbuf to an absolute path if possible fn try_abs_path(pbuf: &PathBuf) -> Cow<str> { - Cow::Owned( - pbuf.canonicalize() - .unwrap_or_else(|_| pbuf.clone()) - .to_string_lossy() - .to_string() - ) + Cow::Owned( + pbuf.canonicalize() + .unwrap_or_else(|_| pbuf.clone()) + .to_string_lossy() + .to_string(), + ) } diff --git a/src/shell/flags.rs b/src/shell/flags.rs index fa124924f2716745ff684bab3677d180425681f6..e2a72ceddd7467196e31585f7ce78a6c31e93d34 100644 --- a/src/shell/flags.rs +++ b/src/shell/flags.rs @@ -1,2 +1,2 @@ -pub const ERR_EXIT: u8 = 1; +pub const ERR_EXIT: u8 = 1; pub const PRINT_COMMS: u8 = 2; diff --git a/src/shell/flow.rs b/src/shell/flow.rs index b1c87a1494491066d965031e85ff1f3f6a01e3c3..2ac80d82b1a1f20b91747627b9730f62c9016010 100644 --- a/src/shell/flow.rs +++ b/src/shell/flow.rs @@ -1,13 +1,14 @@ -use std::io::{self, Write}; -use std::mem; -use super::status::*; + use super::Shell; use super::flags::*; +use super::flow_control::{Case, ElseIf, Function, Statement, collect_cases, collect_if, collect_loops}; use super::job_control::JobControl; -use super::flow_control::{ElseIf, Function, Statement, collect_loops, collect_cases, collect_if, Case}; -use parser::{ForExpression, StatementSplitter, parse_and_validate, expand_string}; +use super::status::*; +use parser::{ForExpression, StatementSplitter, expand_string, parse_and_validate}; use parser::pipelines::Pipeline; use shell::assignments::VariableStore; +use std::io::{self, Write}; +use std::mem; use types::Array; pub enum Condition { @@ -32,15 +33,19 @@ pub trait FlowLogic { fn execute_for(&mut self, variable: &str, values: &[String], statements: Vec<Statement>) -> Condition; /// Conditionally executes branches of statements according to evaluated expressions - fn execute_if(&mut self, expression: Pipeline, success: Vec<Statement>, - else_if: Vec<ElseIf>, failure: Vec<Statement>) -> Condition; + fn execute_if( + &mut self, + expression: Pipeline, + success: Vec<Statement>, + else_if: Vec<ElseIf>, + failure: Vec<Statement>, + ) -> Condition; /// Simply executes all supplied statemnts. fn execute_statements(&mut self, statements: Vec<Statement>) -> Condition; /// Expand an expression and run a branch based on the value of the expanded expression fn execute_match(&mut self, expression: String, cases: Vec<Case>) -> Condition; - } impl<'a> FlowLogic for Shell<'a> { @@ -61,39 +66,48 @@ impl<'a> FlowLogic for Shell<'a> { let _ = writeln!(stderr, "{}", why); self.flow_control.level = 0; self.flow_control.current_if_mode = 0; - return + return; } } } else { // Appends the newly parsed statements onto the existing statement stored in memory. match self.flow_control.current_statement { - Statement::While{ ref mut statements, .. } - | Statement::For { ref mut statements, .. } - | Statement::Function { ref mut statements, .. } => - { + Statement::While { ref mut statements, .. } | + Statement::For { ref mut statements, .. } | + Statement::Function { ref mut statements, .. } => { collect_loops(&mut iterator, statements, &mut self.flow_control.level); - }, - Statement::If { ref mut success, ref mut else_if, ref mut failure, .. } => { - self.flow_control.current_if_mode = match collect_if(&mut iterator, success, - else_if, failure, &mut self.flow_control.level, - self.flow_control.current_if_mode) { - Ok(mode) => mode, - Err(why) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "{}", why); - 4 - } - }; - }, + } + Statement::If { + ref mut success, + ref mut else_if, + ref mut failure, + .. + } => { + self.flow_control.current_if_mode = match collect_if( + &mut iterator, + success, + else_if, + failure, + &mut self.flow_control.level, + self.flow_control.current_if_mode, + ) { + Ok(mode) => mode, + Err(why) => { + let stderr = io::stderr(); + let mut stderr = stderr.lock(); + let _ = writeln!(stderr, "{}", why); + 4 + } + }; + } Statement::Match { ref mut cases, .. } => { if let Err(why) = collect_cases(&mut iterator, cases, &mut self.flow_control.level) { let stderr = io::stderr(); let mut stderr = stderr.lock(); let _ = writeln!(stderr, "{}", why); } - }, - _ => () + } + _ => (), } // If this is true, an error occurred during the if statement @@ -101,7 +115,7 @@ impl<'a> FlowLogic for Shell<'a> { self.flow_control.level = 0; self.flow_control.current_if_mode = 0; self.flow_control.current_statement = Statement::Default; - return + return; } // If the level is set to 0, it means that the statement in memory is finished @@ -116,35 +130,55 @@ impl<'a> FlowLogic for Shell<'a> { Statement::Error(number) => self.previous_status = number, Statement::Let { expression } => { self.previous_status = self.local(expression); - }, + } Statement::Export(expression) => { self.previous_status = self.export(expression); } - Statement::While { expression, statements } => { + Statement::While { + expression, + statements, + } => { if let Condition::SigInt = self.execute_while(expression, statements) { - return + return; } - }, - Statement::For { variable, values, statements } => { + } + Statement::For { + variable, + values, + statements, + } => { if let Condition::SigInt = self.execute_for(&variable, &values, statements) { - return + return; } - }, - Statement::Function { name, args, statements, description } => { - self.functions.insert(name.clone(), Function { - name: name, - args: args, - statements: statements, - description: description, - }); - }, - Statement::If { expression, success, else_if, failure } => { + } + Statement::Function { + name, + args, + statements, + description, + } => { + self.functions.insert( + name.clone(), + Function { + name: name, + args: args, + statements: statements, + description: description, + }, + ); + } + Statement::If { + expression, + success, + else_if, + failure, + } => { self.execute_if(expression, success, else_if, failure); - }, + } Statement::Match { expression, cases } => { self.execute_match(expression, cases); } - _ => () + _ => (), } // Capture any leftover statements. @@ -155,7 +189,7 @@ impl<'a> FlowLogic for Shell<'a> { let _ = writeln!(stderr, "{}", why); self.flow_control.level = 0; self.flow_control.current_if_mode = 0; - return + return; } } } @@ -170,16 +204,18 @@ impl<'a> FlowLogic for Shell<'a> { // ```ignore // matches("foo", "bar") // ``` - fn matches(lhs : &Array, rhs : &Array) -> bool { + fn matches(lhs: &Array, rhs: &Array) -> bool { for v in lhs { - if rhs.contains(&v) { return true; } + if rhs.contains(&v) { + return true; + } } return false; } let value = expand_string(&expression, self, false); let mut condition = Condition::NoOp; for case in cases { - let pattern = case.value.map(|v| { expand_string(&v, self, false) }); + let pattern = case.value.map(|v| expand_string(&v, self, false)); match pattern { None => { condition = self.execute_statements(case.statements); @@ -202,64 +238,93 @@ impl<'a> FlowLogic for Shell<'a> { Statement::Error(number) => self.previous_status = number, Statement::Let { expression } => { self.previous_status = self.local(expression); - }, + } Statement::Export(expression) => { self.previous_status = self.export(expression); } - Statement::While { expression, mut statements } => { + Statement::While { + expression, + mut statements, + } => { self.flow_control.level += 1; collect_loops(&mut iterator, &mut statements, &mut self.flow_control.level); if let Condition::SigInt = self.execute_while(expression, statements) { return Condition::SigInt; } - }, - Statement::For { variable, values, mut statements } => { + } + Statement::For { + variable, + values, + mut statements, + } => { self.flow_control.level += 1; collect_loops(&mut iterator, &mut statements, &mut self.flow_control.level); if let Condition::SigInt = self.execute_for(&variable, &values, statements) { return Condition::SigInt; } - }, - Statement::If { expression, mut success, mut else_if, mut failure } => { + } + Statement::If { + expression, + mut success, + mut else_if, + mut failure, + } => { self.flow_control.level += 1; - if let Err(why) = collect_if(&mut iterator, &mut success, &mut else_if, - &mut failure, &mut self.flow_control.level, 0) + if let Err(why) = collect_if( + &mut iterator, + &mut success, + &mut else_if, + &mut failure, + &mut self.flow_control.level, + 0, + ) { let stderr = io::stderr(); let mut stderr = stderr.lock(); let _ = writeln!(stderr, "{}", why); self.flow_control.level = 0; self.flow_control.current_if_mode = 0; - return Condition::Break + return Condition::Break; } match self.execute_if(expression, success, else_if, failure) { - Condition::Break => return Condition::Break, + Condition::Break => return Condition::Break, Condition::Continue => return Condition::Continue, - Condition::NoOp => (), - Condition::SigInt => return Condition::SigInt, + Condition::NoOp => (), + Condition::SigInt => return Condition::SigInt, } - }, - Statement::Function { name, args, mut statements, description } => { + } + Statement::Function { + name, + args, + mut statements, + description, + } => { self.flow_control.level += 1; collect_loops(&mut iterator, &mut statements, &mut self.flow_control.level); - self.functions.insert(name.clone(), Function { - description: description, - name: name, - args: args, - statements: statements - }); - }, - Statement::Pipeline(mut pipeline) => { + self.functions.insert( + name.clone(), + Function { + description: description, + name: name, + args: args, + statements: statements, + }, + ); + } + Statement::Pipeline(mut pipeline) => { self.run_pipeline(&mut pipeline); if self.flags & ERR_EXIT != 0 && self.previous_status != SUCCESS { let status = self.previous_status; self.exit(status); } - }, - Statement::Break => { return Condition::Break } - Statement::Continue => { return Condition::Continue } - Statement::Match {expression, mut cases} => { + } + Statement::Break => return Condition::Break, + Statement::Continue => return Condition::Continue, + Statement::Match { + expression, + mut cases, + } => { self.flow_control.level += 1; if let Err(why) = collect_cases(&mut iterator, &mut cases, &mut self.flow_control.level) { let stderr = io::stderr(); @@ -267,13 +332,13 @@ impl<'a> FlowLogic for Shell<'a> { let _ = writeln!(stderr, "{}", why); self.flow_control.level = 0; self.flow_control.current_if_mode = 0; - return Condition::Break + return Condition::Break; } match self.execute_match(expression, cases) { - Condition::Break => return Condition::Break, + Condition::Break => return Condition::Break, Condition::Continue => return Condition::Continue, - Condition::NoOp => (), - Condition::SigInt => return Condition::SigInt, + Condition::NoOp => (), + Condition::SigInt => return Condition::SigInt, } } _ => {} @@ -291,74 +356,65 @@ impl<'a> FlowLogic for Shell<'a> { Condition::NoOp } - fn execute_while ( - &mut self, - expression: Pipeline, - statements: Vec<Statement> - ) -> Condition { + fn execute_while(&mut self, expression: Pipeline, statements: Vec<Statement>) -> Condition { while self.run_pipeline(&mut expression.clone()) == Some(SUCCESS) { // Cloning is needed so the statement can be re-iterated again if needed. match self.execute_statements(statements.clone()) { - Condition::Break => break, + Condition::Break => break, Condition::SigInt => return Condition::SigInt, - _ => () + _ => (), } } Condition::NoOp } - fn execute_for ( - &mut self, - variable: &str, - values: &[String], - statements: Vec<Statement> - ) -> Condition { + fn execute_for(&mut self, variable: &str, values: &[String], statements: Vec<Statement>) -> Condition { let ignore_variable = variable == "_"; match ForExpression::new(values, self) { ForExpression::Multiple(ref values) if ignore_variable => { for _ in values.iter() { match self.execute_statements(statements.clone()) { - Condition::Break => break, + Condition::Break => break, Condition::SigInt => return Condition::SigInt, - _ => () + _ => (), } } - }, + } ForExpression::Multiple(values) => { for value in values.iter() { self.variables.set_var(variable, &value); match self.execute_statements(statements.clone()) { - Condition::Break => break, + Condition::Break => break, Condition::SigInt => return Condition::SigInt, - _ => () + _ => (), } } - }, + } ForExpression::Normal(ref values) if ignore_variable => { for _ in values.lines() { match self.execute_statements(statements.clone()) { - Condition::Break => break, + Condition::Break => break, Condition::SigInt => return Condition::SigInt, - _ => () + _ => (), } } - }, + } ForExpression::Normal(values) => { for value in values.lines() { self.variables.set_var(variable, &value); match self.execute_statements(statements.clone()) { - Condition::Break => break, + Condition::Break => break, Condition::SigInt => return Condition::SigInt, - _ => () + _ => (), } } - }, + } ForExpression::Range(start, end) if ignore_variable => { for _ in start..end { match self.execute_statements(statements.clone()) { - Condition::Break => break, + Condition::Break => break, Condition::SigInt => return Condition::SigInt, - _ => () + _ => (), } } } @@ -366,9 +422,9 @@ impl<'a> FlowLogic for Shell<'a> { for value in (start..end).map(|x| x.to_string()) { self.variables.set_var(variable, &value); match self.execute_statements(statements.clone()) { - Condition::Break => break, + Condition::Break => break, Condition::SigInt => return Condition::SigInt, - _ => () + _ => (), } } } @@ -376,12 +432,16 @@ impl<'a> FlowLogic for Shell<'a> { Condition::NoOp } - fn execute_if(&mut self, mut expression: Pipeline, success: Vec<Statement>, - else_if: Vec<ElseIf>, failure: Vec<Statement>) -> Condition - { + fn execute_if( + &mut self, + mut expression: Pipeline, + success: Vec<Statement>, + else_if: Vec<ElseIf>, + failure: Vec<Statement>, + ) -> Condition { match self.run_pipeline(&mut expression) { Some(SUCCESS) => self.execute_statements(success), - _ => { + _ => { for mut elseif in else_if { if self.run_pipeline(&mut elseif.expression) == Some(SUCCESS) { return self.execute_statements(elseif.success); @@ -400,13 +460,16 @@ impl<'a> FlowLogic for Shell<'a> { // Execute a Let Statement Statement::Let { expression } => { self.previous_status = self.local(expression); - }, + } Statement::Export(expression) => { - self.previous_status = self.export(expression); + self.previous_status = self.export(expression); } // Collect the statements for the while loop, and if the loop is complete, // execute the while loop with the provided expression. - Statement::While { expression, mut statements } => { + Statement::While { + expression, + mut statements, + } => { self.flow_control.level += 1; // Collect all of the statements contained within the while block. @@ -422,10 +485,14 @@ impl<'a> FlowLogic for Shell<'a> { statements: statements, } } - }, + } // Collect the statements for the for loop, and if the loop is complete, // execute the for loop with the provided expression. - Statement::For { variable, values, mut statements } => { + Statement::For { + variable, + values, + mut statements, + } => { self.flow_control.level += 1; // Collect all of the statements contained within the for block. @@ -437,22 +504,33 @@ impl<'a> FlowLogic for Shell<'a> { } else { // Store the partial `Statement::For` to memory self.flow_control.current_statement = Statement::For { - variable: variable, - values: values, + variable: variable, + values: values, statements: statements, } } - }, + } // Collect the statements needed for the `success`, `else_if`, and `failure` // conditions; then execute the if statement if it is complete. - Statement::If { expression, mut success, mut else_if, mut failure } => { + Statement::If { + expression, + mut success, + mut else_if, + mut failure, + } => { self.flow_control.level += 1; // Collect all of the success and failure statements within the if condition. // The `mode` value will let us know whether the collector ended while // collecting the success block or the failure block. - let mode = collect_if(iterator, &mut success, &mut else_if, - &mut failure, &mut self.flow_control.level, 0)?; + let mode = collect_if( + iterator, + &mut success, + &mut else_if, + &mut failure, + &mut self.flow_control.level, + 0, + )?; if self.flow_control.level == 0 { // All blocks were read, thus we can immediately execute now @@ -462,15 +540,20 @@ impl<'a> FlowLogic for Shell<'a> { self.flow_control.current_if_mode = mode; self.flow_control.current_statement = Statement::If { expression: expression, - success: success, - else_if: else_if, - failure: failure + success: success, + else_if: else_if, + failure: failure, }; } - }, + } // Collect the statements needed by the function and add the function to the // list of functions if it is complete. - Statement::Function { name, args, mut statements, description } => { + Statement::Function { + name, + args, + mut statements, + description, + } => { self.flow_control.level += 1; // The same logic that applies to loops, also applies here. @@ -478,44 +561,51 @@ impl<'a> FlowLogic for Shell<'a> { if self.flow_control.level == 0 { // All blocks were read, thus we can add it to the list - self.functions.insert(name.clone(), Function { - description: description, - name: name, - args: args, - statements: statements - }); + self.functions.insert( + name.clone(), + Function { + description: description, + name: name, + args: args, + statements: statements, + }, + ); } else { // Store the partial function declaration in memory. self.flow_control.current_statement = Statement::Function { description: description, - name: name, - args: args, - statements: statements + name: name, + args: args, + statements: statements, } } - }, + } // Simply executes a provided pipeline, immediately. - Statement::Pipeline(mut pipeline) => { + Statement::Pipeline(mut pipeline) => { self.run_pipeline(&mut pipeline); if self.flags & ERR_EXIT != 0 && self.previous_status != SUCCESS { let status = self.previous_status; self.exit(status); } - }, + } // At this level, else and else if keywords are forbidden. - Statement::ElseIf{..} | Statement::Else => { + Statement::ElseIf { .. } | + Statement::Else => { let stderr = io::stderr(); let mut stderr = stderr.lock(); let _ = writeln!(stderr, "ion: syntax error: not an if statement"); - }, + } // Likewise to else and else if, the end keyword does nothing here. Statement::End => { let stderr = io::stderr(); let mut stderr = stderr.lock(); let _ = writeln!(stderr, "ion: syntax error: no block to end"); - }, + } // Collect all cases that are being used by a match construct - Statement::Match {expression, mut cases} => { + Statement::Match { + expression, + mut cases, + } => { self.flow_control.level += 1; if let Err(why) = collect_cases(iterator, &mut cases, &mut self.flow_control.level) { let stderr = io::stderr(); @@ -527,7 +617,7 @@ impl<'a> FlowLogic for Shell<'a> { self.execute_match(expression, cases); } else { // Store the partial function declaration in memory. - self.flow_control.current_statement = Statement::Match {expression, cases}; + self.flow_control.current_statement = Statement::Match { expression, cases }; } } _ => {} diff --git a/src/shell/flow_control.rs b/src/shell/flow_control.rs index ee904a159fc967ab2d893f12384fcd7490fe221f..5ec669436252ea389f6e8c938455750883f524f7 100644 --- a/src/shell/flow_control.rs +++ b/src/shell/flow_control.rs @@ -1,22 +1,30 @@ -use types::Identifier; -use parser::pipelines::Pipeline; -use parser::assignments::Binding; + use super::Shell; use super::flow::FlowLogic; -use types::*; use fnv::*; +use parser::assignments::Binding; +use parser::pipelines::Pipeline; +use types::*; +use types::Identifier; #[derive(Debug, PartialEq, Clone)] pub struct ElseIf { pub expression: Pipeline, - pub success: Vec<Statement> + pub success: Vec<Statement>, } #[derive(Debug, PartialEq, Clone, Copy)] -pub enum Type { Float, Int, Bool } +pub enum Type { + Float, + Int, + Bool, +} #[derive(Debug, PartialEq, Clone)] -pub enum FunctionArgument { Typed(String, Type), Untyped(String) } +pub enum FunctionArgument { + Typed(String, Type), + Untyped(String), +} /// Represents a single branch in a match statement. For example, in the expression @@ -42,41 +50,39 @@ pub enum FunctionArgument { Typed(String, Type), Untyped(String) } #[derive(Debug, PartialEq, Clone)] pub struct Case { pub value: Option<String>, - pub statements: Vec<Statement> + pub statements: Vec<Statement>, } #[derive(Debug, PartialEq, Clone)] pub enum Statement { - Let { - expression: Binding, - }, + Let { expression: Binding }, Case(Case), Export(Binding), If { expression: Pipeline, success: Vec<Statement>, else_if: Vec<ElseIf>, - failure: Vec<Statement> + failure: Vec<Statement>, }, ElseIf(ElseIf), Function { name: Identifier, description: String, args: Vec<FunctionArgument>, - statements: Vec<Statement> + statements: Vec<Statement>, }, For { variable: Identifier, values: Vec<String>, - statements: Vec<Statement> + statements: Vec<Statement>, }, While { expression: Pipeline, - statements: Vec<Statement> + statements: Vec<Statement>, }, Match { expression: String, - cases : Vec<Case> + cases: Vec<Case>, }, Else, End, @@ -84,11 +90,10 @@ pub enum Statement { Break, Continue, Pipeline(Pipeline), - Default + Default, } impl Statement { - pub fn short(&self) -> &'static str { match *self { Statement::Let { .. } => "Let { .. }", @@ -106,25 +111,24 @@ impl Statement { Statement::Break => "Break", Statement::Continue => "Continue", Statement::Pipeline(_) => "Pipeline { .. }", - Statement::Default => "Default" + Statement::Default => "Default", } } - } pub struct FlowControl { - pub level: usize, + pub level: usize, pub current_statement: Statement, - pub current_if_mode: u8 // { 0 = SUCCESS; 1 = FAILURE } + pub current_if_mode: u8, // { 0 = SUCCESS; 1 = FAILURE } } impl Default for FlowControl { fn default() -> FlowControl { FlowControl { - level: 0, + level: 0, current_statement: Statement::Default, - current_if_mode: 0, + current_if_mode: 0, } } } @@ -134,7 +138,7 @@ pub struct Function { pub description: String, pub name: Identifier, pub args: Vec<FunctionArgument>, - pub statements: Vec<Statement> + pub statements: Vec<Statement>, } pub enum FunctionError { @@ -149,9 +153,7 @@ impl Function { } let mut variables_backup: FnvHashMap<&str, Option<Value>> = - FnvHashMap::with_capacity_and_hasher ( - 64, Default::default() - ); + FnvHashMap::with_capacity_and_hasher(64, Default::default()); let mut bad_argument: Option<(&str, Type)> = None; for (name_arg, value) in self.args.iter().zip(args.iter().skip(1)) { @@ -163,11 +165,11 @@ impl Function { Type::Bool if *value == "true" || *value == "false" => name.as_str(), _ => { bad_argument = Some((value, *type_)); - break + break; } } - }, - &FunctionArgument::Untyped(ref name) => name.as_str() + } + &FunctionArgument::Untyped(ref name) => name.as_str(), }; variables_backup.insert(name, shell.variables.get_var(name)); shell.variables.set_var(name, value); @@ -178,7 +180,9 @@ impl Function { for (name, value_option) in &variables_backup { match *value_option { Some(ref value) => shell.variables.set_var(name, value), - None => {shell.variables.unset_var(name);}, + None => { + shell.variables.unset_var(name); + } } } @@ -190,7 +194,9 @@ impl Function { for (name, value_option) in &variables_backup { match *value_option { Some(ref value) => shell.variables.set_var(name, value), - None => {shell.variables.unset_var(name);}, + None => { + shell.variables.unset_var(name); + } } } Ok(()) @@ -200,7 +206,7 @@ impl Function { } pub fn collect_cases<I>(iterator: &mut I, cases: &mut Vec<Case>, level: &mut usize) -> Result<(), String> - where I : Iterator<Item=Statement> + where I: Iterator<Item = Statement> { macro_rules! add_to_case { @@ -226,7 +232,7 @@ pub fn collect_cases<I>(iterator: &mut I, cases: &mut Vec<Case>, level: &mut usi // This is just part of the current case block add_to_case!(Statement::Case(case)); } - }, + } Statement::End => { *level -= 1; if *level == 0 { @@ -240,7 +246,7 @@ pub fn collect_cases<I>(iterator: &mut I, cases: &mut Vec<Case>, level: &mut usi Statement::Function { .. } => { *level += 1; add_to_case!(statement); - }, + } Statement::Default | Statement::Else | Statement::ElseIf { .. } | @@ -252,23 +258,29 @@ pub fn collect_cases<I>(iterator: &mut I, cases: &mut Vec<Case>, level: &mut usi Statement::Break => { // This is the default case with all of the other statements explicitly listed add_to_case!(statement); - }, + } } } return Ok(()); } -pub fn collect_loops <I: Iterator<Item = Statement>> ( +pub fn collect_loops<I: Iterator<Item = Statement>>( iterator: &mut I, statements: &mut Vec<Statement>, - level: &mut usize + level: &mut usize, ) { #[allow(while_let_on_iterator)] while let Some(statement) = iterator.next() { match statement { - Statement::While{..} | Statement::For{..} | Statement::If{..} | - Statement::Function{..} | Statement::Match{..} => *level += 1, - Statement::End if *level == 1 => { *level = 0; break }, + Statement::While { .. } | + Statement::For { .. } | + Statement::If { .. } | + Statement::Function { .. } | + Statement::Match { .. } => *level += 1, + Statement::End if *level == 1 => { + *level = 0; + break; + } Statement::End => *level -= 1, _ => (), } @@ -276,33 +288,44 @@ pub fn collect_loops <I: Iterator<Item = Statement>> ( } } -pub fn collect_if<I>(iterator: &mut I, success: &mut Vec<Statement>, else_if: &mut Vec<ElseIf>, - failure: &mut Vec<Statement>, level: &mut usize, mut current_block: u8) - -> Result<u8, &'static str> +pub fn collect_if<I>( + iterator: &mut I, + success: &mut Vec<Statement>, + else_if: &mut Vec<ElseIf>, + failure: &mut Vec<Statement>, + level: &mut usize, + mut current_block: u8, +) -> Result<u8, &'static str> where I: Iterator<Item = Statement> { #[allow(while_let_on_iterator)] while let Some(statement) = iterator.next() { match statement { - Statement::While{..} | Statement::For{..} | Statement::If{..} | - Statement::Function{..} | Statement::Match{..} => *level += 1, + Statement::While { .. } | + Statement::For { .. } | + Statement::If { .. } | + Statement::Function { .. } | + Statement::Match { .. } => *level += 1, Statement::ElseIf(ref elseif) if *level == 1 => { if current_block == 1 { return Err("ion: syntax error: else block already given"); } else { current_block = 2; else_if.push(elseif.clone()); - continue + continue; } } Statement::Else if *level == 1 => { current_block = 1; - continue - }, + continue; + } Statement::Else if *level == 1 && current_block == 1 => { return Err("ion: syntax error: else block already given"); } - Statement::End if *level == 1 => { *level = 0; break }, + Statement::End if *level == 1 => { + *level = 0; + break; + } Statement::End => *level -= 1, _ => (), } @@ -314,7 +337,7 @@ pub fn collect_if<I>(iterator: &mut I, success: &mut Vec<Statement>, else_if: &m let mut last = else_if.last_mut().unwrap(); // This is a bug if there isn't a value last.success.push(statement); } - _ => unreachable!() + _ => unreachable!(), } } diff --git a/src/shell/history.rs b/src/shell/history.rs index 01926dac1f8709fa998143dc4331abb78353db58..abb2c6f647391bf58df78b82b9ea2da7fc09c744 100644 --- a/src/shell/history.rs +++ b/src/shell/history.rs @@ -1,6 +1,7 @@ -use std::io::{self, Write}; -use super::status::*; + use super::Shell; +use super::status::*; +use std::io::{self, Write}; /// Contains all history-related functionality for the `Shell`. pub trait ShellHistory { @@ -23,7 +24,7 @@ pub trait ShellHistory { impl<'a> ShellHistory for Shell<'a> { fn print_history(&self, _arguments: &[&str]) -> i32 { if let Some(context) = self.context.as_ref() { - let mut buffer = Vec::with_capacity(8*1024); + let mut buffer = Vec::with_capacity(8 * 1024); for command in &context.history.buffers { let _ = writeln!(buffer, "{}", command); } diff --git a/src/shell/job.rs b/src/shell/job.rs index f32dbf1fd439f8dedde19676276358ff122f83fe..f4ab72e10e53522fee3680e10369c4c4b312770d 100644 --- a/src/shell/job.rs +++ b/src/shell/job.rs @@ -1,15 +1,22 @@ use std::fs::File; -use std::process::{Command, Stdio}; use std::os::unix::io::{FromRawFd, IntoRawFd}; +use std::process::{Command, Stdio}; //use glob::glob; -use parser::{expand_string, Expander}; + +use parser::{Expander, expand_string}; use parser::pipelines::RedirectFrom; use smallstring::SmallString; use types::*; #[derive(Debug, PartialEq, Clone, Copy)] -pub enum JobKind { And, Background, Last, Or, Pipe(RedirectFrom) } +pub enum JobKind { + And, + Background, + Last, + Or, + Pipe(RedirectFrom), +} #[derive(Debug, PartialEq, Clone)] pub struct Job { @@ -21,7 +28,11 @@ pub struct Job { impl Job { pub fn new(args: Array, kind: JobKind) -> Self { let command = SmallString::from_str(&args[0]); - Job { command, args, kind } + Job { + command, + args, + kind, + } } /// Takes the current job's arguments and expands them, one argument at a @@ -31,15 +42,10 @@ impl Job { expanded.grow(self.args.len()); expanded.extend(self.args.drain().flat_map(|arg| { let res = expand_string(&arg, expanders, false); - if res.is_empty() { - array![""] - } else { - res - } + if res.is_empty() { array![""] } else { res } })); self.args = expanded; } - } /// This represents a job that has been processed and expanded to be run @@ -72,7 +78,7 @@ pub enum RefinedJob { stdout: Option<File>, /// A file corresponding to the standard error for this builtin stderr: Option<File>, - } + }, } macro_rules! set_field { @@ -97,7 +103,7 @@ impl RefinedJob { args, stdin: None, stdout: None, - stderr: None + stderr: None, } } @@ -107,7 +113,7 @@ impl RefinedJob { args, stdin: None, stdout: None, - stderr: None + stderr: None, } } @@ -128,11 +134,14 @@ impl RefinedJob { pub fn short(&self) -> String { match *self { RefinedJob::External(ref cmd) => { - format!("{:?}", cmd).split('"').nth(1).unwrap_or("").to_string() - }, - RefinedJob::Builtin { ref name, .. } | RefinedJob::Function { ref name, .. } => { - name.to_string() + format!("{:?}", cmd) + .split('"') + .nth(1) + .unwrap_or("") + .to_string() } + RefinedJob::Builtin { ref name, .. } | + RefinedJob::Function { ref name, .. } => name.to_string(), } } @@ -143,23 +152,21 @@ impl RefinedJob { let command = format!("{:?}", cmd); let mut arg_iter = command.split_whitespace(); let command = arg_iter.next().unwrap(); - let mut output = String::from(&command[1..command.len()-1]); + let mut output = String::from(&command[1..command.len() - 1]); for argument in arg_iter { output.push(' '); if argument.len() > 2 { - output.push_str(&argument[1..argument.len()-1]); + output.push_str(&argument[1..argument.len() - 1]); } else { output.push_str(&argument); } } output - }, - RefinedJob::Builtin { ref args, .. } | RefinedJob::Function { ref args, .. } => { - format!("{}", args.join(" ")) } + RefinedJob::Builtin { ref args, .. } | + RefinedJob::Function { ref args, .. } => format!("{}", args.join(" ")), } } - } #[cfg(test)] diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 636431f8f9fdefd6ac5aec207aa431a4843fb905..bc74fa39c00a4fbf6d5fb320bdbc2f668ebe2424 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -7,40 +7,40 @@ mod job; mod pipe_exec; pub mod directory_stack; pub mod flags; - +pub mod plugins; pub mod flow_control; pub mod signals; pub mod status; pub mod variables; -pub use self::pipe_exec::{foreground, job_control}; +pub use self::binary::Binary; +pub use self::flow::FlowLogic; pub use self::history::ShellHistory; pub use self::job::{Job, JobKind}; -pub use self::flow::FlowLogic; -pub use self::binary::Binary; +pub use self::pipe_exec::{foreground, job_control}; -use app_dirs::{AppDataType, AppInfo, app_root}; -use builtins::*; -use fnv::FnvHashMap; -use liner::Context; -use parser::{Expander, ArgumentSplitter, Select}; -use parser::pipelines::Pipeline; use self::directory_stack::DirectoryStack; use self::flags::*; use self::flow_control::{FlowControl, Function, FunctionError, Type}; use self::foreground::ForegroundSignals; -use self::job_control::{JobControl, BackgroundProcess}; +use self::job_control::{BackgroundProcess, JobControl}; use self::pipe_exec::PipelineExecution; use self::status::*; use self::variables::Variables; +use app_dirs::{AppDataType, AppInfo, app_root}; +use builtins::*; +use fnv::FnvHashMap; +use liner::Context; +use parser::{ArgumentSplitter, Expander, Select}; +use parser::pipelines::Pipeline; use smallvec::SmallVec; use std::env; use std::fs::File; use std::io::{self, Write}; use std::ops::Deref; use std::process; -use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; +use std::sync::atomic::Ordering; use std::time::SystemTime; use types::*; @@ -75,14 +75,12 @@ pub struct Shell<'a> { /// Set when a signal is received, this will tell the flow control logic to abort. pub break_flow: bool, /// When the `fg` command is run, this will be used to communicate with the specified background process. - pub foreground_signals: Arc<ForegroundSignals> + pub foreground_signals: Arc<ForegroundSignals>, } impl<'a> Shell<'a> { /// Panics if DirectoryStack construction fails - pub fn new ( - builtins: &'a FnvHashMap<&'static str, Builtin> - ) -> Shell<'a> { + pub fn new(builtins: &'a FnvHashMap<&'static str, Builtin>) -> Shell<'a> { Shell { builtins: builtins, context: None, @@ -97,7 +95,7 @@ impl<'a> Shell<'a> { background: Arc::new(Mutex::new(Vec::new())), is_background_shell: false, break_flow: false, - foreground_signals: Arc::new(ForegroundSignals::new()) + foreground_signals: Arc::new(ForegroundSignals::new()), } } @@ -124,20 +122,29 @@ impl<'a> Shell<'a> { fn update_variables(&mut self) { // Update the PWD (Present Working Directory) variable if the current working directory has // been updated. - env::current_dir().ok().map_or_else(|| env::set_var("PWD", "?"), |path| { - let pwd = self.variables.get_var_or_empty("PWD"); - let pwd: &str = &pwd; - let current_dir = path.to_str().unwrap_or("?"); - if pwd != current_dir { - env::set_var("OLDPWD", pwd); - env::set_var("PWD", current_dir); - } - }) + env::current_dir().ok().map_or_else( + || env::set_var("PWD", "?"), + |path| { + let pwd = self.variables.get_var_or_empty("PWD"); + let pwd: &str = &pwd; + let current_dir = path.to_str().unwrap_or("?"); + if pwd != current_dir { + env::set_var("OLDPWD", pwd); + env::set_var("PWD", current_dir); + } + }, + ) } /// Evaluates the source init file in the user's home directory. pub fn evaluate_init_file(&mut self) { - match app_root(AppDataType::UserConfig, &AppInfo{ name: "ion", author: "Redox OS Developers" }) { + match app_root( + AppDataType::UserConfig, + &AppInfo { + name: "ion", + author: "Redox OS Developers", + }, + ) { Ok(mut initrc) => { initrc.push("initrc"); if initrc.exists() { @@ -148,7 +155,7 @@ impl<'a> Shell<'a> { eprintln!("ion: could not create initrc file: {}", why); } } - }, + } Err(why) => { eprintln!("ion: unable to get config root: {}", why); } @@ -167,8 +174,10 @@ impl<'a> Shell<'a> { if let Some(alias) = { let key: &str = pipeline.jobs[job_no].command.as_ref(); self.variables.aliases.get(key) - } { - let new_args = ArgumentSplitter::new(alias).map(String::from) + } + { + let new_args = ArgumentSplitter::new(alias) + .map(String::from) .chain(pipeline.jobs[job_no].args.drain().skip(1)) .collect::<SmallVec<[String; 4]>>(); pipeline.jobs[job_no].command = new_args[0].clone().into(); @@ -181,14 +190,15 @@ impl<'a> Shell<'a> { let exit_status = if let Some(command) = { let key: &str = pipeline.jobs[0].command.as_ref(); builtins.get(key) - } { + } + { // Run the 'main' of the command and set exit_status if !pipeline.requires_piping() { - if self.flags & PRINT_COMMS != 0 { eprintln!("> {}", pipeline.to_string()); } + if self.flags & PRINT_COMMS != 0 { + eprintln!("> {}", pipeline.to_string()); + } let borrowed = &pipeline.jobs[0].args; - let small: SmallVec<[&str; 4]> = borrowed.iter() - .map(|x| x as &str) - .collect(); + let small: SmallVec<[&str; 4]> = borrowed.iter().map(|x| x as &str).collect(); Some((command.main)(&small, self)) } else { Some(self.execute_pipeline(pipeline)) @@ -203,15 +213,19 @@ impl<'a> Shell<'a> { Err(FunctionError::InvalidArgumentCount) => { eprintln!("ion: invalid number of function arguments supplied"); Some(FAILURE) - }, + } Err(FunctionError::InvalidArgumentType(expected_type, value)) => { let type_ = match expected_type { Type::Float => "Float", - Type::Int => "Int", - Type::Bool => "Bool" + Type::Int => "Int", + Type::Bool => "Bool", }; - eprintln!("ion: function argument has invalid type: expected {}, found value \'{}\'", type_, value); + eprintln!( + "ion: function argument has invalid type: expected {}, found value \'{}\'", + type_, + value + ); Some(FAILURE) } } @@ -228,8 +242,11 @@ impl<'a> Shell<'a> { if let Some(context) = self.context.as_mut() { if "1" == self.variables.get_var_or_empty("RECORD_SUMMARY") { if let Ok(elapsed_time) = command_start_time.elapsed() { - let summary = format!("#summary# elapsed real time: {}.{:09} seconds", - elapsed_time.as_secs(), elapsed_time.subsec_nanos()); + let summary = format!( + "#summary# elapsed real time: {}.{:09} seconds", + elapsed_time.as_secs(), + elapsed_time.subsec_nanos() + ); context.history.push(summary.into()).unwrap_or_else(|err| { let stderr = io::stderr(); let mut stderr = stderr.lock(); @@ -246,14 +263,10 @@ impl<'a> Shell<'a> { } exit_status } - - } impl<'a> Expander for Shell<'a> { fn tilde(&self, input: &str) -> Option<String> { - /// XXX: This is a silly implementation: the `Variables` struct - /// should not know nor be responsible for expanding tildes self.variables.tilde_expansion(input, &self.directory_stack) } @@ -261,54 +274,51 @@ impl<'a> Expander for Shell<'a> { fn array(&self, array: &str, selection: Select) -> Option<Array> { use std::iter::FromIterator; let mut found = match self.variables.get_array(array) { - Some(array) => match selection { - Select::None => None, - Select::All => Some(array.clone()), - Select::Index(id) => { - id.resolve(array.len()) - .and_then(|n| array.get(n)) - .map(|x| Array::from_iter(Some(x.to_owned()))) - }, - Select::Range(range) => { - if let Some((start, length)) = range.bounds(array.len()) { - let array = array.iter() - .skip(start) - .take(length) - .map(|x| x.to_owned()) - .collect::<Array>(); - if array.is_empty() { - None + Some(array) => { + match selection { + Select::None => None, + Select::All => Some(array.clone()), + Select::Index(id) => { + id.resolve(array.len()).and_then(|n| array.get(n)).map( + |x| { + Array::from_iter(Some(x.to_owned())) + }, + ) + } + Select::Range(range) => { + if let Some((start, length)) = range.bounds(array.len()) { + let array = array + .iter() + .skip(start) + .take(length) + .map(|x| x.to_owned()) + .collect::<Array>(); + if array.is_empty() { None } else { Some(array) } } else { - Some(array) + None } - } else { - None } - }, - Select::Key(_) => { - None + Select::Key(_) => None, } - }, - None => None + } + None => None, }; if found.is_none() { found = match self.variables.get_map(array) { - Some(map) => match selection { - Select::All => { - let mut arr = Array::new(); - for (_, value) in map { - arr.push(value.clone()); + Some(map) => { + match selection { + Select::All => { + let mut arr = Array::new(); + for (_, value) in map { + arr.push(value.clone()); + } + Some(arr) } - Some(arr) + Select::Key(ref key) => Some(array![map.get(key.get()).unwrap_or(&"".into()).clone()]), + _ => None, } - Select::Key(ref key) => { - Some(array![ - map.get(key.get()).unwrap_or(&"".into()).clone() - ]) - }, - _ => None - }, - None => None + } + None => None, } } found @@ -319,14 +329,13 @@ impl<'a> Expander for Shell<'a> { if quoted { self.variables.get_var(variable) } else { - self.variables.get_var(variable) - .map(|x| x.ascii_replace('\n', ' ').into()) + self.variables.get_var(variable).map(|x| { + x.ascii_replace('\n', ' ').into() + }) } } /// Expand a subshell expression fn command(&self, command: &str) -> Option<Value> { - /// XXX: This is a silly implementation: the `Variables` struct - /// should not know nor be responsible for expanding a subshell self.variables.command_expansion(command) } } diff --git a/src/shell/pipe_exec/foreground.rs b/src/shell/pipe_exec/foreground.rs index 82f888d7c507f852c32e448746f8cf7f842ec5ca..63f0f7f068b581acce2f012f0f5bbf0a7b7631e0 100644 --- a/src/shell/pipe_exec/foreground.rs +++ b/src/shell/pipe_exec/foreground.rs @@ -12,18 +12,18 @@ pub enum BackgroundResult { /// structure to notify a background thread that it needs to wait for and return /// the exit status back to the `fg` function. pub struct ForegroundSignals { - grab: AtomicUsize, // TODO: Use AtomicU32 when stable - status: AtomicUsize, // TODO: Use AtomicU8 when stable - reply: AtomicBool, + grab: AtomicUsize, // TODO: Use AtomicU32 when stable + status: AtomicUsize, // TODO: Use AtomicU8 when stable + reply: AtomicBool, errored: AtomicBool, // TODO: Combine with reply when U8 is stable } impl ForegroundSignals { pub fn new() -> ForegroundSignals { ForegroundSignals { - grab: AtomicUsize::new(0), - status: AtomicUsize::new(0), - reply: AtomicBool::new(false), + grab: AtomicUsize::new(0), + status: AtomicUsize::new(0), + reply: AtomicBool::new(false), errored: AtomicBool::new(false), } } diff --git a/src/shell/pipe_exec/job_control.rs b/src/shell/pipe_exec/job_control.rs index e9b815ce0686f87e3cccb8c69e17bf2ce4286539..417337b862fd9e000f427bad23527cb8aede4051 100644 --- a/src/shell/pipe_exec/job_control.rs +++ b/src/shell/pipe_exec/job_control.rs @@ -59,26 +59,25 @@ pub fn add_to_background( command: String, ) -> u32 { let mut processes = processes.lock().unwrap(); - match (*processes) - .iter() - .position(|x| x.state == ProcessState::Empty) - { + match (*processes).iter().position( + |x| x.state == ProcessState::Empty, + ) { Some(id) => { (*processes)[id] = BackgroundProcess { - pid: pid, + pid: pid, ignore_sighup: false, - state: state, - name: command, + state: state, + name: command, }; id as u32 } None => { let njobs = (*processes).len(); (*processes).push(BackgroundProcess { - pid: pid, + pid: pid, ignore_sighup: false, - state: state, - name: command, + state: state, + name: command, }); njobs as u32 } @@ -91,10 +90,10 @@ pub fn add_to_background( /// as the process ID, state that the process is in, and the command that the /// process is executing. pub struct BackgroundProcess { - pub pid: u32, + pub pid: u32, pub ignore_sighup: bool, - pub state: ProcessState, - pub name: String, + pub state: ProcessState, + pub name: String, } impl<'a> JobControl for Shell<'a> { diff --git a/src/shell/pipe_exec/mod.rs b/src/shell/pipe_exec/mod.rs index aa7d4126789d4873f352a7431ec6fb4a1eb2dd24..1f776b09c3eaf0bda995dcbdb61a196d81bc6e6c 100644 --- a/src/shell/pipe_exec/mod.rs +++ b/src/shell/pipe_exec/mod.rs @@ -11,18 +11,18 @@ use self::fork::{create_process_group, fork_pipe}; use self::job_control::JobControl; use super::{JobKind, Shell}; use super::flags::*; +use super::flow_control::{FunctionError, Type}; use super::job::RefinedJob; use super::signals::{self, SignalHandler}; use super::status::*; -use super::flow_control::{FunctionError, Type}; -use parser::pipelines::{Input, Pipeline, Redirection, RedirectFrom}; +use parser::pipelines::{Input, Pipeline, RedirectFrom, Redirection}; use std::fs::{File, OpenOptions}; use std::io::{self, Error, Write}; use std::iter; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::process::CommandExt; use std::path::Path; -use std::process::{exit, Command}; +use std::process::{Command, exit}; use sys; /// Use dup2 to replace `old` with `new` using `old`s file descriptor ID @@ -73,8 +73,7 @@ fn gen_background_string(pipeline: &Pipeline, print_comm: bool) -> Option<String /// directory path. #[inline(always)] fn is_implicit_cd(argument: &str) -> bool { - (argument.starts_with('.') || argument.starts_with('/') || argument.ends_with('/')) && - Path::new(argument).is_dir() + (argument.starts_with('.') || argument.starts_with('/') || argument.ends_with('/')) && Path::new(argument).is_dir() } /// This function is to be executed when a stdin value is supplied to a pipeline job. @@ -84,29 +83,33 @@ fn is_implicit_cd(argument: &str) -> bool { /// the input error occurred. fn redirect_input(mut input: Input, piped_commands: &mut Vec<(RefinedJob, JobKind)>) -> bool { match input { - Input::File(ref filename) => if let Some(command) = piped_commands.first_mut() { - match File::open(filename) { - Ok(file) => command.0.stdin(file), - Err(e) => { - eprintln!("ion: failed to redirect '{}' into stdin: {}", filename, e); - return true; - }, - } - }, - Input::HereString(ref mut string) => if let Some(command) = piped_commands.first_mut() { - if !string.ends_with('\n') { - string.push('\n'); + Input::File(ref filename) => { + if let Some(command) = piped_commands.first_mut() { + match File::open(filename) { + Ok(file) => command.0.stdin(file), + Err(e) => { + eprintln!("ion: failed to redirect '{}' into stdin: {}", filename, e); + return true; + } + } } - match unsafe { stdin_of(&string) } { - Ok(stdio) => { - command.0.stdin(unsafe { File::from_raw_fd(stdio) }); + } + Input::HereString(ref mut string) => { + if let Some(command) = piped_commands.first_mut() { + if !string.ends_with('\n') { + string.push('\n'); } - Err(e) => { - eprintln!("ion: failed to redirect herestring '{}' into stdin: {}", string, e); - return true; + match unsafe { stdin_of(&string) } { + Ok(stdio) => { + command.0.stdin(unsafe { File::from_raw_fd(stdio) }); + } + Err(e) => { + eprintln!("ion: failed to redirect herestring '{}' into stdin: {}", string, e); + return true; + } } } - }, + } } false } @@ -127,20 +130,24 @@ fn redirect_output(stdout: Redirection, piped_commands: &mut Vec<(RefinedJob, Jo File::create(&stdout.file) }; match file { - Ok(f) => match stdout.from { - RedirectFrom::Both => match f.try_clone() { - Ok(f_copy) => { - command.0.stdout(f); - command.0.stderr(f_copy); - } - Err(e) => { - eprintln!("ion: failed to redirect both stderr and stdout into file '{:?}': {}", f, e); - return true; + Ok(f) => { + match stdout.from { + RedirectFrom::Both => { + match f.try_clone() { + Ok(f_copy) => { + command.0.stdout(f); + command.0.stderr(f_copy); + } + Err(e) => { + eprintln!("ion: failed to redirect both stderr and stdout into file '{:?}': {}", f, e); + return true; + } + } } - }, - RedirectFrom::Stderr => command.0.stderr(f), - RedirectFrom::Stdout => command.0.stdout(f), - }, + RedirectFrom::Stderr => command.0.stderr(f), + RedirectFrom::Stdout => command.0.stdout(f), + } + } Err(err) => { let stderr = io::stderr(); let mut stderr = stderr.lock(); @@ -224,11 +231,15 @@ impl<'a> PipelineExecution for Shell<'a> { let mut piped_commands = self.generate_commands(pipeline); // Redirect the inputs if a custom redirect value was given. if let Some(stdin) = pipeline.stdin.take() { - if redirect_input(stdin, &mut piped_commands) { return COULD_NOT_EXEC; } + if redirect_input(stdin, &mut piped_commands) { + return COULD_NOT_EXEC; + } } // Redirect the outputs if a custom redirect value was given. if let Some(stdout) = pipeline.stdout.take() { - if redirect_output(stdout, &mut piped_commands) { return COULD_NOT_EXEC; } + if redirect_output(stdout, &mut piped_commands) { + return COULD_NOT_EXEC; + } } // If the given pipeline is a background task, fork the shell. if let Some(command_name) = possible_background_name { @@ -287,46 +298,45 @@ impl<'a> PipelineExecution for Shell<'a> { let last_pid = children[children.len() - 1]; // Watch the foreground group, dropping all commands that exit as they exit. - self.watch_foreground( - pgid, - last_pid, - move || as_string, - move |pid| if let Some(id) = children.iter().position(|&x| x as i32 == pid) { - commands.remove(id); - children.remove(id); - }, - ) + self.watch_foreground(pgid, last_pid, move || as_string, move |pid| if let Some(id) = + children.iter().position(|&x| x as i32 == pid) + { + commands.remove(id); + children.remove(id); + }) } fn exec_job(&mut self, job: &mut RefinedJob, foreground: bool) -> i32 { let short = job.short(); let long = job.long(); match *job { - RefinedJob::External(ref mut command) => match { - command - .before_exec(move || { - signals::unblock(); - create_process_group(0); - Ok(()) - }) - .spawn() - } { - Ok(child) => { - if foreground { - let _ = sys::tcsetpgrp(0, child.id()); + RefinedJob::External(ref mut command) => { + match { + command + .before_exec(move || { + signals::unblock(); + create_process_group(0); + Ok(()) + }) + .spawn() + } { + Ok(child) => { + if foreground { + let _ = sys::tcsetpgrp(0, child.id()); + } + self.watch_foreground(child.id(), child.id(), move || long, |_| ()) } - self.watch_foreground(child.id(), child.id(), move || long, |_| ()) - } - Err(e) => { - if e.kind() == io::ErrorKind::NotFound { - eprintln!("ion: command not found: {}", short); - NO_SUCH_COMMAND - } else { - eprintln!("ion: error spawning process: {}", e); - COULD_NOT_EXEC + Err(e) => { + if e.kind() == io::ErrorKind::NotFound { + eprintln!("ion: command not found: {}", short); + NO_SUCH_COMMAND + } else { + eprintln!("ion: error spawning process: {}", e); + COULD_NOT_EXEC + } } } - }, + } RefinedJob::Builtin { ref name, ref args, @@ -350,7 +360,7 @@ impl<'a> PipelineExecution for Shell<'a> { } eprintln!("ion: failed to `dup` STDOUT, STDIN, or STDERR: not running '{}'", long); COULD_NOT_EXEC - }, + } RefinedJob::Function { ref name, ref args, @@ -425,15 +435,19 @@ impl<'a> PipelineExecution for Shell<'a> { Err(FunctionError::InvalidArgumentCount) => { eprintln!("ion: invalid number of function arguments supplied"); FAILURE - }, + } Err(FunctionError::InvalidArgumentType(expected_type, value)) => { let type_ = match expected_type { Type::Float => "Float", - Type::Int => "Int", - Type::Bool => "Bool" + Type::Int => "Int", + Type::Bool => "Bool", }; - eprintln!("ion: function argument has invalid type: expected {}, found value \'{}\'", type_, value); + eprintln!( + "ion: function argument has invalid type: expected {}, found value \'{}\'", + type_, + value + ); FAILURE } } @@ -458,18 +472,22 @@ pub fn pipe(shell: &mut Shell, commands: Vec<(RefinedJob, JobKind)>, foreground: if let Some((mut parent, mut kind)) = commands.next() { // When an `&&` or `||` operator is utilized, execute commands based on the previous status. match previous_kind { - JobKind::And => if previous_status != SUCCESS { - if let JobKind::Or = kind { - previous_kind = kind + JobKind::And => { + if previous_status != SUCCESS { + if let JobKind::Or = kind { + previous_kind = kind + } + continue; } - continue; - }, - JobKind::Or => if previous_status == SUCCESS { - if let JobKind::And = kind { - previous_kind = kind + } + JobKind::Or => { + if previous_status == SUCCESS { + if let JobKind::And = kind { + previous_kind = kind + } + continue; } - continue; - }, + } _ => (), } diff --git a/src/shell/signals.rs b/src/shell/signals.rs index 27589104db63cbe9bd4e75922e4b8554ab26d03d..b15d8163b34bacae31f0ad1bec1e28f2314cffb0 100644 --- a/src/shell/signals.rs +++ b/src/shell/signals.rs @@ -2,7 +2,7 @@ //! will be used to block signals in the shell at startup, and unblock signals for each of the forked //! children of the shell. -use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT}; +use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize}; use sys; @@ -11,14 +11,10 @@ pub use sys::signals::{block, unblock}; pub static PENDING: AtomicUsize = ATOMIC_USIZE_INIT; /// Suspends a given process by it's process ID. -pub fn suspend(pid: u32) { - let _ = sys::killpg(pid, sys::SIGSTOP); -} +pub fn suspend(pid: u32) { let _ = sys::killpg(pid, sys::SIGSTOP); } /// Resumes a given process by it's process ID. -pub fn resume(pid: u32) { - let _ = sys::killpg(pid, sys::SIGCONT); -} +pub fn resume(pid: u32) { let _ = sys::killpg(pid, sys::SIGCONT); } /// The purpose of the signal handler is to ignore signals when it is active, and then continue /// listening to signals once the handler is dropped. @@ -32,7 +28,5 @@ impl SignalHandler { } impl Drop for SignalHandler { - fn drop(&mut self) { - unblock(); - } + fn drop(&mut self) { unblock(); } } diff --git a/src/shell/status.rs b/src/shell/status.rs index 3ce5d3c43711d94f278b8da36b9346416cf37c89..65d0b1ef980c9dc25fbbd08d9fb91aa77e1b6c90 100644 --- a/src/shell/status.rs +++ b/src/shell/status.rs @@ -5,4 +5,4 @@ pub const COULD_NOT_EXEC: i32 = 126; pub const NO_SUCH_COMMAND: i32 = 127; pub const TERMINATED: i32 = 143; -pub fn get_signal_code(signal: i32) -> i32 { 128 + signal } \ No newline at end of file +pub fn get_signal_code(signal: i32) -> i32 { 128 + signal }