diff --git a/src/binary/history.rs b/src/binary/history.rs index 078def1f20045d3de7443bf6d6510d11aa8443e5..2b12df6ad5672203c6c60b008b6c571bfce367e6 100644 --- a/src/binary/history.rs +++ b/src/binary/history.rs @@ -103,7 +103,9 @@ impl<'a> InteractiveBinary<'a> { return false; } - if ignore.no_such_command && self.shell.borrow().previous_status() == NO_SUCH_COMMAND { + if ignore.no_such_command + && self.shell.borrow().previous_status() == Status::NO_SUCH_COMMAND + { return false; } diff --git a/src/binary/mod.rs b/src/binary/mod.rs index 26578da726cf732ec0810308d6b40cd6b51e374e..9a69d2b94db845b2e530e26ceb2bcf95f39a5a20 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -9,7 +9,7 @@ use self::{prompt::prompt, readln::readln}; use ion_shell::{ builtins::man_pages, parser::{Expander, Terminator}, - status::{FAILURE, SUCCESS}, + status::Status, Shell, }; use itertools::Itertools; @@ -114,34 +114,28 @@ impl<'a> InteractiveBinary<'a> { /// Liner. pub fn execute_interactive(self) -> ! { let context_bis = self.context.clone(); - let history = &move |args: &[small::String], _shell: &mut Shell| -> i32 { + let history = &move |args: &[small::String], _shell: &mut Shell| -> Status { if man_pages::check_help(args, MAN_HISTORY) { - return SUCCESS; + return Status::SUCCESS; } print!("{}", context_bis.borrow().history.buffers.iter().format("\n")); - SUCCESS + Status::SUCCESS }; let context_bis = self.context.clone(); - let keybindings = &move |args: &[small::String], _shell: &mut Shell| -> i32 { + let keybindings = &move |args: &[small::String], _shell: &mut Shell| -> Status { match args.get(1).map(|s| s.as_str()) { Some("vi") => { context_bis.borrow_mut().key_bindings = KeyBindings::Vi; - SUCCESS + Status::SUCCESS } Some("emacs") => { context_bis.borrow_mut().key_bindings = KeyBindings::Emacs; - SUCCESS - } - Some(_) => { - eprintln!("Invalid keybindings. Choices are vi and emacs"); - FAILURE - } - None => { - eprintln!("keybindings need an argument"); - FAILURE + Status::SUCCESS } + Some(_) => Status::error("Invalid keybindings. Choices are vi and emacs"), + None => Status::error("keybindings need an argument"), } }; diff --git a/src/binary/readln.rs b/src/binary/readln.rs index be1ee8b2e90bca04b2c99bcb81d6aea51be7d0f5..410532f5c62ce5ca67938b3b2351cd9adab1bf4b 100644 --- a/src/binary/readln.rs +++ b/src/binary/readln.rs @@ -22,7 +22,7 @@ pub fn readln(binary: &InteractiveBinary) -> Option<String> { Err(ref err) if err.kind() == ErrorKind::UnexpectedEof => { let mut shell = binary.shell.borrow_mut(); if !shell.unterminated && shell.exit_block().is_err() { - shell.exit(None); + shell.exit(); } None } diff --git a/src/lib/builtins/command_info.rs b/src/lib/builtins/command_info.rs index 12d0b8e1570aa77532caceecc2b811611a3329d3..204ef8656c2ab83857404f825aab51e006e3745c 100644 --- a/src/lib/builtins/command_info.rs +++ b/src/lib/builtins/command_info.rs @@ -1,15 +1,15 @@ use crate::{ builtins::man_pages::*, - shell::{status::*, Shell, Value}, + shell::{status::Status, Shell, Value}, sys, }; use small; use std::{borrow::Cow, env, path::Path}; -pub fn which(args: &[small::String], shell: &mut Shell) -> Result<i32, ()> { +pub fn which(args: &[small::String], shell: &mut Shell) -> Result<Status, ()> { if check_help(args, MAN_WHICH) { - return Ok(SUCCESS); + return Ok(Status::SUCCESS); } if args.len() == 1 { @@ -17,7 +17,7 @@ pub fn which(args: &[small::String], shell: &mut Shell) -> Result<i32, ()> { return Err(()); } - let mut result = SUCCESS; + let mut result = Status::SUCCESS; for command in &args[1..] { match get_command_info(command, shell) { Ok(c_type) => match c_type.as_ref() { @@ -30,20 +30,20 @@ pub fn which(args: &[small::String], shell: &mut Shell) -> Result<i32, ()> { "builtin" => println!("{}: built-in shell command", command), _path => println!("{}", _path), }, - Err(_) => result = FAILURE, + Err(_) => result = Status::from_exit_code(1), } } Ok(result) } -pub fn find_type(args: &[small::String], shell: &mut Shell) -> Result<i32, ()> { +pub fn find_type(args: &[small::String], shell: &mut Shell) -> Result<Status, ()> { // Type does not accept help flags, aka "--help". if args.len() == 1 { eprintln!("type: Expected at least 1 args, got only 0"); return Err(()); } - let mut result = FAILURE; + let mut result = Status::SUCCESS; for command in &args[1..] { match get_command_info(command, shell) { Ok(c_type) => { @@ -58,9 +58,8 @@ pub fn find_type(args: &[small::String], shell: &mut Shell) -> Result<i32, ()> { "builtin" => println!("{} is a shell builtin", command), _path => println!("{} is {}", command, _path), } - result = SUCCESS; } - Err(_) => eprintln!("type: {}: not found", command), + Err(_) => result = Status::error(format!("type: {}: not found", command)), } } Ok(result) diff --git a/src/lib/builtins/functions.rs b/src/lib/builtins/functions.rs index db6943705cf7fa95ea7b31de29b5a1e53882de1b..adc601b40d148319f8094e912447895726f9a4ef 100644 --- a/src/lib/builtins/functions.rs +++ b/src/lib/builtins/functions.rs @@ -1,7 +1,7 @@ -use crate::shell::{status::*, variables::Variables}; +use crate::shell::{status::Status, variables::Variables}; use std::io::{self, Write}; -pub fn print_functions(vars: &Variables) -> i32 { +pub fn print_functions(vars: &Variables) -> Status { let stdout = io::stdout(); let stdout = &mut stdout.lock(); let _ = writeln!(stdout, "# Functions"); @@ -13,5 +13,5 @@ pub fn print_functions(vars: &Variables) -> i32 { let _ = writeln!(stdout, " {}", fn_name); } } - SUCCESS + Status::SUCCESS } diff --git a/src/lib/builtins/is.rs b/src/lib/builtins/is.rs index 3cc785b657a64e56200869e32dc6fa7dda62f4d0..0fb7b8550d42c8c76a3960da392cc6f89b2a274c 100644 --- a/src/lib/builtins/is.rs +++ b/src/lib/builtins/is.rs @@ -1,24 +1,24 @@ -use crate::{shell::Shell, types}; +use crate::{shell::Shell, status::Status, types}; use small; -pub fn is(args: &[small::String], shell: &mut Shell) -> Result<(), String> { +pub fn is(args: &[small::String], shell: &mut Shell) -> Status { match args.len() { 4 => { if args[1] != "not" { - return Err(format!("Expected 'not' instead found '{}'\n", args[1]).to_string()); + return Status::error(format!("Expected 'not' instead found '{}'", args[1])); } else if eval_arg(&*args[2], shell) == eval_arg(&*args[3], shell) { - return Err("".to_string()); + return Status::error(""); } } 3 => { if eval_arg(&*args[1], shell) != eval_arg(&*args[2], shell) { - return Err("".to_string()); + return Status::error(""); } } - _ => return Err("is needs 3 or 4 arguments\n".to_string()), + _ => return Status::error("is needs 3 or 4 arguments"), } - Ok(()) + Status::SUCCESS } fn eval_arg(arg: &str, shell: &mut Shell) -> types::Str { @@ -49,30 +49,21 @@ fn test_is() { shell.variables_mut().set("y", "0"); // Four arguments - assert_eq!( - is(&vec_string(&["is", " ", " ", " "]), &mut shell), - Err("Expected 'not' instead found ' '\n".to_string()) - ); - assert_eq!(is(&vec_string(&["is", "not", " ", " "]), &mut shell), Err("".to_string())); - assert_eq!(is(&vec_string(&["is", "not", "$x", "$x"]), &mut shell), Err("".to_string())); - assert_eq!(is(&vec_string(&["is", "not", "2", "1"]), &mut shell), Ok(())); - assert_eq!(is(&vec_string(&["is", "not", "$x", "$y"]), &mut shell), Ok(())); + assert!(is(&vec_string(&["is", " ", " ", " "]), &mut shell).is_failure()); + assert!(is(&vec_string(&["is", "not", " ", " "]), &mut shell).is_failure()); + assert!(is(&vec_string(&["is", "not", "$x", "$x"]), &mut shell).is_failure()); + assert!(is(&vec_string(&["is", "not", "2", "1"]), &mut shell).is_success()); + assert!(is(&vec_string(&["is", "not", "$x", "$y"]), &mut shell).is_success()); // Three arguments - assert_eq!(is(&vec_string(&["is", "1", "2"]), &mut shell), Err("".to_string())); - assert_eq!(is(&vec_string(&["is", "$x", "$y"]), &mut shell), Err("".to_string())); - assert_eq!(is(&vec_string(&["is", " ", " "]), &mut shell), Ok(())); - assert_eq!(is(&vec_string(&["is", "$x", "$x"]), &mut shell), Ok(())); + assert!(is(&vec_string(&["is", "1", "2"]), &mut shell).is_failure()); + assert!(is(&vec_string(&["is", "$x", "$y"]), &mut shell).is_failure()); + assert!(is(&vec_string(&["is", " ", " "]), &mut shell).is_success()); + assert!(is(&vec_string(&["is", "$x", "$x"]), &mut shell).is_success()); // Two arguments - assert_eq!( - is(&vec_string(&["is", " "]), &mut shell), - Err("is needs 3 or 4 arguments\n".to_string()) - ); + assert!(is(&vec_string(&["is", " "]), &mut shell).is_failure()); // One argument - assert_eq!( - is(&vec_string(&["is"]), &mut shell), - Err("is needs 3 or 4 arguments\n".to_string()) - ); + assert!(is(&vec_string(&["is"]), &mut shell).is_failure()); } diff --git a/src/lib/builtins/job_control.rs b/src/lib/builtins/job_control.rs index 7ea8a41a1e774ad3cb62a1fc3cd9727081fb6bb3..c419450212318a6038ac1ca96cf1daffabdf6d47 100644 --- a/src/lib/builtins/job_control.rs +++ b/src/lib/builtins/job_control.rs @@ -73,34 +73,31 @@ pub fn jobs(shell: &mut Shell) { /// Hands control of the foreground process to the specified jobs, recording their exit status. /// If the job is stopped, the job will be resumed. /// If multiple jobs are given, then only the last job's exit status will be returned. -pub fn fg(shell: &mut Shell, args: &[small::String]) -> i32 { - fn fg_job(shell: &mut Shell, njob: usize) -> i32 { +pub fn fg(shell: &mut Shell, args: &[small::String]) -> Status { + fn fg_job(shell: &mut Shell, njob: usize) -> Status { if let Some(job) = shell.background_jobs().iter().nth(njob).filter(|p| p.exists()) { // Give the bg task the foreground, and wait for it to finish. Also resume it if it // isn't running shell.set_bg_task_in_foreground(job.pid(), !job.is_running()) } else { // Informs the user that the specified job ID no longer exists. - eprintln!("ion: fg: job {} does not exist", njob); - return FAILURE; + return Status::error(format!("ion: fg: job {} does not exist", njob)); } } - let mut status = 0; + let mut status = Status::SUCCESS; if args.is_empty() { status = if let Some(previous_job) = shell.previous_job() { fg_job(shell, previous_job) } else { - eprintln!("ion: fg: no jobs are running in the background"); - FAILURE + Status::error(format!("ion: fg: no jobs are running in the background")) }; } else { for arg in args { match arg.parse::<usize>() { Ok(njob) => status = fg_job(shell, njob), Err(_) => { - eprintln!("ion: fg: {} is not a valid job number", arg); - status = FAILURE; + status = Status::error(format!("ion: fg: {} is not a valid job number", arg)) } } } @@ -109,44 +106,37 @@ pub fn fg(shell: &mut Shell, args: &[small::String]) -> i32 { } /// Resumes a stopped background process, if it was stopped. -pub fn bg(shell: &mut Shell, args: &[small::String]) -> i32 { - fn bg_job(shell: &mut Shell, njob: usize) -> bool { +pub fn bg(shell: &mut Shell, args: &[small::String]) -> Status { + fn bg_job(shell: &mut Shell, njob: usize) -> Status { if let Some(job) = shell.background_jobs().iter().nth(njob).filter(|p| p.exists()) { if job.is_running() { - eprintln!("ion: bg: job {} is already running", njob); - false + Status::error(format!("ion: bg: job {} is already running", njob)) } else { job.resume(); - true + Status::SUCCESS } } else { - eprintln!("ion: bg: job {} does not exist", njob); - false + Status::error(format!("ion: bg: job {} does not exist", njob)) } } if args.is_empty() { if let Some(previous_job) = shell.previous_job() { - if bg_job(shell, previous_job) { - SUCCESS - } else { - FAILURE - } + bg_job(shell, previous_job) } else { - eprintln!("ion: bg: no jobs are running in the background"); - FAILURE + Status::error(format!("ion: bg: no jobs are running in the background")) } } else { for arg in args { if let Ok(njob) = arg.parse::<usize>() { - if !bg_job(shell, njob) { - return FAILURE; + let status = bg_job(shell, njob); + if !status.is_success() { + return status; } } else { - eprintln!("ion: bg: {} is not a valid job number", arg); - return FAILURE; + return Status::error(format!("ion: bg: {} is not a valid job number", arg)); }; } - SUCCESS + Status::SUCCESS } } diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs index 38cde1ccc56c0e010b3c8771cfb518f5b2fca54e..d4e26d975f4d3ce9697c80e597da47006c88e61f 100644 --- a/src/lib/builtins/mod.rs +++ b/src/lib/builtins/mod.rs @@ -30,8 +30,7 @@ use self::{ use std::{ borrow::Cow, - error::Error, - io::{self, BufRead, Write}, + io::{self, BufRead}, path::PathBuf, }; @@ -39,7 +38,7 @@ use hashbrown::HashMap; use liner::{Completer, Context}; use crate::{ - shell::{status::*, Capture, Shell}, + shell::{status::Status, Capture, Shell}, sys, types, }; use itertools::Itertools; @@ -54,7 +53,7 @@ const DISOWN_DESC: &str = "Disowning a process removes that process from the shell's background process table."; /// The type for builtin functions. Builtins have direct access to the shell -pub type BuiltinFunction<'a> = &'a dyn Fn(&[small::String], &mut Shell) -> i32; +pub type BuiltinFunction<'a> = &'a dyn Fn(&[small::String], &mut Shell) -> Status; macro_rules! map { ($builtins:ident, $($name:expr => $func:ident: $help:expr),+) => {{ @@ -81,12 +80,12 @@ fn parse_numeric_arg(arg: &str) -> Option<(bool, usize)> { /// Note: To reduce allocations, function are provided as pointer rather than boxed closures /// ``` /// use ion_shell::builtins::BuiltinMap; -/// use ion_shell::Shell; +/// use ion_shell::{Shell, status::Status}; /// /// // create a builtin /// let mut custom = |_args: &[small::String], _shell: &mut Shell| { /// println!("Hello world!"); -/// 42 +/// Status::error("Can't proceed") /// }; /// /// // create a builtin map with some predefined builtins @@ -96,9 +95,8 @@ fn parse_numeric_arg(arg: &str) -> Option<(bool, usize)> { /// builtins.add("custom builtin", &mut custom, "Very helpful comment to display to the user"); /// /// // execute a builtin -/// assert_eq!( -/// builtins.get("custom builtin").unwrap()(&["ion".into()], &mut Shell::new(false)), -/// 42, +/// assert!( +/// builtins.get("custom builtin").unwrap()(&["ion".into()], &mut Shell::new(false)).is_failure(), /// ); /// // >> Hello world! pub struct BuiltinMap<'a> { @@ -121,7 +119,7 @@ impl<'a> Default for BuiltinMap<'a> { // If you are implementing a builtin add it to the table below, create a well named manpage in // man_pages and check for help flags by adding to the start of your builtin the following // if check_help(args, MAN_BUILTIN_NAME) { -// return SUCCESS +// return Status::SUCCESS // } impl<'a> BuiltinMap<'a> { /// Create a new, blank builtin map @@ -247,46 +245,36 @@ impl<'a> BuiltinMap<'a> { } } -fn starts_with(args: &[small::String], _: &mut Shell) -> i32 { conditionals::starts_with(args) } -fn ends_with(args: &[small::String], _: &mut Shell) -> i32 { conditionals::ends_with(args) } -fn contains(args: &[small::String], _: &mut Shell) -> i32 { conditionals::contains(args) } +fn starts_with(args: &[small::String], _: &mut Shell) -> Status { + Status::from_exit_code(conditionals::starts_with(args)) +} +fn ends_with(args: &[small::String], _: &mut Shell) -> Status { + Status::from_exit_code(conditionals::ends_with(args)) +} +fn contains(args: &[small::String], _: &mut Shell) -> Status { + Status::from_exit_code(conditionals::contains(args)) +} // Definitions of simple builtins go here -pub fn builtin_status(args: &[small::String], shell: &mut Shell) -> i32 { - match status(args, shell) { - Ok(()) => SUCCESS, - Err(why) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(why.as_bytes()); - FAILURE - } - } -} +pub fn builtin_status(args: &[small::String], shell: &mut Shell) -> Status { status(args, shell) } -pub fn builtin_cd(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_cd(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_CD) { - return SUCCESS; + return Status::SUCCESS; } match shell.cd(args.get(1)) { Ok(()) => { let _ = shell.fork_function(Capture::None, |_| Ok(()), "CD_CHANGE", &["ion"]); - SUCCESS - } - Err(why) => { - eprintln!("{}", why); - FAILURE + Status::SUCCESS } + Err(why) => Status::error(why), } } -pub fn builtin_bool(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_bool(args: &[small::String], shell: &mut Shell) -> Status { if args.len() != 2 { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(b"bool requires one argument\n"); - return FAILURE; + return Status::error("bool requires one argument"); } let opt = if args[1].is_empty() { None } else { shell.variables().get_str(&args[1][1..]) }; @@ -299,29 +287,21 @@ pub fn builtin_bool(args: &[small::String], shell: &mut Shell) -> i32 { "true" => (), "--help" => println!("{}", MAN_BOOL), "-h" => println!("{}", MAN_BOOL), - _ => return FAILURE, + _ => return Status::from_exit_code(1), }, } - SUCCESS + Status::SUCCESS } -pub fn builtin_is(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_is(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_IS) { - return SUCCESS; + return Status::SUCCESS; } - match is(args, shell) { - Ok(()) => SUCCESS, - Err(why) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(why.as_bytes()); - FAILURE - } - } + is(args, shell) } -pub fn builtin_dirs(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_dirs(args: &[small::String], shell: &mut Shell) -> Status { // converts pbuf to an absolute path if possible fn try_abs_path(pbuf: &PathBuf) -> Cow<str> { Cow::Owned( @@ -330,7 +310,7 @@ pub fn builtin_dirs(args: &[small::String], shell: &mut Shell) -> i32 { } if check_help(args, MAN_DIRS) { - return SUCCESS; + return Status::SUCCESS; } let mut clear = false; // -c @@ -373,30 +353,24 @@ pub fn builtin_dirs(args: &[small::String], shell: &mut Shell) -> i32 { Some((false, num)) if shell.dir_stack().count() > num => { shell.dir_stack().count() - num - 1 } - _ => return FAILURE, /* Err(Cow::Owned(format!("ion: dirs: {}: invalid - * argument\n", arg))) */ + _ => return Status::error(format!("ion: dirs: {}: invalid argument", arg)), }; match iter.nth(num) { Some(x) => { println!("{}", x); - SUCCESS + Status::SUCCESS } - None => FAILURE, + None => Status::error(""), } } else { - let folder: fn(String, Cow<str>) -> String = - if multiline { |x, y| x + "\n" + &y } else { |x, y| x + " " + &y }; - - if let Some(x) = iter.next() { - println!("{}", iter.fold(x.to_string(), folder)); - } - SUCCESS + println!("{}", iter.join(if multiline { "\n" } else { " " })); + Status::SUCCESS } } -pub fn builtin_pushd(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_pushd(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_PUSHD) { - return SUCCESS; + return Status::SUCCESS; } enum Action { @@ -421,8 +395,7 @@ pub fn builtin_pushd(args: &[small::String], shell: &mut Shell) -> i32 { None => Action::Push(PathBuf::from(arg)), // no numeric arg => `dir`-parameter }; } else { - eprintln!("ion: pushd: too many arguments"); - return FAILURE; + return Status::error("ion: pushd: too many arguments"); } } @@ -430,31 +403,27 @@ pub fn builtin_pushd(args: &[small::String], shell: &mut Shell) -> i32 { Action::Switch => { if !keep_front { if let Err(why) = shell.swap(1) { - eprintln!("ion: pushd: {}", why); - return FAILURE; + return Status::error(format!("ion: pushd: {}", why)); } } } Action::RotLeft(num) => { if !keep_front { if let Err(why) = shell.rotate_left(num) { - eprintln!("ion: pushd: {}", why); - return FAILURE; + return Status::error(format!("ion: pushd: {}", why)); } } } Action::RotRight(num) => { if !keep_front { if let Err(why) = shell.rotate_right(num) { - eprintln!("ion: pushd: {}", why); - return FAILURE; + return Status::error(format!("ion: pushd: {}", why)); } } } Action::Push(dir) => { if let Err(why) = shell.pushd(dir, keep_front) { - eprintln!("ion: pushd: {}", why); - return FAILURE; + return Status::error(format!("ion: pushd: {}", why)); } } }; @@ -463,18 +432,17 @@ pub fn builtin_pushd(args: &[small::String], shell: &mut Shell) -> i32 { "{}", shell.dir_stack().map(|dir| dir.to_str().unwrap_or("ion: no directory found")).join(" ") ); - SUCCESS + Status::SUCCESS } -pub fn builtin_popd(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_popd(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_POPD) { - return SUCCESS; + return Status::SUCCESS; } let len = shell.dir_stack().len(); if len <= 1 { - eprintln!("ion: popd: directory stack empty"); - return FAILURE; + return Status::error("ion: popd: directory stack empty"); } let mut keep_front = false; // whether the -n option is present @@ -488,8 +456,7 @@ pub fn builtin_popd(args: &[small::String], shell: &mut Shell) -> i32 { let (count_from_front, num) = match parse_numeric_arg(arg) { Some(n) => n, None => { - eprintln!("ion: popd: {}: invalid argument", arg); - return FAILURE; + return Status::error(format!("ion: popd: {}: invalid argument", arg)); } }; @@ -499,8 +466,7 @@ pub fn builtin_popd(args: &[small::String], shell: &mut Shell) -> i32 { } else if let Some(n) = (len - 1).checked_sub(num) { n } else { - eprintln!("ion: popd: negative directory stack index out of range"); - return FAILURE; + return Status::error("ion: popd: negative directory stack index out of range"); }; } } @@ -511,8 +477,7 @@ pub fn builtin_popd(args: &[small::String], shell: &mut Shell) -> i32 { } else if index == 0 { // change to new directory, return if not possible if let Err(why) = shell.set_current_dir_by_index(1) { - eprintln!("ion: popd: {}", why); - return FAILURE; + return Status::error(format!("ion: popd: {}", why)); } } @@ -525,25 +490,24 @@ pub fn builtin_popd(args: &[small::String], shell: &mut Shell) -> i32 { .map(|dir| dir.to_str().unwrap_or("ion: no directory found")) .join(" ") ); - SUCCESS + Status::SUCCESS } else { - eprintln!("ion: popd: {}: directory stack index out of range", index); - FAILURE + Status::error(format!("ion: popd: {}: directory stack index out of range", index)) } } -pub fn builtin_alias(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_alias(args: &[small::String], shell: &mut Shell) -> Status { let args_str = args[1..].join(" "); alias(shell.variables_mut(), &args_str) } -pub fn builtin_unalias(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_unalias(args: &[small::String], shell: &mut Shell) -> Status { drop_alias(shell.variables_mut(), args) } // TODO There is a man page for fn however the -h and --help flags are not // checked for. -pub fn builtin_fn(_: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_fn(_: &[small::String], shell: &mut Shell) -> Status { print_functions(shell.variables()) } @@ -553,9 +517,9 @@ impl Completer for EmptyCompleter { fn completions(&mut self, _start: &str) -> Vec<String> { Vec::new() } } -pub fn builtin_read(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_read(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_READ) { - return SUCCESS; + return Status::SUCCESS; } if sys::isatty(sys::STDIN_FILENO) { @@ -565,7 +529,7 @@ pub fn builtin_read(args: &[small::String], shell: &mut Shell) -> i32 { Ok(buffer) => { shell.variables_mut().set(arg.as_ref(), buffer.trim()); } - Err(_) => return FAILURE, + Err(_) => return Status::error(""), } } } else { @@ -578,12 +542,12 @@ pub fn builtin_read(args: &[small::String], shell: &mut Shell) -> i32 { } } } - SUCCESS + Status::SUCCESS } -pub fn builtin_drop(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_drop(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_DROP) { - return SUCCESS; + return Status::SUCCESS; } if args.len() >= 2 && args[1] == "-a" { drop_array(shell.variables_mut(), args) @@ -592,194 +556,154 @@ pub fn builtin_drop(args: &[small::String], shell: &mut Shell) -> i32 { } } -pub fn builtin_set(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_set(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_SET) { - return SUCCESS; + return Status::SUCCESS; } set::set(args, shell) } -pub fn builtin_eq(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_eq(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_EQ) { - return SUCCESS; + return Status::SUCCESS; } - match is(args, shell) { - Ok(()) => SUCCESS, - Err(why) => { - eprintln!("{}", why); - FAILURE - } - } + is(args, shell) } -pub fn builtin_eval(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_eval(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_EVAL) { - SUCCESS + Status::SUCCESS } else { shell.execute_command(args[1..].join(" ").as_bytes()).unwrap_or_else(|_| { - eprintln!("ion: supplied eval expression was not terminated"); - FAILURE + Status::error(format!("ion: supplied eval expression was not terminated")) }) } } -pub fn builtin_source(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_source(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_SOURCE) { - return SUCCESS; + return Status::SUCCESS; } match source(shell, args) { - Ok(()) => SUCCESS, - Err(why) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(why.as_bytes()); - FAILURE - } + Ok(()) => Status::SUCCESS, + Err(why) => Status::error(why), } } -pub fn builtin_echo(args: &[small::String], _: &mut Shell) -> i32 { +pub fn builtin_echo(args: &[small::String], _: &mut Shell) -> Status { if check_help(args, MAN_ECHO) { - return SUCCESS; + return Status::SUCCESS; } match echo(args) { - Ok(()) => SUCCESS, - Err(why) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(why.description().as_bytes()); - FAILURE - } + Ok(()) => Status::SUCCESS, + Err(why) => Status::error(why.to_string()), } } -pub fn builtin_test(args: &[small::String], _: &mut Shell) -> i32 { +pub fn builtin_test(args: &[small::String], _: &mut Shell) -> Status { // Do not use `check_help` for the `test` builtin. The // `test` builtin contains a "-h" option. match test(args) { - Ok(true) => SUCCESS, - Ok(false) => FAILURE, - Err(why) => { - eprintln!("{}", why); - FAILURE - } + Ok(true) => Status::SUCCESS, + Ok(false) => Status::error(""), + Err(why) => Status::error(why), } } // TODO create manpage. -pub fn builtin_calc(args: &[small::String], _: &mut Shell) -> i32 { +pub fn builtin_calc(args: &[small::String], _: &mut Shell) -> Status { match calc::calc(&args[1..]) { - Ok(()) => SUCCESS, - Err(why) => { - eprintln!("{}", why); - FAILURE - } + Ok(()) => Status::SUCCESS, + Err(why) => Status::error(why), } } -pub fn builtin_random(args: &[small::String], _: &mut Shell) -> i32 { +pub fn builtin_random(args: &[small::String], _: &mut Shell) -> Status { if check_help(args, MAN_RANDOM) { - return SUCCESS; + return Status::SUCCESS; } match random::random(&args[1..]) { - Ok(()) => SUCCESS, - Err(why) => { - eprintln!("{}", why); - FAILURE - } + Ok(()) => Status::SUCCESS, + Err(why) => Status::error(why), } } -pub fn builtin_true(args: &[small::String], _: &mut Shell) -> i32 { +pub fn builtin_true(args: &[small::String], _: &mut Shell) -> Status { check_help(args, MAN_TRUE); - SUCCESS + Status::SUCCESS } -pub fn builtin_false(args: &[small::String], _: &mut Shell) -> i32 { +pub fn builtin_false(args: &[small::String], _: &mut Shell) -> Status { if check_help(args, MAN_FALSE) { - return SUCCESS; + return Status::SUCCESS; } - FAILURE + Status::error("") } // TODO create a manpage -pub fn builtin_wait(_: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_wait(_: &[small::String], shell: &mut Shell) -> Status { shell.wait_for_background(); - SUCCESS + Status::SUCCESS } -pub fn builtin_jobs(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_jobs(args: &[small::String], shell: &mut Shell) -> Status { check_help(args, MAN_JOBS); job_control::jobs(shell); - SUCCESS + Status::SUCCESS } -pub fn builtin_bg(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_bg(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_BG) { - return SUCCESS; + return Status::SUCCESS; } job_control::bg(shell, &args[1..]) } -pub fn builtin_fg(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_fg(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_FG) { - return SUCCESS; + return Status::SUCCESS; } job_control::fg(shell, &args[1..]) } -pub fn builtin_suspend(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_suspend(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_SUSPEND) { - return SUCCESS; + return Status::SUCCESS; } shell.suspend(); - SUCCESS + Status::SUCCESS } -pub fn builtin_disown(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_disown(args: &[small::String], shell: &mut Shell) -> Status { for arg in args { if *arg == "--help" { println!("{}", MAN_DISOWN); - return SUCCESS; + return Status::SUCCESS; } } match job_control::disown(shell, &args[1..]) { - Ok(()) => SUCCESS, - Err(err) => { - eprintln!("ion: disown: {}", err); - FAILURE - } + Ok(()) => Status::SUCCESS, + Err(err) => Status::error(format!("ion: disown: {}", err)), } } -pub fn builtin_help(args: &[small::String], shell: &mut Shell) -> i32 { - let builtins = shell.builtins(); - let stdout = io::stdout(); - let mut stdout = stdout.lock(); +pub fn builtin_help(args: &[small::String], shell: &mut Shell) -> Status { if let Some(command) = args.get(1) { - if let Some(help) = builtins.get_help(command) { - let _ = stdout.write_all(help.as_bytes()); - let _ = stdout.write_all(b"\n"); + if let Some(help) = shell.builtins().get_help(command) { + println!("{}", help); } else { - let _ = stdout.write_all(b"Command helper not found [run 'help']..."); - let _ = stdout.write_all(b"\n"); + println!("Command helper not found [run 'help']..."); } } else { - let commands = builtins.keys(); - - let mut buffer: Vec<u8> = Vec::new(); - for command in commands { - let _ = writeln!(buffer, "{}", command); - } - let _ = stdout.write_all(&buffer); + println!("{}", shell.builtins().keys().join("")); } - SUCCESS + Status::SUCCESS } -pub fn builtin_exit(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_exit(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_EXIT) { - return SUCCESS; + return Status::SUCCESS; } // Kill all active background tasks before exiting the shell. for process in shell.background_jobs().iter() { @@ -787,109 +711,93 @@ pub fn builtin_exit(args: &[small::String], shell: &mut Shell) -> i32 { let _ = sys::kill(process.pid(), sys::SIGTERM); } } - shell.exit(args.get(1).and_then(|status| status.parse::<i32>().ok())) + if let Some(status) = args.get(1).and_then(|status| status.parse::<i32>().ok()) { + shell.exit_with_code(Status::from_exit_code(status)) + } else { + shell.exit() + } } -pub fn builtin_exec(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_exec(args: &[small::String], shell: &mut Shell) -> Status { match exec(shell, &args[1..]) { // Shouldn't ever hit this case. - Ok(()) => SUCCESS, - Err(err) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = writeln!(stderr, "ion: exec: {}", err); - FAILURE - } + Ok(()) => Status::SUCCESS, + Err(err) => Status::error(format!("ion: exec: {}", err)), } } use regex::Regex; -pub fn builtin_matches(args: &[small::String], _: &mut Shell) -> i32 { +pub fn builtin_matches(args: &[small::String], _: &mut Shell) -> Status { if check_help(args, MAN_MATCHES) { - return SUCCESS; + return Status::SUCCESS; } if args[1..].len() != 2 { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr.write_all(b"match takes two arguments\n"); - return BAD_ARG; + eprintln!("match takes two arguments"); + return Status::BAD_ARG; } let input = &args[1]; let re = match Regex::new(&args[2]) { Ok(r) => r, Err(e) => { - let stderr = io::stderr(); - let mut stderr = stderr.lock(); - let _ = stderr - .write_all(format!("couldn't compile input regex {}: {}\n", args[2], e).as_bytes()); - return FAILURE; + return Status::error(format!("couldn't compile input regex {}: {}", args[2], e)); } }; if re.is_match(input) { - SUCCESS + Status::SUCCESS } else { - FAILURE + Status::error("") } } -pub fn builtin_exists(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_exists(args: &[small::String], shell: &mut Shell) -> Status { if check_help(args, MAN_EXISTS) { - return SUCCESS; + return Status::SUCCESS; } match exists(args, shell) { - Ok(true) => SUCCESS, - Ok(false) => FAILURE, - Err(why) => { - eprintln!("{}", why); - FAILURE - } + Ok(true) => Status::SUCCESS, + Ok(false) => Status::error(""), + Err(why) => Status::error(why), } } -pub fn builtin_which(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_which(args: &[small::String], shell: &mut Shell) -> Status { match which(args, shell) { Ok(result) => result, - Err(()) => FAILURE, + Err(()) => Status::error(""), } } -pub fn builtin_type(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn builtin_type(args: &[small::String], shell: &mut Shell) -> Status { match find_type(args, shell) { Ok(result) => result, - Err(()) => FAILURE, + Err(()) => Status::error(""), } } -pub fn builtin_isatty(args: &[small::String], _: &mut Shell) -> i32 { +pub fn builtin_isatty(args: &[small::String], _: &mut Shell) -> Status { if check_help(args, MAN_ISATTY) { - return SUCCESS; + return Status::SUCCESS; } if args.len() > 1 { // sys::isatty expects a usize if compiled for redox but otherwise a i32. #[cfg(target_os = "redox")] - match args[1].parse::<usize>() { - Ok(r) => { - if sys::isatty(r) { - return SUCCESS; - } - } - Err(_) => eprintln!("ion: isatty given bad number"), - } - + let pid = args[1].parse::<usize>(); #[cfg(not(target_os = "redox"))] - match args[1].parse::<i32>() { + let pid = args[1].parse::<i32>(); + + match pid { Ok(r) => { if sys::isatty(r) { - return SUCCESS; + Status::SUCCESS + } else { + Status::error("") } } - Err(_) => eprintln!("ion: isatty given bad number"), + Err(_) => Status::error("ion: isatty given bad number"), } } else { - return SUCCESS; + Status::SUCCESS } - - FAILURE } diff --git a/src/lib/builtins/set.rs b/src/lib/builtins/set.rs index 15115563147ec34e3b8b6b9cf5d5e500ce6f724e..de2095a6c0475ffad47208f9dae14e0a1f433e5f 100644 --- a/src/lib/builtins/set.rs +++ b/src/lib/builtins/set.rs @@ -1,5 +1,5 @@ use crate::{ - shell::{variables::Value, Shell}, + shell::{status::Status, variables::Value, Shell}, types, }; use small; @@ -12,7 +12,7 @@ enum PositionalArgs { use self::PositionalArgs::*; -pub fn set(args: &[small::String], shell: &mut Shell) -> i32 { +pub fn set(args: &[small::String], shell: &mut Shell) -> Status { let mut args_iter = args.iter(); let mut positionals = None; @@ -22,7 +22,7 @@ pub fn set(args: &[small::String], shell: &mut Shell) -> i32 { positionals = Some(UnsetIfNone); break; } - return 0; + return Status::SUCCESS; } else if arg.starts_with('-') { if arg.len() == 1 { positionals = Some(RetainIfNone); @@ -31,7 +31,7 @@ pub fn set(args: &[small::String], shell: &mut Shell) -> i32 { for flag in arg.bytes().skip(1) { match flag { b'e' => shell.opts_mut().err_exit = true, - _ => return 0, + _ => return Status::SUCCESS, } } } else if arg.starts_with('+') { @@ -42,15 +42,13 @@ pub fn set(args: &[small::String], shell: &mut Shell) -> i32 { b'o' => match args_iter.next().map(|s| s as &str) { Some("huponexit") => shell.opts_mut().huponexit = false, Some(_) => { - eprintln!("ion: set: invalid option"); - return 0; + return Status::error(format!("ion: set: invalid option")); } None => { - eprintln!("ion: set: no option given"); - return 0; + return Status::error(format!("ion: set: no option given")); } }, - _ => return 0, + _ => return Status::SUCCESS, } } } @@ -80,5 +78,5 @@ pub fn set(args: &[small::String], shell: &mut Shell) -> i32 { } } - 0 + Status::SUCCESS } diff --git a/src/lib/builtins/status.rs b/src/lib/builtins/status.rs index baa6fcdf0ca64bc2c34d4e5409fb2bef1edf969f..ca6fdbbc89f69626c6649a9f55e41917c3b65dc7 100644 --- a/src/lib/builtins/status.rs +++ b/src/lib/builtins/status.rs @@ -1,8 +1,11 @@ -use crate::{builtins::man_pages::MAN_STATUS, shell::Shell}; +use crate::{ + builtins::man_pages::MAN_STATUS, + shell::{status::Status, Shell}, +}; use small; use std::env; -pub fn status(args: &[small::String], shell: &mut Shell) -> Result<(), String> { +pub fn status(args: &[small::String], shell: &mut Shell) -> Status { let mut help = false; let mut login_shell = false; let mut interactive = false; @@ -33,11 +36,11 @@ pub fn status(args: &[small::String], shell: &mut Shell) -> Result<(), String> { } if login_shell && !is_login { - return Err("".to_string()); + return Status::error(""); } if interactive && shell.opts().is_background_shell { - return Err("".to_string()); + return Status::error(""); } if filename { @@ -55,7 +58,7 @@ pub fn status(args: &[small::String], shell: &mut Shell) -> Result<(), String> { println!("{}", MAN_STATUS); } - Ok(()) + Status::SUCCESS } 1 => { if is_login { @@ -63,8 +66,8 @@ pub fn status(args: &[small::String], shell: &mut Shell) -> Result<(), String> { } else { println!("This is not a login shell"); } - Ok(()) + Status::SUCCESS } - _ => Err("status takes one argument\n".to_string()), + _ => Status::error("status takes one argument"), } } diff --git a/src/lib/builtins/variables.rs b/src/lib/builtins/variables.rs index d03c45b66dbb46e621b0b4cb8c2361f73bda74f7..bef81a0d270eddac0840aa1e3f0e71435bede848 100644 --- a/src/lib/builtins/variables.rs +++ b/src/lib/builtins/variables.rs @@ -3,7 +3,7 @@ use std::io::{self, Write}; use crate::{ - shell::{status::*, variables::Variables}, + shell::{status::Status, variables::Variables}, types, }; @@ -12,11 +12,7 @@ fn print_list(vars: &Variables) { let stdout = &mut stdout.lock(); for (key, value) in vars.aliases() { - let _ = stdout - .write(key.as_bytes()) - .and_then(|_| stdout.write_all(b" = ")) - .and_then(|_| stdout.write_all(value.as_bytes())) - .and_then(|_| stdout.write_all(b"\n")); + writeln!(stdout, "{} = {}", key, value).unwrap(); } } @@ -69,84 +65,72 @@ fn parse_alias(args: &str) -> Binding { /// The `alias` command will define an alias for another command, and thus may be used as a /// command itself. -pub fn alias(vars: &mut Variables, args: &str) -> i32 { +pub fn alias(vars: &mut Variables, args: &str) -> Status { match parse_alias(args) { Binding::InvalidKey(key) => { - eprintln!("ion: alias name, '{}', is invalid", key); - return FAILURE; + return Status::error(format!("ion: alias name, '{}', is invalid", key)); } Binding::KeyValue(key, value) => { vars.set(&key, types::Alias(value)); } Binding::ListEntries => print_list(&vars), Binding::KeyOnly(key) => { - eprintln!("ion: please provide value for alias '{}'", key); - return FAILURE; + return Status::error(format!("ion: please provide value for alias '{}'", key)); } } - SUCCESS + Status::SUCCESS } /// Dropping an alias will erase it from the shell. -pub fn drop_alias<S: AsRef<str>>(vars: &mut Variables, args: &[S]) -> i32 { +pub fn drop_alias<S: AsRef<str>>(vars: &mut Variables, args: &[S]) -> Status { if args.len() <= 1 { - eprintln!("ion: you must specify an alias name"); - return FAILURE; + return Status::error(format!("ion: you must specify an alias name")); } for alias in args.iter().skip(1) { if vars.remove_variable(alias.as_ref()).is_none() { - eprintln!("ion: undefined alias: {}", alias.as_ref()); - return FAILURE; + return Status::error(format!("ion: undefined alias: {}", alias.as_ref())); } } - SUCCESS + Status::SUCCESS } /// Dropping an array will erase it from the shell. -pub fn drop_array<S: AsRef<str>>(vars: &mut Variables, args: &[S]) -> i32 { +pub fn drop_array<S: AsRef<str>>(vars: &mut Variables, args: &[S]) -> Status { if args.len() <= 2 { - eprintln!("ion: you must specify an array name"); - return FAILURE; + return Status::error(format!("ion: you must specify an array name")); } if args[1].as_ref() != "-a" { - eprintln!("ion: drop_array must be used with -a option"); - return FAILURE; + return Status::error(format!("ion: drop_array must be used with -a option")); } for array in args.iter().skip(2) { if vars.remove_variable(array.as_ref()).is_none() { - eprintln!("ion: undefined array: {}", array.as_ref()); - return FAILURE; + return Status::error(format!("ion: undefined array: {}", array.as_ref())); } } - SUCCESS + Status::SUCCESS } /// Dropping a variable will erase it from the shell. -pub fn drop_variable<S: AsRef<str>>(vars: &mut Variables, args: &[S]) -> i32 { +pub fn drop_variable<S: AsRef<str>>(vars: &mut Variables, args: &[S]) -> Status { if args.len() <= 1 { - eprintln!("ion: you must specify a variable name"); - return FAILURE; + return Status::error(format!("ion: you must specify a variable name")); } for variable in args.iter().skip(1) { if vars.remove_variable(variable.as_ref()).is_none() { - eprintln!("ion: undefined variable: {}", variable.as_ref()); - return FAILURE; + return Status::error(format!("ion: undefined variable: {}", variable.as_ref())); } } - SUCCESS + Status::SUCCESS } #[cfg(test)] mod test { use super::*; - use crate::{ - parser::Expander, - shell::status::{FAILURE, SUCCESS}, - }; + use crate::parser::Expander; struct VariableExpander<'a>(pub Variables<'a>); @@ -183,7 +167,7 @@ mod test { let mut variables = Variables::default(); variables.set("FOO", "BAR"); let return_status = drop_variable(&mut variables, &["drop", "FOO"]); - assert_eq!(SUCCESS, return_status); + assert!(return_status.is_success()); let expanded = VariableExpander(variables).expand_string("$FOO").join(""); assert_eq!("", expanded); } @@ -192,14 +176,14 @@ mod test { fn drop_fails_with_no_arguments() { let mut variables = Variables::default(); let return_status = drop_variable(&mut variables, &["drop"]); - assert_eq!(FAILURE, return_status); + assert!(!return_status.is_success()); } #[test] fn drop_fails_with_undefined_variable() { let mut variables = Variables::default(); let return_status = drop_variable(&mut variables, &["drop", "FOO"]); - assert_eq!(FAILURE, return_status); + assert!(!return_status.is_success()); } #[test] @@ -207,7 +191,7 @@ mod test { let mut variables = Variables::default(); variables.set("FOO", array!["BAR"]); let return_status = drop_array(&mut variables, &["drop", "-a", "FOO"]); - assert_eq!(SUCCESS, return_status); + assert_eq!(Status::SUCCESS, return_status); let expanded = VariableExpander(variables).expand_string("@FOO").join(""); assert_eq!("", expanded); } @@ -216,13 +200,13 @@ mod test { fn drop_array_fails_with_no_arguments() { let mut variables = Variables::default(); let return_status = drop_array(&mut variables, &["drop", "-a"]); - assert_eq!(FAILURE, return_status); + assert!(!return_status.is_success()); } #[test] fn drop_array_fails_with_undefined_array() { let mut variables = Variables::default(); let return_status = drop_array(&mut variables, &["drop", "FOO"]); - assert_eq!(FAILURE, return_status); + assert!(!return_status.is_success()); } } diff --git a/src/lib/parser/statement/mod.rs b/src/lib/parser/statement/mod.rs index 6f0fe6b2d7d58371db4c260a2ad1dd0ff7fda1a6..1863302b2497d9d8e237b019b25a2549235527ea 100644 --- a/src/lib/parser/statement/mod.rs +++ b/src/lib/parser/statement/mod.rs @@ -7,7 +7,10 @@ pub use self::{ parse::{is_valid_name, parse}, splitter::{StatementError, StatementSplitter, StatementVariant}, }; -use crate::{builtins::BuiltinMap, shell::flow_control::Statement}; +use crate::{ + builtins::BuiltinMap, + shell::{flow_control::Statement, status::Status}, +}; /// Parses a given statement string and return's the corresponding mapped /// `Statement` @@ -23,7 +26,7 @@ pub fn parse_and_validate<'b>( Ok(StatementVariant::Default(statement)) => parse(statement, builtins), Err(err) => { eprintln!("ion: {}", err); - Statement::Error(-1) + Statement::Error(Status::from_exit_code(-1)) } } } diff --git a/src/lib/parser/statement/parse.rs b/src/lib/parser/statement/parse.rs index 17379be1c42ecdf026208bfea87cfdb953587f5a..f53642cf5213ca5df889d5bf2df49b40d034c3ff 100644 --- a/src/lib/parser/statement/parse.rs +++ b/src/lib/parser/statement/parse.rs @@ -8,7 +8,7 @@ use crate::{ lexers::{assignment_lexer, ArgumentSplitter}, shell::{ flow_control::{Case, ElseIf, ExportAction, IfMode, LocalAction, Statement}, - status::FAILURE, + status::Status, }, }; use small; @@ -27,8 +27,7 @@ pub fn parse<'a>(code: &str, builtins: &BuiltinMap<'a>) -> Statement<'a> { "break" => Statement::Break, "continue" => Statement::Continue, "for" | "match" | "case" => { - eprintln!("ion: syntax error: incomplete control flow statement"); - Statement::Error(FAILURE) + Statement::Error(Status::error("ion: syntax error: incomplete control flow statement")) } "let" => Statement::Let(LocalAction::List), _ if cmd.starts_with("let ") => { @@ -45,11 +44,14 @@ pub fn parse<'a>(code: &str, builtins: &BuiltinMap<'a>) -> Statement<'a> { } None => { if op.is_none() { - eprintln!("ion: assignment error: no operator supplied."); + Statement::Error(Status::error( + "ion: assignment error: no operator supplied.", + )) } else { - eprintln!("ion: assignment error: no values supplied."); + Statement::Error(Status::error( + "ion: assignment error: no values supplied.", + )) } - Statement::Error(FAILURE) } } } @@ -68,13 +70,14 @@ pub fn parse<'a>(code: &str, builtins: &BuiltinMap<'a>) -> Statement<'a> { } None => { if keys.is_none() { - eprintln!("ion: assignment error: no keys supplied.") + Statement::Error(Status::error("ion: assignment error: no keys supplied.")) } else if op.is_some() { - eprintln!("ion: assignment error: no values supplied.") + Statement::Error(Status::error( + "ion: assignment error: no values supplied.", + )) } else { - return Statement::Export(ExportAction::LocalExport(keys.unwrap().into())); + Statement::Export(ExportAction::LocalExport(keys.unwrap().into())) } - Statement::Error(FAILURE) } } } @@ -132,10 +135,9 @@ pub fn parse<'a>(code: &str, builtins: &BuiltinMap<'a>) -> Statement<'a> { values: ArgumentSplitter::new(cmd).map(small::String::from).collect(), statements: Vec::new(), }, - None => { - eprintln!("ion: syntax error: for loop lacks the `in` keyword"); - Statement::Error(FAILURE) - } + None => Statement::Error(Status::error( + "ion: syntax error: for loop lacks the `in` keyword", + )), } } _ if cmd.starts_with("case ") => { @@ -145,8 +147,10 @@ pub fn parse<'a>(code: &str, builtins: &BuiltinMap<'a>) -> Statement<'a> { let (value, binding, conditional) = match case::parse_case(value) { Ok(values) => values, Err(why) => { - eprintln!("ion: case error: {}", why); - return Statement::Error(FAILURE); + return Statement::Error(Status::error(format!( + "ion: case error: {}", + why + ))) } }; let binding = binding.map(Into::into); @@ -168,12 +172,11 @@ pub fn parse<'a>(code: &str, builtins: &BuiltinMap<'a>) -> Statement<'a> { let pos = cmd.find(char::is_whitespace).unwrap_or_else(|| cmd.len()); let name = &cmd[..pos]; if !is_valid_name(name) { - eprintln!( + return Statement::Error(Status::error(format!( "ion: syntax error: '{}' is not a valid function name\n Function names \ may only contain alphanumeric characters", name - ); - return Statement::Error(FAILURE); + ))); } let (args, description) = parse_function(&cmd[pos..]); @@ -184,10 +187,10 @@ pub fn parse<'a>(code: &str, builtins: &BuiltinMap<'a>) -> Statement<'a> { args, statements: Vec::new(), }, - Err(why) => { - eprintln!("ion: function argument error: {}", why); - Statement::Error(FAILURE) - } + Err(why) => Statement::Error(Status::error(format!( + "ion: function argument error: {}", + why + ))), } } _ if cmd.starts_with("time ") => { diff --git a/src/lib/shell/assignments.rs b/src/lib/shell/assignments.rs index cb9b9fcca20e4115ebfedfa1a7bca50468588489..f762e1f5a57a8e02b868040242380151022729e5 100644 --- a/src/lib/shell/assignments.rs +++ b/src/lib/shell/assignments.rs @@ -42,7 +42,7 @@ fn list_vars(shell: &Shell) -> Result<(), io::Error> { /// exporting variables to some global environment impl<'b> Shell<'b> { /// Export a variable to the process environment given a binding - pub fn export(&mut self, action: &ExportAction) -> i32 { + pub fn export(&mut self, action: &ExportAction) -> Status { match action { ExportAction::Assign(ref keys, op, ref vals) => { let actions = AssignmentActions::new(keys, *op, vals); @@ -73,21 +73,19 @@ impl<'b> Shell<'b> { }); if let Err(why) = err { - eprintln!("ion: assignment error: {}", why); - return FAILURE; + return Status::error(format!("ion: assignment error: {}", why)); } } - SUCCESS + Status::SUCCESS } ExportAction::LocalExport(ref key) => match self.variables.get_str(key) { Some(var) => { env::set_var(key, &*var); - SUCCESS + Status::SUCCESS } None => { - eprintln!("ion: cannot export {} because it does not exist.", key); - FAILURE + Status::error(format!("ion: cannot export {} because it does not exist.", key)) } }, ExportAction::List => { @@ -96,7 +94,7 @@ impl<'b> Shell<'b> { for (key, val) in env::vars() { let _ = writeln!(stdout, "{} = \"{}\"", key, val); } - SUCCESS + Status::SUCCESS } } } @@ -159,11 +157,11 @@ impl<'b> Shell<'b> { } /// Set a local variable given a binding - pub fn local(&mut self, action: &LocalAction) -> i32 { + pub fn local(&mut self, action: &LocalAction) -> Status { match action { LocalAction::List => { let _ = list_vars(&self); - SUCCESS + Status::SUCCESS } LocalAction::Assign(ref keys, op, ref vals) => { let actions = AssignmentActions::new(keys, *op, vals); @@ -173,10 +171,9 @@ impl<'b> Shell<'b> { } Ok(()) }) { - eprintln!("ion: assignment error: {}", why); - FAILURE + Status::error(format!("ion: assignment error: {}", why)) } else { - SUCCESS + Status::SUCCESS } } } diff --git a/src/lib/shell/flow.rs b/src/lib/shell/flow.rs index 131c0d346c513c036d05717eb5a23b572106b5c5..dced4229476aeab90c00603ddb13e0cab0472c07 100644 --- a/src/lib/shell/flow.rs +++ b/src/lib/shell/flow.rs @@ -39,7 +39,7 @@ impl<'a> Shell<'a> { if let Condition::SigInt = self.execute_statements(&expression) { return Condition::SigInt; } - if self.previous_status == 0 { + if self.previous_status.is_success() { return self.execute_statements(&success); } @@ -49,7 +49,7 @@ impl<'a> Shell<'a> { return Condition::SigInt; } - if self.previous_status == 0 { + if self.previous_status.is_success() { return self.execute_statements(&success); } } @@ -118,7 +118,7 @@ impl<'a> Shell<'a> { ) -> Condition { loop { self.execute_statements(expression); - if self.previous_status != 0 { + if !self.previous_status.is_success() { return Condition::NoOp; } @@ -136,16 +136,16 @@ impl<'a> Shell<'a> { match statement { Statement::Error(number) => { self.previous_status = *number; - self.variables.set("?", self.previous_status.to_string()); + self.variables.set("?", self.previous_status); self.flow_control.clear(); } Statement::Let(action) => { self.previous_status = self.local(action); - self.variables.set("?", self.previous_status.to_string()); + self.variables.set("?", self.previous_status); } Statement::Export(action) => { self.previous_status = self.export(action); - self.variables.set("?", self.previous_status.to_string()); + self.variables.set("?", self.previous_status); } Statement::While { expression, statements } => { if self.execute_while(&expression, &statements) == Condition::SigInt { @@ -180,17 +180,17 @@ impl<'a> Shell<'a> { if !pipeline.items.is_empty() { self.run_pipeline(pipeline); } - if self.opts.err_exit && self.previous_status != SUCCESS { - self.exit(None); + if self.opts.err_exit && !self.previous_status.is_success() { + self.exit(); } if !statements.is_empty() { self.execute_statements(&statements); } } Err(e) => { - eprintln!("ion: pipeline expansion error: {}", e); - self.previous_status = FAILURE; - self.variables.set("?", self.previous_status.to_string()); + self.previous_status = + Status::error(format!("ion: pipeline expansion error: {}", e)); + self.variables.set("?", self.previous_status); self.flow_control.clear(); return Condition::Break; } @@ -214,9 +214,10 @@ impl<'a> Shell<'a> { } } Statement::And(box_statement) => { - let condition = match self.previous_status { - SUCCESS => self.execute_statement(box_statement), - _ => Condition::NoOp, + let condition = if self.previous_status.is_success() { + self.execute_statement(box_statement) + } else { + Condition::NoOp }; if condition != Condition::NoOp { @@ -224,9 +225,10 @@ impl<'a> Shell<'a> { } } Statement::Or(box_statement) => { - let condition = match self.previous_status { - FAILURE => self.execute_statement(box_statement), - _ => Condition::NoOp, + let condition = if self.previous_status.is_success() { + Condition::NoOp + } else { + self.execute_statement(box_statement) }; if condition != Condition::NoOp { @@ -236,13 +238,8 @@ impl<'a> Shell<'a> { Statement::Not(box_statement) => { // NOTE: Should the condition be used? let _condition = self.execute_statement(box_statement); - match self.previous_status { - FAILURE => self.previous_status = SUCCESS, - SUCCESS => self.previous_status = FAILURE, - _ => (), - } - let previous_status = self.previous_status.to_string(); - self.variables_mut().set("?", previous_status); + self.previous_status.toggle(); + self.variables.set("?", self.previous_status); } Statement::Break => return Condition::Break, Statement::Continue => return Condition::Continue, @@ -257,7 +254,7 @@ impl<'a> Shell<'a> { } if let Some(signal) = signals::SignalHandler.next() { if self.handle_signal(signal) { - self.exit(Some(get_signal_code(signal))); + self.exit_with_code(Status::from_signal(signal)); } Condition::SigInt } else if self.break_flow { @@ -327,7 +324,7 @@ impl<'a> Shell<'a> { if let Some(statement) = case.conditional.as_ref() { self.on_command(statement); - if self.previous_status != SUCCESS { + if self.previous_status.is_failure() { continue; } } diff --git a/src/lib/shell/flow_control.rs b/src/lib/shell/flow_control.rs index b275c96e7947e39131a787e0062798af307b99a0..69967e24c31edd2c9b289d4f9b0c1aa522e7ae0d 100644 --- a/src/lib/shell/flow_control.rs +++ b/src/lib/shell/flow_control.rs @@ -1,7 +1,7 @@ use crate::{ lexers::assignments::{KeyBuf, Operator, Primitive}, parser::{assignments::*, pipelines::Pipeline}, - shell::Shell, + shell::{status::Status, Shell}, types, }; use small; @@ -104,7 +104,7 @@ pub enum Statement<'a> { }, Else, End, - Error(i32), + Error(Status), Break, Continue, Pipeline(Pipeline<'a>), @@ -502,7 +502,7 @@ mod tests { fn return_toplevel() { let mut flow_control = Block::default(); let oks = vec![ - Statement::Error(1), + Statement::Error(Status::from_exit_code(1)), Statement::Time(Box::new(Statement::Default)), Statement::And(Box::new(Statement::Default)), Statement::Or(Box::new(Statement::Default)), diff --git a/src/lib/shell/fork.rs b/src/lib/shell/fork.rs index 52a24bccabf419b707be70a652c1bb6d1ad53b13..941330fbbca77a4082dfb03de46f21e4696e6ae9 100644 --- a/src/lib/shell/fork.rs +++ b/src/lib/shell/fork.rs @@ -127,7 +127,7 @@ impl<'a, 'b> Fork<'a, 'b> { // Execute the given closure within the child's shell. child_func(&mut shell); - sys::fork_exit(shell.previous_status); + sys::fork_exit(shell.previous_status.as_os_code()); } Ok(pid) => { Ok(IonResult { diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs index 1856e7bae342f63328692f3ceef9f8fb3a4cf2ca..ffd1f292e04a0f7c3d7e00ed058c783dded5463b 100644 --- a/src/lib/shell/mod.rs +++ b/src/lib/shell/mod.rs @@ -87,7 +87,7 @@ pub struct Shell<'a> { directory_stack: DirectoryStack, /// When a command is executed, the final result of that command is stored /// here. - previous_status: i32, + previous_status: Status, /// The job ID of the previous command sent to the background. previous_job: usize, /// Contains all the options relative to the shell @@ -184,7 +184,7 @@ impl<'a> Shell<'a> { flow_control: Block::with_capacity(5), directory_stack: DirectoryStack::new(), previous_job: !0, - previous_status: SUCCESS, + previous_status: Status::SUCCESS, opts: ShellOptions { err_exit: false, print_comms: false, @@ -265,7 +265,7 @@ impl<'a> Shell<'a> { &mut self, name: &str, args: &[S], - ) -> Result<i32, IonError> { + ) -> Result<Status, IonError> { if let Some(Value::Function(function)) = self.variables.get_ref(name).cloned() { function .execute(self, args) @@ -290,8 +290,7 @@ impl<'a> Shell<'a> { /// commands as they arrive pub fn execute_script<T: std::io::Read>(&mut self, lines: T) { if let Err(why) = self.execute_command(lines) { - eprintln!("ion: {}", why); - self.previous_status = FAILURE; + self.previous_status = Status::error(format!("ion: {}", why)); } } @@ -302,7 +301,7 @@ impl<'a> Shell<'a> { /// 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<T: std::io::Read>(&mut self, command: T) -> Result<i32, IonError> { + pub fn execute_command<T: std::io::Read>(&mut self, command: T) -> Result<Status, IonError> { for cmd in command .bytes() .filter_map(Result::ok) @@ -312,7 +311,7 @@ impl<'a> Shell<'a> { } if let Some(block) = self.flow_control.last().map(Statement::short) { - self.previous_status = FAILURE; + self.previous_status = Status::from_exit_code(1); Err(IonError::UnclosedBlock { block: block.to_string() }) } else { Ok(self.previous_status) @@ -320,7 +319,7 @@ impl<'a> Shell<'a> { } /// Executes a pipeline and returns the final exit status of the pipeline. - pub fn run_pipeline(&mut self, mut pipeline: Pipeline<'a>) -> Option<i32> { + pub fn run_pipeline(&mut self, mut pipeline: Pipeline<'a>) -> Status { let command_start_time = SystemTime::now(); pipeline.expand(self); @@ -332,12 +331,12 @@ impl<'a> Shell<'a> { eprintln!("> {}", pipeline.to_string()); } if self.opts.no_exec { - Some(SUCCESS) + Status::SUCCESS } else { - Some(main(&pipeline.items[0].job.args, self)) + main(&pipeline.items[0].job.args, self) } } else { - Some(self.execute_pipeline(pipeline)) + self.execute_pipeline(pipeline) } // Branch else if -> input == shell function and set the exit_status } else if let Some(Value::Function(function)) = @@ -345,25 +344,23 @@ impl<'a> Shell<'a> { { if !pipeline.requires_piping() { match function.execute(self, &pipeline.items[0].job.args) { - Ok(()) => None, + Ok(()) => self.previous_status, Err(FunctionError::InvalidArgumentCount) => { - eprintln!("ion: invalid number of function arguments supplied"); - Some(FAILURE) + Status::error("ion: invalid number of function arguments supplied") } Err(FunctionError::InvalidArgumentType(expected_type, value)) => { - eprintln!( + Status::error(format!( "ion: function argument has invalid type: expected {}, found value \ \'{}\'", expected_type, value - ); - Some(FAILURE) + )) } } } else { - Some(self.execute_pipeline(pipeline)) + self.execute_pipeline(pipeline) } } else { - Some(self.execute_pipeline(pipeline)) + self.execute_pipeline(pipeline) }; if let Some(ref callback) = self.on_command { @@ -373,10 +370,8 @@ impl<'a> Shell<'a> { } // Retrieve the exit_status and set the $? variable and history.previous_status - if let Some(code) = exit_status { - self.variables_mut().set("?", code.to_string()); - self.previous_status = code; - } + self.variables_mut().set("?", exit_status); + self.previous_status = exit_status; exit_status } @@ -473,12 +468,15 @@ impl<'a> Shell<'a> { pub fn suspend(&self) { signals::suspend(0); } /// Get the last command's return code and/or the code for the error - pub fn previous_status(&self) -> i32 { self.previous_status } + pub fn previous_status(&self) -> Status { self.previous_status } /// Cleanly exit ion - pub fn exit(&mut self, status: Option<i32>) -> ! { + pub fn exit(&mut self) -> ! { self.exit_with_code(self.previous_status) } + + /// Cleanly exit ion with custom code + pub fn exit_with_code(&mut self, status: Status) -> ! { self.prep_for_exit(); - process::exit(status.unwrap_or(self.previous_status)); + process::exit(status.as_os_code()); } pub fn assign(&mut self, key: &Key, value: Value<'a>) -> Result<(), String> { diff --git a/src/lib/shell/pipe_exec/fork.rs b/src/lib/shell/pipe_exec/fork.rs index 39929969d1323432a9258832b932b6b82b73f81a..43cb23d91d0642858a9e351d039bc4abd47fa08d 100644 --- a/src/lib/shell/pipe_exec/fork.rs +++ b/src/lib/shell/pipe_exec/fork.rs @@ -18,7 +18,7 @@ impl<'a> Shell<'a> { pipeline: Pipeline<'a>, command_name: String, state: ProcessState, - ) -> i32 { + ) -> Status { match unsafe { sys::fork() } { Ok(0) => { self.opts_mut().is_background_shell = true; @@ -31,18 +31,18 @@ impl<'a> Shell<'a> { Self::create_process_group(0); // After execution of it's commands, exit with the last command's status. - sys::fork_exit(self.pipe(pipeline)); + sys::fork_exit(self.pipe(pipeline).as_os_code()); } Ok(pid) => { if state != ProcessState::Empty { // The parent process should add the child fork's PID to the background. self.send_to_background(BackgroundProcess::new(pid, state, command_name)); } - SUCCESS + Status::SUCCESS } Err(why) => { eprintln!("ion: background fork failed: {}", why); - exit(FAILURE); + exit(1); } } } diff --git a/src/lib/shell/pipe_exec/job_control.rs b/src/lib/shell/pipe_exec/job_control.rs index f1f873d6105d78cfd21af0231414f8fb974d4105..a3d362aa67917d2101bcd2dcffdafb351a013577 100644 --- a/src/lib/shell/pipe_exec/job_control.rs +++ b/src/lib/shell/pipe_exec/job_control.rs @@ -151,7 +151,7 @@ impl<'a> Shell<'a> { get_process!(|process| { if fg_was_grabbed { - fg.reply_with(TERMINATED as i8); + fg.reply_with(Status::TERMINATED.as_os_code() as i8); } process.state = ProcessState::Stopped; }); @@ -203,23 +203,24 @@ impl<'a> Shell<'a> { } } - pub fn watch_foreground(&mut self, pgid: u32) -> i32 { - let mut signaled = 0; - let mut exit_status = 0; + pub fn watch_foreground(&mut self, pgid: u32) -> Status { + let mut signaled = Status::SUCCESS; + let mut exit_status = Status::SUCCESS; loop { let mut status = 0; match waitpid(-(pgid as i32), &mut status, WUNTRACED) { Err(errno) => match errno { - ECHILD if signaled == 0 => break exit_status, + ECHILD if signaled.is_success() => break exit_status, ECHILD => break signaled, errno => { - eprintln!("ion: waitpid error: {}", strerror(errno)); - break FAILURE; + break Status::error(format!("ion: waitpid error: {}", strerror(errno))) } }, Ok(0) => (), - Ok(_) if wifexited(status) => exit_status = wexitstatus(status), + Ok(_) if wifexited(status) => { + exit_status = Status::from_exit_code(wexitstatus(status)) + } Ok(pid) if wifsignaled(status) => { let signal = wtermsig(status); if signal == SIGPIPE { @@ -236,7 +237,7 @@ impl<'a> Shell<'a> { self.handle_signal(signal); } } - signaled = 128 + signal as i32; + signaled = Status::from_signal(signal as i32); } } Ok(pid) if wifstopped(status) => { @@ -246,7 +247,7 @@ impl<'a> Shell<'a> { "".to_string(), )); self.break_flow = true; - break 128 + wstopsig(status); + break Status::from_signal(wstopsig(status)); } Ok(_) => (), } @@ -259,7 +260,7 @@ impl<'a> Shell<'a> { while self.background.lock().unwrap().iter().any(|p| p.state == ProcessState::Running) { if let Some(signal) = signals::SignalHandler.find(|&s| s != sys::SIGTSTP) { self.background_send(signal); - self.exit(Some(get_signal_code(signal))); + self.exit_with_code(Status::from_signal(signal)); } sleep(Duration::from_millis(100)); } @@ -276,7 +277,7 @@ impl<'a> Shell<'a> { /// Takes a background tasks's PID and whether or not it needs to be continued; resumes the /// task and sets it as the foreground process. Once the task exits or stops, the exit status /// will be returned, and ownership of the TTY given back to the shell. - pub fn set_bg_task_in_foreground(&self, pid: u32, cont: bool) -> i32 { + pub fn set_bg_task_in_foreground(&self, pid: u32, cont: bool) -> Status { // Pass the TTY to the background job Self::set_foreground_as(pid); // Signal the background thread that is waiting on this process to stop waiting. @@ -291,8 +292,8 @@ impl<'a> Shell<'a> { // 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 i32::from(stat), - Some(BackgroundResult::Errored) => break TERMINATED, + Some(BackgroundResult::Status(stat)) => break Status::from_exit_code(stat as i32), + Some(BackgroundResult::Errored) => break Status::TERMINATED, None => sleep(Duration::from_millis(25)), } }; diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs index 358c07ef2d7d7363449c678a58db059980c92d2c..8fc371116becd19316691c1ded9b1c5dba8884a8 100644 --- a/src/lib/shell/pipe_exec/mod.rs +++ b/src/lib/shell/pipe_exec/mod.rs @@ -20,7 +20,7 @@ use super::{ flow_control::FunctionError, job::{Job, JobVariant, RefinedJob, TeeItem}, signals::{self, SignalHandler}, - status::*, + status::Status, Shell, Value, }; use crate::{ @@ -187,7 +187,7 @@ impl<'b> Shell<'b> { stdin: &Option<File>, stdout: &Option<File>, stderr: &Option<File>, - ) -> i32 { + ) -> Status { let result = sys::fork_and_exec( name, args, @@ -208,12 +208,9 @@ impl<'b> Shell<'b> { } Err(ref err) if err.kind() == io::ErrorKind::NotFound => { self.command_not_found(name); - NO_SUCH_COMMAND - } - Err(ref err) => { - eprintln!("ion: command exec error: {}", err); - FAILURE + Status::NO_SUCH_COMMAND } + Err(ref err) => Status::error(format!("ion: command exec error: {}", err)), } } @@ -222,7 +219,7 @@ impl<'b> Shell<'b> { &mut self, items: &mut (Option<TeeItem>, Option<TeeItem>), redirection: RedirectFrom, - ) -> i32 { + ) -> Status { let res = match *items { (None, None) => panic!("There must be at least one TeeItem, this is a bug"), (Some(ref mut tee_out), None) => match redirection { @@ -239,40 +236,39 @@ impl<'b> Shell<'b> { } }; if let Err(e) = res { - eprintln!("ion: error in multiple output redirection process: {:?}", e); - FAILURE + Status::error(format!("ion: error in multiple output redirection process: {:?}", e)) } else { - SUCCESS + Status::SUCCESS } } /// For cat jobs - fn exec_multi_in(&mut self, sources: &mut [File], stdin: &mut Option<File>) -> i32 { + fn exec_multi_in(&mut self, sources: &mut [File], stdin: &mut Option<File>) -> Status { let stdout = io::stdout(); let mut stdout = stdout.lock(); for file in stdin.iter_mut().chain(sources) { if let Err(why) = std::io::copy(file, &mut stdout) { - eprintln!("ion: error in multiple input redirect process: {:?}", why); - return FAILURE; + return Status::error(format!( + "ion: error in multiple input redirect process: {:?}", + why + )); } } - SUCCESS + Status::SUCCESS } - fn exec_function<S: AsRef<str>>(&mut self, name: &str, args: &[S]) -> i32 { + fn exec_function<S: AsRef<str>>(&mut self, name: &str, args: &[S]) -> Status { if let Some(Value::Function(function)) = self.variables.get_ref(name).cloned() { match function.execute(self, args) { - Ok(()) => SUCCESS, + Ok(()) => Status::SUCCESS, Err(FunctionError::InvalidArgumentCount) => { - eprintln!("ion: invalid number of function arguments supplied"); - FAILURE + Status::error(format!("ion: invalid number of function arguments supplied")) } Err(FunctionError::InvalidArgumentType(expected_type, value)) => { - eprintln!( + Status::error(format!( "ion: function argument has invalid type: expected {}, found value \'{}\'", expected_type, value - ); - FAILURE + )) } } } else { @@ -286,7 +282,7 @@ impl<'b> Shell<'b> { /// * `name`: Name of the builtin to execute. /// * `stdin`, `stdout`, `stderr`: File descriptors that will replace the respective standard /// streams if they are not `None` - fn exec_builtin<'a>(&mut self, main: BuiltinFunction<'a>, args: &[small::String]) -> i32 { + fn exec_builtin<'a>(&mut self, main: BuiltinFunction<'a>, args: &[small::String]) -> Status { main(args, self) } @@ -294,7 +290,7 @@ impl<'b> Shell<'b> { /// /// The aforementioned `RefinedJob` may be either a builtin or external command. /// The purpose of this function is therefore to execute both types accordingly. - fn exec_job(&mut self, job: &RefinedJob<'b>) -> i32 { + fn exec_job(&mut self, job: &RefinedJob<'b>) -> Status { // Duplicate file descriptors, execute command, and redirect back. if let Ok((stdin_bk, stdout_bk, stderr_bk)) = duplicate_streams() { redirect_streams(&job.stdin, &job.stdout, &job.stderr); @@ -314,7 +310,7 @@ impl<'b> Shell<'b> { job.long() ); - COULD_NOT_EXEC + Status::COULD_NOT_EXEC } } @@ -353,10 +349,10 @@ impl<'b> Shell<'b> { /// If a job is stopped, the shell will add that job to a list of background jobs and /// continue to watch the job in the background, printing notifications on status changes /// of that job over time. - pub fn execute_pipeline(&mut self, pipeline: Pipeline<'b>) -> i32 { + pub fn execute_pipeline(&mut self, pipeline: Pipeline<'b>) -> Status { // Don't execute commands when the `-n` flag is passed. if self.opts.no_exec { - return SUCCESS; + return Status::SUCCESS; } // A string representing the command is stored here. @@ -387,10 +383,10 @@ impl<'b> Shell<'b> { /// Executes a piped job `job1 | job2 | job3` /// /// This function will panic if called with an empty slice - fn pipe(&mut self, pipeline: Pipeline<'b>) -> i32 { + fn pipe(&mut self, pipeline: Pipeline<'b>) -> Status { let mut commands = match prepare(self, pipeline) { Ok(c) => c.into_iter().peekable(), - Err(_) => return COULD_NOT_EXEC, + Err(_) => return Status::COULD_NOT_EXEC, }; if let Some((mut parent, mut kind)) = commands.next() { @@ -468,7 +464,7 @@ impl<'b> Shell<'b> { // returning the exit status of the last process in the queue. // Watch the foreground group, dropping all commands that exit as they exit. let status = self.watch_foreground(pgid); - if status == TERMINATED { + if status == Status::TERMINATED { if let Err(why) = sys::killpg(pgid, sys::SIGTERM) { eprintln!("ion: failed to terminate foreground jobs: {}", why); } @@ -486,7 +482,7 @@ impl<'b> Shell<'b> { status } } else { - SUCCESS + Status::SUCCESS } } } @@ -592,7 +588,7 @@ fn fork_exec_internal<F>( pgid: u32, mut exec_action: F, ) where - F: FnMut(Option<File>, Option<File>, Option<File>) -> i32, + F: FnMut(Option<File>, Option<File>, Option<File>) -> Status, { match unsafe { sys::fork() } { Ok(0) => { @@ -600,7 +596,7 @@ fn fork_exec_internal<F>( redirect_streams(&stdin, &stdout, &stderr); let exit_status = exec_action(stdout, stderr, stdin); - exit(exit_status) + exit(exit_status.as_os_code()) } Ok(pid) => { *last_pid = *current_pid; diff --git a/src/lib/shell/shell_expand.rs b/src/lib/shell/shell_expand.rs index e327ad5d16a1a631d9089f2920fc7ea9e7130b34..8d458fe66e8e9ef140c37c811f183f2baeec82c9 100644 --- a/src/lib/shell/shell_expand.rs +++ b/src/lib/shell/shell_expand.rs @@ -36,7 +36,7 @@ impl<'a, 'b> Expander for Shell<'b> { /// Expand a string variable given if its quoted / unquoted fn string(&self, name: &str) -> Option<types::Str> { if name == "?" { - Some(types::Str::from(self.previous_status.to_string())) + Some(self.previous_status.into()) } else { self.variables().get_str(name) } diff --git a/src/lib/shell/status.rs b/src/lib/shell/status.rs index 65d0b1ef980c9dc25fbbd08d9fb91aa77e1b6c90..70cc8ed002a50daa9d06613de060518b83042173 100644 --- a/src/lib/shell/status.rs +++ b/src/lib/shell/status.rs @@ -1,8 +1,42 @@ -pub const SUCCESS: i32 = 0; -pub const FAILURE: i32 = 1; -pub const BAD_ARG: i32 = 2; -pub const COULD_NOT_EXEC: i32 = 126; -pub const NO_SUCH_COMMAND: i32 = 127; -pub const TERMINATED: i32 = 143; - -pub fn get_signal_code(signal: i32) -> i32 { 128 + signal } +use super::{super::types, Value}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)] +pub struct Status(i32); + +impl Status { + pub const BAD_ARG: Self = Status(2); + pub const COULD_NOT_EXEC: Self = Status(126); + pub const NO_SUCH_COMMAND: Self = Status(127); + pub const SUCCESS: Self = Status(0); + pub const TERMINATED: Self = Status(143); + + pub fn from_signal(signal: i32) -> Self { Status(128 + signal) } + + pub fn from_exit_code(code: i32) -> Self { Status(code) } + + pub fn from_bool(b: bool) -> Self { Status(!b as i32) } + + pub fn error<T: AsRef<str>>(err: T) -> Self { + let err = err.as_ref(); + if !err.is_empty() { + eprintln!("{}", err); + } + Status(1) + } + + pub fn is_success(&self) -> bool { self.0 == 0 } + + pub fn is_failure(&self) -> bool { self.0 != 0 } + + pub fn as_os_code(&self) -> i32 { self.0 } + + pub fn toggle(&mut self) { self.0 = if self.is_success() { 1 } else { 0 }; } +} + +impl<'a> From<Status> for Value<'a> { + fn from(status: Status) -> Self { Value::Str(status.into()) } +} + +impl From<Status> for types::Str { + fn from(status: Status) -> Self { types::Str::from(status.as_os_code().to_string()) } +} diff --git a/src/main.rs b/src/main.rs index 45af677cbde58ed17ec78b8dc1d54a19aaba77dc..8fb6c765aee886a95812acfbdb61888e8e81d344 100644 --- a/src/main.rs +++ b/src/main.rs @@ -86,5 +86,5 @@ fn main() { shell.execute_script(BufReader::new(stdin())); } shell.wait_for_background(); - shell.exit(None); + shell.exit(); }