From a8fc86afa98fa6a9e3f566150ed5def4e6e00e65 Mon Sep 17 00:00:00 2001 From: Xavier L'Heureux <xavier.lheureux@icloud.com> Date: Mon, 22 Jul 2019 10:06:50 -0400 Subject: [PATCH] feat: Add `debug` builtin to enable/disable printing command on execution --- src/binary/builtins.rs | 23 ++++++++++++++++++ src/binary/completer.rs | 8 +++---- src/lib/builtins/mod.rs | 4 ++-- src/lib/builtins/set.rs | 41 +++++++++++++------------------- src/lib/expansion/words/mod.rs | 2 +- src/lib/shell/directory_stack.rs | 2 +- src/lib/shell/mod.rs | 4 ++-- src/lib/shell/shell_expand.rs | 27 ++++++++++----------- src/lib/shell/variables.rs | 4 ++-- src/main.rs | 1 + 10 files changed, 66 insertions(+), 50 deletions(-) diff --git a/src/binary/builtins.rs b/src/binary/builtins.rs index a700ef1c..31825b94 100644 --- a/src/binary/builtins.rs +++ b/src/binary/builtins.rs @@ -17,6 +17,29 @@ pub fn suspend(args: &[Str], _shell: &mut Shell<'_>) -> Status { Status::SUCCESS } +#[builtin( + desc = "toggle debug mode (print commands)", + man = " +SYNOPSIS + debug on | off + +DESCRIPTION + Turn on or off the feature to print each command executed to stderr (debug mode)." +)] +pub fn debug(args: &[Str], shell: &mut Shell<'_>) -> Status { + match args.get(1).map(Str::as_str) { + Some("on") => shell.set_pre_command(Some(Box::new(|_shell, pipeline| { + // A string representing the command is stored here. + eprintln!("> {}", pipeline); + }))), + Some("off") => shell.set_pre_command(None), + _ => { + return Status::bad_argument("debug: the debug builtin requires on or off as argument") + } + } + Status::SUCCESS +} + #[builtin( desc = "exit the shell", man = " diff --git a/src/binary/completer.rs b/src/binary/completer.rs index d98baf6b..0f95014f 100644 --- a/src/binary/completer.rs +++ b/src/binary/completer.rs @@ -97,7 +97,7 @@ impl<'a, 'b> Completer for IonCompleter<'a, 'b> { let file_completers: Vec<_> = if let Some(paths) = env::var_os("PATH") { env::split_paths(&paths) .map(|s| { - let s = if !s.to_string_lossy().ends_with("/") { + let s = if !s.to_string_lossy().ends_with('/') { let mut oss = s.into_os_string(); oss.push("/"); oss.into() @@ -134,9 +134,9 @@ impl<'a, 'b> Completer for IonCompleter<'a, 'b> { .nth(index - 1) .map(|(start, end)| event.editor.current_buffer().range(start, end)) .filter(|filename| { - filename.ends_with("|") - || filename.ends_with("&") - || filename.ends_with(";") + filename.ends_with('|') + || filename.ends_with('&') + || filename.ends_with(';') }) .is_some(); if is_pipe { diff --git a/src/lib/builtins/mod.rs b/src/lib/builtins/mod.rs index e76fbd31..a5d452f5 100644 --- a/src/lib/builtins/mod.rs +++ b/src/lib/builtins/mod.rs @@ -296,13 +296,13 @@ pub fn cd(args: &[types::Str], shell: &mut Shell<'_>) -> Status { }) .find(Result::is_ok) .unwrap_or_else(|| { - shell.dir_stack_mut().change_and_push_dir(&Path::new(dir)) + shell.dir_stack_mut().change_and_push_dir(Path::new(dir)) }); shell.dir_stack_mut().popd(1); check_cdpath_first } } else { - shell.dir_stack_mut().change_and_push_dir(&Path::new(dir)) + shell.dir_stack_mut().change_and_push_dir(Path::new(dir)) } } None => shell.dir_stack_mut().switch_to_home_directory(), diff --git a/src/lib/builtins/set.rs b/src/lib/builtins/set.rs index bb051178..cb5e1438 100644 --- a/src/lib/builtins/set.rs +++ b/src/lib/builtins/set.rs @@ -17,7 +17,7 @@ enum PositionalArgs { desc = "Set or unset values of shell options and positional parameters.", man = " SYNOPSIS - set [ --help ] [-e | +e] [-x | +x] [-o [vi | emacs]] [- | --] [STRING]... + set [ --help ] [-e | +e] [- | --] [STRING]... DESCRIPTION Shell options may be set using the '-' character, and unset using the '+' character. @@ -25,45 +25,38 @@ DESCRIPTION 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." + If no arguments are suppled, arguments will not be unset. + +BASHÂ EQUIVALENTS + To set the keybindings, see the `keybindings` builtin + To print commands as they are executed (only with the Ion Shell), see `debug`" )] pub fn set(args: &[types::Str], shell: &mut Shell<'_>) -> Status { let mut args_iter = args.iter(); let mut positionals = None; while let Some(arg) = args_iter.next() { - if arg.starts_with("--") { - if arg.len() == 2 { + match arg.as_str() { + "--" => { positionals = Some(PositionalArgs::UnsetIfNone); break; } - return Status::SUCCESS; - } else if arg.starts_with('-') { - if arg.len() == 1 { + "-" => { positionals = Some(PositionalArgs::RetainIfNone); break; } - for flag in arg.bytes().skip(1) { - match flag { - b'e' => shell.opts_mut().err_exit = true, - _ => return Status::SUCCESS, - } - } - } else if arg.starts_with('+') { - for flag in arg.bytes().skip(1) { - match flag { - b'e' => shell.opts_mut().err_exit = false, - _ => return Status::SUCCESS, - } + "-e" => shell.opts_mut().err_exit = true, + "+e" => shell.opts_mut().err_exit = false, + _ => { + return Status::bad_argument(format!( + "set: argument '{}' is not recognized. Try adding `--` before it to pass it \ + as argument to the shell script", + arg + )) } } } diff --git a/src/lib/expansion/words/mod.rs b/src/lib/expansion/words/mod.rs index da1e5453..484b2aba 100644 --- a/src/lib/expansion/words/mod.rs +++ b/src/lib/expansion/words/mod.rs @@ -646,7 +646,7 @@ impl<'a> WordIterator<'a> { } /// Creates a new iterator with a given expander - pub fn new(data: &'a str, do_glob: bool) -> WordIterator<'a> { + pub const fn new(data: &'a str, do_glob: bool) -> WordIterator<'a> { WordIterator { data, backsl: false, read: 0, quotes: Quotes::None, do_glob } } } diff --git a/src/lib/shell/directory_stack.rs b/src/lib/shell/directory_stack.rs index 7dc10164..fe7ab7e7 100644 --- a/src/lib/shell/directory_stack.rs +++ b/src/lib/shell/directory_stack.rs @@ -143,7 +143,7 @@ impl DirectoryStack { self.dirs.remove(0); println!("{}", prev); - self.change_and_push_dir(&Path::new(&prev)) + self.change_and_push_dir(Path::new(&prev)) } pub fn switch_to_home_directory(&mut self) -> Result<(), DirStackError> { diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs index 0f5d345f..44a261d7 100644 --- a/src/lib/shell/mod.rs +++ b/src/lib/shell/mod.rs @@ -298,8 +298,8 @@ impl<'a> Shell<'a> { let null_file = if pipeline.pipe == PipeType::Disown { File::open(NULL_PATH).ok() } else { None }; let (stderr, stdout) = ( - null_file.as_ref().or(self.stderr.as_ref()), - null_file.as_ref().or(self.stdout.as_ref()), + null_file.as_ref().or_else(|| self.stderr.as_ref()), + null_file.as_ref().or_else(|| self.stdout.as_ref()), ); for item in &mut pipeline.items { diff --git a/src/lib/shell/shell_expand.rs b/src/lib/shell/shell_expand.rs index 52e21b16..295cb6d7 100644 --- a/src/lib/shell/shell_expand.rs +++ b/src/lib/shell/shell_expand.rs @@ -209,7 +209,7 @@ impl<'a, 'b> Expander for Shell<'b> { match tilde_prefix { "+" => Ok(env::var("PWD").unwrap_or_else(|_| "?".into()).into()), - "-" => Ok(self.variables.get_str("OLDPWD")?.into()), + "-" => Ok(self.variables.get_str("OLDPWD")?), _ => { let (neg, tilde_num) = if tilde_prefix.starts_with('+') { (false, &tilde_prefix[1..]) @@ -219,24 +219,23 @@ impl<'a, 'b> Expander for Shell<'b> { (false, tilde_prefix) }; - match tilde_num.parse() { - Ok(num) => if neg { + if let Ok(num) = tilde_num.parse() { + if neg { self.directory_stack.dir_from_top(num) } else { self.directory_stack.dir_from_bottom(num) } .map(|path| path.to_str().unwrap().into()) - .ok_or_else(|| Error::OutOfStack(num)), - Err(_) => { - let user = if tilde_prefix.is_empty() { - users::get_user_by_uid(users::get_current_uid()) - } else { - users::get_user_by_name(tilde_prefix) - }; - match user { - Some(user) => Ok(user.home_dir().to_string_lossy().as_ref().into()), - None => Err(Error::HomeNotFound), - } + .ok_or_else(|| Error::OutOfStack(num)) + } else { + let user = if tilde_prefix.is_empty() { + users::get_user_by_uid(users::get_current_uid()) + } else { + users::get_user_by_name(tilde_prefix) + }; + match user { + Some(user) => Ok(user.home_dir().to_string_lossy().as_ref().into()), + None => Err(Error::HomeNotFound), } } } diff --git a/src/lib/shell/variables.rs b/src/lib/shell/variables.rs index 9f65a419..09f214cb 100644 --- a/src/lib/shell/variables.rs +++ b/src/lib/shell/variables.rs @@ -6,7 +6,7 @@ use crate::{ }; use nix::unistd::{geteuid, gethostname, getpid, getuid}; use scopes::{Namespace, Scope, Scopes}; -use std::{env, rc::Rc}; +use std::{env, ffi::CStr, rc::Rc}; use unicode_segmentation::UnicodeSegmentation; /// Contain a dynamically-typed variable value @@ -256,7 +256,7 @@ impl<'a> Default for Variables<'a> { "HOST", &gethostname(&mut host_name) .ok() - .map_or_else(|| "?".into(), |hostname| hostname.to_string_lossy()) + .map_or_else(|| "?".into(), CStr::to_string_lossy) .as_ref(), ); diff --git a/src/main.rs b/src/main.rs index d66c44a3..31f7d070 100644 --- a/src/main.rs +++ b/src/main.rs @@ -164,6 +164,7 @@ fn main() { let mut builtins = BuiltinMap::default(); builtins .with_unsafe() + .add("debug", &builtins::builtin_debug, "Toggle debug mode (print commands on exec)") .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"); -- GitLab