From d096b4d1eebbe10f227412fed70ef7047e8f630d Mon Sep 17 00:00:00 2001 From: Sag0Sag0 <Sag0Sag0@users.noreply.github.com> Date: Tue, 5 Dec 2017 12:14:10 +1100 Subject: [PATCH] Add manpages for almost all builtins. (#628) * Start adding manpages * Add manpages for all builtins * Fix so that changes pass tests --- manual/src/ch07-01-conditionals.md | 8 +- src/builtins/echo.rs | 47 +-- src/builtins/exists.rs | 143 ++----- src/builtins/is.rs | 28 +- src/builtins/job_control.rs | 32 +- src/builtins/man_pages.rs | 585 +++++++++++++++++++++++++++++ src/builtins/mod.rs | 141 ++++++- src/builtins/set.rs | 33 +- src/builtins/status.rs | 35 +- src/builtins/test.rs | 183 ++------- 10 files changed, 794 insertions(+), 441 deletions(-) create mode 100644 src/builtins/man_pages.rs diff --git a/manual/src/ch07-01-conditionals.md b/manual/src/ch07-01-conditionals.md index 8a2f608a..242c05f0 100644 --- a/manual/src/ch07-01-conditionals.md +++ b/manual/src/ch07-01-conditionals.md @@ -46,15 +46,15 @@ reason for a shell language to have multiple different keywords to end different ## Complete List of Conditional Builtins -- [ ] and +- [x] and - [ ] contains -- [ ] exists +- [x] exists - [ ] intersects -- [ ] is +- [x] is - [ ] isatty - [x] matches - [x] not -- [ ] or +- [x] or - [x] test - [ ] < (Polish Notation) - [ ] <= (Polish Notation) diff --git a/src/builtins/echo.rs b/src/builtins/echo.rs index 1ec9c7f2..45bacc25 100644 --- a/src/builtins/echo.rs +++ b/src/builtins/echo.rs @@ -2,52 +2,18 @@ use std::io::{self, BufWriter, Write}; bitflags! { struct Flags : u8 { - const HELP = 1; - const ESCAPE = 2; - const NO_NEWLINE = 4; - const NO_SPACES = 8; + const ESCAPE = 1; + const NO_NEWLINE = 2; + const NO_SPACES = 4; } } - -const MAN_PAGE: &'static 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) -"#; // @MANEND - pub(crate) fn echo(args: &[&str]) -> Result<(), io::Error> { let mut flags = Flags::empty(); let mut data: Vec<&str> = vec![]; for arg in args { match *arg { - "--help" => flags |= Flags::HELP, "--escape" => flags |= Flags::ESCAPE, "--no-newline" => flags |= Flags::NO_NEWLINE, "--no-spaces" => flags |= Flags::NO_SPACES, @@ -60,7 +26,6 @@ pub(crate) fn echo(args: &[&str]) -> Result<(), io::Error> { 'e' => short_flags |= Flags::ESCAPE, 'n' => short_flags |= Flags::NO_NEWLINE, 's' => short_flags |= Flags::NO_SPACES, - 'h' => short_flags |= Flags::HELP, _ => { is_opts = false; break; @@ -81,12 +46,6 @@ pub(crate) fn echo(args: &[&str]) -> Result<(), io::Error> { let stdout = io::stdout(); let mut buffer = BufWriter::new(stdout.lock()); - if flags.contains(Flags::HELP) { - buffer.write_all(MAN_PAGE.as_bytes())?; - buffer.flush()?; - return Ok(()); - } - let mut first = true; for arg in data[1..].iter().map(|x| x.as_bytes()) { if first { diff --git a/src/builtins/exists.rs b/src/builtins/exists.rs index 09d48f20..2240f639 100644 --- a/src/builtins/exists.rs +++ b/src/builtins/exists.rs @@ -2,92 +2,20 @@ use smallstring::SmallString; #[cfg(test)] use smallvec::SmallVec; -use std::error::Error; use std::fs; -use std::io::{self, BufWriter}; use std::os::unix::fs::PermissionsExt; use shell::Shell; #[cfg(test)] use shell::flow_control::{Function, Statement}; -const MAN_PAGE: &'static str = r#"NAME - exists - check whether items exist - -SYNOPSIS - exists [EXPRESSION] - -DESCRIPTION - Checks whether the given item exists and returns an exit status of 0 if it does, else 1. - -OPTIONS - -a ARRAY - array var is not empty - - -b BINARY - binary is in PATH - - -d PATH - path is a directory - This is the same as test -d - - -f PATH - path is a file - This is the same as test -f - - --fn FUNCTION - function is defined - - -s STRING - string var is not empty - - STRING - string is not empty - This is the same as test -n - -EXAMPLES - Test if the file exists: - exists -f FILE && echo "The FILE exists" || echo "The FILE does not exist" - - Test if some-command exists in the path and is executable: - exists -b some-command && echo "some-command exists" || echo "some-command does not exist" - - Test if variable exists AND is not empty - exists -s myVar && echo "myVar exists: $myVar" || echo "myVar does not exist or is empty" - NOTE: Don't use the '$' sigil, but only the name of the variable to check - - Test if array exists and is not empty - exists -a myArr && echo "myArr exists: @myArr" || echo "myArr does not exist or is empty" - NOTE: Don't use the '@' sigil, but only the name of the array to check - - Test if a function named 'myFunc' exists - exists --fn myFunc && myFunc || echo "No function with name myFunc found" - -AUTHOR - Written by Fabian Würfl. - Heavily based on implementation of the test builtin, which was written by Michael Murph. -"#; // @MANEND - pub(crate) fn exists(args: &[&str], shell: &Shell) -> Result<bool, String> { - let stdout = io::stdout(); - let mut buffer = BufWriter::new(stdout.lock()); - let arguments = &args[1..]; - evaluate_arguments(arguments, &mut buffer, shell) + evaluate_arguments(arguments, shell) } -fn evaluate_arguments<W: io::Write>( - arguments: &[&str], - buffer: &mut W, - shell: &Shell, -) -> Result<bool, String> { +fn evaluate_arguments(arguments: &[&str], shell: &Shell) -> Result<bool, String> { match arguments.first() { - Some(&"--help") => { - // not handled by the second case, so that we don't have to pass the buffer around - buffer.write_all(MAN_PAGE.as_bytes()).map_err(|x| x.description().to_owned())?; - buffer.flush().map_err(|x| x.description().to_owned())?; - Ok(true) - } Some(&s) if s.starts_with("--") => { let (_, option) = s.split_at(2); // If no argument was given, return `SUCCESS`, as this means a string starting @@ -215,47 +143,42 @@ fn function_is_defined(function: &str, shell: &Shell) -> bool { fn test_evaluate_arguments() { use parser::assignments::{KeyBuf, Primitive}; let mut shell = Shell::new(); - let mut sink = BufWriter::new(io::sink()); // assert_eq!(evaluate_arguments(&[], &mut sink, &shell), Ok(false)); // no parameters - assert_eq!(evaluate_arguments(&[], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&[], &shell), Ok(false)); // multiple arguments // ignores all but the first argument - assert_eq!(evaluate_arguments(&["foo", "bar"], &mut sink, &shell), Ok(true)); - - // check whether --help returns SUCCESS - assert_eq!(evaluate_arguments(&["--help"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["--help", "unused", "params"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["foo", "bar"], &shell), Ok(true)); // check `exists STRING` - assert_eq!(evaluate_arguments(&[""], &mut sink, &shell), Ok(false)); - assert_eq!(evaluate_arguments(&["string"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["string with space"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["-startswithdash"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&[""], &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["string"], &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["string with space"], &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-startswithdash"], &shell), Ok(true)); // check `exists -a` // no argument means we treat it as a string - assert_eq!(evaluate_arguments(&["-a"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-a"], &shell), Ok(true)); shell.variables.set_array("emptyarray", SmallVec::from_vec(Vec::new())); - assert_eq!(evaluate_arguments(&["-a", "emptyarray"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-a", "emptyarray"], &shell), Ok(false)); let mut vec = Vec::new(); vec.push("element".to_owned()); shell.variables.set_array("array", SmallVec::from_vec(vec)); - assert_eq!(evaluate_arguments(&["-a", "array"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-a", "array"], &shell), Ok(true)); shell.variables.unset_array("array"); - assert_eq!(evaluate_arguments(&["-a", "array"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-a", "array"], &shell), Ok(false)); // check `exists -b` // TODO: see test_binary_is_in_path() // no argument means we treat it as a string - assert_eq!(evaluate_arguments(&["-b"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-b"], &shell), Ok(true)); let oldpath = shell.get_var("PATH").unwrap_or("/usr/bin".to_owned()); shell.set_var("PATH", "testing/"); - assert_eq!(evaluate_arguments(&["-b", "executable_file"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["-b", "empty_file"], &mut sink, &shell), Ok(false)); - assert_eq!(evaluate_arguments(&["-b", "file_does_not_exist"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-b", "executable_file"], &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-b", "empty_file"], &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-b", "file_does_not_exist"], &shell), Ok(false)); // restore original PATH. Not necessary for the currently defined test cases but this might // change in the future? Better safe than sorry! @@ -263,33 +186,33 @@ fn test_evaluate_arguments() { // check `exists -d` // no argument means we treat it as a string - assert_eq!(evaluate_arguments(&["-d"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["-d", "testing/"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["-d", "testing/empty_file"], &mut sink, &shell), Ok(false)); - assert_eq!(evaluate_arguments(&["-d", "does/not/exist/"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-d"], &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-d", "testing/"], &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-d", "testing/empty_file"], &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-d", "does/not/exist/"], &shell), Ok(false)); // check `exists -f` // no argument means we treat it as a string - assert_eq!(evaluate_arguments(&["-f"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["-f", "testing/"], &mut sink, &shell), Ok(false)); - assert_eq!(evaluate_arguments(&["-f", "testing/empty_file"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["-f", "does-not-exist"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-f"], &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-f", "testing/"], &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-f", "testing/empty_file"], &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-f", "does-not-exist"], &shell), Ok(false)); // check `exists -s` // no argument means we treat it as a string - assert_eq!(evaluate_arguments(&["-s"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-s"], &shell), Ok(true)); shell.set_var("emptyvar", ""); - assert_eq!(evaluate_arguments(&["-s", "emptyvar"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-s", "emptyvar"], &shell), Ok(false)); shell.set_var("testvar", "foobar"); - assert_eq!(evaluate_arguments(&["-s", "testvar"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-s", "testvar"], &shell), Ok(true)); shell.variables.unset_var("testvar"); - assert_eq!(evaluate_arguments(&["-s", "testvar"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-s", "testvar"], &shell), Ok(false)); // also check that it doesn't trigger on arrays let mut vec = Vec::new(); vec.push("element".to_owned()); shell.variables.unset_var("array"); shell.variables.set_array("array", SmallVec::from_vec(vec)); - assert_eq!(evaluate_arguments(&["-s", "array"], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["-s", "array"], &shell), Ok(false)); // check `exists --fn` let name_str = "test_function"; @@ -302,13 +225,13 @@ fn test_evaluate_arguments() { shell.functions.insert(name.clone(), Function::new(Some(description), name, args, statements)); - assert_eq!(evaluate_arguments(&["--fn", name_str], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["--fn", name_str], &shell), Ok(true)); shell.functions.remove(name_str); - assert_eq!(evaluate_arguments(&["--fn", name_str], &mut sink, &shell), Ok(false)); + assert_eq!(evaluate_arguments(&["--fn", name_str], &shell), Ok(false)); // check invalid flags / parameters (should all be treated as strings and therefore succeed) - assert_eq!(evaluate_arguments(&["--foo"], &mut sink, &shell), Ok(true)); - assert_eq!(evaluate_arguments(&["-x"], &mut sink, &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["--foo"], &shell), Ok(true)); + assert_eq!(evaluate_arguments(&["-x"], &shell), Ok(true)); } #[test] diff --git a/src/builtins/is.rs b/src/builtins/is.rs index 39cda14c..79b80a4b 100644 --- a/src/builtins/is.rs +++ b/src/builtins/is.rs @@ -1,22 +1,6 @@ -use std::error::Error; -use std::io::{stdout, Write}; - +use builtins::man_pages::{check_help, MAN_IS}; use shell::Shell; -const MAN_PAGE: &'static str = r#"NAME - is - Checks if two arguments are the same - -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. -"#; // @MANEND - pub(crate) fn is(args: &[&str], shell: &mut Shell) -> Result<(), String> { match args.len() { 4 => if args[1] != "not" { @@ -27,15 +11,7 @@ pub(crate) fn is(args: &[&str], shell: &mut Shell) -> Result<(), String> { 3 => if eval_arg(args[1], shell) != eval_arg(args[2], shell) { return Err("".to_string()); }, - 2 => if args[1] == "-h" || args[1] == "--help" { - let stdout = stdout(); - let mut stdout = stdout.lock(); - - return match stdout.write_all(MAN_PAGE.as_bytes()).and_then(|_| stdout.flush()) { - Ok(_) => Ok(()), - Err(err) => Err(err.description().to_owned()), - }; - } else { + 2 => if !check_help(args, MAN_IS) { return Err("is needs 3 or 4 arguments\n".to_string()); }, _ => return Err("is needs 3 or 4 arguments\n".to_string()), diff --git a/src/builtins/job_control.rs b/src/builtins/job_control.rs index cdf4076e..f50e1a00 100644 --- a/src/builtins/job_control.rs +++ b/src/builtins/job_control.rs @@ -4,37 +4,12 @@ use shell::Shell; use shell::job_control::{JobControl, ProcessState}; use shell::signals; use shell::status::*; -use std::error::Error; -use std::io::{stderr, stdout, Write}; - -const DISOWN_MAN_PAGE: &'static str = r#"NAME - disown - Disown processes - -SYNOPSIS - disown [ --help | -r | -h | -a ][PID...] - -DESCRIPTION - Disowning a process removes that process from the shell's background process table. - -OPTIONS - -r Remove all running jobs from the background process list. - -h Specifies that each job supplied will not receive the SIGHUP signal when the shell receives a SIGHUP. - -a If no job IDs were supplied, remove all jobs from the background process list. -"#; +use std::io::{stderr, Write}; /// Disowns given process job IDs, and optionally marks jobs to not receive SIGHUP signals. /// The `-a` flag selects all jobs, `-r` selects all running jobs, and `-h` specifies to mark /// SIGHUP ignoral. pub(crate) fn disown(shell: &mut Shell, args: &[&str]) -> Result<(), String> { - fn print_help(ret: Result<(), String>) -> Result<(), String> { - let stdout = stdout(); - let mut stdout = stdout.lock(); - - return match stdout.write_all(DISOWN_MAN_PAGE.as_bytes()).and_then(|_| stdout.flush()) { - Ok(_) => ret, - Err(err) => Err(err.description().to_owned()), - }; - } const NO_SIGHUP: u8 = 1; const ALL_JOBS: u8 = 2; @@ -47,9 +22,6 @@ pub(crate) fn disown(shell: &mut Shell, args: &[&str]) -> Result<(), String> { "-a" => flags |= ALL_JOBS, "-h" => flags |= NO_SIGHUP, "-r" => flags |= RUN_JOBS, - "--help" => { - return print_help(Ok(())); - } _ => match arg.parse::<u32>() { Ok(jobspec) => jobspecs.push(jobspec), Err(_) => { @@ -60,7 +32,7 @@ pub(crate) fn disown(shell: &mut Shell, args: &[&str]) -> Result<(), String> { } if flags == 0 { - return print_help(Err("must provide arguments".to_owned())); + return Err("must provide arguments".to_owned()); } else if (flags & ALL_JOBS) == 0 && jobspecs.is_empty() { return Err("must provide a jobspec with -h or -r".to_owned()); } diff --git a/src/builtins/man_pages.rs b/src/builtins/man_pages.rs new file mode 100644 index 00000000..57eb541f --- /dev/null +++ b/src/builtins/man_pages.rs @@ -0,0 +1,585 @@ +use std::error::Error; +use std::io::{stdout, Write}; + +pub(crate) fn print_man(man_page: &'static str) { + let stdout = stdout(); + let mut stdout = stdout.lock(); + match stdout.write_all(man_page.as_bytes()).and_then(|_| stdout.flush()) { + Ok(_) => (), + Err(err) => panic!("{}", err.description().to_owned()), + } +} + +pub(crate) fn check_help(args: &[&str], man_page: &'static str) -> bool { + for arg in args { + if *arg == "-h" || *arg == "--help" { + print_man(man_page); + return true + } + } + false +} + +pub(crate) const MAN_STATUS: &'static str = r#"NAME + status - Evaluates the current runtime status + +SYNOPSIS + status [ -h | --help ] [-l] [-i] + +DESCRIPTION + With no arguments status displays the current login information of the shell. + +OPTIONS + -l + returns true if the shell is a login shell. Also --is-login. + -i + returns true if the shell is interactive. Also --is-interactive. + -f + prints the filename of the currently running script or else stdio. Also --current-filename. +"#; + +pub(crate) const MAN_CD: &'static str = r#"NAME + cd - Change directory. + +SYNOPSIS + cd DIRECTORY + +DESCRIPTION + Without arguments cd changes the working directory to your home directory. + + With arguments cd changes the working directory to the directory you provided. + +"#; + +pub(crate) const MAN_BOOL: &'static str = r#"NAME + bool - Returns true if the value given to it is equal to '1' or 'true'. + +SYNOPSIS + bool VALUE + +DESCRIPTION + Returns true if the value given to it is equal to '1' or 'true'. +"#; + +pub(crate) const MAN_IS: &'static str = r#"NAME + is - Checks if two arguments are the same + +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(crate) const MAN_DIRS: &'static str = r#"NAME + dirs - prints the directory stack + +SYNOPSIS + dirs + +DESCRIPTION + dirs prints the current directory stack. +"#; + +pub(crate) const MAN_PUSHD: &'static str = r#"NAME + pushd - push a directory to the directory stack + +SYNOPSIS + pushd DIRECTORY + +DESCRIPTION + pushd pushes a directory to the directory stack. +"#; + +pub(crate) const MAN_POPD: &'static str = r#"NAME + popd - shift through the directory stack + +SYNOPSIS + popd + +DESCRIPTION + popd removes the top directory from the directory stack and changes the working directory to the new top directory. + pushd adds directories to the stack. +"#; + +/*pub(crate) const MAN_FN: &'static str = r#"NAME + fn - print a list of all functions or create a function + +SYNOPSIS + fn + + fn example arg:int + echo $arg + end + +DESCRIPTION + fn prints a list of all functions that exist in the shell or creates a function when combined + with the 'end' keyword. Functions can have type hints, to tell ion to check the type of a + functions arguments. An error will occur if an argument supplied to a function is of the wrong type. + The supported types in ion are, [], bool, bool[], float, float[], int, int[], str, str[]. + + Functions are called by typing the function name and then the function arguments, separated + by a space. + + fn example arg0:int arg1:int + echo $arg + end + + example 1 +"#;*/ + +pub(crate) const MAN_READ: &'static 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(crate) const MAN_DROP: &'static 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(crate) const MAN_SET: &'static 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(crate) const MAN_EVAL: &'static 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(crate) const MAN_HISTORY: &'static str = r#"NAME + history - print command history + +SYNOPSIS + history + +DESCRIPTION + Prints the command history. +"#; + +pub(crate) const MAN_SOURCE: &'static 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(crate) const MAN_ECHO: &'static 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(crate) const MAN_TEST: &'static str = r#"NAME + test - perform tests on files and text + +SYNOPSIS + test [EXPRESSION] + +DESCRIPTION + Tests the expressions given and returns an exit status of 0 if true, else 1. + +OPTIONS + -n STRING + the length of STRING is nonzero + + STRING + equivalent to -n STRING + + -z STRING + the length of STRING is zero + + STRING = STRING + the strings are equivalent + + STRING != STRING + the strings are not equal + + INTEGER -eq INTEGER + the integers are equal + + INTEGER -ge INTEGER + the first INTEGER is greater than or equal to the first INTEGER + + INTEGER -gt INTEGER + the first INTEGER is greater than the first INTEGER + + INTEGER -le INTEGER + the first INTEGER is less than or equal to the first INTEGER + + INTEGER -lt INTEGER + the first INTEGER is less than the first INTEGER + + INTEGER -ne INTEGER + the first INTEGER is not equal to the first INTEGER + + FILE -ef FILE + both files have the same device and inode numbers + + FILE -nt FILE + the first FILE is newer than the second FILE + + FILE -ot FILE + the first file is older than the second FILE + + -b FILE + FILE exists and is a block device + + -c FILE + FILE exists and is a character device + + -d FILE + FILE exists and is a directory + + -e FILE + FILE exists + + -f FILE + FILE exists and is a regular file + + -h FILE + FILE exists and is a symbolic link (same as -L) + + -L FILE + FILE exists and is a symbolic link (same as -h) + + -r FILE + FILE exists and read permission is granted + + -s FILE + FILE exists and has a file size greater than zero + + -S FILE + FILE exists and is a socket + + -w FILE + FILE exists and write permission is granted + + -x FILE + FILE exists and execute (or search) permission is granted + +EXAMPLES + Test if the file exists: + test -e FILE && echo "The FILE exists" || echo "The FILE does not exist" + + Test if the file exists and is a regular file, and if so, write to it: + test -f FILE && echo "Hello, FILE" >> FILE || echo "Cannot write to a directory" + + Test if 10 is greater than 5: + test 10 -gt 5 && echo "10 is greater than 5" || echo "10 is not greater than 5" + + Test if the user is running a 64-bit OS (POSIX environment only): + test $(getconf LONG_BIT) = 64 && echo "64-bit OS" || echo "32-bit OS" + +AUTHOR + Written by Michael Murphy. +"#; + +pub(crate) const MAN_RANDOM: &'static str = r#"NAME + random - generate a random number + +SYNOPSIS + random + random START END + +DESCRIPTION + random generates a pseudo-random integer. IT IS NOT SECURE. + 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(crate) const MAN_TRUE: &'static str = r#"NAME + true - does nothing successfully + +SYNOPSIS + true + +DESCRIPTION + Sets the exit status to 0. +"#; + +pub(crate) const MAN_FALSE: &'static str = r#"NAME + false - does nothing unsuccessfully + +SYNOPSIS + false + +DESCRIPTION + Sets the exit status to 1. +"#; + +pub(crate) const MAN_JOBS: &'static 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(crate) const MAN_BG: &'static 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(crate) const MAN_FG: &'static str = r#"NAME + fg - bring job to foreground + +SYNOPSIS + fg PID + +DESCRIPTION + fg brings the specified job to foreground resuming it if it has stopped. +"#; + +pub(crate) const MAN_SUSPEND: &'static str = r#"NAME + suspend - suspend the current shell + +SYNOPSIS + suspend + +DESCRIPTION + Suspends the current shell by sending it the SIGTSTP signal, + returning to the parent process. It can be resumed by sending it SIGCONT. +"#; + +pub(crate) const MAN_DISOWN: &'static str = r#"NAME + disown - Disown processes + +SYNOPSIS + disown [ --help | -r | -h | -a ][PID...] + +DESCRIPTION + Disowning a process removes that process from the shell's background process table. + +OPTIONS + -r Remove all running jobs from the background process list. + -h Specifies that each job supplied will not receive the SIGHUP signal when the shell receives a SIGHUP. + -a If no job IDs were supplied, remove all jobs from the background process list. +"#; + +pub(crate) const MAN_EXIT: &'static str = r#"NAME + exit - exit the shell + +SYNOPSIS + exit + +DESCRIPTION + Makes ion exit. The exit status will be that of the last command executed. +"#; + +pub(crate) const MAN_MATCHES: &'static str = r#"NAME + matches - checks if the second argument contains any portion of the first. + +SYNOPSIS + matches VALUE VALUE + +DESCRIPTION + Makes the exit status equal 0 if the first argument contains the second. + Otherwise matches makes the exit status equal 1. + +EXAMPLES + Returns true: + matches xs x + Returns false: + matches x xs +"#; + +pub(crate) const MAN_NOT: &'static str = r#"NAME + not - reverses the exit status of a job + +SYNOPSIS + not + +DESCRIPTION + not reverses the exit status of a job. If the exit status is 1 not returns 0 and vice versa. +"#; + +pub(crate) const MAN_AND: &'static str = r#"NAME + and - check if to execute another command and a boolean gate. + +SYNOPSIS + COMMAND and COMMAND + COMMAND; and COMMAND + +DESCRIPTION + and changes the exit status to 0 if the previous command and the next command are true. + and can also be used to execute multiple commands if they all are successful. '&&' is preferred to 'and' + in if, while and all other similar conditional statements. + +EXAMPLES + Returns an exit of status 0: + true and true + Returns an exit of status 1: + true and false + + Executes all the commands: + echo "1"; and echo "2" + Executes no commands: + false; and echo "1" +"#; + +pub(crate) const MAN_OR: &'static str = r#"NAME + or - conditionally run a command + +SYNOPSIS + COMMAND; or COMMAND + +DESCRIPTION + or can be used to execute a command if the exit status of the previous command is not 0. + or can also be used in if, while and other similar statements, however '||' is preferred. + +EXAMPLE + Executes all of the code block, prints 2: + false; or echo "2" + Does not execute all of the code block, prints 1: + echo "1"; or echo "2" +"#; + +pub(crate) const MAN_EXISTS: &'static str = r#"NAME + exists - check whether items exist + +SYNOPSIS + exists [EXPRESSION] + +DESCRIPTION + Checks whether the given item exists and returns an exit status of 0 if it does, else 1. + +OPTIONS + -a ARRAY + array var is not empty + + -b BINARY + binary is in PATH + + -d PATH + path is a directory + This is the same as test -d + + -f PATH + path is a file + This is the same as test -f + + --fn FUNCTION + function is defined + + -s STRING + string var is not empty + + STRING + string is not empty + This is the same as test -n + +EXAMPLES + Test if the file exists: + exists -f FILE && echo "The FILE exists" || echo "The FILE does not exist" + + Test if some-command exists in the path and is executable: + exists -b some-command && echo "some-command exists" || echo "some-command does not exist" + + Test if variable exists AND is not empty + exists -s myVar && echo "myVar exists: $myVar" || echo "myVar does not exist or is empty" + NOTE: Don't use the '$' sigil, but only the name of the variable to check + + Test if array exists and is not empty + exists -a myArr && echo "myArr exists: @myArr" || echo "myArr does not exist or is empty" + NOTE: Don't use the '@' sigil, but only the name of the array to check + + Test if a function named 'myFunc' exists + exists --fn myFunc && myFunc || echo "No function with name myFunc found" + +AUTHOR + Written by Fabian Würfl. + Heavily based on implementation of the test builtin, which was written by Michael Murph. +"#; + +pub(crate) const MAN_WHICH: &'static str = r#"NAME + which - locate a program file in the current user's path + +SYNOPSIS + which PROGRAM + +DESCRIPTION + The which utility takes a command name and searches the path for the executable file that would + be run had the command been executed. +"#; \ No newline at end of file diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 32e74ae5..72ecc24c 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -6,6 +6,7 @@ pub mod random; mod conditionals; mod job_control; +mod man_pages; mod test; mod echo; mod set; @@ -20,6 +21,7 @@ use self::exists::exists; use self::functions::fn_; use self::ion::ion_docs; use self::is::is; +use self::man_pages::*; use self::source::source; use self::status::status; use self::test::test; @@ -58,6 +60,12 @@ macro_rules! map { } }} +/// 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_CD) { +/// return SUCCESS +/// } + /// Builtins are in A-Z order. pub const BUILTINS: &'static BuiltinMap = &map!( "alias" => builtin_alias : "View, set or unset aliases", @@ -147,6 +155,10 @@ fn builtin_status(args: &[&str], shell: &mut Shell) -> i32 { } pub fn builtin_cd(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_CD) { + return SUCCESS + } + match shell.directory_stack.cd(args, &shell.variables) { Ok(()) => SUCCESS, Err(why) => { @@ -172,16 +184,14 @@ fn builtin_bool(args: &[&str], shell: &mut Shell) -> i32 { None => "", }; - let help_msg = "DESCRIPTION: If the value is '1' or 'true', bool returns the 0 exit \ - status\nusage: bool <value>"; match sh_var { "1" => (), "true" => (), _ => match args[1] { "1" => (), "true" => (), - "--help" => println!("{}", help_msg), - "-h" => println!("{}", help_msg), + "--help" => print_man(MAN_BOOL), + "-h" => print_man(MAN_BOOL), _ => return FAILURE, }, } @@ -200,9 +210,18 @@ fn builtin_is(args: &[&str], shell: &mut Shell) -> i32 { } } -fn builtin_dirs(args: &[&str], shell: &mut Shell) -> i32 { shell.directory_stack.dirs(args) } +fn builtin_dirs(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_DIRS) { + return SUCCESS + } + + shell.directory_stack.dirs(args) +} fn builtin_pushd(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_PUSHD) { + return SUCCESS + } match shell.directory_stack.pushd(args, &shell.variables) { Ok(()) => SUCCESS, Err(why) => { @@ -215,6 +234,10 @@ fn builtin_pushd(args: &[&str], shell: &mut Shell) -> i32 { } fn builtin_popd(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_POPD) { + return SUCCESS + } + match shell.directory_stack.popd(args) { Ok(()) => SUCCESS, Err(why) => { @@ -235,11 +258,22 @@ fn builtin_unalias(args: &[&str], shell: &mut Shell) -> i32 { drop_alias(&mut shell.variables, args) } -fn builtin_fn(_: &[&str], shell: &mut Shell) -> i32 { fn_(&mut shell.functions) } +// TODO There is a man page for fn however the -h and --help flags are not checked for. +fn builtin_fn(_: &[&str], shell: &mut Shell) -> i32 { + fn_(&mut shell.functions) +} -fn builtin_read(args: &[&str], shell: &mut Shell) -> i32 { shell.variables.read(args) } +fn builtin_read(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_READ) { + return SUCCESS + } + shell.variables.read(args) +} fn builtin_drop(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_DROP) { + return SUCCESS + } if args.len() >= 2 && args[1] == "-a" { drop_array(&mut shell.variables, args) } else { @@ -247,9 +281,17 @@ fn builtin_drop(args: &[&str], shell: &mut Shell) -> i32 { } } -fn builtin_set(args: &[&str], shell: &mut Shell) -> i32 { set::set(args, shell) } +fn builtin_set(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_SET) { + return SUCCESS + } + set::set(args, shell) +} fn builtin_eval(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_EVAL) { + return SUCCESS + } let evaluated_command = args[1..].join(" "); let mut buffer = Terminator::new(evaluated_command); if buffer.is_terminated() { @@ -263,9 +305,17 @@ fn builtin_eval(args: &[&str], shell: &mut Shell) -> i32 { } } -fn builtin_history(args: &[&str], shell: &mut Shell) -> i32 { shell.print_history(args) } +fn builtin_history(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_HISTORY) { + return SUCCESS + } + shell.print_history(args) +} fn builtin_source(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_SOURCE) { + return SUCCESS + } match source(shell, args) { Ok(()) => SUCCESS, Err(why) => { @@ -278,6 +328,9 @@ fn builtin_source(args: &[&str], shell: &mut Shell) -> i32 { } fn builtin_echo(args: &[&str], _: &mut Shell) -> i32 { + if check_help(args, MAN_ECHO) { + return SUCCESS + } match echo(args) { Ok(()) => SUCCESS, Err(why) => { @@ -290,6 +343,9 @@ fn builtin_echo(args: &[&str], _: &mut Shell) -> i32 { } fn builtin_test(args: &[&str], _: &mut Shell) -> i32 { + if check_help(args, MAN_TEST) { + return SUCCESS + } match test(args) { Ok(true) => SUCCESS, Ok(false) => FAILURE, @@ -302,6 +358,7 @@ fn builtin_test(args: &[&str], _: &mut Shell) -> i32 { } } +// TODO create manpage. fn builtin_calc(args: &[&str], _: &mut Shell) -> i32 { match calc::calc(&args[1..]) { Ok(()) => SUCCESS, @@ -315,6 +372,9 @@ fn builtin_calc(args: &[&str], _: &mut Shell) -> i32 { } fn builtin_random(args: &[&str], _: &mut Shell) -> i32 { + if check_help(args, MAN_RANDOM) { + return SUCCESS + } match random::random(&args[1..]) { Ok(()) => SUCCESS, Err(why) => { @@ -326,30 +386,59 @@ fn builtin_random(args: &[&str], _: &mut Shell) -> i32 { } } -fn builtin_true(_: &[&str], _: &mut Shell) -> i32 { SUCCESS } +fn builtin_true(args: &[&str], _: &mut Shell) -> i32 { + check_help(args, MAN_TRUE); + SUCCESS +} -fn builtin_false(_: &[&str], _: &mut Shell) -> i32 { FAILURE } +fn builtin_false(args: &[&str], _: &mut Shell) -> i32 { + if check_help(args, MAN_FALSE) { + return SUCCESS + } + FAILURE +} +// TODO create a manpage fn builtin_wait(_: &[&str], shell: &mut Shell) -> i32 { shell.wait_for_background(); SUCCESS } -fn builtin_jobs(_: &[&str], shell: &mut Shell) -> i32 { +fn builtin_jobs(args: &[&str], shell: &mut Shell) -> i32 { + check_help(args, MAN_JOBS); job_control::jobs(shell); SUCCESS } -fn builtin_bg(args: &[&str], shell: &mut Shell) -> i32 { job_control::bg(shell, &args[1..]) } +fn builtin_bg(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_BG) { + return SUCCESS + } + job_control::bg(shell, &args[1..]) +} -fn builtin_fg(args: &[&str], shell: &mut Shell) -> i32 { job_control::fg(shell, &args[1..]) } +fn builtin_fg(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_FG) { + return SUCCESS + } + job_control::fg(shell, &args[1..]) +} -fn builtin_suspend(_: &[&str], _: &mut Shell) -> i32 { +fn builtin_suspend(args: &[&str], _: &mut Shell) -> i32 { + if check_help(args, MAN_SUSPEND) { + return SUCCESS + } shell::signals::suspend(0); SUCCESS } fn builtin_disown(args: &[&str], shell: &mut Shell) -> i32 { + for arg in args { + if *arg == "--help" { + print_man(MAN_DISOWN); + return SUCCESS + } + } match job_control::disown(shell, &args[1..]) { Ok(()) => SUCCESS, Err(err) => { @@ -388,6 +477,9 @@ fn builtin_help(args: &[&str], shell: &mut Shell) -> i32 { } fn builtin_exit(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_EXIT) { + return SUCCESS + } // Kill all active background tasks before exiting the shell. for process in shell.background.lock().unwrap().iter() { if process.state != ProcessState::Empty { @@ -400,6 +492,9 @@ fn builtin_exit(args: &[&str], shell: &mut Shell) -> i32 { use regex::Regex; fn builtin_matches(args: &[&str], _: &mut Shell) -> i32 { + if check_help(args, MAN_MATCHES) { + return SUCCESS + } if args[1..].len() != 2 { let stderr = io::stderr(); let mut stderr = stderr.lock(); @@ -432,6 +527,9 @@ fn args_to_pipeline(args: &[&str]) -> Pipeline { } fn builtin_not(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_NOT) { + return SUCCESS + } shell.run_pipeline(&mut args_to_pipeline(&args[1..])); match shell.previous_status { SUCCESS => FAILURE, @@ -441,6 +539,9 @@ fn builtin_not(args: &[&str], shell: &mut Shell) -> i32 { } fn builtin_and(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_AND) { + return SUCCESS + } match shell.previous_status { SUCCESS => { shell.run_pipeline(&mut args_to_pipeline(&args[1..])); @@ -451,6 +552,9 @@ fn builtin_and(args: &[&str], shell: &mut Shell) -> i32 { } fn builtin_or(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_OR) { + return SUCCESS + } match shell.previous_status { FAILURE => { shell.run_pipeline(&mut args_to_pipeline(&args[1..])); @@ -461,6 +565,9 @@ fn builtin_or(args: &[&str], shell: &mut Shell) -> i32 { } fn builtin_exists(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_EXISTS) { + return SUCCESS + } match exists(args, shell) { Ok(true) => SUCCESS, Ok(false) => FAILURE, @@ -474,6 +581,10 @@ fn builtin_exists(args: &[&str], shell: &mut Shell) -> i32 { } fn builtin_which(args: &[&str], shell: &mut Shell) -> i32 { + if check_help(args, MAN_WHICH) { + return SUCCESS + } + if args[1..].len() != 1 { let stderr = io::stderr(); let mut stderr = stderr.lock(); diff --git a/src/builtins/set.rs b/src/builtins/set.rs index 607b72f2..815ae5cb 100644 --- a/src/builtins/set.rs +++ b/src/builtins/set.rs @@ -4,30 +4,6 @@ use shell::flags::*; use std::io::{self, Write}; use std::iter; -const HELP: &'static 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. -"#; - enum PositionalArgs { UnsetIfNone, RetainIfNone, @@ -36,7 +12,6 @@ enum PositionalArgs { use self::PositionalArgs::*; pub(crate) fn set(args: &[&str], shell: &mut Shell) -> i32 { - let stdout = io::stdout(); let stderr = io::stderr(); let mut args_iter = args.iter(); let mut positionals = None; @@ -47,12 +22,8 @@ pub(crate) fn set(args: &[&str], shell: &mut Shell) -> i32 { positionals = Some(UnsetIfNone); break; } - if &arg[2..] == "help" { - let mut stdout = stdout.lock(); - let _ = stdout.write(HELP.as_bytes()); - } else { - return 0; - } + return 0; + } else if arg.starts_with('-') { if arg.len() == 1 { positionals = Some(RetainIfNone); diff --git a/src/builtins/status.rs b/src/builtins/status.rs index 62da25ad..55d0ac87 100644 --- a/src/builtins/status.rs +++ b/src/builtins/status.rs @@ -1,9 +1,8 @@ -use std::env; -use std::error::Error; -use std::io::{stdout, Write}; - +use builtins::man_pages::{print_man, MAN_STATUS}; use shell::Shell; +use std::env; + bitflags! { struct Flags : u8 { const HELP = 1; @@ -13,24 +12,6 @@ bitflags! { } } -const MAN_PAGE: &'static str = r#"NAME - status - Evaluates the current runtime status - -SYNOPSIS - status [ -h | --help ] [-l] [-i] - -DESCRIPTION - With no arguments status displays the current login information of the shell. - -OPTIONS - -l - returns true if shell is a login shell. Also --is-login. - -i - returns true if shell is interactive. Also --is-interactive. - -f - prints the filename of the currently running script or stdio. Also --current-filename. -"#; // @MANEND - pub(crate) fn status(args: &[&str], shell: &mut Shell) -> Result<(), String> { let mut flags = Flags::empty(); let shell_args: Vec<_> = env::args().collect(); @@ -80,7 +61,7 @@ pub(crate) fn status(args: &[&str], shell: &mut Shell) -> Result<(), String> { } if flags.contains(Flags::FILENAME) { - // TODO: This technique will not work if ion is renamed. + // TODO: This will not work if ion is renamed. let sa_len = shell_args.len() - 1; let last_sa = &shell_args[sa_len]; let last_3: String = last_sa[last_sa.len() - 3..last_sa.len()].to_string(); @@ -92,14 +73,8 @@ pub(crate) fn status(args: &[&str], shell: &mut Shell) -> Result<(), String> { } } - let stdout = stdout(); - let mut stdout = stdout.lock(); - if flags.contains(Flags::HELP) { - return match stdout.write_all(MAN_PAGE.as_bytes()).and_then(|_| stdout.flush()) { - Ok(_) => Ok(()), - Err(err) => Err(err.description().to_owned()), - }; + print_man(MAN_STATUS); } } Ok(()) diff --git a/src/builtins/test.rs b/src/builtins/test.rs index 7346a6ce..36ceff26 100644 --- a/src/builtins/test.rs +++ b/src/builtins/test.rs @@ -1,131 +1,16 @@ use smallstring::SmallString; -use std::error::Error; use std::fs; -use std::io::{self, BufWriter}; use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt}; use std::path::Path; use std::time::SystemTime; -const MAN_PAGE: &'static str = r#"NAME - test - perform tests on files and text - -SYNOPSIS - test [EXPRESSION] - -DESCRIPTION - Tests the expressions given and returns an exit status of 0 if true, else 1. - -OPTIONS - -n STRING - the length of STRING is nonzero - - STRING - equivalent to -n STRING - - -z STRING - the length of STRING is zero - - STRING = STRING - the strings are equivalent - - STRING != STRING - the strings are not equal - - INTEGER -eq INTEGER - the integers are equal - - INTEGER -ge INTEGER - the first INTEGER is greater than or equal to the first INTEGER - - INTEGER -gt INTEGER - the first INTEGER is greater than the first INTEGER - - INTEGER -le INTEGER - the first INTEGER is less than or equal to the first INTEGER - - INTEGER -lt INTEGER - the first INTEGER is less than the first INTEGER - - INTEGER -ne INTEGER - the first INTEGER is not equal to the first INTEGER - - FILE -ef FILE - both files have the same device and inode numbers - - FILE -nt FILE - the first FILE is newer than the second FILE - - FILE -ot FILE - the first file is older than the second FILE - - -b FILE - FILE exists and is a block device - - -c FILE - FILE exists and is a character device - - -d FILE - FILE exists and is a directory - - -e FILE - FILE exists - - -f FILE - FILE exists and is a regular file - - -h FILE - FILE exists and is a symbolic link (same as -L) - - -L FILE - FILE exists and is a symbolic link (same as -h) - - -r FILE - FILE exists and read permission is granted - - -s FILE - FILE exists and has a file size greater than zero - - -S FILE - FILE exists and is a socket - - -w FILE - FILE exists and write permission is granted - - -x FILE - FILE exists and execute (or search) permission is granted - -EXAMPLES - Test if the file exists: - test -e FILE && echo "The FILE exists" || echo "The FILE does not exist" - - Test if the file exists and is a regular file, and if so, write to it: - test -f FILE && echo "Hello, FILE" >> FILE || echo "Cannot write to a directory" - - Test if 10 is greater than 5: - test 10 -gt 5 && echo "10 is greater than 5" || echo "10 is not greater than 5" - - Test if the user is running a 64-bit OS (POSIX environment only): - test $(getconf LONG_BIT) = 64 && echo "64-bit OS" || echo "32-bit OS" - -AUTHOR - Written by Michael Murphy. -"#; // @MANEND - pub(crate) fn test(args: &[&str]) -> Result<bool, String> { - let stdout = io::stdout(); - let mut buffer = BufWriter::new(stdout.lock()); - let arguments = &args[1..]; - evaluate_arguments(arguments, &mut buffer) + evaluate_arguments(arguments) } -fn evaluate_arguments<W: io::Write>(arguments: &[&str], buffer: &mut W) -> Result<bool, String> { +fn evaluate_arguments(arguments: &[&str]) -> Result<bool, String> { match arguments.first() { - Some(&"--help") => { - buffer.write_all(MAN_PAGE.as_bytes()).map_err(|x| x.description().to_owned())?; - buffer.flush().map_err(|x| x.description().to_owned())?; - Ok(true) - } Some(&s) if s.starts_with("-") && s[1..].starts_with(char::is_alphabetic) => { // Access the second character in the flag string: this will be type of the flag. // If no flag was given, return `SUCCESS` @@ -354,8 +239,7 @@ fn test_strings() { #[test] fn test_empty_str() { - let mut empty = BufWriter::new(io::sink()); - let mut eval = |args: Vec<&str>| evaluate_arguments(&args, &mut empty); + let eval = |args: Vec<&str>| evaluate_arguments(&args); assert_eq!(eval(vec![""]), Ok(false)); assert_eq!(eval(vec!["c", "=", ""]), Ok(false)); } @@ -363,48 +247,45 @@ fn test_empty_str() { #[test] fn test_integers_arguments() { - let stdout = io::stdout(); - let mut buffer = BufWriter::new(stdout.lock()); - // Equal To - assert_eq!(evaluate_arguments(&["10", "-eq", "10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["10", "-eq", "5"], &mut buffer), Ok(false)); - assert_eq!(evaluate_arguments(&["-10", "-eq", "-10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["-10", "-eq", "10"], &mut buffer), Ok(false)); + assert_eq!(evaluate_arguments(&["10", "-eq", "10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["10", "-eq", "5"]), Ok(false)); + assert_eq!(evaluate_arguments(&["-10", "-eq", "-10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["-10", "-eq", "10"]), Ok(false)); // Greater Than or Equal To - assert_eq!(evaluate_arguments(&["10", "-ge", "10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["10", "-ge", "5"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["5", "-ge", "10"], &mut buffer), Ok(false)); - assert_eq!(evaluate_arguments(&["-9", "-ge", "-10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["-10", "-ge", "-10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["-10", "-ge", "10"], &mut buffer), Ok(false)); + assert_eq!(evaluate_arguments(&["10", "-ge", "10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["10", "-ge", "5"]), Ok(true)); + assert_eq!(evaluate_arguments(&["5", "-ge", "10"]), Ok(false)); + assert_eq!(evaluate_arguments(&["-9", "-ge", "-10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["-10", "-ge", "-10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["-10", "-ge", "10"]), Ok(false)); // Less Than or Equal To - assert_eq!(evaluate_arguments(&["5", "-le", "5"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["5", "-le", "10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["10", "-le", "5"], &mut buffer), Ok(false)); - assert_eq!(evaluate_arguments(&["-11", "-le", "-10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["-10", "-le", "-10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["10", "-le", "-10"], &mut buffer), Ok(false)); + assert_eq!(evaluate_arguments(&["5", "-le", "5"]), Ok(true)); + assert_eq!(evaluate_arguments(&["5", "-le", "10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["10", "-le", "5"]), Ok(false)); + assert_eq!(evaluate_arguments(&["-11", "-le", "-10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["-10", "-le", "-10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["10", "-le", "-10"]), Ok(false)); // Less Than - assert_eq!(evaluate_arguments(&["5", "-lt", "10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["10", "-lt", "5"], &mut buffer), Ok(false)); - assert_eq!(evaluate_arguments(&["-11", "-lt", "-10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["10", "-lt", "-10"], &mut buffer), Ok(false)); + assert_eq!(evaluate_arguments(&["5", "-lt", "10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["10", "-lt", "5"]), Ok(false)); + assert_eq!(evaluate_arguments(&["-11", "-lt", "-10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["10", "-lt", "-10"]), Ok(false)); // Greater Than - assert_eq!(evaluate_arguments(&["10", "-gt", "5"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["5", "-gt", "10"], &mut buffer), Ok(false)); - assert_eq!(evaluate_arguments(&["-9", "-gt", "-10"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["-10", "-gt", "10"], &mut buffer), Ok(false)); + assert_eq!(evaluate_arguments(&["10", "-gt", "5"]), Ok(true)); + assert_eq!(evaluate_arguments(&["5", "-gt", "10"]), Ok(false)); + assert_eq!(evaluate_arguments(&["-9", "-gt", "-10"]), Ok(true)); + assert_eq!(evaluate_arguments(&["-10", "-gt", "10"]), Ok(false)); // Not Equal To - assert_eq!(evaluate_arguments(&["10", "-ne", "5"], &mut buffer), Ok(true)); - assert_eq!(evaluate_arguments(&["5", "-ne", "5"], &mut buffer), Ok(false)); - assert_eq!(evaluate_arguments(&["-10", "-ne", "-10"], &mut buffer), Ok(false)); - assert_eq!(evaluate_arguments(&["-10", "-ne", "10"], &mut buffer), Ok(true)); + assert_eq!(evaluate_arguments(&["10", "-ne", "5"]), Ok(true)); + assert_eq!(evaluate_arguments(&["5", "-ne", "5"]), Ok(false)); + assert_eq!(evaluate_arguments(&["-10", "-ne", "-10"]), Ok(false)); + assert_eq!(evaluate_arguments(&["-10", "-ne", "10"]), Ok(true)); } #[test] @@ -441,4 +322,4 @@ fn test_file_has_execute_permission() { fn test_file_size_is_greater_than_zero() { assert_eq!(file_size_is_greater_than_zero("testing/file_with_text"), true); assert_eq!(file_size_is_greater_than_zero("testing/empty_file"), false); -} +} \ No newline at end of file -- GitLab