From c50db52da00f4e544a6b3a19ee5b0f54e8503914 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 7 May 2020 14:32:29 -0400 Subject: Added SkipPostcommandHooks exception and made Cmd2ArgparseError inherit from it. Both exception classes have been added to the public API. --- cmd2/__init__.py | 1 + cmd2/cmd2.py | 14 +++++++------- cmd2/exceptions.py | 27 ++++++++++++++++++++++----- 3 files changed, 30 insertions(+), 12 deletions(-) (limited to 'cmd2') diff --git a/cmd2/__init__.py b/cmd2/__init__.py index eb5c275d..d49427f2 100644 --- a/cmd2/__init__.py +++ b/cmd2/__init__.py @@ -25,6 +25,7 @@ from .argparse_custom import DEFAULT_ARGUMENT_PARSER from .cmd2 import Cmd from .constants import COMMAND_NAME, DEFAULT_SHORTCUTS from .decorators import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category +from .exceptions import Cmd2ArgparseError, SkipPostcommandHooks from .parsing import Statement from .py_bridge import CommandResult from .utils import categorize, CompletionError, Settable diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 49c181f1..38fdd7b0 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -46,7 +46,7 @@ from . import ansi, constants, plugin, utils from .argparse_custom import DEFAULT_ARGUMENT_PARSER, CompletionItem from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer from .decorators import with_argparser -from .exceptions import Cmd2ArgparseError, Cmd2ShlexError, EmbeddedConsoleExit, EmptyStatement, RedirectionError +from .exceptions import Cmd2ShlexError, EmbeddedConsoleExit, EmptyStatement, RedirectionError, SkipPostcommandHooks from .history import History, HistoryItem from .parsing import Macro, MacroArg, Statement, StatementParser, shlex_split from .rl_utils import RlType, rl_get_point, rl_make_safe_prompt, rl_set_prompt, rl_type, rl_warning, vt100_support @@ -1670,7 +1670,7 @@ class Cmd(cmd.Cmd): except KeyboardInterrupt as ex: if raise_keyboard_interrupt: raise ex - except (Cmd2ArgparseError, EmptyStatement): + except (SkipPostcommandHooks, EmptyStatement): # Don't do anything, but do allow command finalization hooks to run pass except Cmd2ShlexError as ex: @@ -3894,7 +3894,7 @@ class Cmd(cmd.Cmd): IMPORTANT: This function will not print an alert unless it can acquire self.terminal_lock to ensure a prompt is onscreen. Therefore it is best to acquire the lock before calling this function - to guarantee the alert prints. + to guarantee the alert prints and to avoid raising a RuntimeError. :param alert_msg: the message to display to the user :param new_prompt: if you also want to change the prompt that is displayed, then include it here @@ -3956,7 +3956,7 @@ class Cmd(cmd.Cmd): IMPORTANT: This function will not update the prompt unless it can acquire self.terminal_lock to ensure a prompt is onscreen. Therefore it is best to acquire the lock before calling this function - to guarantee the prompt changes. + to guarantee the prompt changes and to avoid raising a RuntimeError. If user is at a continuation prompt while entering a multiline command, the onscreen prompt will not change. However self.prompt will still be updated and display immediately after the multiline @@ -3971,9 +3971,9 @@ class Cmd(cmd.Cmd): Raises a `RuntimeError` if called while another thread holds `terminal_lock`. - IMPORTANT: This function will not set the title unless it can acquire self.terminal_lock to avoid - writing to stderr while a command is running. Therefore it is best to acquire the lock - before calling this function to guarantee the title changes. + IMPORTANT: This function will not set the title unless it can acquire self.terminal_lock to avoid writing + to stderr while a command is running. Therefore it is best to acquire the lock before calling + this function to guarantee the title changes and to avoid raising a RuntimeError. :param title: the new window title """ diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index 635192e1..d0a922db 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -1,16 +1,33 @@ # coding=utf-8 -"""Custom exceptions for cmd2. These are NOT part of the public API and are intended for internal use only.""" +"""Custom exceptions for cmd2""" -class Cmd2ArgparseError(Exception): +############################################################################################################ +# The following exceptions are part of the public API +############################################################################################################ + +class SkipPostcommandHooks(Exception): """ - Custom exception class for when a command has an error parsing its arguments. - This can be raised by argparse decorators or the command functions themselves. - The main use of this exception is to tell cmd2 not to run Postcommand hooks. + Custom exception class for when a command has a failure bad enough to skip post command + hooks, but not bad enough to print the exception to the user. """ pass +class Cmd2ArgparseError(SkipPostcommandHooks): + """ + A ``SkipPostcommandHooks`` exception for when a command fails parsing its arguments. + This is raised by argparse decorators but can also be raised by command functions. + If a command function still needs to run post command hooks when parsing fails, + just return instead of raising an exception. + """ + pass + + +############################################################################################################ +# The following exceptions are NOT part of the public API and are intended for internal use only. +############################################################################################################ + class Cmd2ShlexError(Exception): """Raised when shlex fails to parse a command line string in StatementParser""" pass -- cgit v1.2.1 From 6460d5707fffc725c50e0642b1e1e2edafa18d87 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 7 May 2020 16:29:59 -0400 Subject: Made following changes to onecmd_plus_hooks() 1. Added SystemExit handling by warning the user it's occured and setting stop to True 2. KeyboardInterrupts won't be raised if stop is already set to True. --- cmd2/cmd2.py | 42 +++++++++++++++++++++++++----------------- cmd2/exceptions.py | 8 ++++---- 2 files changed, 29 insertions(+), 21 deletions(-) (limited to 'cmd2') diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 38fdd7b0..aacb9d93 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1587,9 +1587,9 @@ class Cmd(cmd.Cmd): :param line: command line to run :param add_to_history: If True, then add this command to history. Defaults to True. - :param raise_keyboard_interrupt: if True, then KeyboardInterrupt exceptions will be raised. This is used when - running commands in a loop to be able to stop the whole loop and not just - the current command. Defaults to False. + :param raise_keyboard_interrupt: if True, then KeyboardInterrupt exceptions will be raised if stop isn't already + True. This is used when running commands in a loop to be able to stop the whole + loop and not just the current command. Defaults to False. :param py_bridge_call: This should only ever be set to True by PyBridge to signify the beginning of an app() call from Python. It is used to enable/disable the storage of the command's stdout. @@ -1667,9 +1667,6 @@ class Cmd(cmd.Cmd): if py_bridge_call: # Stop saving command's stdout before command finalization hooks run self.stdout.pause_storage = True - except KeyboardInterrupt as ex: - if raise_keyboard_interrupt: - raise ex except (SkipPostcommandHooks, EmptyStatement): # Don't do anything, but do allow command finalization hooks to run pass @@ -1677,16 +1674,30 @@ class Cmd(cmd.Cmd): self.perror("Invalid syntax: {}".format(ex)) except RedirectionError as ex: self.perror(ex) + except KeyboardInterrupt as ex: + if raise_keyboard_interrupt and not stop: + raise ex + except SystemExit: + self.pwarning("Caught SystemExit. Attempting to stop command loop...") + stop = True except Exception as ex: self.pexcept(ex) finally: - stop = self._run_cmdfinalization_hooks(stop, statement) + try: + stop = self._run_cmdfinalization_hooks(stop, statement) + except KeyboardInterrupt as ex: + if raise_keyboard_interrupt and not stop: + raise ex + except SystemExit: + self.pwarning("Caught SystemExit. Attempting to stop command loop...") + stop = True + except Exception as ex: + self.pexcept(ex) return stop def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) -> bool: """Run the command finalization hooks""" - with self.sigint_protection: if not sys.platform.startswith('win') and self.stdin.isatty(): # Before the next command runs, fix any terminal problems like those @@ -1695,15 +1706,12 @@ class Cmd(cmd.Cmd): proc = subprocess.Popen(['stty', 'sane']) proc.communicate() - try: - data = plugin.CommandFinalizationData(stop, statement) - for func in self._cmdfinalization_hooks: - data = func(data) - # retrieve the final value of stop, ignoring any - # modifications to the statement - return data.stop - except Exception as ex: - self.pexcept(ex) + data = plugin.CommandFinalizationData(stop, statement) + for func in self._cmdfinalization_hooks: + data = func(data) + # retrieve the final value of stop, ignoring any + # modifications to the statement + return data.stop def runcmds_plus_hooks(self, cmds: List[Union[HistoryItem, str]], *, add_to_history: bool = True, stop_on_keyboard_interrupt: bool = True) -> bool: diff --git a/cmd2/exceptions.py b/cmd2/exceptions.py index d0a922db..8a7fd81f 100644 --- a/cmd2/exceptions.py +++ b/cmd2/exceptions.py @@ -16,10 +16,10 @@ class SkipPostcommandHooks(Exception): class Cmd2ArgparseError(SkipPostcommandHooks): """ - A ``SkipPostcommandHooks`` exception for when a command fails parsing its arguments. - This is raised by argparse decorators but can also be raised by command functions. - If a command function still needs to run post command hooks when parsing fails, - just return instead of raising an exception. + A ``SkipPostcommandHooks`` exception for when a command fails to parse its arguments. + Normally argparse raises a SystemExit exception in these cases. To avoid stopping the command + loop, catch the SystemExit and raise this instead. If you still need to run post command hooks + after parsing fails, just return instead of raising an exception. """ pass -- cgit v1.2.1 From 6f91bea16a00f48ff6be729ed12a5920ebac3baa Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 28 May 2020 10:54:10 -0400 Subject: Removed pwarning() calls when command raises SystemExit Added unit tests --- cmd2/cmd2.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'cmd2') diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index aacb9d93..73f7aa79 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1678,7 +1678,6 @@ class Cmd(cmd.Cmd): if raise_keyboard_interrupt and not stop: raise ex except SystemExit: - self.pwarning("Caught SystemExit. Attempting to stop command loop...") stop = True except Exception as ex: self.pexcept(ex) @@ -1689,7 +1688,6 @@ class Cmd(cmd.Cmd): if raise_keyboard_interrupt and not stop: raise ex except SystemExit: - self.pwarning("Caught SystemExit. Attempting to stop command loop...") stop = True except Exception as ex: self.pexcept(ex) -- cgit v1.2.1