Skip to content

Commit 27f3666

Browse files
committed
Adds a base sigint_handler() method to the CommandSet.
The sigint_handler() in Cmd will now delegate to the CommandSet. Addresses #1197
1 parent 9886b82 commit 27f3666

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

cmd2/cmd2.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,9 @@ def __init__(
535535
self.suggest_similar_command = suggest_similar_command
536536
self.default_suggestion_message = "Did you mean {}?"
537537

538+
# the current command being executed
539+
self.current_command: Optional[Statement] = None
540+
538541
def find_commandsets(self, commandset_type: Type[CommandSet], *, subclass_match: bool = False) -> List[CommandSet]:
539542
"""
540543
Find all CommandSets that match the provided CommandSet type.
@@ -2363,7 +2366,13 @@ def sigint_handler(self, signum: int, _: FrameType) -> None:
23632366

23642367
# Check if we are allowed to re-raise the KeyboardInterrupt
23652368
if not self.sigint_protection:
2366-
self._raise_keyboard_interrupt()
2369+
raise_interrupt = True
2370+
if self.current_command is not None:
2371+
command_set = self.find_commandset_for_command(self.current_command.command)
2372+
if command_set is not None:
2373+
raise_interrupt = not command_set.sigint_handler()
2374+
if raise_interrupt:
2375+
self._raise_keyboard_interrupt()
23672376

23682377
def _raise_keyboard_interrupt(self) -> None:
23692378
"""Helper function to raise a KeyboardInterrupt"""
@@ -2953,7 +2962,11 @@ def onecmd(self, statement: Union[Statement, str], *, add_to_history: bool = Tru
29532962
):
29542963
self.history.append(statement)
29552964

2956-
stop = func(statement)
2965+
try:
2966+
self.current_command = statement
2967+
stop = func(statement)
2968+
finally:
2969+
self.current_command = None
29572970

29582971
else:
29592972
stop = self.default(statement)

cmd2/command_definition.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,12 @@ def remove_settable(self, name: str) -> None:
165165
del self._settables[name]
166166
except KeyError:
167167
raise KeyError(name + " is not a settable parameter")
168+
169+
def sigint_handler(self) -> bool:
170+
"""
171+
Handle a SIGINT that occurred for a command in this CommandSet.
172+
173+
:return: True if this completes the interrupt handling and no KeyboardInterrupt will be raised.
174+
False to raise a KeyboardInterrupt.
175+
"""
176+
return False

tests_isolated/test_commandset/test_commandset.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import argparse
8+
import signal
89
from typing import (
910
List,
1011
)
@@ -197,6 +198,9 @@ def test_load_commands(command_sets_manual, capsys):
197198
assert command_sets_manual.find_commandsets(CommandSetA)[0] is cmd_set
198199
assert command_sets_manual.find_commandset_for_command('elderberry') is cmd_set
199200

201+
out = command_sets_manual.app_cmd('apple')
202+
assert 'Apple!' in out.stdout
203+
200204
# Make sure registration callbacks ran
201205
out, err = capsys.readouterr()
202206
assert "in on_register now" in out
@@ -573,6 +577,38 @@ def test_subcommands(command_sets_manual):
573577
command_sets_manual.unregister_command_set(base_cmds)
574578

575579

580+
def test_commandset_sigint(command_sets_manual):
581+
# shows that the command is able to continue execution if the sigint_handler
582+
# returns True that we've handled interrupting the command.
583+
class SigintHandledCommandSet(cmd2.CommandSet):
584+
def do_foo(self, _):
585+
self._cmd.poutput('in foo')
586+
self._cmd.sigint_handler(signal.SIGINT, None)
587+
self._cmd.poutput('end of foo')
588+
589+
def sigint_handler(self) -> bool:
590+
return True
591+
592+
cs1 = SigintHandledCommandSet()
593+
command_sets_manual.register_command_set(cs1)
594+
out = command_sets_manual.app_cmd('foo')
595+
assert 'in foo' in out.stdout
596+
assert 'end of foo' in out.stdout
597+
598+
# shows that the command is interrupted if we don't report we've handled the sigint
599+
class SigintUnhandledCommandSet(cmd2.CommandSet):
600+
def do_bar(self, _):
601+
self._cmd.poutput('in do bar')
602+
self._cmd.sigint_handler(signal.SIGINT, None)
603+
self._cmd.poutput('end of do bar')
604+
605+
cs2 = SigintUnhandledCommandSet()
606+
command_sets_manual.register_command_set(cs2)
607+
out = command_sets_manual.app_cmd('bar')
608+
assert 'in do bar' in out.stdout
609+
assert 'end of do bar' not in out.stdout
610+
611+
576612
def test_nested_subcommands(command_sets_manual):
577613
base_cmds = LoadableBase(1)
578614
pasta_cmds = LoadablePastaStir(1)

0 commit comments

Comments
 (0)