From 1aaa3cd50a7d415db6ae61ece0371e06b3b3af8c Mon Sep 17 00:00:00 2001 From: Xavier L'Heureux <xavier.lheureux@icloud.com> Date: Tue, 25 Jun 2019 08:42:34 -0400 Subject: [PATCH] Move the suspend builtin to the binary and complete moving man_pages --- members/builtins-proc/src/lib.rs | 12 +- src/binary/builtins.rs | 59 +++++---- src/binary/mod.rs | 11 +- src/lib/builtins/command_info.rs | 30 +++-- src/lib/builtins/is.rs | 1 + src/lib/builtins/man_pages.rs | 149 ---------------------- src/lib/builtins/mod.rs | 211 ++++++++++++++++++++----------- src/lib/lib.rs | 1 + src/main.rs | 7 +- tests/exists.out | 4 +- 10 files changed, 213 insertions(+), 272 deletions(-) diff --git a/members/builtins-proc/src/lib.rs b/members/builtins-proc/src/lib.rs index e50cd9fe..1a453b39 100644 --- a/members/builtins-proc/src/lib.rs +++ b/members/builtins-proc/src/lib.rs @@ -12,6 +12,7 @@ pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream { let syn::FnDecl { ref fn_token, ref inputs, ref output, .. } = **decl; let mut help = None; let mut short_description = None; + let mut names = None; let name = syn::Ident::new(&format!("builtin_{}", &ident), input.ident.span()); @@ -29,6 +30,12 @@ pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream { } else { panic!("`desc` attribute should be a string variable"); } + } else if attr.ident == "names" { + if let syn::Lit::Str(h) = &attr.lit { + names = Some(h.value()); + } else { + panic!("`desc` attribute should be a string variable"); + } } else { panic!("Only the `man` and `desc` attributes are allowed"); } @@ -40,8 +47,9 @@ pub fn builtin(attr: TokenStream, item: TokenStream) -> TokenStream { let help = help.trim(); let short_description = short_description .expect("A short description is required! Please add an attribute with name `desc`"); - let man = format!("NAME\n {} - {}\n\n{}", ident, short_description, help); - let help = format!("{}\n\n```txt\n{}\n```", short_description, help); + let names = names.unwrap_or_else(|| ident.to_string()); + let man = format!("NAME\n {} - {}\n\n{}", names, short_description, help); + let help = format!("{} - {}\n\n```txt\n{}\n```", names, short_description, help); let result = quote! { #[doc = #help] diff --git a/src/binary/builtins.rs b/src/binary/builtins.rs index 854d3e06..14f0c2aa 100644 --- a/src/binary/builtins.rs +++ b/src/binary/builtins.rs @@ -1,40 +1,32 @@ -use ion_shell::{ - builtins::{man_pages::check_help, Status}, - types::Str, - Shell, -}; +use ion_shell::{builtin, builtins::Status, types::Str, Shell}; use libc::SIGTERM; use std::{error::Error, os::unix::process::CommandExt, process::Command}; -const MAN_EXEC: &str = r#"NAME - exec - Replace the shell with the given command. - +#[builtin( + desc = "suspend the current shell", + man = " SYNOPSIS - exec [-ch] [--help] [command [arguments ...]] + suspend DESCRIPTION - Execute <command>, replacing the shell with the specified program. - The <arguments> following the command become the arguments to - <command>. - -OPTIONS - -c Execute command with an empty environment."#; - -pub const MAN_EXIT: &str = r#"NAME - exit - exit the shell + Suspends the current shell by sending it the SIGTSTP signal, + returning to the parent process. It can be resumed by sending it SIGCONT." +)] +pub fn suspend(args: &[Str], _shell: &mut Shell<'_>) -> Status { + let _ = unsafe { libc::kill(0, libc::SIGSTOP) }; + Status::SUCCESS +} +#[builtin( + desc = "exit the shell", + man = " SYNOPSIS exit DESCRIPTION - Makes ion exit. The exit status will be that of the last command executed."#; - -/// Executes the givent commmand. - + Makes ion exit. The exit status will be that of the last command executed." +)] pub fn exit(args: &[Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_EXIT) { - return Status::SUCCESS; - } // Kill all active background tasks before exiting the shell. shell.background_send(SIGTERM); let exit_code = args @@ -44,10 +36,21 @@ pub fn exit(args: &[Str], shell: &mut Shell<'_>) -> Status { std::process::exit(exit_code); } +#[builtin( + desc = "replace the shell with the given command", + man = " +SYNOPSIS + exec [-ch] [--help] [command [arguments ...]] + +DESCRIPTION + Execute <command>, replacing the shell with the specified program. + The <arguments> following the command become the arguments to + <command>. + +OPTIONS + -c Execute command with an empty environment." +)] pub fn exec(args: &[Str], _shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_EXEC) { - return Status::SUCCESS; - } let mut clear_env = false; let mut idx = 1; for arg in args.iter().skip(1) { diff --git a/src/binary/mod.rs b/src/binary/mod.rs index 065fd606..651c2c02 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -169,11 +169,12 @@ impl<'a> InteractiveBinary<'a> { // change the lifetime to allow adding local builtins let InteractiveBinary { context, shell } = self; let mut shell = shell.into_inner(); - let builtins = shell.builtins_mut(); - builtins.add("history", history, "Display a log of all commands previously executed"); - builtins.add("keybindings", keybindings, "Change the keybindings"); - builtins.add("exit", exit, "Exits the current session"); - builtins.add("exec", exec, "Replace the shell with the given command."); + shell + .builtins_mut() + .add("history", history, "Display a log of all commands previously executed") + .add("keybindings", keybindings, "Change the keybindings") + .add("exit", exit, "Exits the current session") + .add("exec", exec, "Replace the shell with the given command."); Self::exec_init_file(&mut shell); diff --git a/src/lib/builtins/command_info.rs b/src/lib/builtins/command_info.rs index cf6e800e..5b09f02c 100644 --- a/src/lib/builtins/command_info.rs +++ b/src/lib/builtins/command_info.rs @@ -1,19 +1,26 @@ -use super::{check_help, man_pages::MAN_WHICH, Status}; +use super::Status; +use crate as ion_shell; use crate::{ shell::{Shell, Value}, types, }; +use builtins_proc::builtin; use std::{borrow::Cow, env}; -pub fn which(args: &[types::Str], shell: &mut Shell<'_>) -> Result<Status, ()> { - if check_help(args, MAN_WHICH) { - return Ok(Status::SUCCESS); - } +#[builtin( + desc = "locate a program file in the current user's path", + man = " +SYNOPSIS + which PROGRAM +DESCRIPTION + The which utility takes a list of command names and searches for the + alias/builtin/function/executable that would be executed if you ran that command." +)] +pub fn which(args: &[types::Str], shell: &mut Shell<'_>) -> Status { if args.len() == 1 { - eprintln!("which: Expected at least 1 args, got only 0"); - return Err(()); + return Status::bad_argument("which: Expected at least 1 args, got only 0"); } let mut result = Status::SUCCESS; @@ -32,14 +39,13 @@ pub fn which(args: &[types::Str], shell: &mut Shell<'_>) -> Result<Status, ()> { Err(_) => result = Status::from_exit_code(1), } } - Ok(result) + result } -pub fn find_type(args: &[types::Str], shell: &mut Shell<'_>) -> Result<Status, ()> { +pub fn builtin_type(args: &[types::Str], shell: &mut Shell<'_>) -> 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(()); + return Status::bad_argument("type: Expected at least 1 args, got only 0"); } let mut result = Status::SUCCESS; @@ -61,7 +67,7 @@ pub fn find_type(args: &[types::Str], shell: &mut Shell<'_>) -> Result<Status, ( Err(_) => result = Status::error(format!("type: {}: not found", command)), } } - Ok(result) + result } fn get_command_info<'a>(command: &str, shell: &mut Shell<'_>) -> Result<Cow<'a, str>, ()> { diff --git a/src/lib/builtins/is.rs b/src/lib/builtins/is.rs index 0abd3b10..ae30fbcc 100644 --- a/src/lib/builtins/is.rs +++ b/src/lib/builtins/is.rs @@ -5,6 +5,7 @@ use builtins_proc::builtin; // TODO: Add support for multiple name in builtins man #[builtin( + names = "eq, is", desc = "checks if two arguments are the same", man = " SYNOPSIS diff --git a/src/lib/builtins/man_pages.rs b/src/lib/builtins/man_pages.rs index cc38ebf1..f0ed8ff9 100644 --- a/src/lib/builtins/man_pages.rs +++ b/src/lib/builtins/man_pages.rs @@ -10,28 +10,6 @@ pub fn check_help(args: &[types::Str], man_page: &'static str) -> bool { false } -pub const MAN_IS: &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 const MAN_ISATTY: &str = r#" - isatty - Checks if argument is a file descriptor - -SYNOPSIS - isatty [FD] - -DESCRIPTION - Returns 0 exit status if the supplied file descriptor is a tty."#; - // pub const MAN_FN: &str = r#"NAME // fn - print a list of all functions or create a function // @@ -59,130 +37,3 @@ DESCRIPTION // // example 1 //"#; - -pub const MAN_RANDOM: &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 const MAN_FG: &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 const MAN_SUSPEND: &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 const MAN_DISOWN: &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 const MAN_MATCHES: &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 const MAN_EXISTS: &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 const MAN_WHICH: &str = r#"NAME - which - locate a program file in the current user's path - -SYNOPSIS - which PROGRAM - -DESCRIPTION - The which utility takes a list of command names and searches for the - alias/builtin/function/executable that would be executed if you ran that command."#; diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs index e1b9712e..0e1c8280 100644 --- a/src/lib/builtins/mod.rs +++ b/src/lib/builtins/mod.rs @@ -16,21 +16,21 @@ mod status; mod test; mod variables; -use self::{ - command_info::{find_type, which}, +pub use self::{ + command_info::{builtin_type, builtin_which}, conditionals::{contains, ends_with, starts_with}, echo::builtin_echo, exists::exists, functions::print_functions, + helpers::Status, is::builtin_is, - man_pages::*, + man_pages::check_help, set::builtin_set, source::builtin_source, status::builtin_status, test::test, variables::{alias, drop_alias, drop_array, drop_variable}, }; -pub use self::{helpers::Status, man_pages::check_help}; use crate as ion_shell; use crate::{ shell::{sys, Capture, Shell, Value}, @@ -202,8 +202,8 @@ impl<'a> BuiltinMap<'a> { .add("calc", &builtin_calc, "Calculate a mathematical expression") .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("true", &builtin_true_, "Do nothing, successfully") + .add("false", &builtin_false_, "Do nothing, unsuccessfully") .add( "starts-with", &starts_with, @@ -234,17 +234,15 @@ impl<'a> BuiltinMap<'a> { .add("type", &builtin_type, "indicates how a command would be interpreted") } - /// Utilities specific for a shell, that should probably not be included in an embedded context + /// Utilities that may be a security risk. Not included by default /// - /// Contains `eval`, `exec`, `exit`, `set`, `suspend` - pub fn with_shell_unsafe(&mut self) -> &mut Self { - self.add("eval", &builtin_eval, "Evaluates the evaluated expression") - .add( - "set", - &builtin_set, - "Set or unset values of shell options and positional parameters.", - ) - .add("suspend", &builtin_suspend, "Suspends the shell with a SIGTSTOP signal") + /// Contains `eval`, `set` + pub fn with_unsafe(&mut self) -> &mut Self { + self.add("eval", &builtin_eval, "Evaluates the evaluated expression").add( + "set", + &builtin_set, + "Set or unset values of shell options and positional parameters.", + ) } } @@ -667,18 +665,28 @@ pub fn builtin_calc(args: &[types::Str], _: &mut Shell<'_>) -> Status { } } -pub fn builtin_random(args: &[types::Str], _: &mut Shell<'_>) -> Status { - if check_help(args, MAN_RANDOM) { - return Status::SUCCESS; - } +#[builtin( + desc = "generate a random number", + man = " +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 fn random(args: &[types::Str], _: &mut Shell<'_>) -> Status { match random::random(&args[1..]) { Ok(()) => Status::SUCCESS, Err(why) => Status::error(why), } } -// TODO: Find a workaround #[builtin( + names = "true", desc = "does nothing sucessfully", man = " SYNOPSIS @@ -687,11 +695,10 @@ SYNOPSIS DESCRIPTION Sets the exit status to 0." )] -pub fn tru(args: &[types::Str], _: &mut Shell<'_>) -> Status { - Status::SUCCESS -} +pub fn true_(args: &[types::Str], _: &mut Shell<'_>) -> Status { Status::SUCCESS } #[builtin( + names = "false", desc = "does nothing unsuccessfully", man = " SYNOPSIS @@ -700,9 +707,7 @@ SYNOPSIS DESCRIPTION Sets the exit status to 1." )] -pub fn fals(args: &[types::Str], _: &mut Shell<'_>) -> Status { - Status::error("") -} +pub fn false_(args: &[types::Str], _: &mut Shell<'_>) -> Status { Status::error("") } // TODO create a manpage pub fn builtin_wait(_: &[types::Str], shell: &mut Shell<'_>) -> Status { @@ -737,28 +742,35 @@ pub fn bg(args: &[types::Str], shell: &mut Shell<'_>) -> Status { job_control::bg(shell, &args[1..]) } -pub fn builtin_fg(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_FG) { - return Status::SUCCESS; - } +#[builtin( + desc = "bring job to the foreground", + man = " +SYNOPSIS + fg PID + +DESCRIPTION + fg brings the specified job to foreground resuming it if it has stopped." +)] +pub fn fg(args: &[types::Str], shell: &mut Shell<'_>) -> Status { job_control::fg(shell, &args[1..]) } -pub fn builtin_suspend(args: &[types::Str], _shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_SUSPEND) { - return Status::SUCCESS; - } - let _ = unsafe { libc::kill(0, libc::SIGSTOP) }; - Status::SUCCESS -} +#[builtin( + desc = "disown processes", + man = " +SYNOPSIS + disown [ --help | -r | -h | -a ][PID...] -pub fn builtin_disown(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - for arg in args { - if *arg == "--help" { - println!("{}", MAN_DISOWN); - return Status::SUCCESS; - } - } +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 fn disown(args: &[types::Str], shell: &mut Shell<'_>) -> Status { match job_control::disown(shell, &args[1..]) { Ok(()) => Status::SUCCESS, Err(err) => Status::error(format!("ion: disown: {}", err)), @@ -779,10 +791,23 @@ pub fn builtin_help(args: &[types::Str], shell: &mut Shell<'_>) -> Status { } use regex::Regex; -pub fn builtin_matches(args: &[types::Str], _: &mut Shell<'_>) -> Status { - if check_help(args, MAN_MATCHES) { - return Status::SUCCESS; - } +#[builtin( + desc = "checks if the second argument contains any proportion of the first", + man = " +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 fn matches(args: &[types::Str], _: &mut Shell<'_>) -> Status { if args[1..].len() != 2 { return Status::bad_argument("match takes two arguments"); } @@ -801,10 +826,63 @@ pub fn builtin_matches(args: &[types::Str], _: &mut Shell<'_>) -> Status { } } -pub fn builtin_exists(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - if check_help(args, MAN_EXISTS) { - return Status::SUCCESS; - } +#[builtin( + desc = "check whether items exist", + man = r#" +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 Murphy."# +)] +pub fn exists(args: &[types::Str], shell: &mut Shell<'_>) -> Status { match exists(args, shell) { Ok(true) => Status::SUCCESS, Ok(false) => Status::error(""), @@ -812,25 +890,16 @@ pub fn builtin_exists(args: &[types::Str], shell: &mut Shell<'_>) -> Status { } } -pub fn builtin_which(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - match which(args, shell) { - Ok(result) => result, - Err(()) => Status::error(""), - } -} - -pub fn builtin_type(args: &[types::Str], shell: &mut Shell<'_>) -> Status { - match find_type(args, shell) { - Ok(result) => result, - Err(()) => Status::error(""), - } -} - -pub fn builtin_isatty(args: &[types::Str], _: &mut Shell<'_>) -> Status { - if check_help(args, MAN_ISATTY) { - return Status::SUCCESS; - } +#[builtin( + desc = "checks if the provided file descriptor is a tty", + man = " +SYNOPSIS + isatty [FD] +DESCRIPTION + Returns 0 exit status if the supplied file descriptor is a tty." +)] +pub fn isatty(args: &[types::Str], _: &mut Shell<'_>) -> Status { if args.len() > 1 { // sys::isatty expects a usize if compiled for redox but otherwise a i32. #[cfg(target_os = "redox")] diff --git a/src/lib/lib.rs b/src/lib/lib.rs index 9590dc19..9f6d1663 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -82,3 +82,4 @@ pub use crate::{ builtins::{BuiltinFunction, BuiltinMap}, shell::*, }; +pub use builtins_proc::builtin; diff --git a/src/main.rs b/src/main.rs index 86d216d9..d2628a8d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -135,9 +135,10 @@ fn main() { let mut builtins = BuiltinMap::default(); builtins - .with_shell_unsafe() - .add("exec", &builtins::exec, "Replace the shell with the given command.") - .add("exit", &builtins::exit, "Exits the current session"); + .with_unsafe() + .add("exec", &builtins::builtin_exec, "Replace the shell with the given command.") + .add("exit", &builtins::builtin_exit, "Exits the current session") + .add("suspend", &builtins::builtin_suspend, "Suspends the shell with a SIGTSTOP signal"); let stdin_is_a_tty = atty::is(Stream::Stdin); let mut shell = Shell::with_builtins(builtins); diff --git a/tests/exists.out b/tests/exists.out index 5685604c..f6b66ea1 100644 --- a/tests/exists.out +++ b/tests/exists.out @@ -54,7 +54,7 @@ EXAMPLES AUTHOR Written by Fabian Würfl. - Heavily based on implementation of the test builtin, which was written by Michael Murph. + Heavily based on implementation of the test builtin, which was written by Michael Murphy. 0 NAME exists - check whether items exist @@ -110,7 +110,7 @@ EXAMPLES AUTHOR Written by Fabian Würfl. - Heavily based on implementation of the test builtin, which was written by Michael Murph. + Heavily based on implementation of the test builtin, which was written by Michael Murphy. 0 1 0 -- GitLab