diff --git a/src/lib/builtins/echo.rs b/src/lib/builtins/echo.rs index ac1148e335395a72633931b1f593e0ff859c12c9..ebd5b702714cb3c1bfb3cd45e093c9d0bab40d90 100644 --- a/src/lib/builtins/echo.rs +++ b/src/lib/builtins/echo.rs @@ -1,8 +1,41 @@ -use crate::types; +use super::Status; +use crate as ion_shell; +use crate::{types, Shell}; +use builtins_proc::builtin; use smallvec::SmallVec; use std::io::{self, BufWriter, Write}; -pub fn echo(args: &[types::Str]) -> Result<(), io::Error> { +#[builtin( + desc = "display text", + man = " +SYNOPSIS + echo [ -h | --help ] [-e] [-n] [-s] [STRING]... + +DESCRIPTION + Print the STRING(s) to standard output. + +OPTIONS + -e + enable the interpretation of backslash escapes + -n + do not output the trailing newline + -s + do not separate arguments with spaces + + Escape Sequences + When the -e argument is used, the following sequences will be interpreted: + \\\\ backslash + \\a alert (BEL) + \\b backspace (BS) + \\c produce no further output + \\e escape (ESC) + \\f form feed (FF) + \\n new line + \\r carriage return + \\t horizontal tab (HT) + \\v vertical tab (VT)" +)] +pub fn echo(args: &[types::Str], _: &mut Shell<'_>) -> Status { let mut escape = false; let mut newline = true; let mut spaces = true; @@ -49,78 +82,76 @@ pub fn echo(args: &[types::Str]) -> Result<(), io::Error> { let stdout = io::stdout(); let mut buffer = BufWriter::new(stdout.lock()); - let mut first = true; - for arg in data[1..].iter().map(|x| x.as_bytes()) { - if first { + let mut inner = || -> std::io::Result<()> { + let mut first = true; + for arg in data[1..].iter().map(|x| x.as_bytes()) { + if spaces && !first { + buffer.write_all(&[b' '])?; + } first = false; - } else if spaces { - buffer.write_all(&[b' '])?; - } - if escape { - let mut check = false; - for &byte in arg { - match byte { - b'\\' if check => { - buffer.write_all(&[byte])?; - check = false; - } - b'\\' => check = true, - b'a' if check => { - buffer.write_all(&[7u8])?; // bell - check = false; - } - b'b' if check => { - buffer.write_all(&[8u8])?; // backspace - check = false; - } - b'c' if check => { - buffer.flush()?; - return Ok(()); - } - b'e' if check => { - buffer.write_all(&[27u8])?; // escape - check = false; - } - b'f' if check => { - buffer.write_all(&[12u8])?; // form feed - check = false; - } - b'n' if check => { - buffer.write_all(&[b'\n'])?; // newline - check = false; - } - b'r' if check => { - buffer.write_all(&[b'\r'])?; - check = false; - } - b't' if check => { - buffer.write_all(&[b'\t'])?; - check = false; - } - b'v' if check => { - buffer.write_all(&[11u8])?; // vertical tab - check = false; - } - _ if check => { - buffer.write_all(&[b'\\', byte])?; - check = false; - } - _ => { - buffer.write_all(&[byte])?; + if escape { + let mut check = false; + for &byte in arg { + match byte { + b'\\' if check => { + buffer.write_all(&[byte])?; + check = false; + } + b'\\' => check = true, + b'a' if check => { + buffer.write_all(&[7u8])?; // bell + check = false; + } + b'b' if check => { + buffer.write_all(&[8u8])?; // backspace + check = false; + } + b'c' if check => { + return Ok(()); + } + b'e' if check => { + buffer.write_all(&[27u8])?; // escape + check = false; + } + b'f' if check => { + buffer.write_all(&[12u8])?; // form feed + check = false; + } + b'n' if check => { + buffer.write_all(&[b'\n'])?; // newline + check = false; + } + b'r' if check => { + buffer.write_all(&[b'\r'])?; + check = false; + } + b't' if check => { + buffer.write_all(&[b'\t'])?; + check = false; + } + b'v' if check => { + buffer.write_all(&[11u8])?; // vertical tab + check = false; + } + _ if check => { + buffer.write_all(&[b'\\', byte])?; + check = false; + } + _ => { + buffer.write_all(&[byte])?; + } } } + } else { + buffer.write_all(arg)?; } - } else { - buffer.write_all(arg)?; } - } - - if newline { - buffer.write_all(&[b'\n'])?; - } - - buffer.flush()?; + if newline { + buffer.write_all(&[b'\n'])?; + } + Ok(()) + }; - Ok(()) + inner().and_then(|_| buffer.flush()).into() } diff --git a/src/lib/builtins/helpers.rs b/src/lib/builtins/helpers.rs index 3d948a6f0800984b0fb1261ee2db80f580087caa..22cdd38c35cec3d00115e328f7072c9931d18965 100644 --- a/src/lib/builtins/helpers.rs +++ b/src/lib/builtins/helpers.rs @@ -47,3 +47,12 @@ impl<'a> From<Status> for Value<types::Function<'a>> { impl From<Status> for types::Str { fn from(status: Status) -> Self { types::Str::from(status.as_os_code().to_string()) } } + +impl From<std::io::Result<()>> for Status { + fn from(res: std::io::Result<()>) -> Self { + match res { + Ok(_) => Status::SUCCESS, + Err(err) => Status::error(format!("{}", err)), + } + } +} diff --git a/src/lib/builtins/is.rs b/src/lib/builtins/is.rs index 350f016bf83e616719fe8c5b93b28909469813b4..0abd3b10d984a326d597d903364bf0d5705122d9 100644 --- a/src/lib/builtins/is.rs +++ b/src/lib/builtins/is.rs @@ -1,6 +1,22 @@ use super::Status; +use crate as ion_shell; use crate::{shell::Shell, types}; +use builtins_proc::builtin; +// TODO: Add support for multiple name in builtins man +#[builtin( + desc = "checks if two arguments are the same", + man = " +SYNOPSIS + is [ -h | --help ] [not] + +DESCRIPTION + Returns 0 if the two arguments are equal + +OPTIONS + not + returns 0 if the two arguments are not equal." +)] pub fn is(args: &[types::Str], shell: &mut Shell<'_>) -> Status { match args.len() { 4 => { @@ -52,21 +68,21 @@ fn test_is() { shell.variables_mut().set("y", "0"); // Four arguments - 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()); + assert!(builtin_is(&vec_string(&["is", " ", " ", " "]), &mut shell).is_failure()); + assert!(builtin_is(&vec_string(&["is", "not", " ", " "]), &mut shell).is_failure()); + assert!(builtin_is(&vec_string(&["is", "not", "$x", "$x"]), &mut shell).is_failure()); + assert!(builtin_is(&vec_string(&["is", "not", "2", "1"]), &mut shell).is_success()); + assert!(builtin_is(&vec_string(&["is", "not", "$x", "$y"]), &mut shell).is_success()); // Three arguments - 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()); + assert!(builtin_is(&vec_string(&["is", "1", "2"]), &mut shell).is_failure()); + assert!(builtin_is(&vec_string(&["is", "$x", "$y"]), &mut shell).is_failure()); + assert!(builtin_is(&vec_string(&["is", " ", " "]), &mut shell).is_success()); + assert!(builtin_is(&vec_string(&["is", "$x", "$x"]), &mut shell).is_success()); // Two arguments - assert!(is(&vec_string(&["is", " "]), &mut shell).is_failure()); + assert!(builtin_is(&vec_string(&["is", " "]), &mut shell).is_failure()); // One argument - assert!(is(&vec_string(&["is"]), &mut shell).is_failure()); + assert!(builtin_is(&vec_string(&["is"]), &mut shell).is_failure()); } diff --git a/src/lib/builtins/man_pages.rs b/src/lib/builtins/man_pages.rs index 20be9eeb6f06a8abc27302f52a18d418829ff941..cc38ebf1dd0e664f1ce1e389e3a400a17cec9633 100644 --- a/src/lib/builtins/man_pages.rs +++ b/src/lib/builtins/man_pages.rs @@ -60,115 +60,6 @@ DESCRIPTION // example 1 //"#; -pub const MAN_READ: &str = r#"NAME - read - read a line of input into some variables - -SYNOPSIS - read VARIABLES... - -DESCRIPTION - For each variable reads from standard input and stores the results in the variable."#; - -pub const MAN_DROP: &str = r#"NAME - drop - delete some variables or arrays - -SYNOPSIS - drop [ -a ] VARIABLES... - -DESCRIPTION - Deletes the variables given to it as arguments. The variables name must be supplied. - Instead of '$x' use 'x'. - -OPTIONS - -a - Instead of deleting variables deletes arrays."#; - -pub const MAN_SET: &str = r#"NAME - set - Set or unset values of shell options and positional parameters. - -SYNOPSIS - set [ --help ] [-e | +e] [-x | +x] [-o [vi | emacs]] [- | --] [STRING]... - -DESCRIPTION - Shell options may be set using the '-' character, and unset using the '+' character. - -OPTIONS - -e Exit immediately if a command exits with a non-zero status. - - -o Specifies that an argument will follow that sets the key map. - The keymap argument may be either `vi` or `emacs`. - - -x Specifies that commands will be printed as they are executed. - - -- Following arguments will be set as positional arguments in the shell. - If no argument are supplied, arguments will be unset. - - - Following arguments will be set as positional arguments in the shell. - If no arguments are suppled, arguments will not be unset."#; - -pub const MAN_EQ: &str = r#"NAME - eq - Checks if two arguments are the same - -SYNOPSIS - eq [ -h | --help ] [not] - -DESCRIPTION - Returns 0 if the two arguments are equal - -OPTIONS - not - returns 0 if the two arguments are not equal."#; - -pub const MAN_EVAL: &str = r#"NAME - eval - evaluates the specified commands - -SYNOPSIS - eval COMMANDS... - -DESCRIPTION - eval evaluates the given arguments as a command. If more than one argument is given, - all arguments are joined using a space as a separator."#; - -pub const MAN_SOURCE: &str = r#"NAME - source - evaluates given file - -SYNOPSIS - source FILEPATH - -DESCRIPTION - Evaluates the commands in a specified file in the current shell. All changes in shell - variables will affect the current shell because of this."#; - -pub const MAN_ECHO: &str = r#"NAME - echo - display a line of text - -SYNOPSIS - echo [ -h | --help ] [-e] [-n] [-s] [STRING]... - -DESCRIPTION - Print the STRING(s) to standard output. - -OPTIONS - -e - enable the interpretation of backslash escapes - -n - do not output the trailing newline - -s - do not separate arguments with spaces - - Escape Sequences - When the -e argument is used, the following sequences will be interpreted: - \\ backslash - \a alert (BEL) - \b backspace (BS) - \c produce no further output - \e escape (ESC) - \f form feed (FF) - \n new line - \r carriage return - \t horizontal tab (HT) - \v vertical tab (VT)"#; - pub const MAN_RANDOM: &str = r#"NAME random - generate a random number @@ -181,42 +72,6 @@ DESCRIPTION The range depends on what arguments you pass. If no arguments are given the range is [0, 32767]. If two arguments are given the range is [START, END]."#; -pub const MAN_TRUE: &str = r#"NAME - true - does nothing successfully - -SYNOPSIS - true - -DESCRIPTION - Sets the exit status to 0."#; - -pub const MAN_FALSE: &str = r#"NAME - false - does nothing unsuccessfully - -SYNOPSIS - false - -DESCRIPTION - Sets the exit status to 1."#; - -pub const MAN_JOBS: &str = r#"NAME - jobs - list all jobs running in the background - -SYNOPSIS - jobs - -DESCRIPTION - Prints a list of all jobs running in the background."#; - -pub const MAN_BG: &str = r#"NAME - bg - sends jobs to background - -SYNOPSIS - bg PID - -DESCRIPTION - bg sends the job to the background resuming it if it has stopped."#; - pub const MAN_FG: &str = r#"NAME fg - bring job to foreground diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs index f94703d54a8a5622d4808bf15811af5430729e9f..e1b9712ea49a0a24e586ecc3d1522abaa5061ca2 100644 --- a/src/lib/builtins/mod.rs +++ b/src/lib/builtins/mod.rs @@ -19,12 +19,13 @@ mod variables; use self::{ command_info::{find_type, which}, conditionals::{contains, ends_with, starts_with}, - echo::echo, + echo::builtin_echo, exists::exists, functions::print_functions, - is::is, + is::builtin_is, man_pages::*, - source::source, + set::builtin_set, + source::builtin_source, status::builtin_status, test::test, variables::{alias, drop_alias, drop_array, drop_variable}, @@ -199,10 +200,10 @@ impl<'a> BuiltinMap<'a> { pub fn with_values_tests(&mut self) -> &mut Self { self.add("bool", &builtin_bool, "If the value is '1' or 'true', return 0 exit status") .add("calc", &builtin_calc, "Calculate a mathematical expression") - .add("eq", &builtin_eq, "Simple alternative to == and !=") - .add("is", &builtin_eq, "Simple alternative to == and !=") - .add("true", &builtin_true, "Do nothing, successfully") - .add("false", &builtin_false, "Do nothing, unsuccessfully") + .add("eq", &builtin_is, "Simple alternative to == and !=") + .add("is", &builtin_is, "Simple alternative to == and !=") + .add("true", &builtin_tru, "Do nothing, successfully") + .add("false", &builtin_fals, "Do nothing, unsuccessfully") .add( "starts-with", &starts_with, @@ -575,11 +576,17 @@ impl Completer for EmptyCompleter { fn completions(&mut self, _start: &str) -> Vec<String> { Vec::new() } } -pub fn builtin_read(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_READ) { - return Status::SUCCESS; - } +#[builtin( + desc = "read a line of input into some variables", + man = " +SYNOPSIS + read VARIABLES... +DESCRIPTION + For each variable reads from standard input and stores the results in the variable. +" +)] +pub fn read(args: &[types::Str], shell: &mut Shell<'_>) -> Status { if atty::is(atty::Stream::Stdin) { let mut con = Context::new(); for arg in args.iter().skip(1) { @@ -603,10 +610,22 @@ pub fn builtin_read(args: &[types::Str], shell: &mut Shell<'_>) -> Status { Status::SUCCESS } -pub fn builtin_drop(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_DROP) { - return Status::SUCCESS; - } +#[builtin( + desc = "delete some variables or arrays", + man = " +SYNOPSIS + drop [ -a ] VARIABLES... + +DESCRIPTION + Deletes the variables given to it as arguments. The variables name must be supplied. + Instead of '$x' use 'x'. + +OPTIONS + -a + Instead of deleting variables deletes arrays. +" +)] +pub fn drop(args: &[types::Str], shell: &mut Shell<'_>) -> Status { if args.len() >= 2 && args[1] == "-a" { drop_array(shell.variables_mut(), args) } else { @@ -614,46 +633,20 @@ pub fn builtin_drop(args: &[types::Str], shell: &mut Shell<'_>) -> Status { } } -pub fn builtin_set(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_SET) { - return Status::SUCCESS; - } - set::set(args, shell) -} - -pub fn builtin_eq(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_EQ) { - return Status::SUCCESS; - } - - is(args, shell) -} - -pub fn builtin_eval(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_EVAL) { - Status::SUCCESS - } else { - shell.execute_command(args[1..].join(" ").as_bytes()).unwrap_or_else(|_| { - Status::error("ion: supplied eval expression was not terminated".to_string()) - }) - } -} - -pub fn builtin_source(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_SOURCE) { - return Status::SUCCESS; - } - source(shell, args) -} +#[builtin( + desc = "evaluates the specified commands", + man = " +SYNOPSIS + eval COMMANDS... -pub fn builtin_echo(args: &[types::Str], _: &mut Shell<'_>) -> Status { - if check_help(args, MAN_ECHO) { - return Status::SUCCESS; - } - match echo(args) { - Ok(()) => Status::SUCCESS, - Err(why) => Status::error(why.to_string()), - } +DESCRIPTION + eval evaluates the given arguments as a command. If more than one argument is given, + all arguments are joined using a space as a separator." +)] +pub fn eval(args: &[types::Str], shell: &mut Shell<'_>) -> Status { + shell.execute_command(args[1..].join(" ").as_bytes()).unwrap_or_else(|_| { + Status::error("ion: supplied eval expression was not terminated".to_string()) + }) } pub fn builtin_test(args: &[types::Str], _: &mut Shell<'_>) -> Status { @@ -684,15 +677,30 @@ pub fn builtin_random(args: &[types::Str], _: &mut Shell<'_>) -> Status { } } -pub fn builtin_true(args: &[types::Str], _: &mut Shell<'_>) -> Status { - check_help(args, MAN_TRUE); +// TODO: Find a workaround +#[builtin( + desc = "does nothing sucessfully", + man = " +SYNOPSIS + true + +DESCRIPTION + Sets the exit status to 0." +)] +pub fn tru(args: &[types::Str], _: &mut Shell<'_>) -> Status { Status::SUCCESS } -pub fn builtin_false(args: &[types::Str], _: &mut Shell<'_>) -> Status { - if check_help(args, MAN_FALSE) { - return Status::SUCCESS; - } +#[builtin( + desc = "does nothing unsuccessfully", + man = " +SYNOPSIS + false + +DESCRIPTION + Sets the exit status to 1." +)] +pub fn fals(args: &[types::Str], _: &mut Shell<'_>) -> Status { Status::error("") } @@ -702,16 +710,30 @@ pub fn builtin_wait(_: &[types::Str], shell: &mut Shell<'_>) -> Status { Status::SUCCESS } -pub fn builtin_jobs(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - check_help(args, MAN_JOBS); +#[builtin( + desc = "list all jobs running in the background", + man = " +SYNOPSIS + jobs + +DESCRIPTION + Prints a list of all jobs running in the background." +)] +pub fn jobs(args: &[types::Str], shell: &mut Shell<'_>) -> Status { job_control::jobs(shell); Status::SUCCESS } -pub fn builtin_bg(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_BG) { - return Status::SUCCESS; - } +#[builtin( + desc = "sends jobs to background", + man = " +SYNOPSIS + bg PID + +DESCRIPTION + bg sends the job to the background resuming it if it has stopped." +)] +pub fn bg(args: &[types::Str], shell: &mut Shell<'_>) -> Status { job_control::bg(shell, &args[1..]) } diff --git a/src/lib/builtins/set.rs b/src/lib/builtins/set.rs index d6ee04e04697be813d8ec5486735a5cfa2e576d9..1294d0af83a4b79e1b3475acb3e68a95e1298c52 100644 --- a/src/lib/builtins/set.rs +++ b/src/lib/builtins/set.rs @@ -1,8 +1,10 @@ use super::Status; +use crate as ion_shell; use crate::{ shell::{variables::Value, Shell}, types, }; +use builtins_proc::builtin; use std::iter; enum PositionalArgs { @@ -12,6 +14,29 @@ enum PositionalArgs { use self::PositionalArgs::*; +#[builtin( + desc = "Set or unset values of shell options and positional parameters.", + man = " +SYNOPSIS + set [ --help ] [-e | +e] [-x | +x] [-o [vi | emacs]] [- | --] [STRING]... + +DESCRIPTION + Shell options may be set using the '-' character, and unset using the '+' character. + +OPTIONS + -e Exit immediately if a command exits with a non-zero status. + + -o Specifies that an argument will follow that sets the key map. + The keymap argument may be either `vi` or `emacs`. + + -x Specifies that commands will be printed as they are executed. + + -- Following arguments will be set as positional arguments in the shell. + If no argument are supplied, arguments will be unset. + + - Following arguments will be set as positional arguments in the shell. + If no arguments are suppled, arguments will not be unset." +)] pub fn set(args: &[types::Str], shell: &mut Shell<'_>) -> Status { let mut args_iter = args.iter(); let mut positionals = None; diff --git a/src/lib/builtins/source.rs b/src/lib/builtins/source.rs index e9d1eca2110f6cc6eb2b661920444abbc4803c60..aa1dca552d015f447eeb1de41a96a69c6987eef0 100644 --- a/src/lib/builtins/source.rs +++ b/src/lib/builtins/source.rs @@ -1,10 +1,21 @@ use super::Status; +use crate as ion_shell; use crate::{shell::Shell, types}; +use builtins_proc::builtin; use std::fs::File; -/// Evaluates the given file and returns 'SUCCESS' if it succeeds. -pub fn source(shell: &mut Shell<'_>, arguments: &[types::Str]) -> Status { - match arguments.get(1) { +#[builtin( + desc = "evaluates given file", + man = " +SYNOPSIS + source FILEPATH + +DESCRIPTION + Evaluates the commands in a specified file in the current shell. All changes in shell + variables will affect the current shell because of this." +)] +pub fn source(args: &[types::Str], shell: &mut Shell<'_>) -> Status { + match args.get(1) { Some(argument) => { if let Ok(file) = File::open(argument.as_str()) { if let Err(why) = shell.execute_command(file) {