Skip to content

Commit 662a784

Browse files
committed
Ctrl-C now stops a running text script instead of just the current script command
1 parent 990ec45 commit 662a784

File tree

4 files changed

+40
-23
lines changed

4 files changed

+40
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 1.0.2 (TBD, 2020)
22
* Enhancements
33
* `do_shell()` now saves the return code of the command it runs in `self.last_result` for use in pyscripts
4+
* Ctrl-C now stops a running text script instead of just the current script command
45

56
## 1.0.1 (March 13, 2020)
67
* Bug Fixes

cmd2/cmd2.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
187187

188188
# Attributes which should NOT be dynamically settable via the set command at runtime
189189
self.default_to_shell = False # Attempt to run unrecognized commands as shell commands
190-
self.quit_on_sigint = False # Quit the loop on interrupt instead of just resetting prompt
190+
self.quit_on_sigint = False # Ctrl-C at the prompt will quit the program instead of just resetting prompt
191191
self.allow_redirection = allow_redirection # Security setting to prevent redirection of stdout
192192

193193
# Attributes which ARE dynamically settable via the set command at runtime
@@ -1584,11 +1584,15 @@ def parseline(self, line: str) -> Tuple[str, str, str]:
15841584
statement = self.statement_parser.parse_command_only(line)
15851585
return statement.command, statement.args, statement.command_and_args
15861586

