Commit d096b4d1 authored by Sag0Sag0's avatar Sag0Sag0 Committed by Michael Aaron Murphy
Browse files

Add manpages for almost all builtins. (#628)

* Start adding manpages

* Add manpages for all builtins

* Fix so that changes pass tests
parent b3f52e56
......@@ -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)
......
......@@ -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 {
......
......@@ -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]
......
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()),
......
......@@ -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());
}
......
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.
"#;