From 74bbb9ed4a08a3459343f434af9f633f3111adc3 Mon Sep 17 00:00:00 2001 From: Michael Murphy <mmstickman@gmail.com> Date: Sat, 28 Apr 2018 14:01:02 -0600 Subject: [PATCH] Update rustfmt (#722) --- README.md | 19 +- build.rs | 22 +- rustfmt.toml | 18 +- src/lib/ascii_helpers.rs | 3 +- src/lib/builtins/command_info.rs | 37 +- src/lib/builtins/conditionals.rs | 19 +- src/lib/builtins/exec.rs | 4 +- src/lib/builtins/exists.rs | 9 +- src/lib/builtins/functions.rs | 3 +- src/lib/builtins/ion.rs | 3 +- src/lib/builtins/job_control.rs | 10 +- src/lib/builtins/man_pages.rs | 6 +- src/lib/builtins/mod.rs | 95 +- src/lib/builtins/set.rs | 3 +- src/lib/builtins/source.rs | 3 +- src/lib/builtins/test.rs | 12 +- src/lib/builtins/variables.rs | 3 +- src/lib/lib.rs | 12 +- src/lib/parser/arguments.rs | 52 +- src/lib/parser/assignments/actions.rs | 5 +- src/lib/parser/assignments/checker.rs | 24 +- src/lib/parser/assignments/keys.rs | 43 +- src/lib/parser/assignments/mod.rs | 14 +- src/lib/parser/mod.rs | 14 +- src/lib/parser/pipelines/collector.rs | 500 ++++--- src/lib/parser/pipelines/mod.rs | 28 +- src/lib/parser/quotes.rs | 50 +- src/lib/parser/shell_expand/mod.rs | 6 +- src/lib/parser/shell_expand/ranges.rs | 9 +- src/lib/parser/shell_expand/words/index.rs | 22 +- .../shell_expand/words/methods/arrays.rs | 230 +-- .../parser/shell_expand/words/methods/mod.rs | 21 +- .../shell_expand/words/methods/strings.rs | 38 +- src/lib/parser/shell_expand/words/mod.rs | 717 +++++----- src/lib/parser/shell_expand/words/range.rs | 64 +- src/lib/parser/shell_expand/words/select.rs | 12 +- src/lib/parser/statement/functions.rs | 12 +- src/lib/parser/statement/mod.rs | 6 +- src/lib/parser/statement/parse.rs | 51 +- src/lib/parser/statement/splitter.rs | 89 +- src/lib/shell/assignments.rs | 122 +- src/lib/shell/binary/designators.rs | 15 +- src/lib/shell/binary/mod.rs | 131 +- src/lib/shell/binary/prompt.rs | 3 +- src/lib/shell/binary/readln.rs | 13 +- src/lib/shell/binary/terminate.rs | 3 +- src/lib/shell/colors.rs | 138 +- src/lib/shell/completer.rs | 12 +- src/lib/shell/directory_stack.rs | 654 ++++----- src/lib/shell/flow.rs | 1254 +++++++++-------- src/lib/shell/flow_control.rs | 58 +- src/lib/shell/fork.rs | 18 +- src/lib/shell/history.rs | 95 +- src/lib/shell/job.rs | 176 +-- src/lib/shell/mod.rs | 458 +++--- src/lib/shell/pipe_exec/foreground.rs | 42 +- src/lib/shell/pipe_exec/fork.rs | 14 +- src/lib/shell/pipe_exec/job_control.rs | 182 +-- src/lib/shell/pipe_exec/mod.rs | 777 +++++----- src/lib/shell/pipe_exec/streams.rs | 8 +- src/lib/shell/plugins/methods/redox.rs | 4 +- src/lib/shell/plugins/methods/unix.rs | 63 +- src/lib/shell/plugins/mod.rs | 2 +- src/lib/shell/plugins/namespaces/redox.rs | 4 +- src/lib/shell/plugins/namespaces/unix.rs | 51 +- src/lib/shell/plugins/string.rs | 6 +- src/lib/shell/variables/mod.rs | 389 ++--- src/lib/sys/redox/job_control.rs | 44 +- src/lib/sys/redox/mod.rs | 22 +- src/lib/sys/unix/job_control.rs | 46 +- src/lib/sys/unix/mod.rs | 20 +- src/lib/sys/unix/signals.rs | 3 +- src/main.rs | 15 +- 73 files changed, 3659 insertions(+), 3471 deletions(-) diff --git a/README.md b/README.md index 140b6144..e52ed313 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # Introduction Ion is a modern system shell that features a simple, yet powerful, syntax. It is written entirely -in Rust, which greatly increases the overall quality and security of the shell, eliminating the -possibilities of a [ShellShock](http://www.wikiwand.com/en/Shellshock_(software_bug))-like vulnerability -, and making development easier. It also offers a level of performance that exceeds that of Dash, -when taking advantage of Ion's features. While it is developed alongside, and primarily for, RedoxOS, -it is a fully capable on other \*nix platforms. +in Rust, which greatly increases the overall quality and security of the shell. It also offers a +level of performance that exceeds that of Dash, when taking advantage of Ion's features. While it +is developed alongside, and primarily for, RedoxOS, it is a fully capable on other \*nix platforms. # Ion Shell @@ -37,11 +35,12 @@ We are providing our manual for Ion in the form of a markdown-based book, which ### Code Formatting -When submitting a pull request, be sure to run -`env CFG_RELEASE_CHANNEL=nightly cargo +nightly fmt` on your project with a -nightly version of **rustfmt**. This will prevent me from having to push PR's specifically -to format the code base from time to time. To install **rustfmt-nightly**, simply run -`cargo install rustfmt-nightly --force`. +When submitting a pull request, be sure to run `rustfmt`: + +``` +rustup component add rustfmt-preview +cargo +nightly fmt +``` ### On Unit & Integration Tests diff --git a/build.rs b/build.rs index d1821aed..37f39854 100644 --- a/build.rs +++ b/build.rs @@ -9,11 +9,13 @@ use version_check::is_nightly; // `loop` (RFC 1624, rust-lang/rust GitHub issue #37339). // const MIN_VERSION: &'static str = "1.19.0"; -use std::env; -use std::fs::File; -use std::io::{self, Read, Write}; -use std::path::Path; -use std::process::Command; +use std::{ + env, + fs::File, + io::{self, Read, Write}, + path::Path, + process::Command, +}; fn main() { match is_nightly() { @@ -34,8 +36,14 @@ fn main() { panic!("Aborting compilation due to incompatible compiler.") } _ => { - eprintln!("cargo:warning={}", "Ion was unable to check rustc compatibility."); - eprintln!("cargo:warning={}", "Build may fail due to incompatible rustc version."); + eprintln!( + "cargo:warning={}", + "Ion was unable to check rustc compatibility." + ); + eprintln!( + "cargo:warning={}", + "Build may fail due to incompatible rustc version." + ); } } match write_version_file() { diff --git a/rustfmt.toml b/rustfmt.toml index 1f9ec585..9e639d7c 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,26 +1,16 @@ unstable_features = true -array_horizontal_layout_threshold = 100 -array_width = 100 -attributes_on_same_line_as_field = false -attributes_on_same_line_as_variant = false -chain_one_line_max = 100 -chain_width = 100 comment_width = 100 condense_wildcard_suffixes = true -error_on_line_overflow_comments = false -fn_call_width = 100 fn_single_line = true format_strings = true imports_indent = "Block" -match_pattern_separator_break_point = "Front" max_width = 100 normalize_comments = true -reorder_imported_names = true reorder_imports = true -reorder_imports_in_group = true -single_line_if_else_max_width = 100 +reorder_modules = true +reorder_impl_items = true struct_field_align_threshold = 30 -struct_lit_width = 100 -where_style = "legacy" +use_field_init_shorthand = true wrap_comments = true write_mode = "overwrite" +merge_imports = true diff --git a/src/lib/ascii_helpers.rs b/src/lib/ascii_helpers.rs index 8ca4fafc..bf8c33e1 100644 --- a/src/lib/ascii_helpers.rs +++ b/src/lib/ascii_helpers.rs @@ -1,5 +1,4 @@ -use std::mem::transmute; -use std::ops::DerefMut; +use std::{mem::transmute, ops::DerefMut}; // TODO: These could be generalised to work on non-ASCII characters (and even // strings!) as long as the byte size of the needle and haystack match. diff --git a/src/lib/builtins/command_info.rs b/src/lib/builtins/command_info.rs index f18699cb..dd11439e 100644 --- a/src/lib/builtins/command_info.rs +++ b/src/lib/builtins/command_info.rs @@ -1,20 +1,17 @@ -use sys; -use shell::Shell; -use shell::status::*; use builtins::man_pages::*; +use shell::{status::*, Shell}; +use sys; -use std::env; -use std::path::Path; -use std::borrow::Cow; +use std::{borrow::Cow, env, path::Path}; pub(crate) fn which(args: &[&str], shell: &mut Shell) -> Result<i32, ()> { if check_help(args, MAN_WHICH) { - return Ok(SUCCESS) + return Ok(SUCCESS); } if args.len() == 1 { eprintln!("which: Expected at least 1 args, got only 0"); - return Err(()) + return Err(()); } let mut result = SUCCESS; @@ -24,10 +21,10 @@ pub(crate) fn which(args: &[&str], shell: &mut Shell) -> Result<i32, ()> { "alias" => { let alias = shell.variables.aliases.get(command).unwrap(); println!("{}: alias to {}", command, alias); - }, + } "function" => println!("{}: function", command), "builtin" => println!("{}: built-in shell command", command), - _path => println!("{}", _path) + _path => println!("{}", _path), } } else { result = FAILURE; @@ -40,7 +37,7 @@ pub(crate) fn find_type(args: &[&str], shell: &mut Shell) -> Result<i32, ()> { // Type does not accept help flags, aka "--help". if args.len() == 1 { eprintln!("type: Expected at least 1 args, got only 0"); - return Err(()) + return Err(()); } let mut result = FAILURE; @@ -50,11 +47,11 @@ pub(crate) fn find_type(args: &[&str], shell: &mut Shell) -> Result<i32, ()> { "alias" => { let alias = shell.variables.aliases.get(command).unwrap(); println!("{} is aliased to `{}`", command, alias); - }, + } // TODO Make it print the function. - "function" => println!("{} is a function", command), + "function" => println!("{} is a function", command), "builtin" => println!("{} is a shell builtin", command), - _path => println!("{} is {}", command, _path) + _path => println!("{} is {}", command, _path), } result = SUCCESS; } else { @@ -66,21 +63,21 @@ pub(crate) fn find_type(args: &[&str], shell: &mut Shell) -> Result<i32, ()> { pub(crate) fn get_command_info<'a>(command: &str, shell: &mut Shell) -> Result<Cow<'a, str>, ()> { if shell.variables.aliases.get(command).is_some() { - return Ok("alias".into()) + return Ok("alias".into()); } else if shell.functions.contains_key(command) { - return Ok("function".into()) + return Ok("function".into()); } else if shell.builtins.contains_key(command) { - return Ok("builtin".into()) + return Ok("builtin".into()); } else { for path in env::var("PATH") .unwrap_or("/bin".to_string()) - .split(sys::PATH_SEPARATOR) + .split(sys::PATH_SEPARATOR) { let executable = Path::new(path).join(command); if executable.is_file() { - return Ok(executable.display().to_string().into()) + return Ok(executable.display().to_string().into()); } } } Err(()) -} \ No newline at end of file +} diff --git a/src/lib/builtins/conditionals.rs b/src/lib/builtins/conditionals.rs index bb723670..763fb6c9 100644 --- a/src/lib/builtins/conditionals.rs +++ b/src/lib/builtins/conditionals.rs @@ -1,24 +1,29 @@ -use shell::Shell; -use shell::status::*; +use shell::{status::*, Shell}; macro_rules! string_function { - ($method:tt) => ( + ($method:tt) => { pub(crate) fn $method(args: &[&str], _: &mut Shell) -> i32 { match args.len() { 0...2 => { eprintln!("ion: {}: two arguments must be supplied", args[0]); - return BAD_ARG + return BAD_ARG; + } + 3 => if args[1].$method(&args[2]) { + SUCCESS + } else { + FAILURE }, - 3 => if args[1].$method(&args[2]) { SUCCESS } else { FAILURE }, _ => { for arg in args[2..].iter() { - if args[1].$method(arg) { return SUCCESS } + if args[1].$method(arg) { + return SUCCESS; + } } FAILURE } } } - ) + }; } string_function!(starts_with); diff --git a/src/lib/builtins/exec.rs b/src/lib/builtins/exec.rs index 749e92d0..81b20459 100644 --- a/src/lib/builtins/exec.rs +++ b/src/lib/builtins/exec.rs @@ -28,7 +28,9 @@ pub(crate) fn exec(shell: &mut Shell, args: &[&str]) -> Result<(), String> { &[] }; shell.prep_for_exit(); - Err(execve(argument, args, (flags & CLEAR_ENV) == 1).description().to_owned()) + Err(execve(argument, args, (flags & CLEAR_ENV) == 1) + .description() + .to_owned()) } None => Err("no command provided".to_owned()), } diff --git a/src/lib/builtins/exists.rs b/src/lib/builtins/exists.rs index 82ba1a90..9ba6ede7 100644 --- a/src/lib/builtins/exists.rs +++ b/src/lib/builtins/exists.rs @@ -2,14 +2,13 @@ use smallstring::SmallString; #[cfg(test)] use smallvec::SmallVec; -use std::fs; -use std::os::unix::fs::PermissionsExt; +use std::{fs, os::unix::fs::PermissionsExt}; -use shell::Shell; -#[cfg(test)] -use shell::flow_control::{Function, Statement}; #[cfg(test)] use shell; +#[cfg(test)] +use shell::flow_control::{Function, Statement}; +use shell::Shell; pub(crate) fn exists(args: &[&str], shell: &Shell) -> Result<bool, String> { let arguments = &args[1..]; diff --git a/src/lib/builtins/functions.rs b/src/lib/builtins/functions.rs index 3efda681..ab663e2e 100644 --- a/src/lib/builtins/functions.rs +++ b/src/lib/builtins/functions.rs @@ -1,6 +1,5 @@ use fnv::FnvHashMap; -use shell::flow_control::Function; -use shell::status::*; +use shell::{flow_control::Function, status::*}; use std::io::{self, Write}; use types::Identifier; diff --git a/src/lib/builtins/ion.rs b/src/lib/builtins/ion.rs index bc52c853..74f7f0f7 100644 --- a/src/lib/builtins/ion.rs +++ b/src/lib/builtins/ion.rs @@ -1,5 +1,4 @@ -use shell::Shell; -use shell::status::*; +use shell::{status::*, Shell}; use std::path::Path; use std::process::Command; diff --git a/src/lib/builtins/job_control.rs b/src/lib/builtins/job_control.rs index 9d409e34..56dc7864 100644 --- a/src/lib/builtins/job_control.rs +++ b/src/lib/builtins/job_control.rs @@ -1,10 +1,12 @@ //! Contains the `jobs`, `disown`, `bg`, and `fg` commands that manage job //! control in the shell. -use shell::Shell; -use shell::job_control::{JobControl, ProcessState}; -use shell::signals; -use shell::status::*; +use shell::{ + job_control::{JobControl, ProcessState}, + signals, + status::*, + Shell, +}; /// Disowns given process job IDs, and optionally marks jobs to not receive SIGHUP signals. /// The `-a` flag selects all jobs, `-r` selects all running jobs, and `-h` specifies to mark diff --git a/src/lib/builtins/man_pages.rs b/src/lib/builtins/man_pages.rs index 514b3813..95c9afd2 100644 --- a/src/lib/builtins/man_pages.rs +++ b/src/lib/builtins/man_pages.rs @@ -1,5 +1,7 @@ -use std::error::Error; -use std::io::{stdout, Write}; +use std::{ + error::Error, + io::{stdout, Write}, +}; pub(crate) fn print_man(man_page: &'static str) { let stdout = stdout(); diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs index 7dbd6738..aca916cd 100644 --- a/src/lib/builtins/mod.rs +++ b/src/lib/builtins/mod.rs @@ -1,45 +1,54 @@ -pub mod source; -pub mod variables; -pub mod functions; pub mod calc; +pub mod functions; pub mod random; +pub mod source; +pub mod variables; mod command_info; mod conditionals; -mod job_control; -mod man_pages; -mod test; mod echo; -mod set; -mod status; -mod exists; mod exec; +mod exists; mod ion; mod is; +mod job_control; +mod man_pages; +mod set; +mod status; +mod test; -use self::command_info::*; -use self::conditionals::{contains, ends_with, starts_with}; -use self::echo::echo; -use self::exec::exec; -use self::exists::exists; -use self::functions::fn_; -use self::ion::ion_docs; -use self::is::is; -use self::man_pages::*; -use self::source::source; -use self::status::status; -use self::test::test; -use self::variables::{alias, drop_alias, drop_array, drop_variable}; - -use std::env; -use std::error::Error; -use std::io::{self, Write}; +use self::{ + command_info::*, + conditionals::{contains, ends_with, starts_with}, + echo::echo, + exec::exec, + exists::exists, + functions::fn_, + ion::ion_docs, + is::is, + man_pages::*, + source::source, + status::status, + test::test, + variables::{alias, drop_alias, drop_array, drop_variable}, +}; + +use std::{ + env, + error::Error, + io::{self, Write}, +}; use parser::Terminator; -use shell::job_control::{JobControl, ProcessState}; -use shell::fork_function::fork_function; -use shell::status::*; -use shell::{self, FlowLogic, Shell, ShellHistory}; +use shell::{ + self, + fork_function::fork_function, + job_control::{JobControl, ProcessState}, + status::*, + FlowLogic, + Shell, + ShellHistory, +}; use sys; const HELP_DESC: &str = "Display helpful information about a given command or list commands if \ @@ -128,6 +137,10 @@ pub struct BuiltinMap { } impl BuiltinMap { + pub fn contains_key(&self, func: &str) -> bool { self.name.iter().any(|&name| name == func) } + + pub fn keys(&self) -> &'static [&'static str] { self.name } + pub fn get(&self, func: &str) -> Option<Builtin> { self.name.binary_search(&func).ok().map(|pos| unsafe { Builtin { @@ -137,10 +150,6 @@ impl BuiltinMap { } }) } - - pub fn keys(&self) -> &'static [&'static str] { self.name } - - pub fn contains_key(&self, func: &str) -> bool { self.name.iter().any(|&name| name == func) } } // Definitions of simple builtins go here @@ -174,7 +183,7 @@ pub fn builtin_cd(args: &[&str], shell: &mut Shell) -> i32 { shell.set_var("PWD", current_dir); } fork_function(shell, "CD_CHANGE", &["ion"]); - }, + } Err(_) => env::set_var("PWD", "?"), }; SUCCESS @@ -557,20 +566,20 @@ fn builtin_exists(args: &[&str], shell: &mut Shell) -> i32 { fn builtin_which(args: &[&str], shell: &mut Shell) -> i32 { match which(args, shell) { Ok(result) => result, - Err(()) => FAILURE + Err(()) => FAILURE, } } fn builtin_type(args: &[&str], shell: &mut Shell) -> i32 { match find_type(args, shell) { Ok(result) => result, - Err(()) => FAILURE + Err(()) => FAILURE, } } fn builtin_isatty(args: &[&str], _: &mut Shell) -> i32 { if check_help(args, MAN_ISATTY) { - return SUCCESS + return SUCCESS; } if args.len() > 1 { @@ -578,20 +587,20 @@ fn builtin_isatty(args: &[&str], _: &mut Shell) -> i32 { #[cfg(target_os = "redox")] match args[1].parse::<usize>() { Ok(r) => if sys::isatty(r) { - return SUCCESS + return SUCCESS; }, - Err(_) => eprintln!("ion: isatty given bad number") + Err(_) => eprintln!("ion: isatty given bad number"), } #[cfg(not(target_os = "redox"))] match args[1].parse::<i32>() { Ok(r) => if sys::isatty(r) { - return SUCCESS + return SUCCESS; }, - Err(_) => eprintln!("ion: isatty given bad number") + Err(_) => eprintln!("ion: isatty given bad number"), } } else { - return SUCCESS + return SUCCESS; } FAILURE diff --git a/src/lib/builtins/set.rs b/src/lib/builtins/set.rs index e0405f3e..36b810a4 100644 --- a/src/lib/builtins/set.rs +++ b/src/lib/builtins/set.rs @@ -1,6 +1,5 @@ use liner::KeyBindings; -use shell::Shell; -use shell::flags::*; +use shell::{flags::*, Shell}; use std::iter; enum PositionalArgs { diff --git a/src/lib/builtins/source.rs b/src/lib/builtins/source.rs index 8bf58147..bb121ad9 100644 --- a/src/lib/builtins/source.rs +++ b/src/lib/builtins/source.rs @@ -1,6 +1,5 @@ use shell::{FlowLogic, Shell}; -use std::fs::File; -use std::io::Read; +use std::{fs::File, io::Read}; /// Evaluates the given file and returns 'SUCCESS' if it succeeds. pub(crate) fn source(shell: &mut Shell, arguments: &[&str]) -> Result<(), String> { diff --git a/src/lib/builtins/test.rs b/src/lib/builtins/test.rs index d3bbd987..f1eccb85 100644 --- a/src/lib/builtins/test.rs +++ b/src/lib/builtins/test.rs @@ -1,9 +1,11 @@ -use smallstring::SmallString; -use std::fs; -use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; -use std::path::Path; -use std::time::SystemTime; use super::man_pages::{print_man, MAN_TEST}; +use smallstring::SmallString; +use std::{ + fs, + os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}, + path::Path, + time::SystemTime, +}; pub(crate) fn test(args: &[&str]) -> Result<bool, String> { let arguments = &args[1..]; diff --git a/src/lib/builtins/variables.rs b/src/lib/builtins/variables.rs index 2f223df1..7002b6e4 100644 --- a/src/lib/builtins/variables.rs +++ b/src/lib/builtins/variables.rs @@ -2,8 +2,7 @@ use std::io::{self, Write}; -use shell::status::*; -use shell::variables::Variables; +use shell::{status::*, variables::Variables}; use types::*; fn print_list(list: &VariableContext) { diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 733190b7..f79b63a1 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -1,6 +1,5 @@ #![allow(unknown_lints)] #![allow(while_let_on_iterator)] -#![feature(conservative_impl_trait)] #![feature(integer_atomics)] #![feature(pointer_methods)] #![feature(getpid)] @@ -43,12 +42,11 @@ mod sys; mod types; #[macro_use] pub mod parser; +mod ascii_helpers; mod builtins; mod shell; -mod ascii_helpers; -pub use shell::binary::MAN_ION; -pub use shell::flags; -pub use shell::status; -pub use shell::{Binary, Capture, Fork, IonError, IonResult, Shell, ShellBuilder}; -pub use shell::pipe_exec::job_control::JobControl; +pub use shell::{ + binary::MAN_ION, flags, pipe_exec::job_control::JobControl, status, Binary, Capture, Fork, + IonError, IonResult, Shell, ShellBuilder, +}; diff --git a/src/lib/parser/arguments.rs b/src/lib/parser/arguments.rs index 3d0b2c26..dcd47d6c 100644 --- a/src/lib/parser/arguments.rs +++ b/src/lib/parser/arguments.rs @@ -1,31 +1,31 @@ bitflags! { struct ArgumentFlags: u8 { - /// Double quotes + /// Double quotes const DOUBLE = 0b00000001; /// Command flags const COMM_1 = 0b00000010; // found $ const COMM_2 = 0b00000100; // found ( after $ - /// String variable + /// String variable const VARIAB = 0b00001000; - /// Array variable + /// Array variable const ARRAY = 0b00010000; - const METHOD = 0b00100000; + const METHOD = 0b00100000; } } /// An efficient `Iterator` structure for splitting arguments pub struct ArgumentSplitter<'a> { - data: &'a str, + data: &'a str, /// Number of bytes read - read: usize, + read: usize, bitflags: ArgumentFlags, } impl<'a> ArgumentSplitter<'a> { pub fn new(data: &'a str) -> ArgumentSplitter<'a> { ArgumentSplitter { - data: data, - read: 0, + data, + read: 0, bitflags: ArgumentFlags::empty(), } } @@ -61,7 +61,6 @@ impl<'a> Iterator for ArgumentSplitter<'a> { let (mut level, mut alevel) = (0, 0); let mut bytes = data.iter().cloned().skip(self.read); while let Some(character) = bytes.next() { - match character { // Skip the next byte. b'\\' => { @@ -72,18 +71,16 @@ impl<'a> Iterator for ArgumentSplitter<'a> { // Disable COMM_1 and enable COMM_2 + ARRAY. b'@' => { self.bitflags.remove(ArgumentFlags::COMM_1); - self.bitflags.insert( - ArgumentFlags::COMM_2 | ArgumentFlags::ARRAY - ); + self.bitflags + .insert(ArgumentFlags::COMM_2 | ArgumentFlags::ARRAY); self.read += 1; continue; } // Disable COMM_2 and enable COMM_1 + VARIAB. b'$' => { self.bitflags.remove(ArgumentFlags::COMM_2); - self.bitflags.insert( - ArgumentFlags::COMM_1 | ArgumentFlags::VARIAB - ); + self.bitflags + .insert(ArgumentFlags::COMM_1 | ArgumentFlags::VARIAB); self.read += 1; continue; } @@ -95,13 +92,12 @@ impl<'a> Iterator for ArgumentSplitter<'a> { b'(' => { // Disable VARIAB + ARRAY and enable METHOD. // if variab or array are set - if self.bitflags.intersects( - ArgumentFlags::VARIAB | ArgumentFlags::ARRAY - ) { - self.bitflags.remove( - ArgumentFlags::VARIAB | ArgumentFlags::ARRAY - ); - self.bitflags.insert(ArgumentFlags::METHOD); + if self.bitflags + .intersects(ArgumentFlags::VARIAB | ArgumentFlags::ARRAY) + { + self.bitflags + .remove(ArgumentFlags::VARIAB | ArgumentFlags::ARRAY); + self.bitflags.insert(ArgumentFlags::METHOD); } level += 1 } @@ -127,9 +123,10 @@ impl<'a> Iterator for ArgumentSplitter<'a> { // ) && level + alevel == 0) => break, // Break from the loop once a root-level space is found. b' ' => { - if !self.bitflags.intersects( - ArgumentFlags::DOUBLE | ArgumentFlags::METHOD - ) && level == 0 && alevel == 0 { + if !self.bitflags + .intersects(ArgumentFlags::DOUBLE | ArgumentFlags::METHOD) + && level == 0 && alevel == 0 + { break; } } @@ -138,9 +135,8 @@ impl<'a> Iterator for ArgumentSplitter<'a> { self.read += 1; // disable COMM_1 and COMM_2 - self.bitflags.remove( - ArgumentFlags::COMM_1 | ArgumentFlags::COMM_2 - ); + self.bitflags + .remove(ArgumentFlags::COMM_1 | ArgumentFlags::COMM_2); } if start == self.read { diff --git a/src/lib/parser/assignments/actions.rs b/src/lib/parser/assignments/actions.rs index b2f75503..cc1e043b 100644 --- a/src/lib/parser/assignments/actions.rs +++ b/src/lib/parser/assignments/actions.rs @@ -1,6 +1,4 @@ -use super::*; -use super::checker::*; -use super::super::ArgumentSplitter; +use super::{super::ArgumentSplitter, checker::*, *}; use std::fmt::{self, Display, Formatter}; #[derive(Debug, PartialEq)] @@ -49,6 +47,7 @@ impl<'a> AssignmentActions<'a> { impl<'a> Iterator for AssignmentActions<'a> { type Item = Result<Action<'a>, AssignmentError<'a>>; + fn next(&mut self) -> Option<Result<Action<'a>, AssignmentError<'a>>> { if let Some(key) = self.keys.next() { match key { diff --git a/src/lib/parser/assignments/checker.rs b/src/lib/parser/assignments/checker.rs index fffacc42..f2010dda 100644 --- a/src/lib/parser/assignments/checker.rs +++ b/src/lib/parser/assignments/checker.rs @@ -1,6 +1,9 @@ -use super::{Primitive, ReturnValue, TypeError}; -use super::super::Expander; -use super::super::expand_string; +use super::{ + super::{expand_string, Expander}, + Primitive, + ReturnValue, + TypeError, +}; use std::iter::Iterator; @@ -155,8 +158,16 @@ pub(crate) fn value_check<'a, E: Expander>( value: &'a str, expected: Primitive, ) -> Result<ReturnValue, TypeError<'a>> { - macro_rules! string { () => { get_string(shell, value) } } - macro_rules! array { () => { get_array(shell, value) } } + macro_rules! string { + () => { + get_string(shell, value) + }; + } + macro_rules! array { + () => { + get_array(shell, value) + }; + } let is_array = is_array(value); match expected { Primitive::Any if is_array => Ok(array!()), @@ -195,8 +206,7 @@ pub(crate) fn value_check<'a, E: Expander>( #[cfg(test)] mod test { - use super::*; - use super::super::*; + use super::{super::*, *}; #[test] fn is_array_() { diff --git a/src/lib/parser/assignments/keys.rs b/src/lib/parser/assignments/keys.rs index 20116dfc..8da6ae3d 100644 --- a/src/lib/parser/assignments/keys.rs +++ b/src/lib/parser/assignments/keys.rs @@ -106,27 +106,6 @@ pub(crate) struct KeyIterator<'a> { } impl<'a> KeyIterator<'a> { - pub(crate) fn new(data: &'a str) -> KeyIterator<'a> { KeyIterator { data, read: 0 } } - - // Parameters are values that follow the semicolon (':'). - fn parse_parameter(&mut self, name: &'a str) -> Result<Key<'a>, TypeError<'a>> { - let mut start = self.read; - for byte in self.data.bytes().skip(self.read) { - self.read += 1; - match byte { - b' ' if start + 1 == self.read => start += 1, - b' ' => return Key::new(name, &self.data[start..self.read].trim()), - _ => (), - } - } - - if start == self.read { - Err(TypeError::Invalid("")) - } else { - Key::new(name, &self.data[start..self.read].trim()) - } - } - // Executes when a semicolon was not found, but an array character was. fn parse_array(&mut self, name: &'a str) -> Result<Key<'a>, TypeError<'a>> { let start = self.read; @@ -155,10 +134,32 @@ impl<'a> KeyIterator<'a> { data @ _ => return Err(TypeError::Invalid(data)), } } + + // Parameters are values that follow the semicolon (':'). + fn parse_parameter(&mut self, name: &'a str) -> Result<Key<'a>, TypeError<'a>> { + let mut start = self.read; + for byte in self.data.bytes().skip(self.read) { + self.read += 1; + match byte { + b' ' if start + 1 == self.read => start += 1, + b' ' => return Key::new(name, &self.data[start..self.read].trim()), + _ => (), + } + } + + if start == self.read { + Err(TypeError::Invalid("")) + } else { + Key::new(name, &self.data[start..self.read].trim()) + } + } + + pub(crate) fn new(data: &'a str) -> KeyIterator<'a> { KeyIterator { data, read: 0 } } } impl<'a> Iterator for KeyIterator<'a> { type Item = Result<Key<'a>, TypeError<'a>>; + fn next(&mut self) -> Option<Result<Key<'a>, TypeError<'a>>> { let mut start = self.read; for byte in self.data.bytes().skip(self.read) { diff --git a/src/lib/parser/assignments/mod.rs b/src/lib/parser/assignments/mod.rs index b732fc15..fd3a81b7 100644 --- a/src/lib/parser/assignments/mod.rs +++ b/src/lib/parser/assignments/mod.rs @@ -1,15 +1,17 @@ mod actions; mod checker; -mod splitter; mod keys; mod operator; +mod splitter; -pub(crate) use self::actions::{Action, AssignmentActions, AssignmentError}; -pub(crate) use self::checker::{is_array, value_check}; -pub(crate) use self::keys::{Key, KeyBuf, KeyIterator, TypeError}; pub use self::keys::Primitive; -pub(crate) use self::operator::Operator; -pub(crate) use self::splitter::split_assignment; +pub(crate) use self::{ + actions::{Action, AssignmentActions, AssignmentError}, + checker::{is_array, value_check}, + keys::{Key, KeyBuf, KeyIterator, TypeError}, + operator::Operator, + splitter::split_assignment, +}; use types::{Array, Value}; diff --git a/src/lib/parser/mod.rs b/src/lib/parser/mod.rs index 30b7af7c..66926e29 100644 --- a/src/lib/parser/mod.rs +++ b/src/lib/parser/mod.rs @@ -2,13 +2,13 @@ mod arguments; pub(crate) mod assignments; mod loops; pub(crate) mod pipelines; +mod quotes; pub(crate) mod shell_expand; mod statement; -mod quotes; -pub use self::arguments::ArgumentSplitter; -pub use self::assignments::Primitive; -pub(crate) use self::loops::for_grammar::ForExpression; -pub use self::quotes::Terminator; -pub(crate) use self::shell_expand::{expand_string, Expander, Select}; -pub(crate) use self::statement::{parse_and_validate, StatementSplitter}; +pub use self::{arguments::ArgumentSplitter, assignments::Primitive, quotes::Terminator}; +pub(crate) use self::{ + loops::for_grammar::ForExpression, + shell_expand::{expand_string, Expander, Select}, + statement::{parse_and_validate, StatementSplitter}, +}; diff --git a/src/lib/parser/pipelines/collector.rs b/src/lib/parser/pipelines/collector.rs index 0f3631f7..b172ccf5 100644 --- a/src/lib/parser/pipelines/collector.rs +++ b/src/lib/parser/pipelines/collector.rs @@ -1,7 +1,6 @@ #![allow(eq_op)] // Required as a macro sets this clippy warning off. -use std::collections::HashSet; -use std::iter::Peekable; +use std::{collections::HashSet, iter::Peekable}; use super::{Input, PipeItem, Pipeline, RedirectFrom, Redirection}; use shell::{Job, JobKind}; @@ -17,189 +16,6 @@ lazy_static! { } impl<'a> Collector<'a> { - pub(crate) fn new(data: &'a str) -> Self { Collector { data } } - - pub(crate) fn run(data: &'a str) -> Result<Pipeline, &'static str> { - Collector::new(data).parse() - } - - fn peek(&self, index: usize) -> Option<u8> { - if index < self.data.len() { - Some(self.data.as_bytes()[index]) - } else { - None - } - } - - fn single_quoted<I>( - &self, - bytes: &mut Peekable<I>, - start: usize, - ) -> Result<&'a str, &'static str> - where - I: Iterator<Item = (usize, u8)>, - { - while let Some(&(i, b)) = bytes.peek() { - match b { - // We return an inclusive range to keep the quote type intact - b'\'' => { - bytes.next(); - return Ok(&self.data[start..i + 1]); - } - _ => (), - } - bytes.next(); - } - Err("ion: syntax error: unterminated single quote") - } - - fn double_quoted<I>( - &self, - bytes: &mut Peekable<I>, - start: usize, - ) -> Result<&'a str, &'static str> - where - I: Iterator<Item = (usize, u8)>, - { - while let Some(&(i, b)) = bytes.peek() { - match b { - b'\\' => { - bytes.next(); - } - // We return an inclusive range to keep the quote type intact - b'"' => { - bytes.next(); - return Ok(&self.data[start..i + 1]); - } - _ => (), - } - bytes.next(); - } - Err("ion: syntax error: unterminated quote") - } - - fn arg<I>(&self, bytes: &mut Peekable<I>) -> Result<Option<&'a str>, &'static str> - where - I: Iterator<Item = (usize, u8)>, - { - // XXX: I don't think its the responsibility of the pipeline parser to do this - // but I'm not sure of a better solution - let mut array_level = 0; - let mut proc_level = 0; - let mut brace_level = 0; - let mut start = None; - let mut end = None; - - macro_rules! is_toplevel { () => (array_level + proc_level + brace_level == 0) } - - // Skip over any leading whitespace - while let Some(&(_, b)) = bytes.peek() { - match b { - b' ' | b'\t' => { - bytes.next(); - } - _ => break, - } - } - - while let Some(&(i, b)) = bytes.peek() { - if start.is_none() { - start = Some(i) - } - match b { - b'(' => { - proc_level += 1; - bytes.next(); - } - b')' => { - proc_level -= 1; - bytes.next(); - } - b'[' => { - array_level += 1; - bytes.next(); - } - b']' => { - array_level -= 1; - bytes.next(); - } - b'{' => { - brace_level += 1; - bytes.next(); - } - b'}' => { - brace_level -= 1; - bytes.next(); - } - // This is a tricky one: we only end the argment if `^` is followed by a - // redirection character - b'^' => { - if is_toplevel!() { - if let Some(next_byte) = self.peek(i + 1) { - // If the next byte is for stderr to file or next process, end this - // argument - if next_byte == b'>' || next_byte == b'|' { - end = Some(i); - break; - } - } - // Reaching this block means that either there is no next byte, or the next - // byte is none of '>' or '|', indicating that this is not the beginning of - // a redirection for stderr - bytes.next(); - } - } - // Evaluate a quoted string but do not return it - // We pass in i, the index of a quote, but start a character later. This ensures - // the production rules will produce strings with the quotes intact - b'"' => { - bytes.next(); - self.double_quoted(bytes, i)?; - } - b'\'' => { - bytes.next(); - self.single_quoted(bytes, i)?; - } - // If we see a backslash, assume that it is leading up to an escaped character - // and skip the next character - b'\\' => { - bytes.next(); - bytes.next(); - } - // If we see a byte from the follow set, we've definitely reached the end of - // the arguments - c if FOLLOW_ARGS.contains(&c) && is_toplevel!() => { - end = Some(i); - break; - } - // By default just pop the next byte: it will be part of the argument - _ => { - bytes.next(); - } - } - } - if proc_level > 0 { - return Err("ion: syntax error: unmatched left paren"); - } - if array_level > 0 { - return Err("ion: syntax error: unmatched left bracket"); - } - if brace_level > 0 { - return Err("ion: syntax error: unmatched left brace"); - } - if proc_level < 0 { - return Err("ion: syntax error: extra right paren(s)"); - } - if array_level < 0 { - return Err("ion: syntax error: extra right bracket(s)"); - } - match (start, end) { - (Some(i), Some(j)) if i < j => Ok(Some(&self.data[i..j])), - (Some(i), None) => Ok(Some(&self.data[i..])), - _ => Ok(None), - } - } - pub(crate) fn parse(&self) -> Result<Pipeline, &'static str> { let mut bytes = self.data.bytes().enumerate().peekable(); let mut args = Array::new(); @@ -213,13 +29,15 @@ impl<'a> Collector<'a> { if let Some(v) = self.arg(&mut bytes)? { args.push(v.into()); } - }} + }}; } /// Attempt to add a redirection macro_rules! try_redir_out { ($from:expr) => {{ - if let None = outputs { outputs = Some(Vec::new()); } + if let None = outputs { + outputs = Some(Vec::new()); + } let append = if let Some(&(_, b'>')) = bytes.peek() { bytes.next(); true @@ -227,21 +45,23 @@ impl<'a> Collector<'a> { false }; if let Some(file) = self.arg(&mut bytes)? { - outputs.as_mut().map(|o| o.push(Redirection { - from: $from, - file: file.into(), - append - })); + outputs.as_mut().map(|o| { + o.push(Redirection { + from: $from, + file: file.into(), + append, + }) + }); } else { return Err("expected file argument after redirection for output"); } - }} + }}; }; /// Attempt to create a pipeitem and append it to the pipeline macro_rules! try_add_item { ($job_kind:expr) => {{ - if ! args.is_empty() { + if !args.is_empty() { let job = Job::new(args.clone(), $job_kind); args.clear(); let item_out = if let Some(out_tmp) = outputs.take() { @@ -256,7 +76,7 @@ impl<'a> Collector<'a> { }; pipeline.items.push(PipeItem::new(job, item_out, item_in)); } - }} + }}; } while let Some(&(i, b)) = bytes.peek() { @@ -381,14 +201,202 @@ impl<'a> Collector<'a> { Ok(pipeline) } + + fn arg<I>(&self, bytes: &mut Peekable<I>) -> Result<Option<&'a str>, &'static str> + where + I: Iterator<Item = (usize, u8)>, + { + // XXX: I don't think its the responsibility of the pipeline parser to do this + // but I'm not sure of a better solution + let mut array_level = 0; + let mut proc_level = 0; + let mut brace_level = 0; + let mut start = None; + let mut end = None; + + macro_rules! is_toplevel { + () => { + array_level + proc_level + brace_level == 0 + }; + } + + // Skip over any leading whitespace + while let Some(&(_, b)) = bytes.peek() { + match b { + b' ' | b'\t' => { + bytes.next(); + } + _ => break, + } + } + + while let Some(&(i, b)) = bytes.peek() { + if start.is_none() { + start = Some(i) + } + match b { + b'(' => { + proc_level += 1; + bytes.next(); + } + b')' => { + proc_level -= 1; + bytes.next(); + } + b'[' => { + array_level += 1; + bytes.next(); + } + b']' => { + array_level -= 1; + bytes.next(); + } + b'{' => { + brace_level += 1; + bytes.next(); + } + b'}' => { + brace_level -= 1; + bytes.next(); + } + // This is a tricky one: we only end the argment if `^` is followed by a + // redirection character + b'^' => { + if is_toplevel!() { + if let Some(next_byte) = self.peek(i + 1) { + // If the next byte is for stderr to file or next process, end this + // argument + if next_byte == b'>' || next_byte == b'|' { + end = Some(i); + break; + } + } + // Reaching this block means that either there is no next byte, or the next + // byte is none of '>' or '|', indicating that this is not the beginning of + // a redirection for stderr + bytes.next(); + } + } + // Evaluate a quoted string but do not return it + // We pass in i, the index of a quote, but start a character later. This ensures + // the production rules will produce strings with the quotes intact + b'"' => { + bytes.next(); + self.double_quoted(bytes, i)?; + } + b'\'' => { + bytes.next(); + self.single_quoted(bytes, i)?; + } + // If we see a backslash, assume that it is leading up to an escaped character + // and skip the next character + b'\\' => { + bytes.next(); + bytes.next(); + } + // If we see a byte from the follow set, we've definitely reached the end of + // the arguments + c if FOLLOW_ARGS.contains(&c) && is_toplevel!() => { + end = Some(i); + break; + } + // By default just pop the next byte: it will be part of the argument + _ => { + bytes.next(); + } + } + } + if proc_level > 0 { + return Err("ion: syntax error: unmatched left paren"); + } + if array_level > 0 { + return Err("ion: syntax error: unmatched left bracket"); + } + if brace_level > 0 { + return Err("ion: syntax error: unmatched left brace"); + } + if proc_level < 0 { + return Err("ion: syntax error: extra right paren(s)"); + } + if array_level < 0 { + return Err("ion: syntax error: extra right bracket(s)"); + } + match (start, end) { + (Some(i), Some(j)) if i < j => Ok(Some(&self.data[i..j])), + (Some(i), None) => Ok(Some(&self.data[i..])), + _ => Ok(None), + } + } + + fn double_quoted<I>( + &self, + bytes: &mut Peekable<I>, + start: usize, + ) -> Result<&'a str, &'static str> + where + I: Iterator<Item = (usize, u8)>, + { + while let Some(&(i, b)) = bytes.peek() { + match b { + b'\\' => { + bytes.next(); + } + // We return an inclusive range to keep the quote type intact + b'"' => { + bytes.next(); + return Ok(&self.data[start..i + 1]); + } + _ => (), + } + bytes.next(); + } + Err("ion: syntax error: unterminated quote") + } + + fn single_quoted<I>( + &self, + bytes: &mut Peekable<I>, + start: usize, + ) -> Result<&'a str, &'static str> + where + I: Iterator<Item = (usize, u8)>, + { + while let Some(&(i, b)) = bytes.peek() { + match b { + // We return an inclusive range to keep the quote type intact + b'\'' => { + bytes.next(); + return Ok(&self.data[start..i + 1]); + } + _ => (), + } + bytes.next(); + } + Err("ion: syntax error: unterminated single quote") + } + + fn peek(&self, index: usize) -> Option<u8> { + if index < self.data.len() { + Some(self.data.as_bytes()[index]) + } else { + None + } + } + + pub(crate) fn run(data: &'a str) -> Result<Pipeline, &'static str> { + Collector::new(data).parse() + } + + pub(crate) fn new(data: &'a str) -> Self { Collector { data } } } #[cfg(test)] mod tests { - use parser::pipelines::{Input, PipeItem, Pipeline, RedirectFrom, Redirection}; - use parser::statement::parse; - use shell::{Job, JobKind}; - use shell::flow_control::Statement; + use parser::{ + pipelines::{Input, PipeItem, Pipeline, RedirectFrom, Redirection}, + statement::parse, + }; + use shell::{flow_control::Statement, Job, JobKind}; use types::Array; #[test] @@ -400,13 +408,11 @@ mod tests { assert_eq!("--abbrev-ref", pipeline.items[0].job.args[2]); assert_eq!("HEAD", pipeline.items[0].job.args[3]); - let expected = vec![ - Redirection { - from: RedirectFrom::Stderr, - file: "/dev/null".to_owned(), - append: false, - }, - ]; + let expected = vec![Redirection { + from: RedirectFrom::Stderr, + file: "/dev/null".to_owned(), + append: false, + }]; assert_eq!(expected, pipeline.items[0].outputs); } else { @@ -830,13 +836,11 @@ mod tests { PipeItem { job: Job::new(array!["cat"], JobKind::Last), inputs: vec![Input::File("stuff".into())], - outputs: vec![ - Redirection { - from: RedirectFrom::Stderr, - file: "other".into(), - append: true, - }, - ], + outputs: vec![Redirection { + from: RedirectFrom::Stderr, + file: "other".into(), + append: true, + }], }, ], }; @@ -863,13 +867,11 @@ mod tests { PipeItem { job: Job::new(array!["cat"], JobKind::Last), inputs: vec![Input::File("stuff".into())], - outputs: vec![ - Redirection { - from: RedirectFrom::Both, - file: "other".into(), - append: true, - }, - ], + outputs: vec![Redirection { + from: RedirectFrom::Both, + file: "other".into(), + append: true, + }], }, ], }; @@ -915,13 +917,11 @@ mod tests { fn herestring() { let input = "calc <<< $(cat math.txt)"; let expected = Pipeline { - items: vec![ - PipeItem { - job: Job::new(array!["calc"], JobKind::Last), - inputs: vec![Input::HereString("$(cat math.txt)".into())], - outputs: vec![], - }, - ], + items: vec![PipeItem { + job: Job::new(array!["calc"], JobKind::Last), + inputs: vec![Input::HereString("$(cat math.txt)".into())], + outputs: vec![], + }], }; assert_eq!(Statement::Pipeline(expected), parse(input)); } @@ -930,13 +930,11 @@ mod tests { fn heredoc() { let input = "calc << EOF\n1 + 2\n3 + 4\nEOF"; let expected = Pipeline { - items: vec![ - PipeItem { - job: Job::new(array!["calc"], JobKind::Last), - inputs: vec![Input::HereString("1 + 2\n3 + 4".into())], - outputs: vec![], - }, - ], + items: vec![PipeItem { + job: Job::new(array!["calc"], JobKind::Last), + inputs: vec![Input::HereString("1 + 2\n3 + 4".into())], + outputs: vec![], + }], }; assert_eq!(Statement::Pipeline(expected), parse(input)); } @@ -956,13 +954,11 @@ mod tests { PipeItem { job: Job::new(array!["tr", "'o'", "'x'"], JobKind::Last), inputs: vec![Input::HereString("$VAR".into())], - outputs: vec![ - Redirection { - from: RedirectFrom::Stdout, - file: "out.log".into(), - append: false, - }, - ], + outputs: vec![Redirection { + from: RedirectFrom::Stdout, + file: "out.log".into(), + append: false, + }], }, ], }; @@ -990,19 +986,15 @@ mod tests { fn escaped_filenames() { let input = "echo zardoz >> foo\\'bar"; let expected = Pipeline { - items: vec![ - PipeItem { - job: Job::new(array!["echo", "zardoz"], JobKind::Last), - inputs: Vec::new(), - outputs: vec![ - Redirection { - from: RedirectFrom::Stdout, - file: "foo\\'bar".into(), - append: true, - }, - ], - }, - ], + items: vec![PipeItem { + job: Job::new(array!["echo", "zardoz"], JobKind::Last), + inputs: Vec::new(), + outputs: vec![Redirection { + from: RedirectFrom::Stdout, + file: "foo\\'bar".into(), + append: true, + }], + }], }; assert_eq!(parse(input), Statement::Pipeline(expected)); } diff --git a/src/lib/parser/pipelines/mod.rs b/src/lib/parser/pipelines/mod.rs index 301281e8..6aee2137 100644 --- a/src/lib/parser/pipelines/mod.rs +++ b/src/lib/parser/pipelines/mod.rs @@ -44,14 +44,6 @@ pub(crate) struct PipeItem { } impl PipeItem { - pub(crate) fn new(job: Job, outputs: Vec<Redirection>, inputs: Vec<Input>) -> Self { - PipeItem { - job, - outputs, - inputs, - } - } - pub(crate) fn expand(&mut self, shell: &Shell) { self.job.expand(shell); @@ -68,21 +60,29 @@ impl PipeItem { output.file = expand_string(output.file.as_str(), shell, false).join(" "); } } -} -impl Pipeline { - pub(crate) fn new() -> Self { Pipeline { items: Vec::new() } } - - pub(crate) fn expand(&mut self, shell: &Shell) { - self.items.iter_mut().for_each(|i| i.expand(shell)); + pub(crate) fn new(job: Job, outputs: Vec<Redirection>, inputs: Vec<Input>) -> Self { + PipeItem { + job, + outputs, + inputs, + } } +} +impl Pipeline { pub(crate) fn requires_piping(&self) -> bool { self.items.len() > 1 || self.items.iter().any(|it| it.outputs.len() > 0) || self.items.iter().any(|it| it.inputs.len() > 0) || self.items.last().unwrap().job.kind == JobKind::Background || self.items.last().unwrap().job.kind == JobKind::Disown } + + pub(crate) fn expand(&mut self, shell: &Shell) { + self.items.iter_mut().for_each(|i| i.expand(shell)); + } + + pub(crate) fn new() -> Self { Pipeline { items: Vec::new() } } } impl fmt::Display for Pipeline { diff --git a/src/lib/parser/quotes.rs b/src/lib/parser/quotes.rs index fe914953..10ea43c1 100644 --- a/src/lib/parser/quotes.rs +++ b/src/lib/parser/quotes.rs @@ -35,29 +35,8 @@ impl From<String> for Terminator { } impl Terminator { - pub fn new(input: String) -> Terminator { - Terminator { - buffer: input, - eof: None, - eof_buffer: String::new(), - array: 0, - read: 0, - flags: Flags::empty(), - } - } - - /// Appends a string to the internal buffer. - pub fn append(&mut self, input: &str) { - if self.eof.is_none() { - self.buffer.push_str(if self.flags.contains(Flags::TRIM) { - input.trim() - } else { - input - }); - } else { - self.eof_buffer.push_str(input); - } - } + /// Consumes the `Terminator`, and returns the underlying `String`. + pub fn consume(self) -> String { self.buffer } pub fn is_terminated(&mut self) -> bool { let mut eof_line = None; @@ -181,6 +160,27 @@ impl Terminator { status } - /// Consumes the `Terminator`, and returns the underlying `String`. - pub fn consume(self) -> String { self.buffer } + /// Appends a string to the internal buffer. + pub fn append(&mut self, input: &str) { + if self.eof.is_none() { + self.buffer.push_str(if self.flags.contains(Flags::TRIM) { + input.trim() + } else { + input + }); + } else { + self.eof_buffer.push_str(input); + } + } + + pub fn new(input: String) -> Terminator { + Terminator { + buffer: input, + eof: None, + eof_buffer: String::new(), + array: 0, + read: 0, + flags: Flags::empty(), + } + } } diff --git a/src/lib/parser/shell_expand/mod.rs b/src/lib/parser/shell_expand/mod.rs index 71e3880b..687a0be7 100644 --- a/src/lib/parser/shell_expand/mod.rs +++ b/src/lib/parser/shell_expand/mod.rs @@ -6,12 +6,10 @@ mod braces; mod ranges; mod words; -use self::braces::BraceToken; -use self::ranges::parse_range; pub(crate) use self::words::{Index, Range, Select, WordIterator, WordToken}; +use self::{braces::BraceToken, ranges::parse_range}; use glob::glob; -use std::ptr; -use std::str; +use std::{ptr, str}; use types::*; use unicode_segmentation::UnicodeSegmentation; diff --git a/src/lib/parser/shell_expand/ranges.rs b/src/lib/parser/shell_expand/ranges.rs index 114bc93a..923b5f45 100644 --- a/src/lib/parser/shell_expand/ranges.rs +++ b/src/lib/parser/shell_expand/ranges.rs @@ -142,14 +142,19 @@ pub(crate) fn parse_range(input: &str) -> Option<Vec<String>> { } else { return None; } - } + }; } macro_rules! finish { ($inclusive:expr, $read:expr) => { let end_str = &input[$read..]; if let Some((start, end)) = strings_to_isizes(first, end_str) { - return numeric_range(start, end, if start < end { 1 } else { -1 }, $inclusive); + return numeric_range( + start, + end, + if start < end { 1 } else { -1 }, + $inclusive, + ); } else { finish_char!($inclusive, end_str, 1); } diff --git a/src/lib/parser/shell_expand/words/index.rs b/src/lib/parser/shell_expand/words/index.rs index 4e25c65d..dcd9a7b6 100644 --- a/src/lib/parser/shell_expand/words/index.rs +++ b/src/lib/parser/shell_expand/words/index.rs @@ -10,6 +10,17 @@ pub(crate) enum Index { } impl Index { + pub(crate) fn resolve(&self, vector_length: usize) -> Option<usize> { + match *self { + Index::Forward(n) => Some(n), + Index::Backward(n) => if n >= vector_length { + None + } else { + Some(vector_length - (n + 1)) + }, + } + } + /// Construct an index using the following convetions: /// - A positive value `n` represents `Forward(n)` /// - A negative value `-n` reprents `Backwards(n - 1)` such that: @@ -23,15 +34,4 @@ impl Index { Index::Forward(input.abs() as usize) } } - - pub(crate) fn resolve(&self, vector_length: usize) -> Option<usize> { - match *self { - Index::Forward(n) => Some(n), - Index::Backward(n) => if n >= vector_length { - None - } else { - Some(vector_length - (n + 1)) - }, - } - } } diff --git a/src/lib/parser/shell_expand/words/methods/arrays.rs b/src/lib/parser/shell_expand/words/methods/arrays.rs index 87b632df..3c08de92 100644 --- a/src/lib/parser/shell_expand/words/methods/arrays.rs +++ b/src/lib/parser/shell_expand/words/methods/arrays.rs @@ -1,7 +1,13 @@ -use super::Pattern; -use super::strings::unescape; -use super::super::{Index, Select, SelectWithSize}; -use super::super::super::{expand_string, is_expression, Expander}; +use super::{ + super::{ + super::{expand_string, is_expression, Expander}, + Index, + Select, + SelectWithSize, + }, + strings::unescape, + Pattern, +}; use smallstring::SmallString; use std::char; use types::Array; @@ -16,54 +22,66 @@ pub(crate) struct ArrayMethod<'a> { } impl<'a> ArrayMethod<'a> { - pub(crate) fn handle<E: Expander>(&self, current: &mut String, expand_func: &E) { - let res = match self.method { - "split" => self.split(expand_func).map(|r| r.join(" ")), - _ => Err("invalid array method"), - }; - match res { - Ok(output) => current.push_str(&output), - Err(msg) => eprintln!("ion: {}: {}", self.method, msg), - } + fn reverse<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { + let mut result = self.resolve_array(expand_func); + result.reverse(); + Ok(result) } - pub(crate) fn handle_as_array<E: Expander>(&self, expand_func: &E) -> Array { - let res = match self.method { - "split" => self.split(expand_func), - "split_at" => self.split_at(expand_func), - "graphemes" => self.graphemes(expand_func), - "bytes" => self.bytes(expand_func), - "chars" => self.chars(expand_func), - "lines" => self.lines(expand_func), - "reverse" => self.reverse(expand_func), - _ => Err("invalid array method"), - }; + fn lines<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { + let variable = self.resolve_var(expand_func); + Ok(variable + .lines() + .into_iter() + .map(|line| line.to_string()) + .collect()) + } - res.unwrap_or_else(|m| { - eprintln!("ion: {}: {}", self.method, m); - array![] - }) + fn chars<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { + let variable = self.resolve_var(expand_func); + let len = variable.chars().count(); + Ok(variable + .chars() + .map(|c| c.to_string()) + .select(self.selection.clone(), len)) } - #[inline] - fn resolve_var<E: Expander>(&self, expand_func: &E) -> String { - if let Some(variable) = expand_func.variable(self.variable, false) { - variable - } else if is_expression(self.variable) { - expand_string(self.variable, expand_func, false).join(" ") - } else { - "".into() - } + fn bytes<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { + let variable = self.resolve_var(expand_func); + let len = variable.as_bytes().len(); + Ok(variable + .bytes() + .map(|b| b.to_string()) + .select(self.selection.clone(), len)) } - #[inline] - fn resolve_array<E: Expander>(&self, expand_func: &E) -> Array { - if let Some(array) = expand_func.array(self.variable, Select::All) { - array.clone() - } else if is_expression(self.variable) { - expand_string(self.variable, expand_func, false) - } else { - array![] + fn graphemes<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { + let variable = self.resolve_var(expand_func); + let graphemes: Vec<String> = UnicodeSegmentation::graphemes(variable.as_str(), true) + .map(From::from) + .collect(); + let len = graphemes.len(); + Ok(graphemes.into_iter().select(self.selection.clone(), len)) + } + + fn split_at<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { + let variable = self.resolve_var(expand_func); + match self.pattern { + Pattern::StringPattern(string) => if let Ok(value) = + expand_string(string, expand_func, false) + .join(" ") + .parse::<usize>() + { + if value < variable.len() { + let (l, r) = variable.split_at(value); + Ok(array![SmallString::from(l), SmallString::from(r)]) + } else { + Err("value is out of bounds") + } + } else { + Err("requires a valid number as an argument") + }, + Pattern::Whitespace => Err("requires an argument"), } } @@ -72,8 +90,7 @@ impl<'a> ArrayMethod<'a> { let res = match (&self.pattern, self.selection.clone()) { (_, Select::None) => Some("".into()).into_iter().collect(), (&Pattern::StringPattern(pattern), Select::All) => variable - .split(&unescape(&expand_string(pattern, expand_func, false) - .join(" "))?) + .split(&unescape(&expand_string(pattern, expand_func, false).join(" "))?) .map(From::from) .collect(), (&Pattern::Whitespace, Select::All) => variable @@ -82,8 +99,7 @@ impl<'a> ArrayMethod<'a> { .map(From::from) .collect(), (&Pattern::StringPattern(pattern), Select::Index(Index::Forward(id))) => variable - .split(&unescape(&expand_string(pattern, expand_func, false) - .join(" "))?) + .split(&unescape(&expand_string(pattern, expand_func, false).join(" "))?) .nth(id) .map(From::from) .into_iter() @@ -96,8 +112,7 @@ impl<'a> ArrayMethod<'a> { .into_iter() .collect(), (&Pattern::StringPattern(pattern), Select::Index(Index::Backward(id))) => variable - .rsplit(&unescape(&expand_string(pattern, expand_func, false) - .join(" "))?) + .rsplit(&unescape(&expand_string(pattern, expand_func, false).join(" "))?) .nth(id) .map(From::from) .into_iter() @@ -140,92 +155,81 @@ impl<'a> ArrayMethod<'a> { Ok(res) } - fn split_at<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { - let variable = self.resolve_var(expand_func); - match self.pattern { - Pattern::StringPattern(string) => if let Ok(value) = - expand_string(string, expand_func, false) - .join(" ") - .parse::<usize>() - { - if value < variable.len() { - let (l, r) = variable.split_at(value); - Ok(array![SmallString::from(l), SmallString::from(r)]) - } else { - Err("value is out of bounds") - } - } else { - Err("requires a valid number as an argument") - }, - Pattern::Whitespace => Err("requires an argument"), + #[inline] + fn resolve_array<E: Expander>(&self, expand_func: &E) -> Array { + if let Some(array) = expand_func.array(self.variable, Select::All) { + array.clone() + } else if is_expression(self.variable) { + expand_string(self.variable, expand_func, false) + } else { + array![] } } - fn graphemes<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { - let variable = self.resolve_var(expand_func); - let graphemes: Vec<String> = UnicodeSegmentation::graphemes(variable.as_str(), true) - .map(From::from) - .collect(); - let len = graphemes.len(); - Ok(graphemes.into_iter().select(self.selection.clone(), len)) - } - - fn bytes<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { - let variable = self.resolve_var(expand_func); - let len = variable.as_bytes().len(); - Ok(variable - .bytes() - .map(|b| b.to_string()) - .select(self.selection.clone(), len)) + #[inline] + fn resolve_var<E: Expander>(&self, expand_func: &E) -> String { + if let Some(variable) = expand_func.variable(self.variable, false) { + variable + } else if is_expression(self.variable) { + expand_string(self.variable, expand_func, false).join(" ") + } else { + "".into() + } } - fn chars<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { - let variable = self.resolve_var(expand_func); - let len = variable.chars().count(); - Ok(variable - .chars() - .map(|c| c.to_string()) - .select(self.selection.clone(), len)) - } + pub(crate) fn handle_as_array<E: Expander>(&self, expand_func: &E) -> Array { + let res = match self.method { + "split" => self.split(expand_func), + "split_at" => self.split_at(expand_func), + "graphemes" => self.graphemes(expand_func), + "bytes" => self.bytes(expand_func), + "chars" => self.chars(expand_func), + "lines" => self.lines(expand_func), + "reverse" => self.reverse(expand_func), + _ => Err("invalid array method"), + }; - fn lines<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { - let variable = self.resolve_var(expand_func); - Ok(variable - .lines() - .into_iter() - .map(|line| line.to_string()) - .collect()) + res.unwrap_or_else(|m| { + eprintln!("ion: {}: {}", self.method, m); + array![] + }) } - fn reverse<E: Expander>(&self, expand_func: &E) -> Result<Array, &'static str> { - let mut result = self.resolve_array(expand_func); - result.reverse(); - Ok(result) + pub(crate) fn handle<E: Expander>(&self, current: &mut String, expand_func: &E) { + let res = match self.method { + "split" => self.split(expand_func).map(|r| r.join(" ")), + _ => Err("invalid array method"), + }; + match res { + Ok(output) => current.push_str(&output), + Err(msg) => eprintln!("ion: {}: {}", self.method, msg), + } } } #[cfg(test)] mod test { - use super::*; - use super::super::Key; - use super::super::super::Range; + use super::{ + super::{super::Range, Key}, + *, + }; use types::Value; struct VariableExpander; impl Expander for VariableExpander { - fn variable(&self, variable: &str, _: bool) -> Option<Value> { + fn array(&self, variable: &str, _: Select) -> Option<Array> { match variable { - "FOO" => Some("FOOBAR".to_owned()), - "SPACEDFOO" => Some("FOO BAR".to_owned()), - "MULTILINE" => Some("FOO\nBAR".to_owned()), + "ARRAY" => Some(array!["a", "b", "c"].to_owned()), _ => None, } } - fn array(&self, variable: &str, _: Select) -> Option<Array> { + fn variable(&self, variable: &str, _: bool) -> Option<Value> { match variable { - "ARRAY" => Some(array!["a", "b", "c"].to_owned()), + "FOO" => Some("FOOBAR".to_owned()), + "SPACEDFOO" => Some("FOO BAR".to_owned()), + "MULTILINE" => Some("FOO\nBAR".to_owned()), _ => None, } } diff --git a/src/lib/parser/shell_expand/words/methods/mod.rs b/src/lib/parser/shell_expand/words/methods/mod.rs index 34d85533..f27ee7f1 100644 --- a/src/lib/parser/shell_expand/words/methods/mod.rs +++ b/src/lib/parser/shell_expand/words/methods/mod.rs @@ -1,12 +1,10 @@ mod arrays; mod strings; -pub(crate) use self::arrays::ArrayMethod; -pub(crate) use self::strings::StringMethod; +pub(crate) use self::{arrays::ArrayMethod, strings::StringMethod}; use self::strings::unescape; -use super::{expand_string, Expander}; -use super::super::super::ArgumentSplitter; +use super::{super::super::ArgumentSplitter, expand_string, Expander}; #[derive(Debug, PartialEq, Clone)] pub(crate) enum Pattern<'a> { @@ -20,9 +18,10 @@ pub(crate) struct Key { } impl Key { + pub(crate) fn get(&self) -> &::types::Key { return &self.key; } + #[cfg(test)] pub(crate) fn new<K: Into<::types::Key>>(key: K) -> Key { Key { key: key.into() } } - pub(crate) fn get(&self) -> &::types::Key { return &self.key; } } pub(crate) struct MethodArgs<'a, 'b, E: 'b + Expander> { @@ -31,8 +30,10 @@ pub(crate) struct MethodArgs<'a, 'b, E: 'b + Expander> { } impl<'a, 'b, E: 'b + Expander> MethodArgs<'a, 'b, E> { - pub(crate) fn new(args: &'a str, expand: &'b E) -> MethodArgs<'a, 'b, E> { - MethodArgs { args, expand } + pub(crate) fn array<'c>(&'c self) -> impl Iterator<Item = String> + 'c { + ArgumentSplitter::new(self.args) + .flat_map(move |x| expand_string(x, self.expand, false).into_iter()) + .map(|s| unescape(&s).unwrap_or(String::from(""))) } pub(crate) fn join(self, pattern: &str) -> String { @@ -40,9 +41,7 @@ impl<'a, 'b, E: 'b + Expander> MethodArgs<'a, 'b, E> { .unwrap_or(String::from("")) } - pub(crate) fn array<'c>(&'c self) -> impl Iterator<Item = String> + 'c { - ArgumentSplitter::new(self.args) - .flat_map(move |x| expand_string(x, self.expand, false).into_iter()) - .map(|s| unescape(&s).unwrap_or(String::from(""))) + pub(crate) fn new(args: &'a str, expand: &'b E) -> MethodArgs<'a, 'b, E> { + MethodArgs { args, expand } } } diff --git a/src/lib/parser/shell_expand/words/methods/strings.rs b/src/lib/parser/shell_expand/words/methods/strings.rs index 1da1f928..0f37e167 100644 --- a/src/lib/parser/shell_expand/words/methods/strings.rs +++ b/src/lib/parser/shell_expand/words/methods/strings.rs @@ -1,6 +1,10 @@ -use super::MethodArgs; -use super::super::Select; -use super::super::super::{expand_string, is_expression, slice, Expander}; +use super::{ + super::{ + super::{expand_string, is_expression, slice, Expander}, + Select, + }, + MethodArgs, +}; use parser::assignments::is_array; use regex::Regex; use shell::plugins::methods::{self, MethodArguments, StringMethodPlugins}; @@ -102,25 +106,35 @@ impl<'a> StringMethod<'a> { let is_true = if let Some(value) = expand.variable($variable, false) { value.$method(&pattern) } else if is_expression($variable) { - expand_string($variable, expand, false).join(" ").$method(&pattern) + expand_string($variable, expand, false) + .join(" ") + .$method(&pattern) } else { false }; output.push_str(if is_true { "1" } else { "0" }); - }} + }}; } macro_rules! path_eval { ($method:tt) => {{ if let Some(value) = expand.variable(variable, false) { - output.push_str(Path::new(&value).$method() - .and_then(|os_str| os_str.to_str()).unwrap_or(value.as_str())); + output.push_str( + Path::new(&value) + .$method() + .and_then(|os_str| os_str.to_str()) + .unwrap_or(value.as_str()), + ); } else if is_expression(variable) { let word = expand_string(variable, expand, false).join(" "); - output.push_str(Path::new(&word).$method() - .and_then(|os_str| os_str.to_str()).unwrap_or(word.as_str())); + output.push_str( + Path::new(&word) + .$method() + .and_then(|os_str| os_str.to_str()) + .unwrap_or(word.as_str()), + ); } - }} + }}; } macro_rules! string_case { @@ -131,7 +145,7 @@ impl<'a> StringMethod<'a> { let word = expand_string(variable, expand, false).join(" "); output.push_str(word.$method().as_str()); } - }} + }}; } macro_rules! get_var { @@ -141,7 +155,7 @@ impl<'a> StringMethod<'a> { } else { expand_string(variable, expand, false).join(" ") } - }} + }}; } match self.method { diff --git a/src/lib/parser/shell_expand/words/mod.rs b/src/lib/parser/shell_expand/words/mod.rs index 6eb968af..42302538 100644 --- a/src/lib/parser/shell_expand/words/mod.rs +++ b/src/lib/parser/shell_expand/words/mod.rs @@ -1,18 +1,19 @@ -#[cfg(test)] -mod tests; mod index; mod methods; mod range; mod select; +#[cfg(test)] +mod tests; -pub(crate) use self::index::Index; -pub(crate) use self::methods::{ArrayMethod, Pattern, StringMethod}; #[cfg(test)] pub(crate) use self::methods::Key; -pub(crate) use self::range::Range; -pub(crate) use self::select::{Select, SelectWithSize}; -use super::{expand_string, Expander}; -use super::super::ArgumentSplitter; +pub(crate) use self::{ + index::Index, + methods::{ArrayMethod, Pattern, StringMethod}, + range::Range, + select::{Select, SelectWithSize}, +}; +use super::{super::ArgumentSplitter, expand_string, Expander}; // Bit Twiddling Guide: // var & FLAG != 0 checks if FLAG is enabled @@ -55,189 +56,283 @@ pub(crate) struct WordIterator<'a, E: Expander + 'a> { } impl<'a, E: Expander + 'a> WordIterator<'a, E> { - pub(crate) fn new(data: &'a str, expanders: &'a E) -> WordIterator<'a, E> { - WordIterator { - data, - read: 0, - flags: Flags::empty(), - expanders, + fn arithmetic_expression<I: Iterator<Item = u8>>(&mut self, iter: &mut I) -> WordToken<'a> { + let mut paren: i8 = 0; + let start = self.read; + while let Some(character) = iter.next() { + match character { + b'(' => paren += 1, + b')' => { + if paren == 0 { + // Skip the incoming ); we have validated this syntax so it should be OK + let _ = iter.next(); + let output = &self.data[start..self.read]; + self.read += 2; + return WordToken::Arithmetic(output); + } else { + paren -= 1; + } + } + _ => (), + } + self.read += 1; } + panic!("ion: fatal syntax error: unterminated arithmetic expression"); } - // Contains the grammar for collecting whitespace characters - fn whitespaces<I>(&mut self, iterator: &mut I) -> WordToken<'a> + fn glob_check<I>(&mut self, iterator: &mut I) -> bool where - I: Iterator<Item = u8>, + I: Iterator<Item = u8> + Clone, { - let start = self.read; - self.read += 1; - while let Some(character) = iterator.next() { - if character == b' ' { - self.read += 1; - } else { - return WordToken::Whitespace(&self.data[start..self.read]); + // Clone the iterator and scan for illegal characters until the corresponding ] + // is discovered. If none are found, then it's a valid glob signature. + let mut moves = 0; + let mut glob = false; + let mut square_bracket = 0; + let mut iter = iterator.clone(); + while let Some(character) = iter.next() { + moves += 1; + match character { + b'[' => { + square_bracket += 1; + } + b' ' | b'"' | b'\'' | b'$' | b'{' | b'}' => break, + b']' => { + // If the glob is less than three bytes in width, then it's empty and thus + // invalid. If it's not adjacent to text, it's not a glob. + let next_char = iter.clone().next(); + if !(moves <= 3 && square_bracket == 1) + && (next_char != None && next_char != Some(b' ')) + { + glob = true; + break; + } + } + _ => (), } } - WordToken::Whitespace(&self.data[start..self.read]) + if glob { + for _ in 0..moves { + iterator.next(); + } + self.read += moves + 1; + true + } else { + self.read += 1; + false + } } - // Contains the logic for parsing braced variables - fn braced_variable<I>(&mut self, iterator: &mut I) -> WordToken<'a> + /// Contains the grammar for parsing array expression syntax + fn array<I>(&mut self, iterator: &mut I) -> WordToken<'a> where I: Iterator<Item = u8>, { let start = self.read; + let mut level = 0; while let Some(character) = iterator.next() { - if character == b'}' { - let output = &self.data[start..self.read]; - self.read += 1; - return WordToken::Variable(output, self.flags.contains(Flags::DQUOTE), Select::All); + match character { + _ if self.flags.contains(Flags::BACKSL) => self.flags ^= Flags::BACKSL, + b'\\' => self.flags ^= Flags::BACKSL, + b'\'' if !self.flags.contains(Flags::DQUOTE) => self.flags ^= Flags::SQUOTE, + b'"' if !self.flags.contains(Flags::SQUOTE) => self.flags ^= Flags::DQUOTE, + b'[' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) => level += 1, + b']' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) => if level == 0 { + let elements = + ArgumentSplitter::new(&self.data[start..self.read]).collect::<Vec<&str>>(); + self.read += 1; + + return if let Some(&b'[') = self.data.as_bytes().get(self.read) { + let _ = iterator.next(); + WordToken::Array(elements, self.read_selection(iterator)) + } else { + WordToken::Array(elements, Select::All) + }; + } else { + level -= 1; + }, + _ => (), } self.read += 1; } - // The validator at the frontend should catch unterminated braced variables. - panic!("ion: fatal error with syntax validation parsing: unterminated braced variable"); + panic!("ion: fatal error with syntax validation: unterminated array expression") } - /// Contains the logic for parsing variable syntax - fn variable<I>(&mut self, iterator: &mut I) -> WordToken<'a> + /// Contains the grammar for parsing brace expansion syntax + fn braces<I>(&mut self, iterator: &mut I) -> WordToken<'a> where I: Iterator<Item = u8>, { let mut start = self.read; - self.read += 1; + let mut level = 0; + let mut elements = Vec::new(); while let Some(character) = iterator.next() { match character { - b'(' => { - let method = &self.data[start..self.read]; + _ if self.flags.contains(Flags::BACKSL) => self.flags ^= Flags::BACKSL, + b'\\' => self.flags ^= Flags::BACKSL, + b'\'' if !self.flags.contains(Flags::DQUOTE) => self.flags ^= Flags::SQUOTE, + b'"' if !self.flags.contains(Flags::SQUOTE) => self.flags ^= Flags::DQUOTE, + b',' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) && level == 0 => { + elements.push(&self.data[start..self.read]); + start = self.read + 1; + } + b'{' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) => level += 1, + b'}' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) => if level == 0 { + elements.push(&self.data[start..self.read]); self.read += 1; - start = self.read; - let mut depth = 0; - while let Some(character) = iterator.next() { - match character { - b',' if depth == 0 => { - let variable = &self.data[start..self.read]; - self.read += 1; - start = self.read; - while let Some(character) = iterator.next() { - if character == b')' { - self.read += 1; - if depth != 0 { - depth -= 1; - continue; - } - let pattern = &self.data[start..self.read - 1].trim(); - return if let Some(&b'[') = - self.data.as_bytes().get(self.read) - { - let _ = iterator.next(); - WordToken::StringMethod(StringMethod { - method, - variable: variable.trim(), - pattern, - selection: self.read_selection(iterator), - }) - } else { - WordToken::StringMethod(StringMethod { - method, - variable: variable.trim(), - pattern, - selection: Select::All, - }) - }; - } else if character == b'(' { - depth += 1; - } else if character == b'\\' { - self.read += 1; - let _ = iterator.next(); - } - self.read += 1; - } - } - b')' if depth == 0 => { - // If no pattern is supplied, the default is a space. - let variable = &self.data[start..self.read]; - self.read += 1; + return WordToken::Brace(elements); + } else { + level -= 1; + }, + _ => (), + } + self.read += 1; + } - return if let Some(&b'[') = self.data.as_bytes().get(self.read) { - let _ = iterator.next(); - WordToken::StringMethod(StringMethod { - method, - variable: variable.trim(), - pattern: " ", - selection: self.read_selection(iterator), - }) - } else { - WordToken::StringMethod(StringMethod { - method, - variable: variable.trim(), - pattern: " ", - selection: Select::All, - }) - }; - } - b')' => depth -= 1, - b'(' => depth += 1, - _ => (), - } - self.read += 1; - } + panic!("ion: fatal error with syntax validation: unterminated brace") + } - panic!("ion: fatal error with syntax validation parsing: unterminated method"); + /// Contains the logic for parsing array subshell syntax. + fn array_process<I>(&mut self, iterator: &mut I) -> WordToken<'a> + where + I: Iterator<Item = u8>, + { + let start = self.read; + let mut level = 0; + while let Some(character) = iterator.next() { + match character { + _ if self.flags.contains(Flags::BACKSL) => self.flags ^= Flags::BACKSL, + b'\\' => self.flags ^= Flags::BACKSL, + b'\'' if !self.flags.contains(Flags::DQUOTE) => self.flags ^= Flags::SQUOTE, + b'"' if !self.flags.contains(Flags::SQUOTE) => self.flags ^= Flags::DQUOTE, + b'@' if !self.flags.contains(Flags::SQUOTE) => { + if self.data.as_bytes()[self.read + 1] == b'(' { + level += 1; + } } - // Only alphanumerical and underscores are allowed in variable names - 0...47 | 58...64 | 91...94 | 96 | 123...127 => { - let variable = &self.data[start..self.read]; - - return if character == b'[' { - WordToken::Variable( - variable, + b')' if !self.flags.contains(Flags::SQUOTE) => if level == 0 { + let array_process_contents = &self.data[start..self.read]; + self.read += 1; + return if let Some(&b'[') = self.data.as_bytes().get(self.read) { + let _ = iterator.next(); + WordToken::ArrayProcess( + array_process_contents, self.flags.contains(Flags::DQUOTE), self.read_selection(iterator), ) } else { - WordToken::Variable( - variable, + WordToken::ArrayProcess( + array_process_contents, self.flags.contains(Flags::DQUOTE), Select::All, ) }; - } + } else { + level -= 1; + }, _ => (), } self.read += 1; } - WordToken::Variable( - &self.data[start..], - self.flags.contains(Flags::DQUOTE), - Select::All, - ) + // The validator at the frontend should catch unterminated processes. + panic!("ion: fatal error with syntax validation: unterminated array process"); } - fn read_selection<I>(&mut self, iterator: &mut I) -> Select + /// Contains the logic for parsing subshell syntax. + fn process<I>(&mut self, iterator: &mut I) -> WordToken<'a> where I: Iterator<Item = u8>, { - self.read += 1; let start = self.read; + let mut level = 0; while let Some(character) = iterator.next() { - if let b']' = character { - let value = - expand_string(&self.data[start..self.read], self.expanders, false).join(" "); - let selection = match value.parse::<Select>() { - Ok(selection) => selection, - Err(_) => Select::None, - }; - self.read += 1; - return selection; + match character { + _ if self.flags.contains(Flags::BACKSL) => self.flags ^= Flags::BACKSL, + b'\\' => self.flags ^= Flags::BACKSL, + b'\'' if !self.flags.contains(Flags::DQUOTE) => self.flags ^= Flags::SQUOTE, + b'"' if !self.flags.contains(Flags::SQUOTE) => self.flags ^= Flags::DQUOTE, + b'$' if !self.flags.contains(Flags::SQUOTE) => { + if self.data.as_bytes()[self.read + 1] == b'(' { + level += 1; + } + } + b')' if !self.flags.contains(Flags::SQUOTE) => if level == 0 { + let output = &self.data[start..self.read]; + self.read += 1; + return if let Some(&b'[') = self.data.as_bytes().get(self.read) { + let _ = iterator.next(); + WordToken::Process( + output, + self.flags.contains(Flags::DQUOTE), + self.read_selection(iterator), + ) + } else { + WordToken::Process(output, self.flags.contains(Flags::DQUOTE), Select::All) + }; + } else { + level -= 1; + }, + _ => (), } self.read += 1; } - panic!() + // The validator at the frontend should catch unterminated processes. + panic!("ion: fatal error with syntax validation: unterminated process"); + } + + fn braced_array_variable<I>(&mut self, iterator: &mut I) -> WordToken<'a> + where + I: Iterator<Item = u8>, + { + let start = self.read; + // self.read += 1; + while let Some(character) = iterator.next() { + match character { + b'[' => { + let result = WordToken::ArrayVariable( + &self.data[start..self.read], + self.flags.contains(Flags::DQUOTE), + self.read_selection(iterator), + ); + self.read += 1; + if let Some(b'}') = iterator.next() { + return result; + } + panic!( + "ion: fatal with syntax validation error: unterminated braced array \ + expression" + ); + } + b'}' => { + let output = &self.data[start..self.read]; + self.read += 1; + return WordToken::ArrayVariable( + output, + self.flags.contains(Flags::DQUOTE), + Select::All, + ); + } + // Only alphanumerical and underscores are allowed in variable names + 0...47 | 58...64 | 91...94 | 96 | 123...127 => { + return WordToken::ArrayVariable( + &self.data[start..self.read], + self.flags.contains(Flags::DQUOTE), + Select::All, + ) + } + _ => (), + } + self.read += 1; + } + WordToken::ArrayVariable( + &self.data[start..], + self.flags.contains(Flags::DQUOTE), + Select::All, + ) } /// Contains the logic for parsing array variable syntax @@ -344,283 +439,189 @@ impl<'a, E: Expander + 'a> WordIterator<'a, E> { ) } - fn braced_array_variable<I>(&mut self, iterator: &mut I) -> WordToken<'a> + fn read_selection<I>(&mut self, iterator: &mut I) -> Select where I: Iterator<Item = u8>, { + self.read += 1; let start = self.read; - // self.read += 1; while let Some(character) = iterator.next() { - match character { - b'[' => { - let result = WordToken::ArrayVariable( - &self.data[start..self.read], - self.flags.contains(Flags::DQUOTE), - self.read_selection(iterator), - ); - self.read += 1; - if let Some(b'}') = iterator.next() { - return result; - } - panic!( - "ion: fatal with syntax validation error: unterminated braced array \ - expression" - ); - } - b'}' => { - let output = &self.data[start..self.read]; - self.read += 1; - return WordToken::ArrayVariable( - output, - self.flags.contains(Flags::DQUOTE), - Select::All, - ); - } - // Only alphanumerical and underscores are allowed in variable names - 0...47 | 58...64 | 91...94 | 96 | 123...127 => { - return WordToken::ArrayVariable( - &self.data[start..self.read], - self.flags.contains(Flags::DQUOTE), - Select::All, - ) - } - _ => (), + if let b']' = character { + let value = + expand_string(&self.data[start..self.read], self.expanders, false).join(" "); + let selection = match value.parse::<Select>() { + Ok(selection) => selection, + Err(_) => Select::None, + }; + self.read += 1; + return selection; } self.read += 1; } - WordToken::ArrayVariable( - &self.data[start..], - self.flags.contains(Flags::DQUOTE), - Select::All, - ) + + panic!() } - /// Contains the logic for parsing subshell syntax. - fn process<I>(&mut self, iterator: &mut I) -> WordToken<'a> + /// Contains the logic for parsing variable syntax + fn variable<I>(&mut self, iterator: &mut I) -> WordToken<'a> where I: Iterator<Item = u8>, { - let start = self.read; - let mut level = 0; + let mut start = self.read; + self.read += 1; while let Some(character) = iterator.next() { match character { - _ if self.flags.contains(Flags::BACKSL) => self.flags ^= Flags::BACKSL, - b'\\' => self.flags ^= Flags::BACKSL, - b'\'' if !self.flags.contains(Flags::DQUOTE) => self.flags ^= Flags::SQUOTE, - b'"' if !self.flags.contains(Flags::SQUOTE) => self.flags ^= Flags::DQUOTE, - b'$' if !self.flags.contains(Flags::SQUOTE) => { - if self.data.as_bytes()[self.read + 1] == b'(' { - level += 1; - } - } - b')' if !self.flags.contains(Flags::SQUOTE) => if level == 0 { - let output = &self.data[start..self.read]; + b'(' => { + let method = &self.data[start..self.read]; self.read += 1; - return if let Some(&b'[') = self.data.as_bytes().get(self.read) { - let _ = iterator.next(); - WordToken::Process( - output, - self.flags.contains(Flags::DQUOTE), - self.read_selection(iterator), - ) - } else { - WordToken::Process(output, self.flags.contains(Flags::DQUOTE), Select::All) - }; - } else { - level -= 1; - }, - _ => (), - } - self.read += 1; - } - - // The validator at the frontend should catch unterminated processes. - panic!("ion: fatal error with syntax validation: unterminated process"); - } + start = self.read; + let mut depth = 0; + while let Some(character) = iterator.next() { + match character { + b',' if depth == 0 => { + let variable = &self.data[start..self.read]; + self.read += 1; + start = self.read; + while let Some(character) = iterator.next() { + if character == b')' { + self.read += 1; + if depth != 0 { + depth -= 1; + continue; + } + let pattern = &self.data[start..self.read - 1].trim(); + return if let Some(&b'[') = + self.data.as_bytes().get(self.read) + { + let _ = iterator.next(); + WordToken::StringMethod(StringMethod { + method, + variable: variable.trim(), + pattern, + selection: self.read_selection(iterator), + }) + } else { + WordToken::StringMethod(StringMethod { + method, + variable: variable.trim(), + pattern, + selection: Select::All, + }) + }; + } else if character == b'(' { + depth += 1; + } else if character == b'\\' { + self.read += 1; + let _ = iterator.next(); + } + self.read += 1; + } + } + b')' if depth == 0 => { + // If no pattern is supplied, the default is a space. + let variable = &self.data[start..self.read]; + self.read += 1; - /// Contains the logic for parsing array subshell syntax. - fn array_process<I>(&mut self, iterator: &mut I) -> WordToken<'a> - where - I: Iterator<Item = u8>, - { - let start = self.read; - let mut level = 0; - while let Some(character) = iterator.next() { - match character { - _ if self.flags.contains(Flags::BACKSL) => self.flags ^= Flags::BACKSL, - b'\\' => self.flags ^= Flags::BACKSL, - b'\'' if !self.flags.contains(Flags::DQUOTE) => self.flags ^= Flags::SQUOTE, - b'"' if !self.flags.contains(Flags::SQUOTE) => self.flags ^= Flags::DQUOTE, - b'@' if !self.flags.contains(Flags::SQUOTE) => { - if self.data.as_bytes()[self.read + 1] == b'(' { - level += 1; + return if let Some(&b'[') = self.data.as_bytes().get(self.read) { + let _ = iterator.next(); + WordToken::StringMethod(StringMethod { + method, + variable: variable.trim(), + pattern: " ", + selection: self.read_selection(iterator), + }) + } else { + WordToken::StringMethod(StringMethod { + method, + variable: variable.trim(), + pattern: " ", + selection: Select::All, + }) + }; + } + b')' => depth -= 1, + b'(' => depth += 1, + _ => (), + } + self.read += 1; } + + panic!("ion: fatal error with syntax validation parsing: unterminated method"); } - b')' if !self.flags.contains(Flags::SQUOTE) => if level == 0 { - let array_process_contents = &self.data[start..self.read]; - self.read += 1; - return if let Some(&b'[') = self.data.as_bytes().get(self.read) { - let _ = iterator.next(); - WordToken::ArrayProcess( - array_process_contents, + // Only alphanumerical and underscores are allowed in variable names + 0...47 | 58...64 | 91...94 | 96 | 123...127 => { + let variable = &self.data[start..self.read]; + + return if character == b'[' { + WordToken::Variable( + variable, self.flags.contains(Flags::DQUOTE), self.read_selection(iterator), ) } else { - WordToken::ArrayProcess( - array_process_contents, + WordToken::Variable( + variable, self.flags.contains(Flags::DQUOTE), Select::All, ) }; - } else { - level -= 1; - }, + } _ => (), } self.read += 1; } - // The validator at the frontend should catch unterminated processes. - panic!("ion: fatal error with syntax validation: unterminated array process"); + WordToken::Variable( + &self.data[start..], + self.flags.contains(Flags::DQUOTE), + Select::All, + ) } - /// Contains the grammar for parsing brace expansion syntax - fn braces<I>(&mut self, iterator: &mut I) -> WordToken<'a> + // Contains the logic for parsing braced variables + fn braced_variable<I>(&mut self, iterator: &mut I) -> WordToken<'a> where I: Iterator<Item = u8>, { - let mut start = self.read; - let mut level = 0; - let mut elements = Vec::new(); + let start = self.read; while let Some(character) = iterator.next() { - match character { - _ if self.flags.contains(Flags::BACKSL) => self.flags ^= Flags::BACKSL, - b'\\' => self.flags ^= Flags::BACKSL, - b'\'' if !self.flags.contains(Flags::DQUOTE) => self.flags ^= Flags::SQUOTE, - b'"' if !self.flags.contains(Flags::SQUOTE) => self.flags ^= Flags::DQUOTE, - b',' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) && level == 0 => { - elements.push(&self.data[start..self.read]); - start = self.read + 1; - } - b'{' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) => level += 1, - b'}' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) => if level == 0 { - elements.push(&self.data[start..self.read]); - self.read += 1; - return WordToken::Brace(elements); - } else { - level -= 1; - }, - _ => (), + if character == b'}' { + let output = &self.data[start..self.read]; + self.read += 1; + return WordToken::Variable(output, self.flags.contains(Flags::DQUOTE), Select::All); } self.read += 1; } - panic!("ion: fatal error with syntax validation: unterminated brace") + // The validator at the frontend should catch unterminated braced variables. + panic!("ion: fatal error with syntax validation parsing: unterminated braced variable"); } - /// Contains the grammar for parsing array expression syntax - fn array<I>(&mut self, iterator: &mut I) -> WordToken<'a> + // Contains the grammar for collecting whitespace characters + fn whitespaces<I>(&mut self, iterator: &mut I) -> WordToken<'a> where I: Iterator<Item = u8>, { let start = self.read; - let mut level = 0; + self.read += 1; while let Some(character) = iterator.next() { - match character { - _ if self.flags.contains(Flags::BACKSL) => self.flags ^= Flags::BACKSL, - b'\\' => self.flags ^= Flags::BACKSL, - b'\'' if !self.flags.contains(Flags::DQUOTE) => self.flags ^= Flags::SQUOTE, - b'"' if !self.flags.contains(Flags::SQUOTE) => self.flags ^= Flags::DQUOTE, - b'[' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) => level += 1, - b']' if !self.flags.intersects(Flags::SQUOTE | Flags::DQUOTE) => if level == 0 { - let elements = - ArgumentSplitter::new(&self.data[start..self.read]).collect::<Vec<&str>>(); - self.read += 1; - - return if let Some(&b'[') = self.data.as_bytes().get(self.read) { - let _ = iterator.next(); - WordToken::Array(elements, self.read_selection(iterator)) - } else { - WordToken::Array(elements, Select::All) - }; - } else { - level -= 1; - }, - _ => (), - } - self.read += 1; - } - - panic!("ion: fatal error with syntax validation: unterminated array expression") - } - - fn glob_check<I>(&mut self, iterator: &mut I) -> bool - where - I: Iterator<Item = u8> + Clone, - { - // Clone the iterator and scan for illegal characters until the corresponding ] - // is discovered. If none are found, then it's a valid glob signature. - let mut moves = 0; - let mut glob = false; - let mut square_bracket = 0; - let mut iter = iterator.clone(); - while let Some(character) = iter.next() { - moves += 1; - match character { - b'[' => { - square_bracket += 1; - } - b' ' | b'"' | b'\'' | b'$' | b'{' | b'}' => break, - b']' => { - // If the glob is less than three bytes in width, then it's empty and thus - // invalid. If it's not adjacent to text, it's not a glob. - let next_char = iter.clone().next(); - if !(moves <= 3 && square_bracket == 1) - && (next_char != None && next_char != Some(b' ')) - { - glob = true; - break; - } - } - _ => (), + if character == b' ' { + self.read += 1; + } else { + return WordToken::Whitespace(&self.data[start..self.read]); } } - if glob { - for _ in 0..moves { - iterator.next(); - } - self.read += moves + 1; - true - } else { - self.read += 1; - false - } + WordToken::Whitespace(&self.data[start..self.read]) } - fn arithmetic_expression<I: Iterator<Item = u8>>(&mut self, iter: &mut I) -> WordToken<'a> { - let mut paren: i8 = 0; - let start = self.read; - while let Some(character) = iter.next() { - match character { - b'(' => paren += 1, - b')' => { - if paren == 0 { - // Skip the incoming ); we have validated this syntax so it should be OK - let _ = iter.next(); - let output = &self.data[start..self.read]; - self.read += 2; - return WordToken::Arithmetic(output); - } else { - paren -= 1; - } - } - _ => (), - } - self.read += 1; + pub(crate) fn new(data: &'a str, expanders: &'a E) -> WordIterator<'a, E> { + WordIterator { + data, + read: 0, + flags: Flags::empty(), + expanders, } - panic!("ion: fatal syntax error: unterminated arithmetic expression"); } } diff --git a/src/lib/parser/shell_expand/words/range.rs b/src/lib/parser/shell_expand/words/range.rs index 6b1a40c8..d3c779e5 100644 --- a/src/lib/parser/shell_expand/words/range.rs +++ b/src/lib/parser/shell_expand/words/range.rs @@ -13,38 +13,6 @@ pub(crate) struct Range { } impl Range { - pub(crate) fn to(end: Index) -> Range { - Range { - start: Index::new(0), - end, - inclusive: false, - } - } - - pub(crate) fn from(start: Index) -> Range { - Range { - start, - end: Index::new(-1), - inclusive: true, - } - } - - pub(crate) fn inclusive(start: Index, end: Index) -> Range { - Range { - start, - end, - inclusive: true, - } - } - - pub(crate) fn exclusive(start: Index, end: Index) -> Range { - Range { - start, - end, - inclusive: false, - } - } - /// Returns the bounds of this range as a tuple containing: /// - The starting point of the range /// - The length of the range @@ -73,4 +41,36 @@ impl Range { None } } + + pub(crate) fn exclusive(start: Index, end: Index) -> Range { + Range { + start, + end, + inclusive: false, + } + } + + pub(crate) fn inclusive(start: Index, end: Index) -> Range { + Range { + start, + end, + inclusive: true, + } + } + + pub(crate) fn from(start: Index) -> Range { + Range { + start, + end: Index::new(-1), + inclusive: true, + } + } + + pub(crate) fn to(end: Index) -> Range { + Range { + start: Index::new(0), + end, + inclusive: false, + } + } } diff --git a/src/lib/parser/shell_expand/words/select.rs b/src/lib/parser/shell_expand/words/select.rs index 0bc07bed..7cf44711 100644 --- a/src/lib/parser/shell_expand/words/select.rs +++ b/src/lib/parser/shell_expand/words/select.rs @@ -1,8 +1,8 @@ -use super::{Index, Range}; -use super::methods::Key; -use super::super::ranges::parse_index_range; -use std::iter::{empty, FromIterator}; -use std::str::FromStr; +use super::{super::ranges::parse_index_range, methods::Key, Index, Range}; +use std::{ + iter::{empty, FromIterator}, + str::FromStr, +}; /// Represents a filter on a vector-like object #[derive(Debug, PartialEq, Clone)] @@ -31,6 +31,7 @@ where I: Iterator<Item = T>, { type Item = T; + fn select<O>(&mut self, s: Select, size: usize) -> O where O: FromIterator<Self::Item>, @@ -54,6 +55,7 @@ where impl FromStr for Select { type Err = (); + fn from_str(data: &str) -> Result<Select, ()> { if ".." == data { return Ok(Select::All); diff --git a/src/lib/parser/statement/functions.rs b/src/lib/parser/statement/functions.rs index 3bd6a01c..52a1623e 100644 --- a/src/lib/parser/statement/functions.rs +++ b/src/lib/parser/statement/functions.rs @@ -1,5 +1,7 @@ -use super::split_pattern; -use super::super::assignments::{KeyBuf, KeyIterator, TypeError}; +use super::{ + super::assignments::{KeyBuf, KeyIterator, TypeError}, + split_pattern, +}; /// The arguments expression given to a function declaration goes into here, which will be /// converted into a tuple consisting of a `KeyIterator` iterator, which will collect type @@ -19,8 +21,10 @@ pub(crate) fn collect_arguments<'a>(args: KeyIterator<'a>) -> Result<Vec<KeyBuf> #[cfg(test)] mod tests { - use super::*; - use super::super::super::assignments::{KeyBuf, Primitive}; + use super::{ + super::super::assignments::{KeyBuf, Primitive}, + *, + }; #[test] fn function_parsing() { diff --git a/src/lib/parser/statement/mod.rs b/src/lib/parser/statement/mod.rs index 476ee5aa..35611320 100644 --- a/src/lib/parser/statement/mod.rs +++ b/src/lib/parser/statement/mod.rs @@ -3,8 +3,10 @@ mod functions; mod parse; mod splitter; -pub(crate) use self::parse::parse; -pub(crate) use self::splitter::{StatementError, StatementSplitter}; +pub(crate) use self::{ + parse::parse, + splitter::{StatementError, StatementSplitter}, +}; use shell::flow_control::Statement; /// Parses a given statement string and return's the corresponding mapped diff --git a/src/lib/parser/statement/parse.rs b/src/lib/parser/statement/parse.rs index 5e7c9da6..4a28c826 100644 --- a/src/lib/parser/statement/parse.rs +++ b/src/lib/parser/statement/parse.rs @@ -1,8 +1,12 @@ -use super::case; -use super::functions::{collect_arguments, parse_function}; -use super::super::{pipelines, ArgumentSplitter}; -use super::super::assignments::{split_assignment, Operator}; -use super::super::pipelines::Pipeline; +use super::{ + super::{ + assignments::{split_assignment, Operator}, + pipelines::{self, Pipeline}, + ArgumentSplitter, + }, + case, + functions::{collect_arguments, parse_function}, +}; use shell::flow_control::{Case, ElseIf, ExportAction, LocalAction, Statement}; use std::char; @@ -223,9 +227,7 @@ pub(crate) fn parse(code: &str) -> Statement { return Statement::And(Box::new(parse(cmd[3..].trim_left()))) } _ if cmd.eq("and") => return Statement::And(Box::new(Statement::Default)), - _ if cmd.starts_with("or ") => { - return Statement::Or(Box::new(parse(cmd[2..].trim_left()))) - } + _ if cmd.starts_with("or ") => return Statement::Or(Box::new(parse(cmd[2..].trim_left()))), _ if cmd.eq("or") => return Statement::Or(Box::new(Statement::Default)), _ if cmd.starts_with("not ") => { return Statement::Not(Box::new(parse(cmd[3..].trim_left()))) @@ -246,8 +248,7 @@ mod tests { use self::pipelines::PipeItem; use super::*; use parser::assignments::{KeyBuf, Primitive}; - use shell::{Job, JobKind}; - use shell::flow_control::Statement; + use shell::{flow_control::Statement, Job, JobKind}; #[test] fn parsing_ifs() { @@ -255,22 +256,20 @@ mod tests { let parsed_if = parse("if test 1 -eq 2"); let correct_parse = Statement::If { expression: Pipeline { - items: vec![ - PipeItem { - job: Job::new( - vec![ - "test".to_owned(), - "1".to_owned(), - "-eq".to_owned(), - "2".to_owned(), - ].into_iter() - .collect(), - JobKind::Last, - ), - outputs: Vec::new(), - inputs: Vec::new(), - }, - ], + items: vec![PipeItem { + job: Job::new( + vec![ + "test".to_owned(), + "1".to_owned(), + "-eq".to_owned(), + "2".to_owned(), + ].into_iter() + .collect(), + JobKind::Last, + ), + outputs: Vec::new(), + inputs: Vec::new(), + }], }, success: vec![], else_if: vec![], diff --git a/src/lib/parser/statement/splitter.rs b/src/lib/parser/statement/splitter.rs index c59c3ff6..2651ccb3 100644 --- a/src/lib/parser/statement/splitter.rs +++ b/src/lib/parser/statement/splitter.rs @@ -2,9 +2,11 @@ // - Rewrite this in the same style as shell_expand::words. // - Validate syntax in methods -use std::fmt::{self, Display, Formatter}; -use std::u16; -use std::cmp::max; +use std::{ + cmp::max, + fmt::{self, Display, Formatter}, + u16, +}; bitflags! { pub struct Flags : u16 { @@ -80,19 +82,6 @@ pub(crate) struct StatementSplitter { } impl<'a> StatementSplitter { - pub(crate) fn new(data: String) -> StatementSplitter { - StatementSplitter { - data: data, - read: 0, - flags: Flags::empty(), - a_level: 0, - ap_level: 0, - p_level: 0, - brace_level: 0, - math_paren_level: 0, - } - } - fn single_quote<B: Iterator<Item = u8>>(&mut self, bytes: &mut B) -> usize { let mut read = 0; while let Some(character) = bytes.next() { @@ -106,10 +95,24 @@ impl<'a> StatementSplitter { } read } + + pub(crate) fn new(data: String) -> StatementSplitter { + StatementSplitter { + data, + read: 0, + flags: Flags::empty(), + a_level: 0, + ap_level: 0, + p_level: 0, + brace_level: 0, + math_paren_level: 0, + } + } } impl Iterator for StatementSplitter { type Item = Result<String, StatementError>; + fn next(&mut self) -> Option<Result<String, StatementError>> { let start = self.read; let mut first_arg_found = false; @@ -251,10 +254,12 @@ impl Iterator for StatementSplitter { self.read = 0; return match error { Some(error) => Some(Err(error)), - None => Some(Ok(String::from(statement[..max(statement.len()-2,0)].trim()))), - } + None => Some(Ok(String::from( + statement[..max(statement.len() - 2, 0)].trim(), + ))), + }; } - _ => () + _ => (), } } } @@ -272,10 +277,12 @@ impl Iterator for StatementSplitter { self.read = 0; return match error { Some(error) => Some(Err(error)), - None => Some(Ok(String::from(statement[..max(statement.len()-2,0)].trim()))), - } + None => Some(Ok(String::from( + statement[..max(statement.len() - 2, 0)].trim(), + ))), + }; } - _ => () + _ => (), } } } @@ -358,9 +365,9 @@ impl Iterator for StatementSplitter { } b'|' => Some(Err(StatementError::ExpectedCommandButFound("pipe"))), b'&' => Some(Err(StatementError::ExpectedCommandButFound("&"))), - b'*' | b'%' | b'?' | b'{' | b'}' => { - Some(Err(StatementError::IllegalCommandName(String::from(output)))) - } + b'*' | b'%' | b'?' | b'{' | b'}' => Some(Err( + StatementError::IllegalCommandName(String::from(output)), + )), _ => Some(Ok(String::from(output))), } } @@ -372,7 +379,8 @@ impl Iterator for StatementSplitter { #[test] fn syntax_errors() { let command = "echo (echo one); echo $( (echo one); echo ) two; echo $(echo one"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!(results[0], Err(StatementError::InvalidCharacter('(', 6))); assert_eq!(results[1], Err(StatementError::InvalidCharacter('(', 26))); assert_eq!(results[2], Err(StatementError::InvalidCharacter(')', 43))); @@ -380,7 +388,8 @@ fn syntax_errors() { assert_eq!(results.len(), 4); let command = ">echo"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!( results[0], Err(StatementError::ExpectedCommandButFound("redirection")) @@ -429,16 +438,21 @@ fn process_with_statements() { #[test] fn quotes() { let command = "echo \"This ;'is a test\"; echo 'This ;\" is also a test'"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!(results.len(), 2); assert_eq!(results[0], Ok(String::from("echo \"This ;'is a test\""))); - assert_eq!(results[1], Ok(String::from("echo 'This ;\" is also a test'"))); + assert_eq!( + results[1], + Ok(String::from("echo 'This ;\" is also a test'")) + ); } #[test] fn comments() { let command = "echo $(echo one # two); echo three # four"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!(results.len(), 2); assert_eq!(results[0], Ok(String::from("echo $(echo one # two)"))); assert_eq!(results[1], Ok(String::from("echo three"))); @@ -447,12 +461,14 @@ fn comments() { #[test] fn nested_process() { let command = "echo $(echo one $(echo two) three)"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!(results.len(), 1); assert_eq!(results[0], Ok(String::from(command))); let command = "echo $(echo $(echo one; echo two); echo two)"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!(results.len(), 1); assert_eq!(results[0], Ok(String::from(command))); } @@ -460,12 +476,14 @@ fn nested_process() { #[test] fn nested_array_process() { let command = "echo @(echo one @(echo two) three)"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!(results.len(), 1); assert_eq!(results[0], Ok(String::from(command))); let command = "echo @(echo @(echo one; echo two); echo two)"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!(results.len(), 1); assert_eq!(results[0], Ok(String::from(command))); } @@ -473,7 +491,8 @@ fn nested_array_process() { #[test] fn braced_variables() { let command = "echo ${foo}bar ${bar}baz ${baz}quux @{zardoz}wibble"; - let results = StatementSplitter::new(String::from(command)).collect::<Vec<Result<String, StatementError>>>(); + let results = StatementSplitter::new(String::from(command)) + .collect::<Vec<Result<String, StatementError>>>(); assert_eq!(results.len(), 1); assert_eq!(results, vec![Ok(String::from(command))]); } diff --git a/src/lib/shell/assignments.rs b/src/lib/shell/assignments.rs index a021d78d..faf5707b 100644 --- a/src/lib/shell/assignments.rs +++ b/src/lib/shell/assignments.rs @@ -1,16 +1,20 @@ -use super::Shell; -use super::flow_control::{ExportAction, LocalAction}; -use super::status::*; +use super::{ + flow_control::{ExportAction, LocalAction}, + status::*, + Shell, +}; use itoa; use parser::assignments::*; use shell::history::ShellHistory; -use std::env; -use std::ffi::OsStr; -use std::fmt::{self, Display}; -use std::io::{self, BufWriter, Write}; -use std::mem; -use std::os::unix::ffi::OsStrExt; -use std::str; +use std::{ + env, + ffi::OsStr, + fmt::{self, Display}, + io::{self, BufWriter, Write}, + mem, + os::unix::ffi::OsStrExt, + str, +}; fn list_vars(shell: &Shell) { let stdout = io::stdout(); @@ -58,27 +62,34 @@ pub(crate) trait VariableStore { } impl VariableStore for Shell { - fn local(&mut self, action: LocalAction) -> i32 { + fn export(&mut self, action: ExportAction) -> i32 { let actions = match action { - LocalAction::List => { - list_vars(&self); + ExportAction::Assign(ref keys, op, ref vals) => AssignmentActions::new(keys, op, vals), + ExportAction::LocalExport(ref key) => match self.get_var(key) { + Some(var) => { + env::set_var(key, &var); + return SUCCESS; + } + None => { + eprintln!("ion: cannot export {} because it does not exist.", key); + return FAILURE; + } + }, + ExportAction::List => { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + for (key, val) in env::vars() { + let _ = writeln!(stdout, "{} =\"{}\"", key, val); + } return SUCCESS; } - LocalAction::Assign(ref keys, op, ref vals) => AssignmentActions::new(keys, op, vals), }; + for action in actions { match action { Ok(Action::UpdateArray(key, Operator::Equal, expression)) => { match value_check(self, &expression, key.kind) { - Ok(ReturnValue::Vector(values)) => { - // When we changed the HISTORY_IGNORE variable, update the - // ignore patterns. This happens first because `set_array` - // consumes 'values' - if key.name == "HISTORY_IGNORE" { - self.update_ignore_patterns(&values); - } - self.variables.set_array(key.name, values) - } + Ok(ReturnValue::Vector(values)) => env::set_var(key.name, values.join(" ")), Err(why) => { eprintln!("ion: assignment error: {}: {}", key.name, why); return FAILURE; @@ -93,11 +104,6 @@ impl VariableStore for Shell { return FAILURE; } Ok(Action::UpdateString(key, operator, expression)) => { - if ["HOME", "PWD", "MWD", "SWD", "?"].contains(&key.name) { - eprintln!("ion: not allowed to set {}", key.name); - return FAILURE; - } - match value_check(self, &expression, key.kind) { Ok(ReturnValue::Str(value)) => { let key_name: &str = &key.name; @@ -105,14 +111,11 @@ impl VariableStore for Shell { .variables .get(key_name) .map(|x| x.as_str()) - .unwrap_or("0") as *const str; + .unwrap_or("0"); - let result = - math(unsafe { &*lhs }, key.kind, operator, &value, |value| { - self.set_var(key_name, unsafe { - str::from_utf8_unchecked(value) - }) - }); + let result = math(&lhs, key.kind, operator, &value, |value| { + env::set_var(key_name, &OsStr::from_bytes(value)) + }); if let Err(why) = result { eprintln!("ion: assignment error: {}", why); @@ -136,34 +139,27 @@ impl VariableStore for Shell { SUCCESS } - fn export(&mut self, action: ExportAction) -> i32 { + fn local(&mut self, action: LocalAction) -> i32 { let actions = match action { - ExportAction::Assign(ref keys, op, ref vals) => AssignmentActions::new(keys, op, vals), - ExportAction::LocalExport(ref key) => match self.get_var(key) { - Some(var) => { - env::set_var(key, &var); - return SUCCESS; - } - None => { - eprintln!("ion: cannot export {} because it does not exist.", key); - return FAILURE; - } - }, - ExportAction::List => { - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - for (key, val) in env::vars() { - let _ = writeln!(stdout, "{} =\"{}\"", key, val); - } + LocalAction::List => { + list_vars(&self); return SUCCESS; } + LocalAction::Assign(ref keys, op, ref vals) => AssignmentActions::new(keys, op, vals), }; - for action in actions { match action { Ok(Action::UpdateArray(key, Operator::Equal, expression)) => { match value_check(self, &expression, key.kind) { - Ok(ReturnValue::Vector(values)) => env::set_var(key.name, values.join(" ")), + Ok(ReturnValue::Vector(values)) => { + // When we changed the HISTORY_IGNORE variable, update the + // ignore patterns. This happens first because `set_array` + // consumes 'values' + if key.name == "HISTORY_IGNORE" { + self.update_ignore_patterns(&values); + } + self.variables.set_array(key.name, values) + } Err(why) => { eprintln!("ion: assignment error: {}: {}", key.name, why); return FAILURE; @@ -178,6 +174,11 @@ impl VariableStore for Shell { return FAILURE; } Ok(Action::UpdateString(key, operator, expression)) => { + if ["HOME", "PWD", "MWD", "SWD", "?"].contains(&key.name) { + eprintln!("ion: not allowed to set {}", key.name); + return FAILURE; + } + match value_check(self, &expression, key.kind) { Ok(ReturnValue::Str(value)) => { let key_name: &str = &key.name; @@ -185,11 +186,14 @@ impl VariableStore for Shell { .variables .get(key_name) .map(|x| x.as_str()) - .unwrap_or("0"); + .unwrap_or("0") as *const str; - let result = math(&lhs, key.kind, operator, &value, |value| { - env::set_var(key_name, &OsStr::from_bytes(value)) - }); + let result = + math(unsafe { &*lhs }, key.kind, operator, &value, |value| { + self.set_var(key_name, unsafe { + str::from_utf8_unchecked(value) + }) + }); if let Err(why) = result { eprintln!("ion: assignment error: {}", why); diff --git a/src/lib/shell/binary/designators.rs b/src/lib/shell/binary/designators.rs index 7ef640d6..fddf0cad 100644 --- a/src/lib/shell/binary/designators.rs +++ b/src/lib/shell/binary/designators.rs @@ -1,7 +1,6 @@ use parser::ArgumentSplitter; use shell::Shell; -use std::borrow::Cow; -use std::str; +use std::{borrow::Cow, str}; bitflags! { struct Flags: u8 { @@ -23,18 +22,18 @@ struct DesignatorSearcher<'a> { } impl<'a> DesignatorSearcher<'a> { + fn grab_and_shorten(&mut self, id: usize) -> &'a str { + let output = unsafe { str::from_utf8_unchecked(&self.data[..id]) }; + self.data = &self.data[id..]; + output + } + fn new(data: &'a [u8]) -> DesignatorSearcher { DesignatorSearcher { data, flags: Flags::empty(), } } - - fn grab_and_shorten(&mut self, id: usize) -> &'a str { - let output = unsafe { str::from_utf8_unchecked(&self.data[..id]) }; - self.data = &self.data[id..]; - output - } } impl<'a> Iterator for DesignatorSearcher<'a> { diff --git a/src/lib/shell/binary/mod.rs b/src/lib/shell/binary/mod.rs index e7c5eaf5..785bc2b1 100644 --- a/src/lib/shell/binary/mod.rs +++ b/src/lib/shell/binary/mod.rs @@ -4,19 +4,14 @@ mod prompt; mod readln; mod terminate; -use self::prompt::{prompt, prompt_fn}; -use self::readln::readln; -use self::terminate::{terminate_quotes, terminate_script_quotes}; -use super::{FlowLogic, Shell, ShellHistory}; -use super::flow_control::Statement; -use super::status::*; +use self::{ + prompt::{prompt, prompt_fn}, + readln::readln, + terminate::{terminate_quotes, terminate_script_quotes}, +}; +use super::{flow_control::Statement, status::*, FlowLogic, Shell, ShellHistory}; use liner::{Buffer, Context}; -use std::env; -use std::fs::File; -use std::io::ErrorKind; -use std::iter; -use std::path::Path; -use std::process; +use std::{env, fs::File, io::ErrorKind, iter, path::Path, process}; pub const MAN_ION: &'static str = r#"NAME ion - ion shell @@ -65,45 +60,38 @@ pub trait Binary { } impl Binary for Shell { - fn prompt(&mut self) -> String { prompt(self) } - - fn prompt_fn(&mut self) -> Option<String> { prompt_fn(self) } - - fn readln(&mut self) -> Option<String> { readln(self) } - - fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, lines: I) -> i32 { - terminate_script_quotes(self, lines) - } - - fn terminate_quotes(&mut self, command: String) -> Result<String, ()> { - terminate_quotes(self, command) + fn display_version(&self) { + println!("{}", include!(concat!(env!("OUT_DIR"), "/version_string"))); + process::exit(0); } - fn execute_arguments<A: Iterator<Item = String>>(&mut self, mut args: A) { - if let Some(mut arg) = args.next() { - for argument in args { - arg.push(' '); - if argument == "" { - arg.push_str("''"); - } else { - arg.push_str(&argument); - } + fn save_command(&mut self, cmd: &str) { + if cmd.starts_with('~') { + if !cmd.ends_with('/') + && self.variables + .tilde_expansion(cmd, &self.directory_stack) + .map_or(false, |ref path| Path::new(path).is_dir()) + { + self.save_command_in_history(&[cmd, "/"].concat()); + } else { + self.save_command_in_history(cmd); } - self.on_command(&arg); - } else { - eprintln!("ion: -c requires an argument"); - self.exit(FAILURE); + return; } - if self.flow_control.level != 0 { - eprintln!( - "ion: unexpected end of arguments: expected end block for `{}`", - self.flow_control.current_statement.short() - ); - self.exit(FAILURE); + if Path::new(cmd).is_dir() & !cmd.ends_with('/') { + self.save_command_in_history(&[cmd, "/"].concat()); + } else { + self.save_command_in_history(cmd); } } + fn reset_flow(&mut self) { + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + self.flow_control.current_statement = Statement::Default; + } + fn execute_interactive(mut self) { self.context = Some({ let mut context = Context::new(); @@ -158,37 +146,44 @@ impl Binary for Shell { } } - fn reset_flow(&mut self) { - self.flow_control.level = 0; - self.flow_control.current_if_mode = 0; - self.flow_control.current_statement = Statement::Default; - } - - fn save_command(&mut self, cmd: &str) { - if cmd.starts_with('~') { - if !cmd.ends_with('/') - && self.variables - .tilde_expansion(cmd, &self.directory_stack) - .map_or(false, |ref path| Path::new(path).is_dir()) - { - self.save_command_in_history(&[cmd, "/"].concat()); - } else { - self.save_command_in_history(cmd); + fn execute_arguments<A: Iterator<Item = String>>(&mut self, mut args: A) { + if let Some(mut arg) = args.next() { + for argument in args { + arg.push(' '); + if argument == "" { + arg.push_str("''"); + } else { + arg.push_str(&argument); + } } - return; + self.on_command(&arg); + } else { + eprintln!("ion: -c requires an argument"); + self.exit(FAILURE); } - if Path::new(cmd).is_dir() & !cmd.ends_with('/') { - self.save_command_in_history(&[cmd, "/"].concat()); - } else { - self.save_command_in_history(cmd); + if self.flow_control.level != 0 { + eprintln!( + "ion: unexpected end of arguments: expected end block for `{}`", + self.flow_control.current_statement.short() + ); + self.exit(FAILURE); } } - fn display_version(&self) { - println!("{}", include!(concat!(env!("OUT_DIR"), "/version_string"))); - process::exit(0); + fn terminate_quotes(&mut self, command: String) -> Result<String, ()> { + terminate_quotes(self, command) + } + + fn terminate_script_quotes<I: Iterator<Item = String>>(&mut self, lines: I) -> i32 { + terminate_script_quotes(self, lines) } + + fn readln(&mut self) -> Option<String> { readln(self) } + + fn prompt_fn(&mut self) -> Option<String> { prompt_fn(self) } + + fn prompt(&mut self) -> String { prompt(self) } } // TODO: Convert this into an iterator to eliminate heap allocations. @@ -208,7 +203,7 @@ fn word_divide(buf: &Buffer) -> Vec<(usize, usize)> { word_start = Some($index); } } - }} + }}; } let mut iter = buf.chars().enumerate(); diff --git a/src/lib/shell/binary/prompt.rs b/src/lib/shell/binary/prompt.rs index 65a36841..3f7288e7 100644 --- a/src/lib/shell/binary/prompt.rs +++ b/src/lib/shell/binary/prompt.rs @@ -1,7 +1,6 @@ use super::super::{Capture, Function, Shell}; use parser::shell_expand::expand_string; -use std::io::Read; -use std::process; +use std::{io::Read, process}; use sys; pub(crate) fn prompt(shell: &mut Shell) -> String { diff --git a/src/lib/shell/binary/readln.rs b/src/lib/shell/binary/readln.rs index 8f6f2899..9ac6f078 100644 --- a/src/lib/shell/binary/readln.rs +++ b/src/lib/shell/binary/readln.rs @@ -1,11 +1,12 @@ -use super::super::{Binary, DirectoryStack, Shell, Variables}; -use super::super::completer::*; +use super::super::{completer::*, Binary, DirectoryStack, Shell, Variables}; use liner::{BasicCompleter, CursorPosition, Event, EventKind}; use smallstring::SmallString; -use std::env; -use std::io::{self, ErrorKind, Write}; -use std::mem; -use std::path::PathBuf; +use std::{ + env, + io::{self, ErrorKind, Write}, + mem, + path::PathBuf, +}; use sys; use types::*; diff --git a/src/lib/shell/binary/terminate.rs b/src/lib/shell/binary/terminate.rs index 3ef4658c..40916288 100644 --- a/src/lib/shell/binary/terminate.rs +++ b/src/lib/shell/binary/terminate.rs @@ -1,5 +1,4 @@ -use super::super::{Binary, FlowLogic, Shell}; -use super::super::status::*; +use super::super::{status::*, Binary, FlowLogic, Shell}; use parser::Terminator; pub(crate) fn terminate_script_quotes<I: Iterator<Item = String>>( diff --git a/src/lib/shell/colors.rs b/src/lib/shell/colors.rs index 91e790cf..88a542a9 100644 --- a/src/lib/shell/colors.rs +++ b/src/lib/shell/colors.rs @@ -87,48 +87,43 @@ pub(crate) struct Colors { } impl Colors { - /// Parses the given input and returns a structure obtaining the text data needed for proper - /// transformation into ANSI code parameters, which may be obtained by calling the - /// `into_string()` method on the newly-created `Colors` structure. - pub(crate) fn collect(input: &str) -> Colors { - let mut colors = Colors { - foreground: None, - background: None, - attributes: None, - }; - for variable in input.split(",") { - if variable == "reset" { - return Colors { - foreground: None, - background: None, - attributes: Some(vec!["0"]), - }; - } else if let Some(attribute) = ATTRIBUTES.get(&variable) { - colors.append_attribute(attribute); - } else if let Some(color) = COLORS.get(&variable) { - colors.foreground = Some(Mode::Name(color)); - } else if let Some(color) = BG_COLORS.get(&variable) { - colors.background = Some(Mode::Name(color)); - } else if !colors.parse_colors(variable) { - eprintln!("ion: {} is not a valid color", variable) + /// Attempts to transform the data in the structure into the corresponding ANSI code + /// representation. It would very ugly to require shell scripters to have to interface + /// with these codes directly. + pub(crate) fn into_string(self) -> Option<String> { + let mut output = String::from("\x1b["); + + let foreground = match self.foreground { + Some(Mode::Name(string)) => Some(string.to_owned()), + Some(Mode::Range256(value)) => Some(format!("38;5;{}", value)), + Some(Mode::TrueColor(red, green, blue)) => { + Some(format!("38;2;{};{};{}", red, green, blue)) } - } - colors - } + None => None, + }; - /// Attributes can be stacked, so this function serves to enable that - /// stacking. - fn append_attribute(&mut self, attribute: &'static str) { - let vec_exists = match self.attributes.as_mut() { - Some(vec) => { - vec.push(attribute); - true + let background = match self.background { + Some(Mode::Name(string)) => Some(string.to_owned()), + Some(Mode::Range256(value)) => Some(format!("48;5;{}", value)), + Some(Mode::TrueColor(red, green, blue)) => { + Some(format!("48;2;{};{};{}", red, green, blue)) } - None => false, + None => None, }; - if !vec_exists { - self.attributes = Some(vec![attribute]); + if let Some(attr) = self.attributes { + output.push_str(&attr.join(";")); + match (foreground, background) { + (Some(c), None) | (None, Some(c)) => Some([&output, ";", &c, "m"].concat()), + (None, None) => Some([&output, "m"].concat()), + (Some(fg), Some(bg)) => Some([&output, ";", &fg, ";", &bg, "m"].concat()), + } + } else { + match (foreground, background) { + (Some(c), None) | (None, Some(c)) => Some([&output, &c, "m"].concat()), + (None, None) => None, + (Some(fg), Some(bg)) => Some([&output, &fg, ";", &bg, "m"].concat()), + } } } @@ -186,44 +181,49 @@ impl Colors { false } - /// Attempts to transform the data in the structure into the corresponding ANSI code - /// representation. It would very ugly to require shell scripters to have to interface - /// with these codes directly. - pub(crate) fn into_string(self) -> Option<String> { - let mut output = String::from("\x1b["); - - let foreground = match self.foreground { - Some(Mode::Name(string)) => Some(string.to_owned()), - Some(Mode::Range256(value)) => Some(format!("38;5;{}", value)), - Some(Mode::TrueColor(red, green, blue)) => { - Some(format!("38;2;{};{};{}", red, green, blue)) + /// Attributes can be stacked, so this function serves to enable that + /// stacking. + fn append_attribute(&mut self, attribute: &'static str) { + let vec_exists = match self.attributes.as_mut() { + Some(vec) => { + vec.push(attribute); + true } - None => None, + None => false, }; - let background = match self.background { - Some(Mode::Name(string)) => Some(string.to_owned()), - Some(Mode::Range256(value)) => Some(format!("48;5;{}", value)), - Some(Mode::TrueColor(red, green, blue)) => { - Some(format!("48;2;{};{};{}", red, green, blue)) - } - None => None, - }; + if !vec_exists { + self.attributes = Some(vec![attribute]); + } + } - if let Some(attr) = self.attributes { - output.push_str(&attr.join(";")); - match (foreground, background) { - (Some(c), None) | (None, Some(c)) => Some([&output, ";", &c, "m"].concat()), - (None, None) => Some([&output, "m"].concat()), - (Some(fg), Some(bg)) => Some([&output, ";", &fg, ";", &bg, "m"].concat()), - } - } else { - match (foreground, background) { - (Some(c), None) | (None, Some(c)) => Some([&output, &c, "m"].concat()), - (None, None) => None, - (Some(fg), Some(bg)) => Some([&output, &fg, ";", &bg, "m"].concat()), + /// Parses the given input and returns a structure obtaining the text data needed for proper + /// transformation into ANSI code parameters, which may be obtained by calling the + /// `into_string()` method on the newly-created `Colors` structure. + pub(crate) fn collect(input: &str) -> Colors { + let mut colors = Colors { + foreground: None, + background: None, + attributes: None, + }; + for variable in input.split(",") { + if variable == "reset" { + return Colors { + foreground: None, + background: None, + attributes: Some(vec!["0"]), + }; + } else if let Some(attribute) = ATTRIBUTES.get(&variable) { + colors.append_attribute(attribute); + } else if let Some(color) = COLORS.get(&variable) { + colors.foreground = Some(Mode::Name(color)); + } else if let Some(color) = BG_COLORS.get(&variable) { + colors.background = Some(Mode::Name(color)); + } else if !colors.parse_colors(variable) { + eprintln!("ion: {} is not a valid color", variable) } } + colors } } diff --git a/src/lib/shell/completer.rs b/src/lib/shell/completer.rs index 8df962e3..5b0ec081 100644 --- a/src/lib/shell/completer.rs +++ b/src/lib/shell/completer.rs @@ -1,5 +1,4 @@ -use super::directory_stack::DirectoryStack; -use super::variables::Variables; +use super::{directory_stack::DirectoryStack, variables::Variables}; use liner::{Completer, FilenameCompleter}; /// Performs escaping to an inner `FilenameCompleter` to enable a handful of special cases @@ -22,8 +21,8 @@ impl IonFileCompleter { ) -> IonFileCompleter { IonFileCompleter { inner: FilenameCompleter::new(path), - dir_stack: dir_stack, - vars: vars, + dir_stack, + vars, } } } @@ -45,8 +44,7 @@ impl Completer for IonFileCompleter { // 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); @@ -151,7 +149,7 @@ where A: Completer, B: Completer, { - pub(crate) fn new(a: Vec<A>, b: B) -> MultiCompleter<A, B> { MultiCompleter { a: a, b: b } } + pub(crate) fn new(a: Vec<A>, b: B) -> MultiCompleter<A, B> { MultiCompleter { a, b } } } impl<A, B> Completer for MultiCompleter<A, B> diff --git a/src/lib/shell/directory_stack.rs b/src/lib/shell/directory_stack.rs index 9628de29..2eea62f3 100644 --- a/src/lib/shell/directory_stack.rs +++ b/src/lib/shell/directory_stack.rs @@ -1,175 +1,243 @@ -use std::borrow::Cow; -use std::collections::VecDeque; -use std::env::{current_dir, home_dir, set_current_dir}; -use std::path::PathBuf; -use super::status::{FAILURE, SUCCESS}; -use super::variables::Variables; +use super::{ + status::{FAILURE, SUCCESS}, + variables::Variables, +}; +use std::{ + borrow::Cow, + collections::VecDeque, + env::{current_dir, home_dir, set_current_dir}, + path::PathBuf, +}; pub struct DirectoryStack { dirs: VecDeque<PathBuf>, // The top is always the current directory } impl DirectoryStack { - /// Create a new `DirectoryStack` containing the current working directory, - /// if available. - pub(crate) fn new() -> DirectoryStack { - let mut dirs: VecDeque<PathBuf> = VecDeque::new(); - match current_dir() { - 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 } - } + fn normalize_path(&mut self, dir: &str) -> PathBuf { + use std::path::{Component, Path}; + // Create a clone of the current directory. + let mut new_dir = match self.dirs.front() { + Some(cur_dir) => cur_dir.clone(), + None => PathBuf::new(), + }; + + // Iterate through components of the specified directory + // and calculate the new path based on them. + for component in Path::new(dir).components() { + match component { + Component::CurDir => {} + Component::ParentDir => { + new_dir.pop(); + } + _ => { + new_dir.push(component); + } + }; } + + return new_dir; } - /// This function will take a map of variables as input and attempt to parse the value of - /// the - /// 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) + // pushd -<num> + fn rotate_right(&mut self, num: usize) { + let len = self.dirs.len(); + self.rotate_left(len - (num % len)); } - /// Attempts to set the current directory to the directory stack's previous directory, - /// and then removes the front directory from the stack. - pub(crate) fn popd<I: IntoIterator>(&mut self, args: I, variables: &mut Variables) -> Result<(), Cow<'static, str>> + // 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(); + } + } + + // 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 + )) + })?; + + set_current_dir(dir) + .map_err(|_| Cow::Owned(format!("ion: {}: Failed setting current dir\n", caller))) + } + + fn print_dirs(&self) { + let dir = self.dirs.iter().fold(String::new(), |acc, dir| { + acc + " " + dir.to_str().unwrap_or("ion: no directory found") + }); + println!("{}", dir.trim_left()); + } + + pub(crate) fn dir_from_bottom(&self, num: usize) -> Option<&PathBuf> { + self.dirs.iter().rev().nth(num) + } + + pub(crate) fn dir_from_top(&self, num: usize) -> Option<&PathBuf> { self.dirs.get(num) } + + pub(crate) fn dirs<I: IntoIterator>(&mut self, args: I) -> i32 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; + 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(); - 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 - ))) - } - }; + 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))) */ + }; + } } } - let len: usize = self.dirs.len(); - if len <= 1 { - return Err(Cow::Borrowed("ion: popd: directory stack empty\n")); + if dirs_args & CLEAR > 0 { + self.dirs.truncate(1); } - 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" - )) - })? + 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(), }; - // apply -n - if index == 0 && keep_front { - index = 1; - } + let mut iter = self.dirs.iter().enumerate().map(mapper); - // change to new directory, return if not possible - if index == 0 { - self.set_current_dir_by_index(1, "popd")?; - } + 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, + }; - // pop element - if self.dirs.remove(index).is_none() { - return Err(Cow::Owned(format!( - "ion: popd: {}: directory stack index out of range\n", - index - ))); + let first = match iter.next() { + Some(x) => x.to_string(), + None => return SUCCESS, + }; + + println!("{}", iter.fold(first, folder)); } + SUCCESS + } - self.update_env_variables(variables); - self.print_dirs(); - Ok(()) + fn insert_dir(&mut self, index: usize, path: PathBuf, variables: &Variables) { + self.dirs.insert(index, path); + self.dirs.truncate(DirectoryStack::get_size(variables)); } - pub(crate) fn pushd<I: IntoIterator>( + fn push_dir(&mut self, path: PathBuf, variables: &Variables) { + self.dirs.push_front(path); + + self.dirs.truncate(DirectoryStack::get_size(variables)); + } + + pub(crate) fn change_and_push_dir( &mut self, - args: I, - variables: &mut Variables, - ) -> Result<(), Cow<'static, str>> - where - I::Item: AsRef<str>, - { - enum Action { - Switch, // <no arguments> - RotLeft(usize), // +[num] - RotRight(usize), // -[num] - Push(PathBuf), // [dir] + dir: &str, + variables: &Variables, + ) -> Result<(), Cow<'static, str>> { + let new_dir = self.normalize_path(dir); + + // Try to change into the new directory + match set_current_dir(&new_dir) { + Ok(()) => { + // Push the new current directory onto the directory stack. + self.push_dir(new_dir, variables); + Ok(()) + } + Err(err) => Err(Cow::Owned(format!( + "ion: failed to set current dir to {}: {}\n", + new_dir.to_string_lossy(), + err + ))), } + } - let mut keep_front = false; // whether the -n option is present - let mut action: Action = Action::Switch; + fn get_previous_dir(&self, variables: &Variables) -> Option<String> { + let previous_pwd = variables.get_var_or_empty("OLDPWD"); + if previous_pwd == "?" || previous_pwd == "" { + None + } else { + Some(previous_pwd) + } + } - 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")); + fn switch_to_previous_directory( + &mut self, + variables: &Variables, + ) -> Result<(), Cow<'static, str>> { + match self.get_previous_dir(variables) { + Some(prev) => { + self.dirs.remove(0); + let prev = prev.to_string(); + println!("{}", prev); + self.change_and_push_dir(&prev, variables) } + None => Err(Cow::Borrowed("ion: no previous directory to switch to")), } + } - 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); + 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| self.change_and_push_dir(home, variables), + ) }, - Action::Push(dir) => { - let index = if keep_front { 1 } else { 0 }; - let new_dir = self.normalize_path(dir.to_str().unwrap()); - self.insert_dir(index, new_dir, variables); - self.set_current_dir_by_index(index, "pushd")?; - } - }; + ) + } - self.update_env_variables(variables); - self.print_dirs(); - Ok(()) + fn update_env_variables(&mut self, variables: &mut Variables) { + // Update $OLDPWD + let old_pwd = variables.get_var_or_empty("PWD"); + if old_pwd.is_empty() { + variables.set_var("OLDPWD", "?"); + } else { + variables.set_var("OLDPWD", &old_pwd); + } + + // Update $PWD + match current_dir() { + Ok(current_dir) => variables.set_var("PWD", current_dir.to_str().unwrap_or("?")), + Err(_) => variables.set_var("PWD", "?"), + } } pub(crate) fn cd<I: IntoIterator>( @@ -195,226 +263,170 @@ impl DirectoryStack { } } - fn update_env_variables(&mut self, variables: &mut Variables) { - // Update $OLDPWD - let old_pwd = variables.get_var_or_empty("PWD"); - if old_pwd.is_empty() { - variables.set_var("OLDPWD", "?"); - } else { - variables.set_var("OLDPWD", &old_pwd); - } - - // Update $PWD - match current_dir() { - Ok(current_dir) => variables.set_var("PWD", current_dir.to_str().unwrap_or("?")), - Err(_) => variables.set_var("PWD", "?"), - } - } - - 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| self.change_and_push_dir(home, variables), - ) - }, - ) - } - - fn switch_to_previous_directory( + pub(crate) fn pushd<I: IntoIterator>( &mut self, - variables: &Variables, - ) -> Result<(), Cow<'static, str>> { - match self.get_previous_dir(variables) { - Some(prev) => { - self.dirs.remove(0); - let prev = prev.to_string(); - println!("{}", prev); - self.change_and_push_dir(&prev, variables) - }, - None => Err(Cow::Borrowed("ion: no previous directory to switch to")), - } - } - - fn get_previous_dir(&self, variables: &Variables) -> Option<String> { - let previous_pwd = variables.get_var_or_empty("OLDPWD"); - if previous_pwd == "?" || previous_pwd == "" { - None - } else { - Some(previous_pwd) + args: I, + variables: &mut Variables, + ) -> Result<(), Cow<'static, str>> + where + I::Item: AsRef<str>, + { + enum Action { + Switch, // <no arguments> + RotLeft(usize), // +[num] + RotRight(usize), // -[num] + Push(PathBuf), // [dir] } - } - pub(crate) fn change_and_push_dir( - &mut self, - dir: &str, - variables: &Variables, - ) -> Result<(), Cow<'static, str>> { - - let new_dir = self.normalize_path(dir); + let mut keep_front = false; // whether the -n option is present + let mut action: Action = Action::Switch; - // Try to change into the new directory - match set_current_dir(&new_dir) { - Ok(()) => { - // Push the new current directory onto the directory stack. - self.push_dir(new_dir, variables); - Ok(()) + 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")); } - Err(err) => Err(Cow::Owned(format!( - "ion: failed to set current dir to {}: {}\n", - new_dir.to_string_lossy(), err - ))) } - } - - fn push_dir(&mut self, path: PathBuf, variables: &Variables) { - self.dirs.push_front(path); - self.dirs.truncate(DirectoryStack::get_size(variables)); - } + 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 }; + let new_dir = self.normalize_path(dir.to_str().unwrap()); + self.insert_dir(index, new_dir, variables); + self.set_current_dir_by_index(index, "pushd")?; + } + }; - fn insert_dir(&mut self, index: usize, path: PathBuf, variables: &Variables) { - self.dirs.insert(index, path); - self.dirs.truncate(DirectoryStack::get_size(variables)); + self.update_env_variables(variables); + self.print_dirs(); + Ok(()) } - pub(crate) fn dirs<I: IntoIterator>(&mut self, args: I) -> i32 + /// Attempts to set the current directory to the directory stack's previous directory, + /// and then removes the front directory from the stack. + pub(crate) fn popd<I: IntoIterator>( + &mut self, + args: I, + variables: &mut Variables, + ) -> Result<(), Cow<'static, str>> 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; + 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(); - 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 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 + ))) + } + }; } } - if dirs_args & CLEAR > 0 { - self.dirs.truncate(1); + let len: usize = self.dirs.len(); + if len <= 1 { + return Err(Cow::Borrowed("ion: popd: directory stack empty\n")); } - 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, - }; + let mut index: usize = if count_from_front { + num } 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, - }; + (len - 1).checked_sub(num).ok_or_else(|| { + Cow::Owned(format!( + "ion: popd: negative directory stack index out of range\n" + )) + })? + }; - println!("{}", iter.fold(first, folder)); + // apply -n + if index == 0 && keep_front { + index = 1; } - SUCCESS - } - - pub(crate) fn dir_from_top(&self, num: usize) -> Option<&PathBuf> { self.dirs.get(num) } - - pub(crate) 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") - }); - println!("{}", dir.trim_left()); - } - - // 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 - )) - })?; - set_current_dir(dir) - .map_err(|_| Cow::Owned(format!("ion: {}: Failed setting current dir\n", caller))) - } + // change to new directory, return if not possible + if index == 0 { + self.set_current_dir_by_index(1, "popd")?; + } - // 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(); + // pop element + if self.dirs.remove(index).is_none() { + return Err(Cow::Owned(format!( + "ion: popd: {}: directory stack index out of range\n", + index + ))); } - } - // pushd -<num> - fn rotate_right(&mut self, num: usize) { - let len = self.dirs.len(); - self.rotate_left(len - (num % len)); + self.update_env_variables(variables); + self.print_dirs(); + Ok(()) } - fn normalize_path(&mut self, dir: &str) -> PathBuf { - use std::path::{Component, Path}; - // Create a clone of the current directory. - let mut new_dir = match self.dirs.front() { - Some(cur_dir) => cur_dir.clone(), - None => PathBuf::new() - }; + /// This function will take a map of variables as input and attempt to parse the value of + /// the + /// 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) + } - // Iterate through components of the specified directory - // and calculate the new path based on them. - for component in Path::new(dir).components() { - match component { - Component::CurDir => { }, - Component::ParentDir => { new_dir.pop(); }, - _ => { new_dir.push(component); } - }; + /// Create a new `DirectoryStack` containing the current working directory, + /// if available. + pub(crate) fn new() -> DirectoryStack { + let mut dirs: VecDeque<PathBuf> = VecDeque::new(); + match current_dir() { + Ok(curr_dir) => { + dirs.push_front(curr_dir); + DirectoryStack { dirs } + } + Err(_) => { + eprintln!("ion: failed to get current directory when building directory stack"); + DirectoryStack { dirs } + } } - - return new_dir; } } diff --git a/src/lib/shell/flow.rs b/src/lib/shell/flow.rs index a57ae9fa..d35ea7dd 100644 --- a/src/lib/shell/flow.rs +++ b/src/lib/shell/flow.rs @@ -1,15 +1,24 @@ -use super::Shell; -use super::flags::*; -use super::flow_control::{collect_cases, collect_if, collect_loops, Case, ElseIf, Function, Statement}; -use super::job_control::JobControl; -use super::status::*; -use parser::{expand_string, parse_and_validate, ForExpression, StatementSplitter}; -use parser::assignments::{is_array, ReturnValue}; -use parser::pipelines::Pipeline; +use super::{ + flags::*, + flow_control::{collect_cases, collect_if, collect_loops, Case, ElseIf, Function, Statement}, + job_control::JobControl, + status::*, + Shell, +}; +use parser::{ + assignments::{is_array, ReturnValue}, + expand_string, + parse_and_validate, + pipelines::Pipeline, + ForExpression, + StatementSplitter, +}; use shell::assignments::VariableStore; -use std::io::{stdout, Write}; -use std::iter; -use std::mem; +use std::{ + io::{stdout, Write}, + iter, + mem, +}; use types::Array; pub(crate) enum Condition { @@ -70,338 +79,352 @@ pub(crate) trait FlowLogic { } impl FlowLogic for Shell { - fn on_command(&mut self, command_string: &str) { - self.break_flow = false; - let mut iterator = StatementSplitter::new(String::from(command_string)).map(parse_and_validate); + fn execute_toplevel<I>( + &mut self, + iterator: &mut I, + statement: Statement, + ) -> Result<(), &'static str> + where + I: Iterator<Item = Statement>, + { + match statement { + Statement::Error(number) => self.previous_status = number, + // Execute a Let Statement + Statement::Let(action) => { + self.previous_status = self.local(action); + } + Statement::Export(action) => { + self.previous_status = self.export(action); + } + // 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, + } => { + self.flow_control.level += 1; - // If the value is set to `0`, this means that we don't need to append to an - // existing partial statement block in memory, but can read and execute - // new statements. - if self.flow_control.level == 0 { - while let Some(statement) = iterator.next() { - // Executes all statements that it can, and stores the last remaining partial - // statement in memory if needed. We can tell if there is a partial statement - // later if the value of `level` is not set to `0`. - if let Err(why) = self.execute_toplevel(&mut iterator, statement) { - eprintln!("{}", why); - self.flow_control.level = 0; - self.flow_control.current_if_mode = 0; - return; + // Collect all of the statements contained within the while block. + collect_loops(iterator, &mut statements, &mut self.flow_control.level); + + if self.flow_control.level == 0 { + // All blocks were read, thus we can immediately execute now + self.execute_while(expression, statements); + } else { + // Store the partial `Statement::While` to memory + self.flow_control.current_statement = Statement::While { + expression, + statements, + } } } - } else { - fn append_new_commands<I: Iterator<Item = Statement>>( - mut iterator: &mut I, - current_statement: &mut Statement, - level: &mut usize, - current_if_mode: &mut u8, - ) { - match current_statement { - &mut Statement::While { - ref mut statements, .. - } - | &mut Statement::For { - ref mut statements, .. - } - | &mut Statement::Function { - ref mut statements, .. - } => { - collect_loops(&mut iterator, statements, level); - } - &mut Statement::If { - ref mut success, - ref mut else_if, - ref mut failure, - .. - } => { - *current_if_mode = match collect_if( - &mut iterator, - success, - else_if, - failure, - level, - *current_if_mode, - ) { - Ok(mode) => mode, - Err(why) => { - eprintln!("{}", why); - 4 - } - }; - } - &mut Statement::Match { ref mut cases, .. } => { - if let Err(why) = collect_cases(&mut iterator, cases, level) { - eprintln!("{}", why); - } - } - &mut Statement::Time(ref mut box_stmt) => { - append_new_commands(iterator, box_stmt.as_mut(), level, current_if_mode); - } - &mut Statement::And(ref mut box_stmt) => { - append_new_commands(iterator, box_stmt.as_mut(), level, current_if_mode); - } - &mut Statement::Or(ref mut box_stmt) => { - append_new_commands(iterator, box_stmt.as_mut(), level, current_if_mode); - } - &mut Statement::Not(ref mut box_stmt) => { - append_new_commands(iterator, box_stmt.as_mut(), level, current_if_mode); + // 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, + } => { + self.flow_control.level += 1; + + // Collect all of the statements contained within the for block. + collect_loops(iterator, &mut statements, &mut self.flow_control.level); + + if self.flow_control.level == 0 { + // All blocks were read, thus we can immediately execute now + self.execute_for(&variable, &values, statements); + } else { + // Store the partial `Statement::For` to memory + self.flow_control.current_statement = Statement::For { + variable, + values, + 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, + } => { + self.flow_control.level += 1; - append_new_commands( - &mut iterator, - &mut self.flow_control.current_statement, - &mut self.flow_control.level, - &mut self.flow_control.current_if_mode, - ); + // 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, + )?; - // If this is true, an error occurred during the if statement - if self.flow_control.current_if_mode == 4 { - self.flow_control.level = 0; - self.flow_control.current_if_mode = 0; - self.flow_control.current_statement = Statement::Default; - return; + if self.flow_control.level == 0 { + // All blocks were read, thus we can immediately execute now + self.execute_if(expression, success, else_if, failure); + } else { + // Set the mode and partial if statement in memory. + self.flow_control.current_if_mode = mode; + self.flow_control.current_statement = Statement::If { + expression, + success, + else_if, + 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, + } => { + self.flow_control.level += 1; - // If the level is set to 0, it means that the statement in memory is finished - // and thus is ready for execution. - if self.flow_control.level == 0 { - // Replaces the `current_statement` with a `Default` value to avoid the - // need to clone the value, and clearing it at the same time. - let mut replacement = Statement::Default; - mem::swap(&mut self.flow_control.current_statement, &mut replacement); - - fn execute_final(shell: &mut Shell, statement: Statement) -> Condition { - match statement { - Statement::Error(number) => shell.previous_status = number, - Statement::Let(action) => { - shell.previous_status = shell.local(action); - } - Statement::Export(action) => { - shell.previous_status = shell.export(action); - } - Statement::While { - expression, - statements, - } => { - if let Condition::SigInt = shell.execute_while(expression, statements) { - return Condition::SigInt; - } - } - Statement::For { - variable, - values, - statements, - } => { - if let Condition::SigInt = - shell.execute_for(&variable, &values, statements) - { - return Condition::SigInt; - } - } - Statement::Function { - name, - args, - statements, - description, - } => { - shell.functions.insert( - name.clone(), - Function::new(description, name, args, statements), - ); - } - Statement::If { - expression, - success, - else_if, - failure, - } => { - shell.execute_if(expression, success, else_if, failure); - } - Statement::Match { expression, cases } => { - shell.execute_match(expression, cases); - } - Statement::Time(box_stmt) => { - let time = ::std::time::Instant::now(); + // The same logic that applies to loops, also applies here. + collect_loops(iterator, &mut statements, &mut self.flow_control.level); - let condition = execute_final(shell, *box_stmt); + if self.flow_control.level == 0 { + // All blocks were read, thus we can add it to the list + self.functions.insert( + name.clone(), + Function::new(description, name, args, statements), + ); + } else { + // Store the partial function declaration in memory. + self.flow_control.current_statement = Statement::Function { + description, + name, + args, + statements, + } + } + } + // Simply executes a provided pipeline, immediately. + 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::Time(box_statement) => { + let time = ::std::time::Instant::now(); - let duration = time.elapsed(); - let seconds = duration.as_secs(); - let nanoseconds = duration.subsec_nanos(); + if let Err(why) = self.execute_toplevel(iterator, *box_statement) { + eprintln!("{}", why); + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + } + // Collect timing here so we do not count anything but the execution. + let duration = time.elapsed(); + let seconds = duration.as_secs(); + let nanoseconds = duration.subsec_nanos(); - let stdout = stdout(); - let mut stdout = stdout.lock(); - let _ = if seconds > 60 { - writeln!( - stdout, - "real {}m{:02}.{:09}s", - seconds / 60, - seconds % 60, - nanoseconds - ) - } else { - writeln!(stdout, "real {}.{:09}s", seconds, nanoseconds) - }; - return condition; - } - Statement::And(box_stmt) => { - match shell.previous_status { - SUCCESS => { - execute_final(shell, *box_stmt); - }, - _ => () - } - } - Statement::Or(box_stmt) => { - match shell.previous_status { - FAILURE => { - execute_final(shell, *box_stmt); - }, - _ => () + if self.flow_control.level == 0 { + // A statement was executed, output the time + let stdout = stdout(); + let mut stdout = stdout.lock(); + let _ = if seconds > 60 { + writeln!( + stdout, + "real {}m{:02}.{:09}s", + seconds / 60, + seconds % 60, + nanoseconds + ) + } else { + writeln!(stdout, "real {}.{:09}s", seconds, nanoseconds) + }; + } else { + // A statement wasn't executed , which means that current_statement has been + // set to the inner statement. We fix this here. + self.flow_control.current_statement = + Statement::Time(Box::new(self.flow_control.current_statement.clone())); + } + } + Statement::And(box_statement) => { + if self.flow_control.level == 0 { + match self.previous_status { + SUCCESS => { + if let Err(why) = self.execute_toplevel(iterator, *box_statement) { + eprintln!("{}", why); + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; } } - Statement::Not(box_stmt) => { - execute_final(shell, *box_stmt); - match shell.previous_status { - FAILURE => shell.previous_status = SUCCESS, - SUCCESS => shell.previous_status = FAILURE, - _ => () + _ => (), + } + } else { + // A statement wasn't executed , which means that current_statement has been + // set to the inner statement. We fix this here. + self.flow_control.current_statement = + Statement::And(Box::new(self.flow_control.current_statement.clone())); + } + } + Statement::Or(box_statement) => { + if self.flow_control.level == 0 { + match self.previous_status { + FAILURE => { + if let Err(why) = self.execute_toplevel(iterator, *box_statement) { + eprintln!("{}", why); + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; } - shell.variables.set_var("?", &shell.previous_status.to_string()); } _ => (), } - Condition::NoOp - } - - if let Condition::SigInt = execute_final(self, replacement) { - return; + } else { + // A statement wasn't executed , which means that current_statement has been + // set to the inner statement. We fix this here. + self.flow_control.current_statement = + Statement::Or(Box::new(self.flow_control.current_statement.clone())); } - - // Capture any leftover statements. - while let Some(statement) = iterator.next() { - if let Err(why) = self.execute_toplevel(&mut iterator, statement) { + } + Statement::Not(box_statement) => { + if self.flow_control.level == 0 { + if let Err(why) = self.execute_toplevel(iterator, *box_statement) { eprintln!("{}", why); self.flow_control.level = 0; self.flow_control.current_if_mode = 0; - return; } + match self.previous_status { + FAILURE => self.previous_status = SUCCESS, + SUCCESS => self.previous_status = FAILURE, + _ => (), + } + let status = self.previous_status.to_string(); + self.set_var("?", &status); + } else { + // A statement wasn't executed , which means that current_statement has been + // set to the inner statement. We fix this here. + self.flow_control.current_statement = + Statement::Not(Box::new(self.flow_control.current_statement.clone())); + } + } + // At this level, else and else if keywords are forbidden. + Statement::ElseIf { .. } | Statement::Else => { + eprintln!("ion: syntax error: not an if statement"); + } + // Likewise to else and else if, the end keyword does nothing here. + Statement::End => { + eprintln!("ion: syntax error: no block to end"); + } + // Collect all cases that are being used by a match construct + Statement::Match { + expression, + mut cases, + } => { + self.flow_control.level += 1; + if let Err(why) = collect_cases(iterator, &mut cases, &mut self.flow_control.level) + { + eprintln!("{}", why); + } + if self.flow_control.level == 0 { + // If all blocks were read we execute the statement + self.execute_match(expression, cases); + } else { + // Store the partial function declaration in memory. + self.flow_control.current_statement = Statement::Match { expression, cases }; } } + _ => {} } + Ok(()) } - fn execute_match(&mut self, expression: String, cases: Vec<Case>) -> Condition { - // Logic for determining if the LHS of a match-case construct (the value we are - // matching against) matches the RHS of a match-case construct (a value - // in a case statement). For example, checking to see if the value - // "foo" matches the pattern "bar" would be invoked like so : - // ```ignore - // matches("foo", "bar") - // ``` - fn matches(lhs: &Array, rhs: &Array) -> bool { - for v in lhs { - if rhs.contains(&v) { - return true; - } + fn execute_if( + &mut self, + expression: Pipeline, + success: Vec<Statement>, + else_if: Vec<ElseIf>, + failure: Vec<Statement>, + ) -> Condition { + let first_condition = iter::once((expression, success)); + let else_conditions = else_if + .into_iter() + .map(|cond| (cond.expression, cond.success)); + + for (mut condition, mut statements) in first_condition.chain(else_conditions) { + if self.run_pipeline(&mut condition) == Some(SUCCESS) { + return self.execute_statements(statements); } - false } - let is_array = is_array(&expression); - let value = expand_string(&expression, self, false); - let mut condition = Condition::NoOp; - for case in cases { - // let pattern_is_array = is_array(&value); - let pattern = case.value.map(|v| expand_string(&v, self, false)); - match pattern { - None => { - let mut previous_bind = None; - if let Some(ref bind) = case.binding { - if is_array { - previous_bind = self.variables - .get_array(bind) - .map(|x| ReturnValue::Vector(x.clone())); - self.variables.set_array(&bind, value.clone()); - } else { - previous_bind = self.get_var(bind).map(|x| ReturnValue::Str(x)); - self.set_var(&bind, &value.join(" ")); - } - } - - if let Some(statement) = case.conditional { - self.on_command(&statement); - if self.previous_status != SUCCESS { - continue; - } - } - - condition = self.execute_statements(case.statements); - - if let Some(ref bind) = case.binding { - if let Some(value) = previous_bind { - match value { - ReturnValue::Str(value) => self.set_var(bind, &value), - ReturnValue::Vector(values) => { - self.variables.set_array(bind, values) - } - } - } - } + self.execute_statements(failure) + } - break; + 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::SigInt => return Condition::SigInt, + _ => (), } - Some(ref v) if matches(v, &value) => { - let mut previous_bind = None; - if let Some(ref bind) = case.binding { - if is_array { - previous_bind = self.variables - .get_array(bind) - .map(|x| ReturnValue::Vector(x.clone())); - self.variables.set_array(&bind, value.clone()); - } else { - previous_bind = self.get_var(bind).map(|x| ReturnValue::Str(x)); - self.set_var(&bind, &value.join(" ")); - } - } - - if let Some(statement) = case.conditional { - self.on_command(&statement); - if self.previous_status != SUCCESS { - continue; - } - } - - condition = self.execute_statements(case.statements); - - if let Some(ref bind) = case.binding { - if let Some(value) = previous_bind { - match value { - ReturnValue::Str(value) => self.set_var(bind, &value), - ReturnValue::Vector(values) => { - self.variables.set_array(bind, values) - } - } - } - } - - break; + }, + ForExpression::Multiple(values) => for value in values.iter() { + self.set_var(variable, &value); + match self.execute_statements(statements.clone()) { + Condition::Break => break, + Condition::SigInt => return Condition::SigInt, + _ => (), } - Some(_) => (), - } + }, + ForExpression::Normal(ref values) if ignore_variable => for _ in values.lines() { + match self.execute_statements(statements.clone()) { + Condition::Break => break, + Condition::SigInt => return Condition::SigInt, + _ => (), + } + }, + ForExpression::Normal(values) => for value in values.lines() { + self.set_var(variable, &value); + match self.execute_statements(statements.clone()) { + 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::SigInt => return Condition::SigInt, + _ => (), + } + }, + ForExpression::Range(start, end) => for value in (start..end).map(|x| x.to_string()) { + self.set_var(variable, &value); + match self.execute_statements(statements.clone()) { + Condition::Break => break, + Condition::SigInt => return Condition::SigInt, + _ => (), + } + }, } - condition + Condition::NoOp } - fn execute_statements(&mut self, mut statements: Vec<Statement>) -> Condition { - let mut iterator = statements.drain(..); - while let Some(statement) = iterator.next() { - match self.execute_statement(&mut iterator, statement) { - Condition::NoOp => {} - cond => return cond, + 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::SigInt => return Condition::SigInt, + _ => (), } } Condition::NoOp @@ -522,8 +545,8 @@ impl FlowLogic for Shell { match self.previous_status { SUCCESS => { condition = self.execute_statement(iterator, *box_statement); - }, - _ => condition = Condition::NoOp + } + _ => condition = Condition::NoOp, } match condition { @@ -538,8 +561,8 @@ impl FlowLogic for Shell { match self.previous_status { FAILURE => { condition = self.execute_statement(iterator, *box_statement); - }, - _ => condition = Condition::NoOp + } + _ => condition = Condition::NoOp, } match condition { @@ -554,7 +577,7 @@ impl FlowLogic for Shell { match self.previous_status { FAILURE => self.previous_status = SUCCESS, SUCCESS => self.previous_status = FAILURE, - _ => () + _ => (), } let status = self.previous_status.to_string(); self.set_var("?", &status); @@ -592,360 +615,343 @@ impl FlowLogic for Shell { self.break_flow = false; Condition::SigInt } else { - Condition::NoOp - } - } - - 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::SigInt => return Condition::SigInt, - _ => (), - } - } - Condition::NoOp - } - - 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::SigInt => return Condition::SigInt, - _ => (), - } - }, - ForExpression::Multiple(values) => for value in values.iter() { - self.set_var(variable, &value); - match self.execute_statements(statements.clone()) { - 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::SigInt => return Condition::SigInt, - _ => (), - } - }, - ForExpression::Normal(values) => for value in values.lines() { - self.set_var(variable, &value); - match self.execute_statements(statements.clone()) { - 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::SigInt => return Condition::SigInt, - _ => (), - } - }, - ForExpression::Range(start, end) => for value in (start..end).map(|x| x.to_string()) { - self.set_var(variable, &value); - match self.execute_statements(statements.clone()) { - Condition::Break => break, - Condition::SigInt => return Condition::SigInt, - _ => (), - } - }, + Condition::NoOp } - Condition::NoOp } - fn execute_if( - &mut self, - expression: Pipeline, - success: Vec<Statement>, - else_if: Vec<ElseIf>, - failure: Vec<Statement>, - ) -> Condition { - let first_condition = iter::once((expression, success)); - let else_conditions = else_if - .into_iter() - .map(|cond| (cond.expression, cond.success)); - - for (mut condition, mut statements) in first_condition.chain(else_conditions) { - if self.run_pipeline(&mut condition) == Some(SUCCESS) { - return self.execute_statements(statements); + fn execute_statements(&mut self, mut statements: Vec<Statement>) -> Condition { + let mut iterator = statements.drain(..); + while let Some(statement) = iterator.next() { + match self.execute_statement(&mut iterator, statement) { + Condition::NoOp => {} + cond => return cond, } } - - self.execute_statements(failure) + Condition::NoOp } - fn execute_toplevel<I>( - &mut self, - iterator: &mut I, - statement: Statement, - ) -> Result<(), &'static str> - where - I: Iterator<Item = Statement>, - { - match statement { - Statement::Error(number) => self.previous_status = number, - // Execute a Let Statement - Statement::Let(action) => { - self.previous_status = self.local(action); - } - Statement::Export(action) => { - self.previous_status = self.export(action); + fn execute_match(&mut self, expression: String, cases: Vec<Case>) -> Condition { + // Logic for determining if the LHS of a match-case construct (the value we are + // matching against) matches the RHS of a match-case construct (a value + // in a case statement). For example, checking to see if the value + // "foo" matches the pattern "bar" would be invoked like so : + // ```ignore + // matches("foo", "bar") + // ``` + fn matches(lhs: &Array, rhs: &Array) -> bool { + for v in lhs { + if rhs.contains(&v) { + return true; + } } - // 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, - } => { - self.flow_control.level += 1; + false + } - // Collect all of the statements contained within the while block. - collect_loops(iterator, &mut statements, &mut self.flow_control.level); + let is_array = is_array(&expression); + let value = expand_string(&expression, self, false); + let mut condition = Condition::NoOp; + for case in cases { + // let pattern_is_array = is_array(&value); + let pattern = case.value.map(|v| expand_string(&v, self, false)); + match pattern { + None => { + let mut previous_bind = None; + if let Some(ref bind) = case.binding { + if is_array { + previous_bind = self.variables + .get_array(bind) + .map(|x| ReturnValue::Vector(x.clone())); + self.variables.set_array(&bind, value.clone()); + } else { + previous_bind = self.get_var(bind).map(|x| ReturnValue::Str(x)); + self.set_var(&bind, &value.join(" ")); + } + } - if self.flow_control.level == 0 { - // All blocks were read, thus we can immediately execute now - self.execute_while(expression, statements); - } else { - // Store the partial `Statement::While` to memory - self.flow_control.current_statement = Statement::While { - expression: expression, - statements: statements, + if let Some(statement) = case.conditional { + self.on_command(&statement); + if self.previous_status != SUCCESS { + continue; + } + } + + condition = self.execute_statements(case.statements); + + if let Some(ref bind) = case.binding { + if let Some(value) = previous_bind { + match value { + ReturnValue::Str(value) => self.set_var(bind, &value), + ReturnValue::Vector(values) => { + self.variables.set_array(bind, values) + } + } + } } + + break; } - } - // 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, - } => { - self.flow_control.level += 1; + Some(ref v) if matches(v, &value) => { + let mut previous_bind = None; + if let Some(ref bind) = case.binding { + if is_array { + previous_bind = self.variables + .get_array(bind) + .map(|x| ReturnValue::Vector(x.clone())); + self.variables.set_array(&bind, value.clone()); + } else { + previous_bind = self.get_var(bind).map(|x| ReturnValue::Str(x)); + self.set_var(&bind, &value.join(" ")); + } + } - // Collect all of the statements contained within the for block. - collect_loops(iterator, &mut statements, &mut self.flow_control.level); + if let Some(statement) = case.conditional { + self.on_command(&statement); + if self.previous_status != SUCCESS { + continue; + } + } - if self.flow_control.level == 0 { - // All blocks were read, thus we can immediately execute now - self.execute_for(&variable, &values, statements); - } else { - // Store the partial `Statement::For` to memory - self.flow_control.current_statement = Statement::For { - variable: variable, - values: values, - statements: statements, + condition = self.execute_statements(case.statements); + + if let Some(ref bind) = case.binding { + if let Some(value) = previous_bind { + match value { + ReturnValue::Str(value) => self.set_var(bind, &value), + ReturnValue::Vector(values) => { + self.variables.set_array(bind, values) + } + } + } } + + break; } + Some(_) => (), } - // 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, - } => { - self.flow_control.level += 1; + } + condition + } - // 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, - )?; + fn on_command(&mut self, command_string: &str) { + self.break_flow = false; + let mut iterator = + StatementSplitter::new(String::from(command_string)).map(parse_and_validate); - if self.flow_control.level == 0 { - // All blocks were read, thus we can immediately execute now - self.execute_if(expression, success, else_if, failure); - } else { - // Set the mode and partial if statement in memory. - self.flow_control.current_if_mode = mode; - self.flow_control.current_statement = Statement::If { - expression: expression, - success: success, - else_if: else_if, - failure: failure, - }; + // If the value is set to `0`, this means that we don't need to append to an + // existing partial statement block in memory, but can read and execute + // new statements. + if self.flow_control.level == 0 { + while let Some(statement) = iterator.next() { + // Executes all statements that it can, and stores the last remaining partial + // statement in memory if needed. We can tell if there is a partial statement + // later if the value of `level` is not set to `0`. + if let Err(why) = self.execute_toplevel(&mut iterator, statement) { + eprintln!("{}", why); + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + return; + } + } + } else { + fn append_new_commands<I: Iterator<Item = Statement>>( + mut iterator: &mut I, + current_statement: &mut Statement, + level: &mut usize, + current_if_mode: &mut u8, + ) { + match current_statement { + &mut Statement::While { + ref mut statements, .. + } + | &mut Statement::For { + ref mut statements, .. + } + | &mut Statement::Function { + ref mut statements, .. + } => { + collect_loops(&mut iterator, statements, level); + } + &mut Statement::If { + ref mut success, + ref mut else_if, + ref mut failure, + .. + } => { + *current_if_mode = match collect_if( + &mut iterator, + success, + else_if, + failure, + level, + *current_if_mode, + ) { + Ok(mode) => mode, + Err(why) => { + eprintln!("{}", why); + 4 + } + }; + } + &mut Statement::Match { ref mut cases, .. } => { + if let Err(why) = collect_cases(&mut iterator, cases, level) { + eprintln!("{}", why); + } + } + &mut Statement::Time(ref mut box_stmt) => { + append_new_commands(iterator, box_stmt.as_mut(), level, current_if_mode); + } + &mut Statement::And(ref mut box_stmt) => { + append_new_commands(iterator, box_stmt.as_mut(), level, current_if_mode); + } + &mut Statement::Or(ref mut box_stmt) => { + append_new_commands(iterator, box_stmt.as_mut(), level, current_if_mode); + } + &mut Statement::Not(ref mut box_stmt) => { + append_new_commands(iterator, box_stmt.as_mut(), level, current_if_mode); + } + _ => (), } } - // 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, - } => { - self.flow_control.level += 1; - // The same logic that applies to loops, also applies here. - collect_loops(iterator, &mut statements, &mut self.flow_control.level); + append_new_commands( + &mut iterator, + &mut self.flow_control.current_statement, + &mut self.flow_control.level, + &mut self.flow_control.current_if_mode, + ); - if self.flow_control.level == 0 { - // All blocks were read, thus we can add it to the list - self.functions.insert( - name.clone(), - Function::new(description, name, args, statements), - ); - } else { - // Store the partial function declaration in memory. - self.flow_control.current_statement = Statement::Function { - description: description, - name: name, - args: args, - statements: statements, - } - } - } - // Simply executes a provided pipeline, immediately. - 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); - } + // If this is true, an error occurred during the if statement + if self.flow_control.current_if_mode == 4 { + self.flow_control.level = 0; + self.flow_control.current_if_mode = 0; + self.flow_control.current_statement = Statement::Default; + return; } - Statement::Time(box_statement) => { - let time = ::std::time::Instant::now(); - if let Err(why) = self.execute_toplevel(iterator, *box_statement) { - eprintln!("{}", why); - self.flow_control.level = 0; - self.flow_control.current_if_mode = 0; - } - // Collect timing here so we do not count anything but the execution. - let duration = time.elapsed(); - let seconds = duration.as_secs(); - let nanoseconds = duration.subsec_nanos(); + // If the level is set to 0, it means that the statement in memory is finished + // and thus is ready for execution. + if self.flow_control.level == 0 { + // Replaces the `current_statement` with a `Default` value to avoid the + // need to clone the value, and clearing it at the same time. + let mut replacement = Statement::Default; + mem::swap(&mut self.flow_control.current_statement, &mut replacement); - if self.flow_control.level == 0 { - // A statement was executed, output the time - let stdout = stdout(); - let mut stdout = stdout.lock(); - let _ = if seconds > 60 { - writeln!( - stdout, - "real {}m{:02}.{:09}s", - seconds / 60, - seconds % 60, - nanoseconds - ) - } else { - writeln!(stdout, "real {}.{:09}s", seconds, nanoseconds) - }; - } else { - // A statement wasn't executed , which means that current_statement has been - // set to the inner statement. We fix this here. - self.flow_control.current_statement = - Statement::Time(Box::new(self.flow_control.current_statement.clone())); - } - } - Statement::And(box_statement) => { - if self.flow_control.level == 0 { - match self.previous_status { - SUCCESS => { - if let Err(why) = self.execute_toplevel(iterator, *box_statement) { - eprintln!("{}", why); - self.flow_control.level = 0; - self.flow_control.current_if_mode = 0; + fn execute_final(shell: &mut Shell, statement: Statement) -> Condition { + match statement { + Statement::Error(number) => shell.previous_status = number, + Statement::Let(action) => { + shell.previous_status = shell.local(action); + } + Statement::Export(action) => { + shell.previous_status = shell.export(action); + } + Statement::While { + expression, + statements, + } => { + if let Condition::SigInt = shell.execute_while(expression, statements) { + return Condition::SigInt; + } + } + Statement::For { + variable, + values, + statements, + } => { + if let Condition::SigInt = + shell.execute_for(&variable, &values, statements) + { + return Condition::SigInt; } + } + Statement::Function { + name, + args, + statements, + description, + } => { + shell.functions.insert( + name.clone(), + Function::new(description, name, args, statements), + ); + } + Statement::If { + expression, + success, + else_if, + failure, + } => { + shell.execute_if(expression, success, else_if, failure); + } + Statement::Match { expression, cases } => { + shell.execute_match(expression, cases); + } + Statement::Time(box_stmt) => { + let time = ::std::time::Instant::now(); + + let condition = execute_final(shell, *box_stmt); + + let duration = time.elapsed(); + let seconds = duration.as_secs(); + let nanoseconds = duration.subsec_nanos(); + + let stdout = stdout(); + let mut stdout = stdout.lock(); + let _ = if seconds > 60 { + writeln!( + stdout, + "real {}m{:02}.{:09}s", + seconds / 60, + seconds % 60, + nanoseconds + ) + } else { + writeln!(stdout, "real {}.{:09}s", seconds, nanoseconds) + }; + return condition; + } + Statement::And(box_stmt) => match shell.previous_status { + SUCCESS => { + execute_final(shell, *box_stmt); + } + _ => (), }, - _ => () - } - } else { - // A statement wasn't executed , which means that current_statement has been - // set to the inner statement. We fix this here. - self.flow_control.current_statement = - Statement::And(Box::new(self.flow_control.current_statement.clone())); - } - } - Statement::Or(box_statement) => { - if self.flow_control.level == 0 { - match self.previous_status { - FAILURE => { - if let Err(why) = self.execute_toplevel(iterator, *box_statement) { - eprintln!("{}", why); - self.flow_control.level = 0; - self.flow_control.current_if_mode = 0; + Statement::Or(box_stmt) => match shell.previous_status { + FAILURE => { + execute_final(shell, *box_stmt); } + _ => (), }, - _ => () + Statement::Not(box_stmt) => { + execute_final(shell, *box_stmt); + match shell.previous_status { + FAILURE => shell.previous_status = SUCCESS, + SUCCESS => shell.previous_status = FAILURE, + _ => (), + } + shell + .variables + .set_var("?", &shell.previous_status.to_string()); + } + _ => (), } + Condition::NoOp + } - } else { - // A statement wasn't executed , which means that current_statement has been - // set to the inner statement. We fix this here. - self.flow_control.current_statement = - Statement::Or(Box::new(self.flow_control.current_statement.clone())); + if let Condition::SigInt = execute_final(self, replacement) { + return; } - } - Statement::Not(box_statement) => { - if self.flow_control.level == 0 { - if let Err(why) = self.execute_toplevel(iterator, *box_statement) { + // Capture any leftover statements. + while let Some(statement) = iterator.next() { + if let Err(why) = self.execute_toplevel(&mut iterator, statement) { eprintln!("{}", why); self.flow_control.level = 0; self.flow_control.current_if_mode = 0; + return; } - match self.previous_status { - FAILURE => self.previous_status = SUCCESS, - SUCCESS => self.previous_status = FAILURE, - _ => () - } - let status = self.previous_status.to_string(); - self.set_var("?", &status); - } else { - // A statement wasn't executed , which means that current_statement has been - // set to the inner statement. We fix this here. - self.flow_control.current_statement = - Statement::Not(Box::new(self.flow_control.current_statement.clone())); - } - } - // At this level, else and else if keywords are forbidden. - Statement::ElseIf { .. } | Statement::Else => { - eprintln!("ion: syntax error: not an if statement"); - } - // Likewise to else and else if, the end keyword does nothing here. - Statement::End => { - eprintln!("ion: syntax error: no block to end"); - } - // Collect all cases that are being used by a match construct - Statement::Match { - expression, - mut cases, - } => { - self.flow_control.level += 1; - if let Err(why) = collect_cases(iterator, &mut cases, &mut self.flow_control.level) - { - eprintln!("{}", why); - } - if self.flow_control.level == 0 { - // If all blocks were read we execute the statement - self.execute_match(expression, cases); - } else { - // Store the partial function declaration in memory. - self.flow_control.current_statement = Statement::Match { expression, cases }; } } - _ => {} } - Ok(()) } } diff --git a/src/lib/shell/flow_control.rs b/src/lib/shell/flow_control.rs index 4ca12bad..3709379b 100644 --- a/src/lib/shell/flow_control.rs +++ b/src/lib/shell/flow_control.rs @@ -1,11 +1,8 @@ -use super::Shell; -use super::flow::FlowLogic; +use super::{flow::FlowLogic, Shell}; use fnv::*; -use parser::assignments::*; -use parser::pipelines::Pipeline; +use parser::{assignments::*, pipelines::Pipeline}; use std::fmt::{self, Display, Formatter}; -use types::*; -use types::Identifier; +use types::{Identifier, *}; #[derive(Debug, PartialEq, Clone)] pub(crate) struct ElseIf { @@ -27,7 +24,10 @@ pub(crate) struct ElseIf { /// ``` /// would be represented by the Case object: /// ```rust,ignore -/// Case { value: Some(value), statements: vec![statement0, statement1, ... statementN]} +/// Case { +/// value: Some(value), +/// statements: vec![statement0, statement1, ... statementN], +/// } /// ``` /// The wildcard branch, a branch that matches any value, is represented as such: /// ```rust,ignore @@ -167,22 +167,6 @@ impl Display for FunctionError { } impl Function { - pub(crate) fn new( - description: Option<String>, - name: Identifier, - args: Vec<KeyBuf>, - statements: Vec<Statement>, - ) -> Function { - Function { - description, - name, - args, - statements, - } - } - - pub(crate) fn get_description<'a>(&'a self) -> Option<&'a String> { self.description.as_ref() } - pub(crate) fn execute(self, shell: &mut Shell, args: &[&str]) -> Result<(), FunctionError> { if args.len() - 1 != self.args.len() { return Err(FunctionError::InvalidArgumentCount); @@ -240,6 +224,22 @@ impl Function { Ok(()) } + + pub(crate) fn get_description<'a>(&'a self) -> Option<&'a String> { self.description.as_ref() } + + pub(crate) fn new( + description: Option<String>, + name: Identifier, + args: Vec<KeyBuf>, + statements: Vec<Statement>, + ) -> Function { + Function { + description, + name, + args, + statements, + } + } } pub(crate) fn collect_cases<I>( @@ -254,12 +254,16 @@ where ($statement:expr) => { match cases.last_mut() { // XXX: When does this actually happen? What syntax error is this??? - None => return Err(["ion: syntax error: encountered ", - $statement.short(), - " outside of `case ...` block"].concat()), + None => { + return Err([ + "ion: syntax error: encountered ", + $statement.short(), + " outside of `case ...` block", + ].concat()) + } Some(ref mut case) => case.statements.push($statement), } - } + }; } while let Some(statement) = iterator.next() { diff --git a/src/lib/shell/fork.rs b/src/lib/shell/fork.rs index 3e8c1f11..5e4847b6 100644 --- a/src/lib/shell/fork.rs +++ b/src/lib/shell/fork.rs @@ -1,6 +1,8 @@ use super::{IonError, Shell}; -use std::fs::File; -use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::{ + fs::File, + os::unix::io::{AsRawFd, FromRawFd}, +}; use sys; #[repr(u8)] @@ -50,9 +52,6 @@ pub struct IonResult { } impl<'a> Fork<'a> { - /// Creates a new `Fork` state from an existing shell. - pub fn new(shell: &'a Shell, capture: Capture) -> Fork<'a> { Fork { shell, capture } } - /// Executes a closure within the child of the fork, and returning an `IonResult` in a /// non-blocking fashion. pub fn exec<F: FnMut(&mut Shell)>(&self, mut child_func: F) -> Result<IonResult, IonError> { @@ -132,10 +131,11 @@ impl<'a> Fork<'a> { // `waitpid()` is required to reap the child. status: sys::wait_for_child(pid).map_err(|why| IonError::Fork { why })?, }) - }, - Err(why) => { - Err(IonError::Fork { why: why }) - }, + } + Err(why) => Err(IonError::Fork { why }), } } + + /// Creates a new `Fork` state from an existing shell. + pub fn new(shell: &'a Shell, capture: Capture) -> Fork<'a> { Fork { shell, capture } } } diff --git a/src/lib/shell/history.rs b/src/lib/shell/history.rs index 2feb4501..2510a1e2 100644 --- a/src/lib/shell/history.rs +++ b/src/lib/shell/history.rs @@ -1,5 +1,4 @@ -use super::Shell; -use super::status::*; +use super::{status::*, Shell}; use regex::Regex; use std::io::{self, Write}; @@ -70,18 +69,44 @@ trait ShellHistoryPrivate { } impl ShellHistory for Shell { - fn print_history(&self, _arguments: &[&str]) -> i32 { - if let Some(context) = self.context.as_ref() { - let mut buffer = Vec::with_capacity(8 * 1024); - for command in &context.history.buffers { - let _ = writeln!(buffer, "{}", command); + fn update_ignore_patterns(&mut self, patterns: &Array) { + let mut flags = IgnoreFlags::empty(); + let mut regexes = Vec::new(); + // for convenience and to avoid typos + let regex_prefix = "regex:"; + for pattern in patterns { + match pattern.as_ref() { + "all" => flags |= IgnoreFlags::ALL, + "no_such_command" => flags |= IgnoreFlags::NO_SUCH_COMMAND, + "whitespace" => flags |= IgnoreFlags::WHITESPACE, + // The length check is there to just ignore empty regex definitions + _ if pattern.starts_with(regex_prefix) && pattern.len() > regex_prefix.len() => { + flags |= IgnoreFlags::BASED_ON_REGEX; + let regex_string = &pattern[regex_prefix.len()..]; + // We save the compiled regexes, as compiling them can be an expensive task + if let Ok(regex) = Regex::new(regex_string) { + regexes.push(regex); + } + } + _ => continue, } - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - let _ = stdout.write_all(&buffer); - SUCCESS + } + + self.ignore_setting.flags = flags; + self.ignore_setting.regexes = if regexes.len() > 0 { + Some(regexes) } else { - FAILURE + None + } + } + + fn save_command_in_history(&mut self, command: &str) { + if self.should_save_command(command) { + // Mark the command in the context history + self.set_context_history_from_vars(); + if let Err(err) = self.context.as_mut().unwrap().history.push(command.into()) { + eprintln!("ion: {}", err); + } } } @@ -109,44 +134,18 @@ impl ShellHistory for Shell { } } - fn save_command_in_history(&mut self, command: &str) { - if self.should_save_command(command) { - // Mark the command in the context history - self.set_context_history_from_vars(); - if let Err(err) = self.context.as_mut().unwrap().history.push(command.into()) { - eprintln!("ion: {}", err); - } - } - } - - fn update_ignore_patterns(&mut self, patterns: &Array) { - let mut flags = IgnoreFlags::empty(); - let mut regexes = Vec::new(); - // for convenience and to avoid typos - let regex_prefix = "regex:"; - for pattern in patterns { - match pattern.as_ref() { - "all" => flags |= IgnoreFlags::ALL, - "no_such_command" => flags |= IgnoreFlags::NO_SUCH_COMMAND, - "whitespace" => flags |= IgnoreFlags::WHITESPACE, - // The length check is there to just ignore empty regex definitions - _ if pattern.starts_with(regex_prefix) && pattern.len() > regex_prefix.len() => { - flags |= IgnoreFlags::BASED_ON_REGEX; - let regex_string = &pattern[regex_prefix.len()..]; - // We save the compiled regexes, as compiling them can be an expensive task - if let Ok(regex) = Regex::new(regex_string) { - regexes.push(regex); - } - } - _ => continue, + fn print_history(&self, _arguments: &[&str]) -> i32 { + if let Some(context) = self.context.as_ref() { + let mut buffer = Vec::with_capacity(8 * 1024); + for command in &context.history.buffers { + let _ = writeln!(buffer, "{}", command); } - } - - self.ignore_setting.flags = flags; - self.ignore_setting.regexes = if regexes.len() > 0 { - Some(regexes) + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + let _ = stdout.write_all(&buffer); + SUCCESS } else { - None + FAILURE } } } diff --git a/src/lib/shell/job.rs b/src/lib/shell/job.rs index 4ad46d3d..6357ff92 100644 --- a/src/lib/shell/job.rs +++ b/src/lib/shell/job.rs @@ -1,11 +1,8 @@ use super::Shell; use builtins::{BuiltinFunction, BUILTINS}; -use parser::expand_string; -use parser::pipelines::RedirectFrom; +use parser::{expand_string, pipelines::RedirectFrom}; use smallstring::SmallString; -use std::fmt; -use std::fs::File; -use std::str; +use std::{fmt, fs::File, str}; use types::*; #[derive(Debug, PartialEq, Clone, Copy)] @@ -27,6 +24,15 @@ pub(crate) struct Job { } impl Job { + /// Takes the current job's arguments and expands them, one argument at a + /// time, returning a new `Job` with the expanded arguments. + pub(crate) fn expand(&mut self, shell: &Shell) { + let mut expanded = Array::new(); + expanded.grow(self.args.len()); + expanded.extend(self.args.drain().flat_map(|arg| expand_arg(&arg, shell))); + self.args = expanded; + } + pub(crate) fn new(args: Array, kind: JobKind) -> Self { let command = SmallString::from_str(&args[0]); let builtin = BUILTINS.get(command.as_ref()).map(|b| b.main); @@ -37,15 +43,6 @@ impl Job { builtin, } } - - /// Takes the current job's arguments and expands them, one argument at a - /// time, returning a new `Job` with the expanded arguments. - pub(crate) fn expand(&mut self, shell: &Shell) { - let mut expanded = Array::new(); - expanded.grow(self.args.len()); - expanded.extend(self.args.drain().flat_map(|arg| expand_arg(&arg, shell))); - self.args = expanded; - } } impl PartialEq for Job { @@ -79,25 +76,25 @@ fn expand_arg(arg: &str, shell: &Shell) -> Array { pub(crate) enum RefinedJob { /// An external program that is executed by this shell External { - name: Identifier, - args: Array, - stdin: Option<File>, + name: Identifier, + args: Array, + stdin: Option<File>, stdout: Option<File>, stderr: Option<File>, }, /// A procedure embedded into Ion Builtin { - main: BuiltinFunction, - args: Array, - stdin: Option<File>, + main: BuiltinFunction, + args: Array, + stdin: Option<File>, stdout: Option<File>, stderr: Option<File>, }, /// Functions can act as commands too! Function { - name: Identifier, - args: Array, - stdin: Option<File>, + name: Identifier, + args: Array, + stdin: Option<File>, stdout: Option<File>, stderr: Option<File>, }, @@ -130,8 +127,10 @@ impl TeeItem { /// should /// never be `RedirectFrom`::Both` pub(crate) fn write_to_all(&mut self, extra: Option<RedirectFrom>) -> ::std::io::Result<()> { - use std::io::{self, Read, Write}; - use std::os::unix::io::*; + use std::{ + io::{self, Read, Write}, + os::unix::io::*, + }; fn write_out<R>(source: &mut R, sinks: &mut [File]) -> io::Result<()> where R: Read, @@ -182,54 +181,61 @@ impl TeeItem { macro_rules! set_field { ($self:expr, $field:ident, $arg:expr) => { match *$self { - RefinedJob::External { ref mut $field, .. } | - RefinedJob::Builtin { ref mut $field, .. } | - RefinedJob::Function { ref mut $field, .. } | - RefinedJob::Tee { ref mut $field, .. } => { + RefinedJob::External { ref mut $field, .. } + | RefinedJob::Builtin { ref mut $field, .. } + | RefinedJob::Function { ref mut $field, .. } + | RefinedJob::Tee { ref mut $field, .. } => { *$field = Some($arg); } // Do nothing for Cat _ => {} } - } + }; } impl RefinedJob { - pub(crate) fn external(name: Identifier, args: Array) -> Self { - RefinedJob::External { - name, - args, - stdin: None, - stdout: None, - stderr: None, + /// Returns a long description of this job: the commands and arguments + pub(crate) fn long(&self) -> String { + match *self { + RefinedJob::External { ref args, .. } + | RefinedJob::Builtin { ref args, .. } + | RefinedJob::Function { ref args, .. } => format!("{}", args.join(" ")), + // TODO: Figure out real printing + RefinedJob::Cat { .. } | RefinedJob::Tee { .. } => "".into(), } } - pub(crate) fn builtin(main: BuiltinFunction, args: Array) -> Self { - RefinedJob::Builtin { - main, - args, - stdin: None, - stdout: None, - stderr: None, + /// Returns a short description of this job: often just the command + /// or builtin name + pub(crate) fn short(&self) -> String { + match *self { + RefinedJob::Builtin { .. } => String::from("Shell Builtin"), + RefinedJob::Function { ref name, .. } | RefinedJob::External { ref name, .. } => { + name.to_string() + } + // TODO: Print for real + RefinedJob::Cat { .. } => "multi-input".into(), + RefinedJob::Tee { .. } => "multi-output".into(), } } - pub(crate) fn function(name: Identifier, args: Array) -> Self { - RefinedJob::Function { - name, - args, - stdin: None, - stdout: None, - stderr: None, + pub(crate) fn stderr(&mut self, file: File) { + set_field!(self, stderr, file); + } + + pub(crate) fn stdout(&mut self, file: File) { + if let &mut RefinedJob::Cat { ref mut stdout, .. } = self { + *stdout = Some(file); + } else { + set_field!(self, stdout, file); } } - pub(crate) fn cat(sources: Vec<File>) -> Self { - RefinedJob::Cat { - sources, - stdin: None, - stdout: None, + pub(crate) fn stdin(&mut self, file: File) { + if let &mut RefinedJob::Cat { ref mut stdin, .. } = self { + *stdin = Some(file); + } else { + set_field!(self, stdin, file); } } @@ -242,47 +248,41 @@ impl RefinedJob { } } - pub(crate) fn stdin(&mut self, file: File) { - if let &mut RefinedJob::Cat { ref mut stdin, .. } = self { - *stdin = Some(file); - } else { - set_field!(self, stdin, file); + pub(crate) fn cat(sources: Vec<File>) -> Self { + RefinedJob::Cat { + sources, + stdin: None, + stdout: None, } } - pub(crate) fn stdout(&mut self, file: File) { - if let &mut RefinedJob::Cat { ref mut stdout, .. } = self { - *stdout = Some(file); - } else { - set_field!(self, stdout, file); + pub(crate) fn function(name: Identifier, args: Array) -> Self { + RefinedJob::Function { + name, + args, + stdin: None, + stdout: None, + stderr: None, } } - pub(crate) fn stderr(&mut self, file: File) { - set_field!(self, stderr, file); - } - - /// Returns a short description of this job: often just the command - /// or builtin name - pub(crate) fn short(&self) -> String { - match *self { - RefinedJob::Builtin { .. } => String::from("Shell Builtin"), - RefinedJob::Function { ref name, .. } | - RefinedJob::External { ref name, .. } => name.to_string(), - // TODO: Print for real - RefinedJob::Cat { .. } => "multi-input".into(), - RefinedJob::Tee { .. } => "multi-output".into(), + pub(crate) fn builtin(main: BuiltinFunction, args: Array) -> Self { + RefinedJob::Builtin { + main, + args, + stdin: None, + stdout: None, + stderr: None, } } - /// Returns a long description of this job: the commands and arguments - pub(crate) fn long(&self) -> String { - match *self { - RefinedJob::External { ref args, .. } | - RefinedJob::Builtin { ref args, .. } | - RefinedJob::Function { ref args, .. } => format!("{}", args.join(" ")), - // TODO: Figure out real printing - RefinedJob::Cat { .. } | RefinedJob::Tee { .. } => "".into(), + pub(crate) fn external(name: Identifier, args: Array) -> Self { + RefinedJob::External { + name, + args, + stdin: None, + stdout: None, + stderr: None, } } } diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs index dfdf25cb..846dda3d 100644 --- a/src/lib/shell/mod.rs +++ b/src/lib/shell/mod.rs @@ -1,62 +1,71 @@ mod assignments; +pub(crate) mod binary; +pub(crate) mod colors; mod completer; +pub(crate) mod directory_stack; +pub mod flags; mod flow; +pub(crate) mod flow_control; mod fork; +pub mod fork_function; mod history; mod job; -pub mod flags; -pub mod fork_function; -pub mod status; -pub mod variables; -pub(crate) mod binary; -pub(crate) mod colors; -pub(crate) mod directory_stack; -pub(crate) mod flow_control; pub(crate) mod pipe_exec; pub(crate) mod plugins; pub(crate) mod signals; +pub mod status; +pub mod variables; -pub use self::binary::Binary; -pub(crate) use self::flow::FlowLogic; -pub use self::fork::{Capture, Fork, IonResult}; -pub(crate) use self::history::{IgnoreSetting, ShellHistory}; -pub(crate) use self::job::{Job, JobKind}; -pub(crate) use self::pipe_exec::{foreground, job_control}; - -use self::directory_stack::DirectoryStack; -use self::flags::*; -use self::flow_control::{FlowControl, Function, FunctionError}; -use self::foreground::ForegroundSignals; -use self::job_control::{BackgroundProcess, JobControl}; -use self::pipe_exec::PipelineExecution; -use self::status::*; -use self::variables::Variables; +pub use self::{ + binary::Binary, + fork::{Capture, Fork, IonResult}, +}; +pub(crate) use self::{ + flow::FlowLogic, + history::{IgnoreSetting, ShellHistory}, + job::{Job, JobKind}, + pipe_exec::{foreground, job_control}, +}; + +use self::{ + directory_stack::DirectoryStack, + flags::*, + flow_control::{FlowControl, Function, FunctionError}, + foreground::ForegroundSignals, + job_control::{BackgroundProcess, JobControl}, + pipe_exec::PipelineExecution, + status::*, + variables::Variables, +}; use builtins::{BuiltinMap, BUILTINS}; use fnv::FnvHashMap; use liner::Context; -use parser::{ArgumentSplitter, Expander, Select}; -use parser::Terminator; -use parser::pipelines::Pipeline; +use parser::{pipelines::Pipeline, ArgumentSplitter, Expander, Select, Terminator}; use smallvec::SmallVec; -use std::fs::File; -use std::io::{self, Read, Write}; -use std::iter::FromIterator; -use std::ops::Deref; -use std::path::Path; -use std::process; -use std::sync::{Arc, Mutex}; -use std::sync::atomic::Ordering; -use std::time::SystemTime; +use std::{ + fs::File, + io::{self, Read, Write}, + iter::FromIterator, + ops::Deref, + path::Path, + process, + sync::{atomic::Ordering, Arc, Mutex}, + time::SystemTime, +}; use sys; use types::*; use xdg::BaseDirectories; #[derive(Debug, Fail)] pub enum IonError { - #[fail(display = "failed to fork: {}", why)] Fork { why: io::Error }, - #[fail(display = "element does not exist")] DoesNotExist, - #[fail(display = "input was not terminated")] Unterminated, - #[fail(display = "function error: {}", why)] Function { why: FunctionError }, + #[fail(display = "failed to fork: {}", why)] + Fork { why: io::Error }, + #[fail(display = "element does not exist")] + DoesNotExist, + #[fail(display = "input was not terminated")] + Unterminated, + #[fail(display = "function error: {}", why)] + Function { why: FunctionError }, } /// The shell structure is a megastructure that manages all of the state of the shell throughout @@ -106,7 +115,27 @@ pub struct Shell { pub struct ShellBuilder; impl ShellBuilder { - pub fn new() -> ShellBuilder { ShellBuilder } + pub fn as_binary(self) -> Shell { Shell::new(false) } + + pub fn as_library(self) -> Shell { Shell::new(true) } + + pub fn set_unique_pid(self) -> ShellBuilder { + if let Ok(pid) = sys::getpid() { + if sys::setpgid(0, pid).is_ok() { + let _ = sys::tcsetpgrp(0, pid); + } + } + + self + } + + pub fn block_signals(self) -> ShellBuilder { + // This will block SIGTSTP, SIGTTOU, SIGTTIN, and SIGCHLD, which is required + // for this shell to manage its own process group / children / etc. + signals::block(); + + self + } pub fn install_signal_handler(self) -> ShellBuilder { extern "C" fn handler(signal: i32) { @@ -135,102 +164,90 @@ impl ShellBuilder { self } - pub fn block_signals(self) -> ShellBuilder { - // This will block SIGTSTP, SIGTTOU, SIGTTIN, and SIGCHLD, which is required - // for this shell to manage its own process group / children / etc. - signals::block(); + pub fn new() -> ShellBuilder { ShellBuilder } +} - self +impl<'a> Shell { + /// A method for capturing the output of the shell, and performing actions without modifying + /// the state of the original shell. This performs a fork, taking a closure that controls + /// the shell in the child of the fork. + /// + /// The method is non-blocking, and therefore will immediately return file handles to the + /// stdout and stderr of the child. The PID of the child is returned, which may be used to + /// wait for and obtain the exit status. + pub fn fork<F: FnMut(&mut Shell)>( + &self, + capture: Capture, + child_func: F, + ) -> Result<IonResult, IonError> { + Fork::new(self, capture).exec(child_func) } - pub fn set_unique_pid(self) -> ShellBuilder { - if let Ok(pid) = sys::getpid() { - if sys::setpgid(0, pid).is_ok() { - let _ = sys::tcsetpgrp(0, pid); - } - } - - self + /// A method for executing a function with the given `name`, using `args` as the input. + /// If the function does not exist, an `IonError::DoesNotExist` is returned. + pub fn execute_function(&mut self, name: &str, args: &[&str]) -> Result<i32, IonError> { + self.functions + .get_mut(name.into()) + .ok_or(IonError::DoesNotExist) + .map(|fnc| fnc.clone()) + .and_then(|function| { + function + .execute(self, args) + .map(|_| self.previous_status) + .map_err(|err| IonError::Function { why: err }) + }) } - pub fn as_library(self) -> Shell { Shell::new(true) } - - pub fn as_binary(self) -> Shell { Shell::new(false) } -} - -impl<'a> Shell { - pub(crate) fn new(is_library: bool) -> Shell { - Shell { - builtins: BUILTINS, - context: None, - variables: Variables::default(), - flow_control: FlowControl::default(), - directory_stack: DirectoryStack::new(), - functions: FnvHashMap::default(), - previous_job: !0, - previous_status: 0, - flags: 0, - background: Arc::new(Mutex::new(Vec::new())), - is_background_shell: false, - is_library, - break_flow: false, - foreground_signals: Arc::new(ForegroundSignals::new()), - ignore_setting: IgnoreSetting::default(), + /// A method for executing scripts in the Ion shell without capturing. Given a `Path`, this + /// method will attempt to execute that file as a script, and then returns the final exit + /// status of the evaluated script. + pub fn execute_script<SCRIPT: AsRef<Path>>(&mut self, script: SCRIPT) -> io::Result<i32> { + let mut script = File::open(script.as_ref())?; + let capacity = script.metadata().ok().map_or(0, |x| x.len()); + let mut command_list = String::with_capacity(capacity as usize); + let _ = script.read_to_string(&mut command_list)?; + if FAILURE == self.terminate_script_quotes(command_list.lines().map(|x| x.to_owned())) { + self.previous_status = FAILURE; } + Ok(self.previous_status) } - pub(crate) fn next_signal(&self) -> Option<i32> { - match signals::PENDING.swap(0, Ordering::SeqCst) { - 0 => None, - signals::SIGINT => Some(sys::SIGINT), - signals::SIGHUP => Some(sys::SIGHUP), - signals::SIGTERM => Some(sys::SIGTERM), - _ => unreachable!(), + /// A method for executing commands in the Ion shell without capturing. It takes command(s) + /// as + /// a string argument, parses them, and executes them the same as it would if you had + /// executed + /// the command(s) in the command line REPL interface for Ion. If the supplied command is + /// not + /// terminated, then an error will be returned. + pub fn execute_command<CMD>(&mut self, command: CMD) -> Result<i32, IonError> + where + CMD: Into<Terminator>, + { + let mut terminator = command.into(); + if terminator.is_terminated() { + self.on_command(&terminator.consume()); + Ok(self.previous_status) + } else { + Err(IonError::Unterminated) } } - pub(crate) fn prep_for_exit(&mut self) { - // The context has two purposes: if it exists, this is an interactive shell; and the - // context will also be sent a signal to commit all changes to the history file, - // and waiting for the history thread in the background to finish. - if self.context.is_some() { - if self.flags & HUPONEXIT != 0 { - self.resume_stopped(); - self.background_send(sys::SIGHUP); - } - let context = self.context.as_mut().unwrap(); - context.history.commit_history(); - } + /// Gets an array variable, if it exists within the shell's array map. + pub fn get_array(&self, name: &str) -> Option<&[String]> { + self.variables.get_array(name).map(SmallVec::as_ref) } - /// Cleanly exit ion - pub fn exit(&mut self, status: i32) -> ! { - self.prep_for_exit(); - process::exit(status); + /// Obtains a variable, returning an empty string if it does not exist. + pub(crate) fn get_var_or_empty(&self, name: &str) -> String { + self.variables.get_var_or_empty(name) } - /// Evaluates the source init file in the user's home directory. - pub fn evaluate_init_file(&mut self) { - let base_dirs = match BaseDirectories::with_prefix("ion") { - Ok(base_dirs) => base_dirs, - Err(err) => { - eprintln!("ion: unable to get base directory: {}", err); - return; - } - }; - match base_dirs.find_config_file("initrc") { - Some(initrc) => { - if let Err(err) = self.execute_script(&initrc) { - eprintln!("ion: {}", err); - } - } - None => { - if let Err(err) = base_dirs.place_config_file("initrc") { - eprintln!("ion: could not create initrc file: {}", err); - } - } - } - } + /// Gets a string variable, if it exists within the shell's variable map. + pub fn get_var(&self, name: &str) -> Option<String> { self.variables.get_var(name) } + + /// Sets a variable of `name` with the given `value` in the shell's + /// variable map. + pub fn set_var(&mut self, name: &str, value: &str) { self.variables.set_var(name, value); } /// Executes a pipeline and returns the final exit status of the pipeline. pub(crate) fn run_pipeline(&mut self, pipeline: &mut Pipeline) -> Option<i32> { @@ -338,91 +355,115 @@ impl<'a> Shell { exit_status } - /// Sets a variable of `name` with the given `value` in the shell's - /// variable map. - pub fn set_var(&mut self, name: &str, value: &str) { self.variables.set_var(name, value); } - - /// Gets a string variable, if it exists within the shell's variable map. - pub fn get_var(&self, name: &str) -> Option<String> { self.variables.get_var(name) } - - /// Obtains a variable, returning an empty string if it does not exist. - pub(crate) fn get_var_or_empty(&self, name: &str) -> String { - self.variables.get_var_or_empty(name) + /// Evaluates the source init file in the user's home directory. + pub fn evaluate_init_file(&mut self) { + let base_dirs = match BaseDirectories::with_prefix("ion") { + Ok(base_dirs) => base_dirs, + Err(err) => { + eprintln!("ion: unable to get base directory: {}", err); + return; + } + }; + match base_dirs.find_config_file("initrc") { + Some(initrc) => { + if let Err(err) = self.execute_script(&initrc) { + eprintln!("ion: {}", err); + } + } + None => { + if let Err(err) = base_dirs.place_config_file("initrc") { + eprintln!("ion: could not create initrc file: {}", err); + } + } + } } - /// Gets an array variable, if it exists within the shell's array map. - pub fn get_array(&self, name: &str) -> Option<&[String]> { - self.variables.get_array(name).map(SmallVec::as_ref) + /// Cleanly exit ion + pub fn exit(&mut self, status: i32) -> ! { + self.prep_for_exit(); + process::exit(status); } - /// A method for executing commands in the Ion shell without capturing. It takes command(s) - /// as - /// a string argument, parses them, and executes them the same as it would if you had - /// executed - /// the command(s) in the command line REPL interface for Ion. If the supplied command is - /// not - /// terminated, then an error will be returned. - pub fn execute_command<CMD>(&mut self, command: CMD) -> Result<i32, IonError> - where - CMD: Into<Terminator>, - { - let mut terminator = command.into(); - if terminator.is_terminated() { - self.on_command(&terminator.consume()); - Ok(self.previous_status) - } else { - Err(IonError::Unterminated) + pub(crate) fn prep_for_exit(&mut self) { + // The context has two purposes: if it exists, this is an interactive shell; and the + // context will also be sent a signal to commit all changes to the history file, + // and waiting for the history thread in the background to finish. + if self.context.is_some() { + if self.flags & HUPONEXIT != 0 { + self.resume_stopped(); + self.background_send(sys::SIGHUP); + } + let context = self.context.as_mut().unwrap(); + context.history.commit_history(); } } - /// A method for executing scripts in the Ion shell without capturing. Given a `Path`, this - /// method will attempt to execute that file as a script, and then returns the final exit - /// status of the evaluated script. - pub fn execute_script<SCRIPT: AsRef<Path>>(&mut self, script: SCRIPT) -> io::Result<i32> { - let mut script = File::open(script.as_ref())?; - let capacity = script.metadata().ok().map_or(0, |x| x.len()); - let mut command_list = String::with_capacity(capacity as usize); - let _ = script.read_to_string(&mut command_list)?; - if FAILURE == self.terminate_script_quotes(command_list.lines().map(|x| x.to_owned())) { - self.previous_status = FAILURE; + pub(crate) fn next_signal(&self) -> Option<i32> { + match signals::PENDING.swap(0, Ordering::SeqCst) { + 0 => None, + signals::SIGINT => Some(sys::SIGINT), + signals::SIGHUP => Some(sys::SIGHUP), + signals::SIGTERM => Some(sys::SIGTERM), + _ => unreachable!(), } - Ok(self.previous_status) - } - - /// A method for executing a function with the given `name`, using `args` as the input. - /// If the function does not exist, an `IonError::DoesNotExist` is returned. - pub fn execute_function(&mut self, name: &str, args: &[&str]) -> Result<i32, IonError> { - self.functions - .get_mut(name.into()) - .ok_or(IonError::DoesNotExist) - .map(|fnc| fnc.clone()) - .and_then(|function| { - function - .execute(self, args) - .map(|_| self.previous_status) - .map_err(|err| IonError::Function { why: err }) - }) } - /// A method for capturing the output of the shell, and performing actions without modifying - /// the state of the original shell. This performs a fork, taking a closure that controls - /// the shell in the child of the fork. - /// - /// The method is non-blocking, and therefore will immediately return file handles to the - /// stdout and stderr of the child. The PID of the child is returned, which may be used to - /// wait for and obtain the exit status. - pub fn fork<F: FnMut(&mut Shell)>( - &self, - capture: Capture, - child_func: F, - ) -> Result<IonResult, IonError> { - Fork::new(self, capture).exec(child_func) + pub(crate) fn new(is_library: bool) -> Shell { + Shell { + builtins: BUILTINS, + context: None, + variables: Variables::default(), + flow_control: FlowControl::default(), + directory_stack: DirectoryStack::new(), + functions: FnvHashMap::default(), + previous_job: !0, + previous_status: 0, + flags: 0, + background: Arc::new(Mutex::new(Vec::new())), + is_background_shell: false, + is_library, + break_flow: false, + foreground_signals: Arc::new(ForegroundSignals::new()), + ignore_setting: IgnoreSetting::default(), + } } } impl<'a> Expander for Shell { - fn tilde(&self, input: &str) -> Option<String> { - self.variables.tilde_expansion(input, &self.directory_stack) + /// Uses a subshell to expand a given command. + fn command(&self, command: &str) -> Option<Value> { + let mut output = None; + match self.fork(Capture::StdoutThenIgnoreStderr, move |shell| { + shell.on_command(command) + }) { + Ok(result) => { + let mut string = String::with_capacity(1024); + match result.stdout.unwrap().read_to_string(&mut string) { + Ok(_) => output = Some(string), + Err(why) => { + eprintln!("ion: error reading stdout of child: {}", why); + } + } + } + Err(why) => { + eprintln!("ion: fork error: {}", why); + } + } + + // Ensure that the parent retains ownership of the terminal before exiting. + let _ = sys::tcsetpgrp(sys::STDIN_FILENO, process::id()); + output + } + + /// Expand a string variable given if its quoted / unquoted + fn variable(&self, variable: &str, quoted: bool) -> Option<Value> { + use ascii_helpers::AsciiReplace; + if quoted { + self.get_var(variable) + } else { + self.get_var(variable) + .map(|x| x.ascii_replace('\n', ' ').into()) + } } /// Expand an array variable with some selection @@ -472,37 +513,8 @@ impl<'a> Expander for Shell { } found } - /// Expand a string variable given if its quoted / unquoted - fn variable(&self, variable: &str, quoted: bool) -> Option<Value> { - use ascii_helpers::AsciiReplace; - if quoted { - self.get_var(variable) - } else { - self.get_var(variable) - .map(|x| x.ascii_replace('\n', ' ').into()) - } - } - /// Uses a subshell to expand a given command. - fn command(&self, command: &str) -> Option<Value> { - let mut output = None; - match self.fork(Capture::StdoutThenIgnoreStderr, move |shell| shell.on_command(command)) { - Ok(result) => { - let mut string = String::with_capacity(1024); - match result.stdout.unwrap().read_to_string(&mut string) { - Ok(_) => output = Some(string), - Err(why) => { - eprintln!("ion: error reading stdout of child: {}", why); - } - } - } - Err(why) => { - eprintln!("ion: fork error: {}", why); - } - } - - // Ensure that the parent retains ownership of the terminal before exiting. - let _ = sys::tcsetpgrp(sys::STDIN_FILENO, process::id()); - output + fn tilde(&self, input: &str) -> Option<String> { + self.variables.tilde_expansion(input, &self.directory_stack) } } diff --git a/src/lib/shell/pipe_exec/foreground.rs b/src/lib/shell/pipe_exec/foreground.rs index ff76295d..8b860232 100644 --- a/src/lib/shell/pipe_exec/foreground.rs +++ b/src/lib/shell/pipe_exec/foreground.rs @@ -21,26 +21,7 @@ pub(crate) struct ForegroundSignals { } impl ForegroundSignals { - pub(crate) fn new() -> ForegroundSignals { - ForegroundSignals { - grab: AtomicU32::new(0), - status: AtomicU8::new(0), - reply: AtomicU8::new(0), - } - } - - pub(crate) fn signal_to_grab(&self, pid: u32) { self.grab.store(pid, Ordering::Relaxed); } - - pub(crate) fn reply_with(&self, status: i8) { - self.grab.store(0, Ordering::Relaxed); - self.status.store(status as u8, Ordering::Relaxed); - self.reply.store(REPLIED, Ordering::Relaxed); - } - - pub(crate) fn errored(&self) { - self.grab.store(0, Ordering::Relaxed); - self.reply.store(ERRORED, Ordering::Relaxed); - } + pub(crate) fn was_grabbed(&self, pid: u32) -> bool { self.grab.load(Ordering::Relaxed) == pid } pub(crate) fn was_processed(&self) -> Option<BackgroundResult> { let reply = self.reply.load(Ordering::Relaxed); @@ -56,5 +37,24 @@ impl ForegroundSignals { } } - pub(crate) fn was_grabbed(&self, pid: u32) -> bool { self.grab.load(Ordering::Relaxed) == pid } + pub(crate) fn errored(&self) { + self.grab.store(0, Ordering::Relaxed); + self.reply.store(ERRORED, Ordering::Relaxed); + } + + pub(crate) fn reply_with(&self, status: i8) { + self.grab.store(0, Ordering::Relaxed); + self.status.store(status as u8, Ordering::Relaxed); + self.reply.store(REPLIED, Ordering::Relaxed); + } + + pub(crate) fn signal_to_grab(&self, pid: u32) { self.grab.store(pid, Ordering::Relaxed); } + + pub(crate) fn new() -> ForegroundSignals { + ForegroundSignals { + grab: AtomicU32::new(0), + status: AtomicU8::new(0), + reply: AtomicU8::new(0), + } + } } diff --git a/src/lib/shell/pipe_exec/fork.rs b/src/lib/shell/pipe_exec/fork.rs index 17e40b0c..8a6066fb 100644 --- a/src/lib/shell/pipe_exec/fork.rs +++ b/src/lib/shell/pipe_exec/fork.rs @@ -3,11 +3,15 @@ use sys; /// Ensures that the forked child is given a unique process ID. pub(crate) fn create_process_group(pgid: u32) { let _ = sys::setpgid(0, pgid); } -use super::job_control::{JobControl, ProcessState}; -use super::pipe; -use super::super::Shell; -use super::super::job::{JobKind, RefinedJob}; -use super::super::status::*; +use super::{ + super::{ + job::{JobKind, RefinedJob}, + status::*, + Shell, + }, + job_control::{JobControl, ProcessState}, + pipe, +}; use std::process::exit; /// Forks the shell, adding the child to the parent's background list, and executing diff --git a/src/lib/shell/pipe_exec/job_control.rs b/src/lib/shell/pipe_exec/job_control.rs index b2d26a3f..079a2efd 100644 --- a/src/lib/shell/pipe_exec/job_control.rs +++ b/src/lib/shell/pipe_exec/job_control.rs @@ -1,12 +1,14 @@ -use super::foreground::BackgroundResult; -use super::super::Shell; -use super::super::signals; -use super::super::status::*; -use std::fmt; -use std::process; -use std::sync::{Arc, Mutex}; -use std::thread::{sleep, spawn}; -use std::time::Duration; +use super::{ + super::{signals, status::*, Shell}, + foreground::BackgroundResult, +}; +use std::{ + fmt, + process, + sync::{Arc, Mutex}, + thread::{sleep, spawn}, + time::Duration, +}; use sys; use sys::job_control as self_sys; @@ -67,9 +69,9 @@ pub(crate) fn add_to_background( { Some(id) => { (*processes)[id] = BackgroundProcess { - pid: pid, + pid, ignore_sighup: false, - state: state, + state, name: command, }; id as u32 @@ -77,9 +79,9 @@ pub(crate) fn add_to_background( None => { let njobs = (*processes).len(); (*processes).push(BackgroundProcess { - pid: pid, + pid, ignore_sighup: false, - state: state, + state, name: command, }); njobs as u32 @@ -100,64 +102,35 @@ pub struct BackgroundProcess { } impl JobControl for Shell { - fn set_bg_task_in_foreground(&self, pid: u32, cont: bool) -> i32 { - // Resume the background task, if needed. - if cont { - signals::resume(pid); + /// If a SIGTERM is received, a SIGTERM will be sent to all background processes + /// before the shell terminates itself. + fn handle_signal(&self, signal: i32) -> bool { + if signal == sys::SIGTERM || signal == sys::SIGHUP { + self.background_send(signal); + true + } else { + false } - // Pass the TTY to the background job - set_foreground_as(pid); - // Signal the background thread that is waiting on this process to stop waiting. - self.foreground_signals.signal_to_grab(pid); - let status = loop { - // When the background thread that is monitoring the task receives an exit/stop - // signal, the status of that process will be communicated back. To - // avoid consuming CPU cycles, we wait 25 ms between polls. - match self.foreground_signals.was_processed() { - Some(BackgroundResult::Status(stat)) => break stat as i32, - Some(BackgroundResult::Errored) => break TERMINATED, - None => sleep(Duration::from_millis(25)), - } - }; - // Have the shell reclaim the TTY - set_foreground_as(process::id()); - status } - /// Waits until all running background tasks have completed, and listens for signals in the - /// event that a signal is sent to kill the running tasks. - fn wait_for_background(&mut self) { - let sigcode; - 'event: loop { - for process in self.background.lock().unwrap().iter() { - if let ProcessState::Running = process.state { - while let Some(signal) = self.next_signal() { - if signal != sys::SIGTSTP { - self.background_send(signal); - sigcode = get_signal_code(signal); - break 'event; - } - } - sleep(Duration::from_millis(100)); - continue 'event; - } - } - return; - } - self.exit(sigcode); - } + fn send_to_background(&mut self, pid: u32, state: ProcessState, command: String) { + // Increment the `Arc` counters so that these fields can be moved into + // the upcoming background thread. + let processes = self.background.clone(); + let fg_signals = self.foreground_signals.clone(); - fn watch_foreground(&mut self, pid: i32, command: &str) -> i32 { - self_sys::watch_foreground(self, pid, command) - } + // Add the process to the background list, and mark the job's ID as + // the previous job in the shell (in case fg/bg is executed w/ no args). + let njob = add_to_background(processes.clone(), pid, state, command); + self.previous_job = njob; + eprintln!("ion: bg [{}] {}", njob, pid); - /// Resumes all stopped background jobs - fn resume_stopped(&mut self) { - for process in self.background.lock().unwrap().iter() { - if process.state == ProcessState::Stopped { - signals::resume(process.pid); - } - } + // Spawn a background thread that will monitor the progress of the + // background process, updating it's state changes until it finally + // exits. + let _ = spawn(move || { + watch_background(fg_signals, processes, pid, njob as usize); + }); } /// Send a kill signal to all running background tasks. @@ -177,34 +150,63 @@ impl JobControl for Shell { } } - fn send_to_background(&mut self, pid: u32, state: ProcessState, command: String) { - // Increment the `Arc` counters so that these fields can be moved into - // the upcoming background thread. - let processes = self.background.clone(); - let fg_signals = self.foreground_signals.clone(); + /// Resumes all stopped background jobs + fn resume_stopped(&mut self) { + for process in self.background.lock().unwrap().iter() { + if process.state == ProcessState::Stopped { + signals::resume(process.pid); + } + } + } - // Add the process to the background list, and mark the job's ID as - // the previous job in the shell (in case fg/bg is executed w/ no args). - let njob = add_to_background(processes.clone(), pid, state, command); - self.previous_job = njob; - eprintln!("ion: bg [{}] {}", njob, pid); + fn watch_foreground(&mut self, pid: i32, command: &str) -> i32 { + self_sys::watch_foreground(self, pid, command) + } - // Spawn a background thread that will monitor the progress of the - // background process, updating it's state changes until it finally - // exits. - let _ = spawn(move || { - watch_background(fg_signals, processes, pid, njob as usize); - }); + /// Waits until all running background tasks have completed, and listens for signals in the + /// event that a signal is sent to kill the running tasks. + fn wait_for_background(&mut self) { + let sigcode; + 'event: loop { + for process in self.background.lock().unwrap().iter() { + if let ProcessState::Running = process.state { + while let Some(signal) = self.next_signal() { + if signal != sys::SIGTSTP { + self.background_send(signal); + sigcode = get_signal_code(signal); + break 'event; + } + } + sleep(Duration::from_millis(100)); + continue 'event; + } + } + return; + } + self.exit(sigcode); } - /// If a SIGTERM is received, a SIGTERM will be sent to all background processes - /// before the shell terminates itself. - fn handle_signal(&self, signal: i32) -> bool { - if signal == sys::SIGTERM || signal == sys::SIGHUP { - self.background_send(signal); - true - } else { - false + fn set_bg_task_in_foreground(&self, pid: u32, cont: bool) -> i32 { + // Resume the background task, if needed. + if cont { + signals::resume(pid); } + // Pass the TTY to the background job + set_foreground_as(pid); + // Signal the background thread that is waiting on this process to stop waiting. + self.foreground_signals.signal_to_grab(pid); + let status = loop { + // When the background thread that is monitoring the task receives an exit/stop + // signal, the status of that process will be communicated back. To + // avoid consuming CPU cycles, we wait 25 ms between polls. + match self.foreground_signals.was_processed() { + Some(BackgroundResult::Status(stat)) => break stat as i32, + Some(BackgroundResult::Errored) => break TERMINATED, + None => sleep(Duration::from_millis(25)), + } + }; + // Have the shell reclaim the TTY + set_foreground_as(process::id()); + status } } diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs index 9af781d7..dc82fb42 100644 --- a/src/lib/shell/pipe_exec/mod.rs +++ b/src/lib/shell/pipe_exec/mod.rs @@ -5,29 +5,36 @@ //! the background, handling pipeline and conditional operators, and //! std{in,out,err} redirections. -mod fork; -mod streams; pub mod foreground; +mod fork; pub mod job_control; +mod streams; +use self::{ + fork::fork_pipe, + job_control::{JobControl, ProcessState}, + streams::{duplicate_streams, redir, redirect_streams}, +}; +use super::{ + flags::*, + flow_control::FunctionError, + fork_function::command_not_found, + job::{RefinedJob, TeeItem}, + signals::{self, SignalHandler}, + status::*, + JobKind, + Shell, +}; use builtins::{self, BuiltinFunction}; use parser::pipelines::{Input, PipeItem, Pipeline, RedirectFrom, Redirection}; -use self::fork::fork_pipe; -use self::job_control::{JobControl, ProcessState}; -use self::streams::{duplicate_streams, redir, redirect_streams}; -use std::fs::{File, OpenOptions}; -use std::io::{self, Error, Write}; -use std::iter; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::path::Path; -use std::process::{self, exit}; -use super::flags::*; -use super::flow_control::FunctionError; -use super::fork_function::command_not_found; -use super::job::{RefinedJob, TeeItem}; -use super::signals::{self, SignalHandler}; -use super::status::*; -use super::{JobKind, Shell}; +use std::{ + fs::{File, OpenOptions}, + io::{self, Error, Write}, + iter, + os::unix::io::{AsRawFd, FromRawFd, RawFd}, + path::Path, + process::{self, exit}, +}; use sys; type RefinedItem = (RefinedJob, JobKind, Vec<Redirection>, Vec<Input>); @@ -99,14 +106,16 @@ fn do_redirection(piped_commands: Vec<RefinedItem>) -> Option<Vec<(RefinedJob, J match unsafe { stdin_of(&string) } { Ok(stdio) => Some(unsafe { File::from_raw_fd(stdio) }), Err(e) => { - eprintln!("ion: failed to redirect herestring '{}' to stdin: {}", - string, e); + eprintln!( + "ion: failed to redirect herestring '{}' to stdin: {}", + string, e + ); None } } } } - } + }; } let need_tee = |outs: &[_], kind| { @@ -142,7 +151,11 @@ fn do_redirection(piped_commands: Vec<RefinedItem>) -> Option<Vec<(RefinedJob, J // XXX: Possibly add an assertion here for correctness for output in $outputs { match if output.append { - OpenOptions::new().create(true).write(true).append(true).open(&output.file) + OpenOptions::new() + .create(true) + .write(true) + .append(true) + .open(&output.file) } else { File::create(&output.file) } { @@ -153,15 +166,16 @@ fn do_redirection(piped_commands: Vec<RefinedItem>) -> Option<Vec<(RefinedJob, J Ok(f_copy) => { $job.stdout(f); $job.stderr(f_copy); - }, + } Err(e) => { eprintln!( - "ion: failed to redirect both stdout and stderr to file '{:?}': {}", - f, - e); + "ion: failed to redirect both stdout and stderr to file \ + '{:?}': {}", + f, e + ); return None; } - } + }, }, Err(e) => { eprintln!("ion: failed to redirect output into {}: {}", output.file, e); @@ -169,15 +183,22 @@ fn do_redirection(piped_commands: Vec<RefinedItem>) -> Option<Vec<(RefinedJob, J } } } - } + }; } macro_rules! set_one_tee { ($new:ident, $outputs:ident, $job:ident, $kind:ident, $teed:ident, $other:ident) => {{ - let mut tee = TeeItem { sinks: Vec::new(), source: None }; + let mut tee = TeeItem { + sinks: Vec::new(), + source: None, + }; for output in $outputs { match if output.append { - OpenOptions::new().create(true).write(true).append(true).open(&output.file) + OpenOptions::new() + .create(true) + .write(true) + .append(true) + .open(&output.file) } else { File::create(&output.file) } { @@ -196,15 +217,15 @@ fn do_redirection(piped_commands: Vec<RefinedItem>) -> Option<Vec<(RefinedJob, J $job.stdout(f); } tee.sinks.push(f_copy); - }, + } Err(e) => { eprintln!( "ion: failed to redirect both stdout and stderr to file '{:?}': {}", - f, - e); + f, e + ); return None; } - } + }, }, Err(e) => { eprintln!("ion: failed to redirect output into {}: {}", output.file, e); @@ -220,7 +241,7 @@ fn do_redirection(piped_commands: Vec<RefinedItem>) -> Option<Vec<(RefinedJob, J }; let tee = RefinedJob::tee(items.0, items.1); $new.push((tee, $kind)); - }} + }}; } // Real logic begins here @@ -410,88 +431,198 @@ pub(crate) trait PipelineExecution { } impl PipelineExecution for Shell { - fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32 { - // If the supplied pipeline is a background, a string representing the command - // and a boolean representing whether it should be disowned is stored here. - let possible_background_name = - gen_background_string(&pipeline, self.flags & PRINT_COMMS != 0); - // Generates commands for execution, differentiating between external and - // builtin commands. - let piped_commands = match self.generate_commands(pipeline) { - Ok(commands) => commands, - Err(error) => return error, - }; + fn exec_external( + &mut self, + name: &str, + args: &[&str], + stdin: &Option<File>, + stdout: &Option<File>, + stderr: &Option<File>, + ) -> i32 { + let result = sys::fork_and_exec( + name, + &args, + if let Some(ref f) = *stdin { + Some(f.as_raw_fd()) + } else { + None + }, + if let Some(ref f) = *stdout { + Some(f.as_raw_fd()) + } else { + None + }, + if let Some(ref f) = *stderr { + Some(f.as_raw_fd()) + } else { + None + }, + false, + || prepare_child(true, 0), + ); - // Don't execute commands when the `-n` flag is passed. - if self.flags & NO_EXEC != 0 { - return SUCCESS; + match result { + Ok(pid) => { + let _ = sys::setpgid(pid, pid); + let _ = sys::tcsetpgrp(0, pid); + let _ = sys::wait_for_interrupt(pid); + let _ = sys::kill(pid, sys::SIGCONT); + self.watch_foreground(-(pid as i32), "") + } + Err(ref err) if err.kind() == io::ErrorKind::NotFound => { + if !command_not_found(self, &name) { + eprintln!("ion: command not found: {}", name); + } + NO_SUCH_COMMAND + } + Err(ref err) => { + eprintln!("ion: command exec error: {}", err); + FAILURE + } } + } - let piped_commands = match do_redirection(piped_commands) { - Some(c) => c, - None => return COULD_NOT_EXEC + fn exec_multi_out( + &mut self, + items: &mut (Option<TeeItem>, Option<TeeItem>), + stdout: &Option<File>, + stderr: &Option<File>, + stdin: &Option<File>, + kind: JobKind, + ) -> i32 { + if let Some(ref file) = *stdin { + redir(file.as_raw_fd(), sys::STDIN_FILENO); + } + if let Some(ref file) = *stdout { + redir(file.as_raw_fd(), sys::STDOUT_FILENO); + } + if let Some(ref file) = *stderr { + redir(file.as_raw_fd(), sys::STDERR_FILENO); + } + let res = match items { + &mut (None, None) => panic!("There must be at least one TeeItem, this is a bug"), + &mut (Some(ref mut tee_out), None) => match kind { + JobKind::Pipe(RedirectFrom::Stderr) => tee_out.write_to_all(None), + JobKind::Pipe(_) => tee_out.write_to_all(Some(RedirectFrom::Stdout)), + _ => tee_out.write_to_all(None), + }, + &mut (None, Some(ref mut tee_err)) => match kind { + JobKind::Pipe(RedirectFrom::Stdout) => tee_err.write_to_all(None), + JobKind::Pipe(_) => tee_err.write_to_all(Some(RedirectFrom::Stderr)), + _ => tee_err.write_to_all(None), + }, + &mut (Some(ref mut tee_out), Some(ref mut tee_err)) => { + // TODO Make it work with pipes + if let Err(e) = tee_out.write_to_all(None) { + Err(e) + } else { + tee_err.write_to_all(None) + } + } }; - - // If the given pipeline is a background task, fork the shell. - if let Some((command_name, disown)) = possible_background_name { - fork_pipe( - self, - piped_commands, - command_name, - if disown { ProcessState::Empty } else { ProcessState::Running } - ) + if let Err(e) = res { + eprintln!("ion: error in multiple output redirection process: {:?}", e); + FAILURE } else { - // While active, the SIGTTOU signal will be ignored. - let _sig_ignore = SignalHandler::new(); - let foreground = !self.is_background_shell; - // Execute each command in the pipeline, giving each command the foreground. - let exit_status = pipe(self, piped_commands, foreground); - // Set the shell as the foreground process again to regain the TTY. - if foreground && !self.is_library { - let _ = sys::tcsetpgrp(0, process::id()); - } - exit_status + SUCCESS } } - fn generate_commands(&self, pipeline: &mut Pipeline) -> Result<Vec<RefinedItem>, i32> { - let mut results = Vec::new(); - for item in pipeline.items.drain(..) { - let PipeItem { - mut job, - outputs, - inputs, - } = item; - let refined = { - if is_implicit_cd(&job.args[0]) { - RefinedJob::builtin( - builtins::builtin_cd, - iter::once("cd".into()).chain(job.args.drain()).collect(), - ) - } else if self.functions.contains_key(job.args[0].as_str()) { - RefinedJob::function(job.args[0].clone().into(), job.args.drain().collect()) - } else if let Some(builtin) = job.builtin { - RefinedJob::builtin(builtin, job.args.drain().collect()) - } else { - RefinedJob::external(job.args[0].clone().into(), job.args.drain().collect()) + fn exec_multi_in( + &mut self, + sources: &mut [File], + stdout: &Option<File>, + stdin: &mut Option<File>, + ) -> i32 { + if let Some(ref file) = *stdin { + redir(file.as_raw_fd(), sys::STDIN_FILENO) + } + if let Some(ref file) = *stdout { + redir(file.as_raw_fd(), sys::STDOUT_FILENO) + } + + fn read_and_write<R: io::Read>(src: &mut R, stdout: &mut io::StdoutLock) -> io::Result<()> { + let mut buf = [0; 4096]; + loop { + let len = src.read(&mut buf)?; + if len == 0 { + return Ok(()); + }; + let mut total = 0; + loop { + let wrote = stdout.write(&buf[total..len])?; + total += wrote; + if total == len { + break; + } } - }; - results.push((refined, job.kind, outputs, inputs)); + } + }; + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + for file in stdin.iter_mut().chain(sources) { + if let Err(why) = read_and_write(file, &mut stdout) { + eprintln!("ion: error in multiple input redirect process: {:?}", why); + return FAILURE; + } } + SUCCESS + } - Ok(results) + fn exec_function( + &mut self, + name: &str, + args: &[&str], + stdout: &Option<File>, + stderr: &Option<File>, + stdin: &Option<File>, + ) -> i32 { + if let Some(ref file) = *stdin { + redir(file.as_raw_fd(), sys::STDIN_FILENO); + } + if let Some(ref file) = *stdout { + redir(file.as_raw_fd(), sys::STDOUT_FILENO); + } + if let Some(ref file) = *stderr { + redir(file.as_raw_fd(), sys::STDERR_FILENO); + } + + let function = self.functions.get(name).cloned().unwrap(); + match function.execute(self, args) { + Ok(()) => SUCCESS, + Err(FunctionError::InvalidArgumentCount) => { + eprintln!("ion: invalid number of function arguments supplied"); + FAILURE + } + Err(FunctionError::InvalidArgumentType(expected_type, value)) => { + eprintln!( + "ion: function argument has invalid type: expected {}, found value \'{}\'", + expected_type, value + ); + FAILURE + } + } } - fn wait(&mut self, pgid: u32, commands: Vec<RefinedJob>) -> i32 { - // TODO: Find a way to only do this when absolutely necessary. - let as_string = commands - .iter() - .map(RefinedJob::long) - .collect::<Vec<String>>() - .join(" | "); + fn exec_builtin( + &mut self, + main: BuiltinFunction, + args: &[&str], + stdout: &Option<File>, + stderr: &Option<File>, + stdin: &Option<File>, + ) -> i32 { + if let Some(ref file) = *stdin { + redir(file.as_raw_fd(), sys::STDIN_FILENO); + } + if let Some(ref file) = *stdout { + redir(file.as_raw_fd(), sys::STDOUT_FILENO); + } + if let Some(ref file) = *stderr { + redir(file.as_raw_fd(), sys::STDERR_FILENO); + } - // Watch the foreground group, dropping all commands that exit as they exit. - self.watch_foreground(-(pgid as i32), &as_string) + main(args, self) } fn exec_job(&mut self, job: &mut RefinedJob, _foreground: bool) -> i32 { @@ -558,207 +689,117 @@ impl PipelineExecution for Shell { } } - fn exec_builtin( - &mut self, - main: BuiltinFunction, - args: &[&str], - stdout: &Option<File>, - stderr: &Option<File>, - stdin: &Option<File>, - ) -> i32 { - if let Some(ref file) = *stdin { - redir(file.as_raw_fd(), sys::STDIN_FILENO); - } - if let Some(ref file) = *stdout { - redir(file.as_raw_fd(), sys::STDOUT_FILENO); - } - if let Some(ref file) = *stderr { - redir(file.as_raw_fd(), sys::STDERR_FILENO); - } + fn wait(&mut self, pgid: u32, commands: Vec<RefinedJob>) -> i32 { + // TODO: Find a way to only do this when absolutely necessary. + let as_string = commands + .iter() + .map(RefinedJob::long) + .collect::<Vec<String>>() + .join(" | "); - main(args, self) + // Watch the foreground group, dropping all commands that exit as they exit. + self.watch_foreground(-(pgid as i32), &as_string) } - fn exec_function( - &mut self, - name: &str, - args: &[&str], - stdout: &Option<File>, - stderr: &Option<File>, - stdin: &Option<File>, - ) -> i32 { - if let Some(ref file) = *stdin { - redir(file.as_raw_fd(), sys::STDIN_FILENO); - } - if let Some(ref file) = *stdout { - redir(file.as_raw_fd(), sys::STDOUT_FILENO); - } - if let Some(ref file) = *stderr { - redir(file.as_raw_fd(), sys::STDERR_FILENO); + fn generate_commands(&self, pipeline: &mut Pipeline) -> Result<Vec<RefinedItem>, i32> { + let mut results = Vec::new(); + for item in pipeline.items.drain(..) { + let PipeItem { + mut job, + outputs, + inputs, + } = item; + let refined = { + if is_implicit_cd(&job.args[0]) { + RefinedJob::builtin( + builtins::builtin_cd, + iter::once("cd".into()).chain(job.args.drain()).collect(), + ) + } else if self.functions.contains_key(job.args[0].as_str()) { + RefinedJob::function(job.args[0].clone().into(), job.args.drain().collect()) + } else if let Some(builtin) = job.builtin { + RefinedJob::builtin(builtin, job.args.drain().collect()) + } else { + RefinedJob::external(job.args[0].clone().into(), job.args.drain().collect()) + } + }; + results.push((refined, job.kind, outputs, inputs)); } - let function = self.functions.get(name).cloned().unwrap(); - match function.execute(self, args) { - Ok(()) => SUCCESS, - Err(FunctionError::InvalidArgumentCount) => { - eprintln!("ion: invalid number of function arguments supplied"); - FAILURE - } - Err(FunctionError::InvalidArgumentType(expected_type, value)) => { - eprintln!( - "ion: function argument has invalid type: expected {}, found value \'{}\'", - expected_type, value - ); - FAILURE - } - } + Ok(results) } - fn exec_multi_in( - &mut self, - sources: &mut [File], - stdout: &Option<File>, - stdin: &mut Option<File>, - ) -> i32 { - if let Some(ref file) = *stdin { - redir(file.as_raw_fd(), sys::STDIN_FILENO) - } - if let Some(ref file) = *stdout { - redir(file.as_raw_fd(), sys::STDOUT_FILENO) + fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32 { + // If the supplied pipeline is a background, a string representing the command + // and a boolean representing whether it should be disowned is stored here. + let possible_background_name = + gen_background_string(&pipeline, self.flags & PRINT_COMMS != 0); + // Generates commands for execution, differentiating between external and + // builtin commands. + let piped_commands = match self.generate_commands(pipeline) { + Ok(commands) => commands, + Err(error) => return error, + }; + + // Don't execute commands when the `-n` flag is passed. + if self.flags & NO_EXEC != 0 { + return SUCCESS; } - fn read_and_write<R: io::Read>(src: &mut R, stdout: &mut io::StdoutLock) -> io::Result<()> { - let mut buf = [0; 4096]; - loop { - let len = src.read(&mut buf)?; - if len == 0 { - return Ok(()); - }; - let mut total = 0; - loop { - let wrote = stdout.write(&buf[total..len])?; - total += wrote; - if total == len { - break; - } - } - } + let piped_commands = match do_redirection(piped_commands) { + Some(c) => c, + None => return COULD_NOT_EXEC, }; - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - for file in stdin.iter_mut().chain(sources) { - if let Err(why) = read_and_write(file, &mut stdout) { - eprintln!("ion: error in multiple input redirect process: {:?}", why); - return FAILURE; - } - } - SUCCESS - } - fn exec_multi_out( - &mut self, - items: &mut (Option<TeeItem>, Option<TeeItem>), - stdout: &Option<File>, - stderr: &Option<File>, - stdin: &Option<File>, - kind: JobKind, - ) -> i32 { - if let Some(ref file) = *stdin { - redir(file.as_raw_fd(), sys::STDIN_FILENO); - } - if let Some(ref file) = *stdout { - redir(file.as_raw_fd(), sys::STDOUT_FILENO); - } - if let Some(ref file) = *stderr { - redir(file.as_raw_fd(), sys::STDERR_FILENO); - } - let res = match items { - &mut (None, None) => panic!("There must be at least one TeeItem, this is a bug"), - &mut (Some(ref mut tee_out), None) => match kind { - JobKind::Pipe(RedirectFrom::Stderr) => tee_out.write_to_all(None), - JobKind::Pipe(_) => tee_out.write_to_all(Some(RedirectFrom::Stdout)), - _ => tee_out.write_to_all(None), - }, - &mut (None, Some(ref mut tee_err)) => match kind { - JobKind::Pipe(RedirectFrom::Stdout) => tee_err.write_to_all(None), - JobKind::Pipe(_) => tee_err.write_to_all(Some(RedirectFrom::Stderr)), - _ => tee_err.write_to_all(None), - }, - &mut (Some(ref mut tee_out), Some(ref mut tee_err)) => { - // TODO Make it work with pipes - if let Err(e) = tee_out.write_to_all(None) { - Err(e) + // If the given pipeline is a background task, fork the shell. + if let Some((command_name, disown)) = possible_background_name { + fork_pipe( + self, + piped_commands, + command_name, + if disown { + ProcessState::Empty } else { - tee_err.write_to_all(None) - } - } - }; - if let Err(e) = res { - eprintln!("ion: error in multiple output redirection process: {:?}", e); - FAILURE + ProcessState::Running + }, + ) } else { - SUCCESS - } - } - - fn exec_external( - &mut self, - name: &str, - args: &[&str], - stdin: &Option<File>, - stdout: &Option<File>, - stderr: &Option<File>, - ) -> i32 { - let result = sys::fork_and_exec( - name, - &args, - if let Some(ref f) = *stdin { Some(f.as_raw_fd()) } else { None }, - if let Some(ref f) = *stdout { Some(f.as_raw_fd()) } else { None }, - if let Some(ref f) = *stderr { Some(f.as_raw_fd()) } else { None }, - false, - || prepare_child(true, 0) - ); - - match result { - Ok(pid) => { - let _ = sys::setpgid(pid, pid); - let _ = sys::tcsetpgrp(0, pid); - let _ = sys::wait_for_interrupt(pid); - let _ = sys::kill(pid, sys::SIGCONT); - self.watch_foreground(-(pid as i32), "") - } - Err(ref err) if err.kind() == io::ErrorKind::NotFound => { - if !command_not_found(self, &name) { - eprintln!("ion: command not found: {}", name); - } - NO_SUCH_COMMAND - } - Err(ref err) => { - eprintln!("ion: command exec error: {}", err); - FAILURE + // While active, the SIGTTOU signal will be ignored. + let _sig_ignore = SignalHandler::new(); + let foreground = !self.is_background_shell; + // Execute each command in the pipeline, giving each command the foreground. + let exit_status = pipe(self, piped_commands, foreground); + // Set the shell as the foreground process again to regain the TTY. + if foreground && !self.is_library { + let _ = sys::tcsetpgrp(0, process::id()); } + exit_status } } } /// When the `&&` or `||` operator is utilized, commands should be executed /// based on the previously-recorded exit status. This function will return -/// **true** to indicate that the current job should be skipped. -fn should_skip( - previous: &mut JobKind, - previous_status: i32, - current: JobKind, -) -> bool { +/// **true** to indicate that the current job should be skipped. +fn should_skip(previous: &mut JobKind, previous_status: i32, current: JobKind) -> bool { match *previous { JobKind::And if previous_status != SUCCESS => { - *previous = if JobKind::Or == current { current } else { *previous }; + *previous = if JobKind::Or == current { + current + } else { + *previous + }; true } JobKind::Or if previous_status == SUCCESS => { - *previous = if JobKind::And == current { current } else { *previous }; + *previous = if JobKind::And == current { + current + } else { + *previous + }; true } - _ => false + _ => false, } } @@ -777,7 +818,9 @@ pub(crate) fn pipe( loop { if let Some((mut parent, mut kind)) = commands.next() { - if should_skip(&mut previous_kind, previous_status, kind) { continue } + if should_skip(&mut previous_kind, previous_status, kind) { + continue; + } match kind { JobKind::Pipe(mut mode) => { @@ -813,7 +856,8 @@ pub(crate) fn pipe( Some(unsafe { File::from_raw_fd(out_reader) }); parent.stdout(unsafe { File::from_raw_fd(out_writer) }); if is_external { - possible_external_stdio_pipes.get_or_insert(vec![]) + possible_external_stdio_pipes + .get_or_insert(vec![]) .push(unsafe { File::from_raw_fd(out_writer) }); } } @@ -825,7 +869,8 @@ pub(crate) fn pipe( Some(unsafe { File::from_raw_fd(err_reader) }); parent.stderr(unsafe { File::from_raw_fd(err_writer) }); if is_external { - possible_external_stdio_pipes.get_or_insert(vec![]) + possible_external_stdio_pipes + .get_or_insert(vec![]) .push(unsafe { File::from_raw_fd(err_writer) }); } } @@ -837,20 +882,17 @@ pub(crate) fn pipe( } Ok((reader, writer)) => { if is_external { - possible_external_stdio_pipes.get_or_insert(vec![]) + possible_external_stdio_pipes + .get_or_insert(vec![]) .push(unsafe { File::from_raw_fd(writer) }); } child.stdin(unsafe { File::from_raw_fd(reader) }); match mode { RedirectFrom::Stderr => { - parent.stderr(unsafe { - File::from_raw_fd(writer) - }); + parent.stderr(unsafe { File::from_raw_fd(writer) }); } RedirectFrom::Stdout => { - parent.stdout(unsafe { - File::from_raw_fd(writer) - }); + parent.stdout(unsafe { File::from_raw_fd(writer) }); } RedirectFrom::Both => { let temp = unsafe { File::from_raw_fd(writer) }; @@ -873,14 +915,24 @@ pub(crate) fn pipe( } } - match spawn_proc(shell, parent, kind, block_child, &mut last_pid, &mut current_pid, pgid) { + match spawn_proc( + shell, + parent, + kind, + block_child, + &mut last_pid, + &mut current_pid, + pgid, + ) { SUCCESS => (), - error_code => return error_code + error_code => return error_code, } possible_external_stdio_pipes = None; - if set_process_group(&mut pgid, current_pid) && foreground && !shell.is_library { + if set_process_group(&mut pgid, current_pid) && foreground + && !shell.is_library + { let _ = sys::tcsetpgrp(0, pgid); } @@ -892,9 +944,17 @@ pub(crate) fn pipe( } else { kind = ckind; block_child = false; - match spawn_proc(shell, child, kind, block_child, &mut last_pid, &mut current_pid, pgid) { + match spawn_proc( + shell, + child, + kind, + block_child, + &mut last_pid, + &mut current_pid, + pgid, + ) { SUCCESS => (), - error_code => return error_code + error_code => return error_code, } resume_prior_process(&mut last_pid, current_pid); @@ -934,20 +994,38 @@ fn spawn_proc( block_child: bool, last_pid: &mut u32, current_pid: &mut u32, - pgid: u32 + pgid: u32, ) -> i32 { let short = cmd.short(); match cmd { - RefinedJob::External { ref name, ref args, ref stdout, ref stderr, ref stdin} => { + RefinedJob::External { + ref name, + ref args, + ref stdout, + ref stderr, + ref stdin, + } => { let args: Vec<&str> = args.iter().skip(1).map(|x| x as &str).collect(); let result = sys::fork_and_exec( name, &args, - if let Some(ref f) = *stdin { Some(f.as_raw_fd()) } else { None }, - if let Some(ref f) = *stdout { Some(f.as_raw_fd()) } else { None }, - if let Some(ref f) = *stderr { Some(f.as_raw_fd()) } else { None }, + if let Some(ref f) = *stdin { + Some(f.as_raw_fd()) + } else { + None + }, + if let Some(ref f) = *stdout { + Some(f.as_raw_fd()) + } else { + None + }, + if let Some(ref f) = *stderr { + Some(f.as_raw_fd()) + } else { + None + }, false, - || prepare_child(block_child, pgid) + || prepare_child(block_child, pgid), ); match result { @@ -965,7 +1043,13 @@ fn spawn_proc( } } } - RefinedJob::Builtin { main, ref args, ref stdout, ref stderr, ref stdin } => { + RefinedJob::Builtin { + main, + ref args, + ref stdout, + ref stderr, + ref stdin, + } => { let args: Vec<&str> = args.iter().map(|x| x as &str).collect(); match unsafe { sys::fork() } { Ok(0) => { @@ -975,20 +1059,26 @@ fn spawn_proc( close(stderr); close(stdin); exit(ret) - }, + } Ok(pid) => { close(stdin); close(stdout); close(stderr); *last_pid = *current_pid; *current_pid = pid; - }, + } Err(e) => { eprintln!("ion: failed to fork {}: {}", short, e); } } } - RefinedJob::Function { ref name, ref args, ref stdout, ref stderr, ref stdin, } => { + RefinedJob::Function { + ref name, + ref args, + ref stdout, + ref stderr, + ref stdin, + } => { let args: Vec<&str> = args.iter().map(|x| x as &str).collect(); match unsafe { sys::fork() } { Ok(0) => { @@ -998,59 +1088,64 @@ fn spawn_proc( close(stderr); close(stdin); exit(ret) - }, + } Ok(pid) => { close(stdin); close(stdout); close(stderr); *last_pid = *current_pid; *current_pid = pid; - }, + } Err(e) => { eprintln!("ion: failed to fork {}: {}", short, e); } } } - RefinedJob::Cat { ref mut sources, ref stdout, ref mut stdin } => { - match unsafe { sys::fork() } { - Ok(0) => { - prepare_child(block_child, pgid); + RefinedJob::Cat { + ref mut sources, + ref stdout, + ref mut stdin, + } => match unsafe { sys::fork() } { + Ok(0) => { + prepare_child(block_child, pgid); - let ret = shell.exec_multi_in(sources, stdout, stdin); - close(stdout); - close(stdin); - exit(ret); - } - Ok(pid) => { - close(stdin); - close(stdout); - *last_pid = *current_pid; - *current_pid = pid; - } - Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), + let ret = shell.exec_multi_in(sources, stdout, stdin); + close(stdout); + close(stdin); + exit(ret); } + Ok(pid) => { + close(stdin); + close(stdout); + *last_pid = *current_pid; + *current_pid = pid; + } + Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), }, - RefinedJob::Tee { ref mut items, ref stdout, ref stderr, ref stdin } => { - match unsafe { sys::fork() } { - Ok(0) => { - prepare_child(block_child, pgid); + RefinedJob::Tee { + ref mut items, + ref stdout, + ref stderr, + ref stdin, + } => match unsafe { sys::fork() } { + Ok(0) => { + prepare_child(block_child, pgid); - let ret = shell.exec_multi_out(items, stdout, stderr, stdin, kind); - close(stdout); - close(stderr); - close(stdin); - exit(ret); - }, - Ok(pid) => { - close(stdin); - close(stdout); - close(stderr); - *last_pid = *current_pid; - *current_pid = pid; - } - Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), + let ret = shell.exec_multi_out(items, stdout, stderr, stdin, kind); + close(stdout); + close(stderr); + close(stdin); + exit(ret); } - } + Ok(pid) => { + close(stdin); + close(stdout); + close(stderr); + *last_pid = *current_pid; + *current_pid = pid; + } + Err(e) => eprintln!("ion: failed to fork {}: {}", short, e), + }, } SUCCESS } @@ -1091,7 +1186,9 @@ fn resume_prior_process(last_pid: &mut u32, current_pid: u32) { fn set_process_group(pgid: &mut u32, pid: u32) -> bool { let pgid_set = *pgid == 0; - if pgid_set { *pgid = pid; } + if pgid_set { + *pgid = pid; + } let _ = sys::setpgid(pid, *pgid); pgid_set } diff --git a/src/lib/shell/pipe_exec/streams.rs b/src/lib/shell/pipe_exec/streams.rs index 4bb99a2b..205a8ae9 100644 --- a/src/lib/shell/pipe_exec/streams.rs +++ b/src/lib/shell/pipe_exec/streams.rs @@ -1,6 +1,8 @@ -use std::fs::File; -use std::io; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::{ + fs::File, + io, + os::unix::io::{AsRawFd, FromRawFd, RawFd}, +}; use sys; /// Use dup2 to replace `old` with `new` using `old`s file descriptor ID diff --git a/src/lib/shell/plugins/methods/redox.rs b/src/lib/shell/plugins/methods/redox.rs index 0f77cb0e..01ed5c66 100644 --- a/src/lib/shell/plugins/methods/redox.rs +++ b/src/lib/shell/plugins/methods/redox.rs @@ -9,8 +9,6 @@ pub(crate) enum MethodArguments { pub(crate) struct StringMethodPlugins; impl StringMethodPlugins { - pub(crate) fn new() -> StringMethodPlugins { StringMethodPlugins } - pub(crate) fn execute( &self, _function: &str, @@ -18,6 +16,8 @@ impl StringMethodPlugins { ) -> Result<Option<String>, StringError> { Ok(None) } + + pub(crate) fn new() -> StringMethodPlugins { StringMethodPlugins } } /// Collects all dynamically-loaded namespaces and their associated symbols all at once. diff --git a/src/lib/shell/plugins/methods/unix.rs b/src/lib/shell/plugins/methods/unix.rs index e365fb4c..a0e2e563 100644 --- a/src/lib/shell/plugins/methods/unix.rs +++ b/src/lib/shell/plugins/methods/unix.rs @@ -1,14 +1,7 @@ use super::super::{config_dir, LibraryIterator, StringError}; use fnv::FnvHashMap; -use libloading::{Library, Symbol}; -use libloading::os::unix::Symbol as RawSymbol; -use std::ffi::CString; -use std::fs::read_dir; -use std::mem::forget; -use std::os::raw::c_char; -use std::ptr; -use std::slice; -use std::str; +use libloading::{os::unix::Symbol as RawSymbol, Library, Symbol}; +use std::{ffi::CString, fs::read_dir, mem::forget, os::raw::c_char, ptr, slice, str}; use types::Identifier; /// Either one or the other will be set. Optional status can be conveyed by setting the @@ -109,10 +102,28 @@ pub(crate) struct StringMethodPlugins { } impl StringMethodPlugins { - pub(crate) fn new() -> StringMethodPlugins { - StringMethodPlugins { - libraries: Vec::new(), - symbols: FnvHashMap::default(), + /// Attempts to execute a function within a dynamically-loaded namespace. + /// + /// If the function exists, it is executed, and it's return value is then converted into a + /// proper Rusty type. + pub(crate) fn execute( + &self, + function: &str, + arguments: MethodArguments, + ) -> Result<Option<String>, StringError> { + let func = self.symbols + .get(function.into()) + .ok_or(StringError::FunctionMissing(function.into()))?; + unsafe { + let data = (*func)(RawMethodArguments::from(arguments)); + if data.is_null() { + Ok(None) + } else { + match CString::from_raw(data as *mut c_char).to_str() { + Ok(string) => Ok(Some(string.to_owned())), + Err(_) => Err(StringError::UTF8Result), + } + } } } @@ -199,28 +210,10 @@ impl StringMethodPlugins { } } - /// Attempts to execute a function within a dynamically-loaded namespace. - /// - /// If the function exists, it is executed, and it's return value is then converted into a - /// proper Rusty type. - pub(crate) fn execute( - &self, - function: &str, - arguments: MethodArguments, - ) -> Result<Option<String>, StringError> { - let func = self.symbols - .get(function.into()) - .ok_or(StringError::FunctionMissing(function.into()))?; - unsafe { - let data = (*func)(RawMethodArguments::from(arguments)); - if data.is_null() { - Ok(None) - } else { - match CString::from_raw(data as *mut c_char).to_str() { - Ok(string) => Ok(Some(string.to_owned())), - Err(_) => Err(StringError::UTF8Result), - } - } + pub(crate) fn new() -> StringMethodPlugins { + StringMethodPlugins { + libraries: Vec::new(), + symbols: FnvHashMap::default(), } } } diff --git a/src/lib/shell/plugins/mod.rs b/src/lib/shell/plugins/mod.rs index d792595c..1928f659 100644 --- a/src/lib/shell/plugins/mod.rs +++ b/src/lib/shell/plugins/mod.rs @@ -1,6 +1,6 @@ +mod library_iter; pub mod methods; pub mod namespaces; -mod library_iter; mod string; #[cfg(not(target_os = "redox"))] diff --git a/src/lib/shell/plugins/namespaces/redox.rs b/src/lib/shell/plugins/namespaces/redox.rs index 480b69ce..b6667b31 100644 --- a/src/lib/shell/plugins/namespaces/redox.rs +++ b/src/lib/shell/plugins/namespaces/redox.rs @@ -6,11 +6,11 @@ use types::Identifier; pub(crate) struct StringNamespace; impl StringNamespace { - pub(crate) fn new() -> Result<StringNamespace, StringError> { Ok(StringNamespace) } - pub(crate) fn execute(&self, _function: Identifier) -> Result<Option<String>, StringError> { Ok(None) } + + pub(crate) fn new() -> Result<StringNamespace, StringError> { Ok(StringNamespace) } } pub(crate) fn collect() -> FnvHashMap<Identifier, StringNamespace> { diff --git a/src/lib/shell/plugins/namespaces/unix.rs b/src/lib/shell/plugins/namespaces/unix.rs index a57c158d..910c2d45 100644 --- a/src/lib/shell/plugins/namespaces/unix.rs +++ b/src/lib/shell/plugins/namespaces/unix.rs @@ -1,12 +1,7 @@ use super::super::{config_dir, LibraryIterator, StringError}; use fnv::FnvHashMap; -use libloading::{Library, Symbol}; -use libloading::os::unix::Symbol as RawSymbol; -use std::ffi::CString; -use std::fs::read_dir; -use std::os::raw::c_char; -use std::slice; -use std::str; +use libloading::{os::unix::Symbol as RawSymbol, Library, Symbol}; +use std::{ffi::CString, fs::read_dir, os::raw::c_char, slice, str}; use types::Identifier; /// A dynamically-loaded string namespace from an external library. @@ -27,6 +22,27 @@ pub(crate) struct StringNamespace { } impl StringNamespace { + /// Attempts to execute a function within a dynamically-loaded namespace. + /// + /// If the function exists, it is executed, and it's return value is then converted into a + /// proper Rusty type. + pub(crate) fn execute(&self, function: Identifier) -> Result<Option<String>, StringError> { + let func = self.symbols + .get(&function) + .ok_or(StringError::FunctionMissing(function.clone()))?; + unsafe { + let data = (*func)(); + if data.is_null() { + Ok(None) + } else { + match CString::from_raw(data as *mut c_char).to_str() { + Ok(string) => Ok(Some(string.to_owned())), + Err(_) => Err(StringError::UTF8Result), + } + } + } + } + pub(crate) fn new(library: Library) -> Result<StringNamespace, StringError> { unsafe { let mut symbols = FnvHashMap::default(); @@ -107,27 +123,6 @@ impl StringNamespace { Ok(StringNamespace { library, symbols }) } } - - /// Attempts to execute a function within a dynamically-loaded namespace. - /// - /// If the function exists, it is executed, and it's return value is then converted into a - /// proper Rusty type. - pub(crate) fn execute(&self, function: Identifier) -> Result<Option<String>, StringError> { - let func = self.symbols - .get(&function) - .ok_or(StringError::FunctionMissing(function.clone()))?; - unsafe { - let data = (*func)(); - if data.is_null() { - Ok(None) - } else { - match CString::from_raw(data as *mut c_char).to_str() { - Ok(string) => Ok(Some(string.to_owned())), - Err(_) => Err(StringError::UTF8Result), - } - } - } - } } /// Collects all dynamically-loaded namespaces and their associated symbols all at once. diff --git a/src/lib/shell/plugins/string.rs b/src/lib/shell/plugins/string.rs index 09e6f79a..85fde4f2 100644 --- a/src/lib/shell/plugins/string.rs +++ b/src/lib/shell/plugins/string.rs @@ -1,5 +1,7 @@ -use std::fmt::{self, Display, Formatter}; -use std::io; +use std::{ + fmt::{self, Display, Formatter}, + io, +}; use types::Identifier; #[derive(Debug)] diff --git a/src/lib/shell/variables/mod.rs b/src/lib/shell/variables/mod.rs index 29009158..ecfe8f61 100644 --- a/src/lib/shell/variables/mod.rs +++ b/src/lib/shell/variables/mod.rs @@ -1,13 +1,16 @@ -use super::colors::Colors; -use super::directory_stack::DirectoryStack; -use super::plugins::namespaces::{self, StringNamespace}; -use super::status::{FAILURE, SUCCESS}; +use super::{ + colors::Colors, + directory_stack::DirectoryStack, + plugins::namespaces::{self, StringNamespace}, + status::{FAILURE, SUCCESS}, +}; use fnv::FnvHashMap; use liner::Context; -use std::env; -use std::io::{self, BufRead}; -use sys::{self, geteuid, getpid, getuid, is_root}; -use sys::variables as self_sys; +use std::{ + env, + io::{self, BufRead}, +}; +use sys::{self, geteuid, getpid, getuid, is_root, variables as self_sys}; use types::{ Array, ArrayVariableContext, HashMap, HashMapVariableContext, Identifier, Key, Value, VariableContext, @@ -88,131 +91,111 @@ impl Default for Variables { const PLUGIN: u8 = 1; impl Variables { - pub(crate) fn has_plugin_support(&self) -> bool { self.flags & PLUGIN != 0 } - - pub(crate) fn enable_plugins(&mut self) { self.flags |= PLUGIN; } - - pub(crate) fn disable_plugins(&mut self) { self.flags &= 255 ^ PLUGIN; } - - pub(crate) fn read<I: IntoIterator>(&mut self, args: I) -> i32 - where - I::Item: AsRef<str>, - { - if sys::isatty(sys::STDIN_FILENO) { - let mut con = Context::new(); - for arg in args.into_iter().skip(1) { - match con.read_line(format!("{}=", arg.as_ref().trim()), &mut |_| {}) { - Ok(buffer) => self.set_var(arg.as_ref(), buffer.trim()), - Err(_) => return FAILURE, - } - } - } else { - let stdin = io::stdin(); - let handle = stdin.lock(); - let mut lines = handle.lines(); - for arg in args.into_iter().skip(1) { - if let Some(Ok(line)) = lines.next() { - self.set_var(arg.as_ref(), line.trim()); - } - } - } - SUCCESS - } + #[allow(dead_code)] + pub(crate) fn is_hashmap_reference(key: &str) -> Option<(Identifier, Key)> { + let mut key_iter = key.split('['); - pub fn set_var(&mut self, name: &str, value: &str) { - if !name.is_empty() { - if value.is_empty() { - self.variables.remove(name); - } else { - if name == "NS_PLUGINS" { - match value { - "0" => self.disable_plugins(), - "1" => self.enable_plugins(), - _ => eprintln!( - "ion: unsupported value for NS_PLUGINS. Value must be either 0 or 1." - ), + if let Some(map_name) = key_iter.next() { + if Variables::is_valid_variable_name(map_name) { + if let Some(mut inner_key) = key_iter.next() { + if inner_key.ends_with(']') { + inner_key = inner_key.split(']').next().unwrap_or(""); + inner_key = inner_key.trim_matches(|c| c == '\'' || c == '\"'); + return Some((map_name.into(), inner_key.into())); } - return; } - self.variables.insert(name.into(), value.into()); } } + None } - pub fn set_array(&mut self, name: &str, value: Array) { - if !name.is_empty() { - if value.is_empty() { - self.arrays.remove(name); - } else { - self.arrays.insert(name.into(), value); - } - } - } + pub(crate) fn tilde_expansion(&self, word: &str, dir_stack: &DirectoryStack) -> Option<String> { + let mut chars = word.char_indices(); - #[allow(dead_code)] - pub(crate) fn set_hashmap_value(&mut self, name: &str, key: &str, value: &str) { - if !name.is_empty() { - if let Some(map) = self.hashmaps.get_mut(name) { - map.insert(key.into(), value.into()); - return; - } + let tilde_prefix; + let remainder; - let mut map = HashMap::with_capacity_and_hasher(4, Default::default()); - map.insert(key.into(), value.into()); - self.hashmaps.insert(name.into(), map); + loop { + if let Some((ind, c)) = chars.next() { + if c == '/' || c == '$' { + tilde_prefix = &word[1..ind]; + remainder = &word[ind..]; + break; + } + } else { + tilde_prefix = &word[1..]; + remainder = ""; + break; + } } - } - pub fn get_map(&self, name: &str) -> Option<&HashMap> { self.hashmaps.get(name) } - - pub fn get_array(&self, name: &str) -> Option<&Array> { self.arrays.get(name) } - - pub fn unset_array(&mut self, name: &str) -> Option<Array> { self.arrays.remove(name) } + match tilde_prefix { + "" => if let Some(home) = env::home_dir() { + return Some(home.to_string_lossy().to_string() + remainder); + }, + "+" => if let Some(pwd) = self.get_var("PWD") { + return Some(pwd.to_string() + remainder); + } else if let Ok(pwd) = env::current_dir() { + return Some(pwd.to_string_lossy().to_string() + remainder); + }, + "-" => if let Some(oldpwd) = self.get_var("OLDPWD") { + return Some(oldpwd.to_string() + remainder); + }, + _ => { + let neg; + let tilde_num; - /// Obtains the value for the **SWD** variable. - /// - /// Useful for getting smaller prompts, this will produce a simplified variant of the - /// working directory which the leading `HOME` prefix replaced with a tilde character. - fn get_simplified_directory(&self) -> Value { - self.get_var("PWD") - .unwrap() - .replace(&self.get_var("HOME").unwrap(), "~") - } + if tilde_prefix.starts_with('+') { + tilde_num = &tilde_prefix[1..]; + neg = false; + } else if tilde_prefix.starts_with('-') { + tilde_num = &tilde_prefix[1..]; + neg = true; + } else { + tilde_num = tilde_prefix; + neg = false; + } - /// Obtains the value for the **MWD** variable. - /// - /// Further minimizes the directory path in the same manner that Fish does by default. - /// That is, if more than two parents are visible in the path, all parent directories - /// of the current directory will be reduced to a single character. - fn get_minimal_directory(&self) -> Value { - let swd = self.get_simplified_directory(); + match tilde_num.parse() { + Ok(num) => { + let res = if neg { + dir_stack.dir_from_top(num) + } else { + dir_stack.dir_from_bottom(num) + }; - { - // Temporarily borrow the `swd` variable while we attempt to assemble a minimal - // variant of the directory path. If that is not possible, we will cancel the - // borrow and return `swd` itself as the minified path. - let elements = swd.split("/") - .filter(|s| !s.is_empty()) - .collect::<Vec<&str>>(); - if elements.len() > 2 { - let mut output = String::new(); - for element in &elements[0..elements.len() - 1] { - let mut segmenter = UnicodeSegmentation::graphemes(*element, true); - let grapheme = segmenter.next().unwrap(); - output.push_str(grapheme); - if grapheme == "." { - output.push_str(segmenter.next().unwrap()); + if let Some(path) = res { + return Some(path.to_str().unwrap().to_string()); + } } - output.push('/'); + Err(_) => if let Some(home) = self_sys::get_user_home(tilde_prefix) { + return Some(home + remainder); + }, } - output.push_str(&elements[elements.len() - 1]); - return output; } } + None + } - swd + pub(crate) fn is_valid_variable_name(name: &str) -> bool { + name.chars().all(Variables::is_valid_variable_character) + } + + pub(crate) fn is_valid_variable_character(c: char) -> bool { + c.is_alphanumeric() || c == '_' || c == '?' + } + + pub fn get_vars<'a>(&'a self) -> impl Iterator<Item = Identifier> + 'a { + self.variables + .keys() + .cloned() + .chain(env::vars().map(|(k, _)| k.into())) } + pub fn unset_var(&mut self, name: &str) -> Option<Value> { self.variables.remove(name) } + + pub fn get_var_or_empty(&self, name: &str) -> Value { self.get_var(name).unwrap_or_default() } + pub fn get_var(&self, name: &str) -> Option<Value> { match name { "MWD" => return Some(self.get_minimal_directory()), @@ -274,110 +257,130 @@ impl Variables { } } - pub fn get_var_or_empty(&self, name: &str) -> Value { self.get_var(name).unwrap_or_default() } + /// Obtains the value for the **MWD** variable. + /// + /// Further minimizes the directory path in the same manner that Fish does by default. + /// That is, if more than two parents are visible in the path, all parent directories + /// of the current directory will be reduced to a single character. + fn get_minimal_directory(&self) -> Value { + let swd = self.get_simplified_directory(); - pub fn unset_var(&mut self, name: &str) -> Option<Value> { self.variables.remove(name) } + { + // Temporarily borrow the `swd` variable while we attempt to assemble a minimal + // variant of the directory path. If that is not possible, we will cancel the + // borrow and return `swd` itself as the minified path. + let elements = swd.split("/") + .filter(|s| !s.is_empty()) + .collect::<Vec<&str>>(); + if elements.len() > 2 { + let mut output = String::new(); + for element in &elements[0..elements.len() - 1] { + let mut segmenter = UnicodeSegmentation::graphemes(*element, true); + let grapheme = segmenter.next().unwrap(); + output.push_str(grapheme); + if grapheme == "." { + output.push_str(segmenter.next().unwrap()); + } + output.push('/'); + } + output.push_str(&elements[elements.len() - 1]); + return output; + } + } - pub fn get_vars<'a>(&'a self) -> impl Iterator<Item = Identifier> + 'a { - self.variables - .keys() - .cloned() - .chain(env::vars().map(|(k, _)| k.into())) + swd } - pub(crate) fn is_valid_variable_character(c: char) -> bool { - c.is_alphanumeric() || c == '_' || c == '?' + /// Obtains the value for the **SWD** variable. + /// + /// Useful for getting smaller prompts, this will produce a simplified variant of the + /// working directory which the leading `HOME` prefix replaced with a tilde character. + fn get_simplified_directory(&self) -> Value { + self.get_var("PWD") + .unwrap() + .replace(&self.get_var("HOME").unwrap(), "~") } - pub(crate) fn is_valid_variable_name(name: &str) -> bool { - name.chars().all(Variables::is_valid_variable_character) - } + pub fn unset_array(&mut self, name: &str) -> Option<Array> { self.arrays.remove(name) } - pub(crate) fn tilde_expansion(&self, word: &str, dir_stack: &DirectoryStack) -> Option<String> { - let mut chars = word.char_indices(); + pub fn get_array(&self, name: &str) -> Option<&Array> { self.arrays.get(name) } - let tilde_prefix; - let remainder; + pub fn get_map(&self, name: &str) -> Option<&HashMap> { self.hashmaps.get(name) } - loop { - if let Some((ind, c)) = chars.next() { - if c == '/' || c == '$' { - tilde_prefix = &word[1..ind]; - remainder = &word[ind..]; - break; - } - } else { - tilde_prefix = &word[1..]; - remainder = ""; - break; + #[allow(dead_code)] + pub(crate) fn set_hashmap_value(&mut self, name: &str, key: &str, value: &str) { + if !name.is_empty() { + if let Some(map) = self.hashmaps.get_mut(name) { + map.insert(key.into(), value.into()); + return; } - } - - match tilde_prefix { - "" => if let Some(home) = env::home_dir() { - return Some(home.to_string_lossy().to_string() + remainder); - }, - "+" => if let Some(pwd) = self.get_var("PWD") { - return Some(pwd.to_string() + remainder); - } else if let Ok(pwd) = env::current_dir() { - return Some(pwd.to_string_lossy().to_string() + remainder); - }, - "-" => if let Some(oldpwd) = self.get_var("OLDPWD") { - return Some(oldpwd.to_string() + remainder); - }, - _ => { - let neg; - let tilde_num; - if tilde_prefix.starts_with('+') { - tilde_num = &tilde_prefix[1..]; - neg = false; - } else if tilde_prefix.starts_with('-') { - tilde_num = &tilde_prefix[1..]; - neg = true; - } else { - tilde_num = tilde_prefix; - neg = false; - } + let mut map = HashMap::with_capacity_and_hasher(4, Default::default()); + map.insert(key.into(), value.into()); + self.hashmaps.insert(name.into(), map); + } + } - match tilde_num.parse() { - Ok(num) => { - let res = if neg { - dir_stack.dir_from_top(num) - } else { - dir_stack.dir_from_bottom(num) - }; + pub fn set_array(&mut self, name: &str, value: Array) { + if !name.is_empty() { + if value.is_empty() { + self.arrays.remove(name); + } else { + self.arrays.insert(name.into(), value); + } + } + } - if let Some(path) = res { - return Some(path.to_str().unwrap().to_string()); - } + pub fn set_var(&mut self, name: &str, value: &str) { + if !name.is_empty() { + if value.is_empty() { + self.variables.remove(name); + } else { + if name == "NS_PLUGINS" { + match value { + "0" => self.disable_plugins(), + "1" => self.enable_plugins(), + _ => eprintln!( + "ion: unsupported value for NS_PLUGINS. Value must be either 0 or 1." + ), } - Err(_) => if let Some(home) = self_sys::get_user_home(tilde_prefix) { - return Some(home + remainder); - }, + return; } + self.variables.insert(name.into(), value.into()); } } - None } - #[allow(dead_code)] - pub(crate) fn is_hashmap_reference(key: &str) -> Option<(Identifier, Key)> { - let mut key_iter = key.split('['); - - if let Some(map_name) = key_iter.next() { - if Variables::is_valid_variable_name(map_name) { - if let Some(mut inner_key) = key_iter.next() { - if inner_key.ends_with(']') { - inner_key = inner_key.split(']').next().unwrap_or(""); - inner_key = inner_key.trim_matches(|c| c == '\'' || c == '\"'); - return Some((map_name.into(), inner_key.into())); - } + pub(crate) fn read<I: IntoIterator>(&mut self, args: I) -> i32 + where + I::Item: AsRef<str>, + { + if sys::isatty(sys::STDIN_FILENO) { + let mut con = Context::new(); + for arg in args.into_iter().skip(1) { + match con.read_line(format!("{}=", arg.as_ref().trim()), &mut |_| {}) { + Ok(buffer) => self.set_var(arg.as_ref(), buffer.trim()), + Err(_) => return FAILURE, + } + } + } else { + let stdin = io::stdin(); + let handle = stdin.lock(); + let mut lines = handle.lines(); + for arg in args.into_iter().skip(1) { + if let Some(Ok(line)) = lines.next() { + self.set_var(arg.as_ref(), line.trim()); } } } - None + SUCCESS } + + pub(crate) fn disable_plugins(&mut self) { self.flags &= 255 ^ PLUGIN; } + + pub(crate) fn enable_plugins(&mut self) { self.flags |= PLUGIN; } + + pub(crate) fn has_plugin_support(&self) -> bool { self.flags & PLUGIN != 0 } } #[cfg(test)] diff --git a/src/lib/sys/redox/job_control.rs b/src/lib/sys/redox/job_control.rs index 3eea4136..c0be50d7 100644 --- a/src/lib/sys/redox/job_control.rs +++ b/src/lib/sys/redox/job_control.rs @@ -1,17 +1,17 @@ -use shell::Shell; -use shell::foreground::ForegroundSignals; -use shell::job_control::*; -use shell::status::{FAILURE, TERMINATED}; -use std::sync::{Arc, Mutex}; -use std::thread::sleep; -use std::time::Duration; +use shell::{ + foreground::ForegroundSignals, + job_control::*, + status::{FAILURE, TERMINATED}, + Shell, +}; +use std::{ + sync::{Arc, Mutex}, + thread::sleep, + time::Duration, +}; use syscall::{ - kill, waitpid, - ECHILD, - SIGINT, SIGPIPE, - WCONTINUED, WNOHANG, WUNTRACED, - wifcontinued, wifexited, wifsignaled, wifstopped, - wcoredump, wexitstatus, wstopsig, wtermsig + kill, waitpid, wcoredump, wexitstatus, wifcontinued, wifexited, wifsignaled, wifstopped, + wstopsig, wtermsig, ECHILD, SIGINT, SIGPIPE, WCONTINUED, WNOHANG, WUNTRACED, }; const OPTS: usize = WUNTRACED | WCONTINUED | WNOHANG; @@ -91,24 +91,22 @@ pub(crate) fn watch_foreground(shell: &mut Shell, pid: i32, command: &str) -> i3 unsafe { status = 0; match waitpid(pid as usize, &mut status, WUNTRACED) { - Err(err) => { - match err.errno { - ECHILD => break signaled, - errno => { - eprintln!("ion: waitpid error: {}", errno); - break FAILURE; - } + Err(err) => match err.errno { + ECHILD => break signaled, + errno => { + eprintln!("ion: waitpid error: {}", errno); + break FAILURE; } - } + }, Ok(0) => (), Ok(_pid) if wifexited(status) => break wexitstatus(status) as i32, Ok(pid) if wifsignaled(status) => { let signal = wtermsig(status); if signal == SIGPIPE { - continue + continue; } else if wcoredump(status) { eprintln!("ion: process ({}) had a core dump", pid); - continue + continue; } eprintln!("ion: process ({}) ended by signal {}", pid, signal); diff --git a/src/lib/sys/redox/mod.rs b/src/lib/sys/redox/mod.rs index 49e30169..3f283a6c 100644 --- a/src/lib/sys/redox/mod.rs +++ b/src/lib/sys/redox/mod.rs @@ -1,13 +1,15 @@ extern crate syscall; -use std::{io, mem, slice}; -use std::env; -use std::os::unix::ffi::OsStrExt; -use std::os::unix::io::RawFd; -use std::path::PathBuf; -use std::process::{exit, ExitStatus}; -use std::os::unix::process::ExitStatusExt; -use syscall::{EINTR, WUNTRACED, SigAction, waitpid}; +use std::{ + env, + io, + mem, + os::unix::{ffi::OsStrExt, io::RawFd, process::ExitStatusExt}, + path::PathBuf, + process::{exit, ExitStatus}, + slice, +}; +use syscall::{waitpid, SigAction, EINTR, WUNTRACED}; pub mod job_control; @@ -58,7 +60,7 @@ pub fn wait_for_child(pid: u32) -> io::Result<u8> { match waitpid(pid as usize, &mut status, WUNTRACED) { Err(ref error) if error.errno == ECHILD => break, Err(error) => return Err(io::Error::from_raw_os_error(error.errno)), - _ => () + _ => (), } } @@ -93,7 +95,7 @@ pub(crate) fn fork_and_exec<F: Fn()>( stdout: Option<RawFd>, stderr: Option<RawFd>, clear_env: bool, - before_exec: F + before_exec: F, ) -> io::Result<u32> { unsafe { match fork()? { diff --git a/src/lib/sys/unix/job_control.rs b/src/lib/sys/unix/job_control.rs index 2d3696d0..3dc6a89a 100644 --- a/src/lib/sys/unix/job_control.rs +++ b/src/lib/sys/unix/job_control.rs @@ -1,12 +1,16 @@ -use libc::*; -use shell::Shell; -use shell::foreground::ForegroundSignals; -use shell::job_control::*; -use shell::status::{FAILURE, TERMINATED}; -use std::sync::{Arc, Mutex}; -use std::thread::sleep; -use std::time::Duration; use super::{errno, write_errno}; +use libc::*; +use shell::{ + foreground::ForegroundSignals, + job_control::*, + status::{FAILURE, TERMINATED}, + Shell, +}; +use std::{ + sync::{Arc, Mutex}, + thread::sleep, + time::Duration, +}; const OPTS: i32 = WUNTRACED | WCONTINUED | WNOHANG; @@ -86,25 +90,23 @@ pub(crate) fn watch_foreground(shell: &mut Shell, pid: i32, command: &str) -> i3 unsafe { status = 0; match waitpid(pid, &mut status, WUNTRACED) { - -1 => { - match errno() { - ECHILD if signaled == 0 => break exit_status, - ECHILD => break signaled, - errno => { - write_errno("ion: waitpid error: ", errno); - break FAILURE; - } + -1 => match errno() { + ECHILD if signaled == 0 => break exit_status, + ECHILD => break signaled, + errno => { + write_errno("ion: waitpid error: ", errno); + break FAILURE; } - } + }, 0 => (), _pid if WIFEXITED(status) => exit_status = WEXITSTATUS(status), pid if WIFSIGNALED(status) => { let signal = WTERMSIG(status); if signal == SIGPIPE { - continue + continue; } else if WCOREDUMP(status) { eprintln!("ion: process ({}) had a core dump", pid); - continue + continue; } eprintln!("ion: process ({}) ended by signal {}", pid, signal); @@ -120,7 +122,11 @@ pub(crate) fn watch_foreground(shell: &mut Shell, pid: i32, command: &str) -> i3 signaled = 128 + signal as i32; } pid if WIFSTOPPED(status) => { - shell.send_to_background(pid.abs() as u32, ProcessState::Stopped, command.into()); + shell.send_to_background( + pid.abs() as u32, + ProcessState::Stopped, + command.into(), + ); shell.break_flow = true; break 128 + WSTOPSIG(status); } diff --git a/src/lib/sys/unix/mod.rs b/src/lib/sys/unix/mod.rs index 40623c04..99e34995 100644 --- a/src/lib/sys/unix/mod.rs +++ b/src/lib/sys/unix/mod.rs @@ -3,11 +3,16 @@ extern crate libc; pub mod job_control; pub mod signals; -use libc::{c_char, c_int, pid_t, sighandler_t, strerror, waitpid, ECHILD, EINTR, WEXITSTATUS, WUNTRACED}; -use std::{io, ptr, env}; -use std::io::Write; -use std::ffi::{CStr, CString}; -use std::os::unix::io::RawFd; +use libc::{ + c_char, c_int, pid_t, sighandler_t, strerror, waitpid, ECHILD, EINTR, WEXITSTATUS, WUNTRACED, +}; +use std::{ + env, + ffi::{CStr, CString}, + io::{self, Write}, + os::unix::io::RawFd, + ptr, +}; pub(crate) const PATH_SEPARATOR: &str = ":"; pub(crate) const NULL_PATH: &str = "/dev/null"; @@ -63,7 +68,7 @@ pub fn wait_for_interrupt(pid: u32) -> io::Result<()> { match unsafe { waitpid(pid as i32, &mut status, WUNTRACED) } { -1 if errno() == EINTR => continue, -1 => break Err(io::Error::from_raw_os_error(errno())), - _ => break Ok(()) + _ => break Ok(()), } } } @@ -358,8 +363,7 @@ fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> { } pub mod variables { - use users_unix::get_user_by_name; - use users_unix::os::unix::UserExt; + use users_unix::{get_user_by_name, os::unix::UserExt}; pub(crate) fn get_user_home(username: &str) -> Option<String> { match get_user_by_name(username) { diff --git a/src/lib/sys/unix/signals.rs b/src/lib/sys/unix/signals.rs index 97fa4d51..5b466a02 100644 --- a/src/lib/sys/unix/signals.rs +++ b/src/lib/sys/unix/signals.rs @@ -1,6 +1,5 @@ use libc::*; -use std::mem; -use std::ptr; +use std::{mem, ptr}; /// Blocks the SIGTSTP/SIGTTOU/SIGTTIN/SIGCHLD signals so that the shell never receives /// them. diff --git a/src/main.rs b/src/main.rs index ad618c3c..9d3a8f73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,14 @@ extern crate ion_shell; extern crate smallvec; -use ion_shell::JobControl; -use ion_shell::MAN_ION; -use ion_shell::flags::NO_EXEC; -use ion_shell::{Binary, ShellBuilder}; +use ion_shell::{flags::NO_EXEC, Binary, JobControl, ShellBuilder, MAN_ION}; use smallvec::SmallVec; -use std::env; -use std::error::Error; -use std::io::{stdout, Write}; -use std::iter::FromIterator; +use std::{ + env, + error::Error, + io::{stdout, Write}, + iter::FromIterator, +}; fn main() { let mut shell = ShellBuilder::new() -- GitLab