1587-
def onecmd_plus_hooks(self, line: str, *, add_to_history: bool = True, py_bridge_call: bool = False) -> bool:
1587+
def onecmd_plus_hooks(self, line: str, *, add_to_history: bool = True,
1588+
raise_keyboard_interrupt: bool = False, py_bridge_call: bool = False) -> bool:
15881589
"""Top-level function called by cmdloop() to handle parsing a line and running the command and all of its hooks.
15891590
15901591
:param line: command line to run
15911592
:param add_to_history: If True, then add this command to history. Defaults to True.
1593+
:param raise_keyboard_interrupt: if True, then KeyboardInterrupt exceptions will be raised. This is used when
1594+
running commands in a loop to be able to stop the whole loop and not just
1595+
the current command. Defaults to False.
15921596
:param py_bridge_call: This should only ever be set to True by PyBridge to signify the beginning
15931597
of an app() call from Python. It is used to enable/disable the storage of the
15941598
command's stdout.
@@ -1681,14 +1685,18 @@ def onecmd_plus_hooks(self, line: str, *, add_to_history: bool = True, py_bridge
16811685
if py_bridge_call:
16821686
# Stop saving command's stdout before command finalization hooks run
16831687
self.stdout.pause_storage = True
1684-
1688+
except KeyboardInterrupt as e:
1689+
if raise_keyboard_interrupt:
1690+
raise e
16851691
except (Cmd2ArgparseError, EmptyStatement):
16861692
# Don't do anything, but do allow command finalization hooks to run
16871693
pass
16881694
except Exception as ex:
16891695
self.pexcept(ex)
16901696
finally:
1691-
return self._run_cmdfinalization_hooks(stop, statement)
1697+
stop = self._run_cmdfinalization_hooks(stop, statement)
1698+
1699+
return stop
16921700

16931701
def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement]) -> bool:
16941702
"""Run the command finalization hooks"""
@@ -1711,13 +1719,16 @@ def _run_cmdfinalization_hooks(self, stop: bool, statement: Optional[Statement])
17111719
except Exception as ex:
17121720
self.pexcept(ex)
17131721

1714-
def runcmds_plus_hooks(self, cmds: List[Union[HistoryItem, str]], *, add_to_history: bool = True) -> bool:
1722+
def runcmds_plus_hooks(self, cmds: List[Union[HistoryItem, str]], *, add_to_history: bool = True,
1723+
stop_on_keyboard_interrupt: bool = True) -> bool:
17151724
"""
17161725
Used when commands are being run in an automated fashion like text scripts or history replays.
17171726
The prompt and command line for each command will be printed if echo is True.
17181727
17191728
:param cmds: commands to run
17201729
:param add_to_history: If True, then add these commands to history. Defaults to True.
1730+
:param stop_on_keyboard_interrupt: stop command loop if Ctrl-C is pressed instead of just
1731+
moving to the next command. Defaults to True.
17211732
:return: True if running of commands should stop
17221733
"""
17231734
for line in cmds:
@@ -1727,8 +1738,14 @@ def runcmds_plus_hooks(self, cmds: List[Union[HistoryItem, str]], *, add_to_hist
17271738
if self.echo:
17281739
self.poutput('{}{}'.format(self.prompt, line))
17291740

1730-
if self.onecmd_plus_hooks(line, add_to_history=add_to_history):
1731-
return True
1741+
try:
1742+
if self.onecmd_plus_hooks(line, add_to_history=add_to_history,
1743+
raise_keyboard_interrupt=stop_on_keyboard_interrupt):
1744+
return True
1745+
except KeyboardInterrupt as e:
1746+
if stop_on_keyboard_interrupt:
1747+
self.perror(e)
1748+
break
17321749

17331750
return False
17341751

@@ -3238,8 +3255,10 @@ def py_quit():
32383255
# noinspection PyBroadException
32393256
try:
32403257
interp.runcode(py_code_to_run)
3258+
except KeyboardInterrupt as e:
3259+
raise e
32413260
except BaseException:
3242-
# We don't care about any exception that happened in the Python code
3261+
# We don't care about any other exceptions that happened in the Python code
32433262
pass
32443263

32453264
# Otherwise we will open an interactive Python shell
@@ -3269,9 +3288,6 @@ def py_quit():
32693288
if saved_cmd2_env is not None:
32703289
self._restore_cmd2_env(saved_cmd2_env)
32713290

3272-
except KeyboardInterrupt:
3273-
pass
3274-
32753291
finally:
32763292
with self.sigint_protection:
32773293
if saved_sys_path is not None:
@@ -3302,8 +3318,6 @@ def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]:
33023318
if selection != 'Yes':
33033319
return
33043320

3305-
py_return = False
3306-
33073321
# Save current command line arguments
33083322
orig_args = sys.argv
33093323

@@ -3314,9 +3328,6 @@ def do_run_pyscript(self, args: argparse.Namespace) -> Optional[bool]:
33143328
# noinspection PyTypeChecker
33153329
py_return = self.do_py('--pyscript {}'.format(utils.quote_string(args.script_path)))
33163330

3317-
except KeyboardInterrupt:
3318-
pass
3319-
33203331
finally:
33213332
# Restore command line arguments to original state
33223333
sys.argv = orig_args
@@ -3629,7 +3640,12 @@ def _generate_transcript(self, history: List[Union[HistoryItem, str]], transcrip
36293640
self.stdout = utils.StdSim(self.stdout)
36303641

36313642
# then run the command and let the output go into our buffer
3632-
stop = self.onecmd_plus_hooks(history_item)
3643+
try:
3644+
stop = self.onecmd_plus_hooks(history_item, raise_keyboard_interrupt=True)
3645+
except KeyboardInterrupt as e:
3646+
self.perror(e)
3647+
stop = True
3648+
36333649
commands_run += 1
36343650

36353651
# add the regex-escaped output to the transcript

docs/features/initialization.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ override:
146146
everything available with **self_in_py**)
147147
- **quiet**: if ``True`` then completely suppress nonessential output (Default:
148148
``False``)
149-
- **quit_on_sigint**: if ``True`` quit the main loop on interrupt instead of
150-
just resetting prompt
149+
- **quit_on_sigint**: if ``True`` Ctrl-C at the prompt will quit the program
150+
instead of just resetting prompt
151151
- **settable**: dictionary that controls which of these instance attributes
152152
are settable at runtime using the *set* command
153153
- **timing**: if ``True`` display execution time for each command (Default:

docs/features/misc.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ method be called.
104104
Quit on SIGINT
105105
--------------
106106

107-
On many shells, SIGINT (most often triggered by the user pressing Ctrl+C) only
108-
cancels the current line, not the entire command loop. By default, a ``cmd2``
109-
application will quit on receiving this signal. However, if ``quit_on_sigint``
110-
is set to ``False``, then the current line will simply be cancelled.
107+
On many shells, SIGINT (most often triggered by the user pressing Ctrl+C)
108+
while at the prompt only cancels the current line, not the entire command
109+
loop. By default, a ``cmd2`` application matches this behavior. However, if
110+
``quit_on_sigint`` is set to ``True``, the command loop will quit instead.
111111

112112
::
113113

0 commit comments

Comments
 (0)