summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2018-03-13 17:53:49 -0400
committerGitHub <noreply@github.com>2018-03-13 17:53:49 -0400
commit2ba567b9883aa5b4cedb58791a3cf27b41f6a484 (patch)
tree5deafdd151a8bd2e0673a17af09bf42d966349ed
parent2aa397cf872f713843945038e1bcdd282b20ebc2 (diff)
parent40d3e5bfeb286eaa507363b78637d6198372737b (diff)
downloadcmd2-git-2ba567b9883aa5b4cedb58791a3cf27b41f6a484.tar.gz
Merge branch 'master' into feature/copy-back-shared-attrs
-rwxr-xr-xcmd2.py14
-rw-r--r--tests/test_cmd2.py41
-rw-r--r--tests/test_submenu.py15
3 files changed, 66 insertions, 4 deletions
diff --git a/cmd2.py b/cmd2.py
index 552e8fec..023db442 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -868,14 +868,14 @@ class AddSubmenu(object):
raise ValueError("reformat_prompt should be either a format string or None")
self.reformat_prompt = reformat_prompt
+ self.shared_attributes = {} if shared_attributes is None else shared_attributes
if require_predefined_shares:
- for attr in shared_attributes.keys():
+ for attr in self.shared_attributes.keys():
if not hasattr(submenu, attr):
raise AttributeError("The shared attribute '{attr}' is not defined in {cmd}. Either define {attr} "
"in {cmd} or set require_predefined_shares=False."
.format(cmd=submenu.__class__.__name__, attr=attr))
- self.shared_attributes = {} if shared_attributes is None else shared_attributes
self.create_subclass = create_subclass
self.preserve_shares = preserve_shares
@@ -1024,6 +1024,7 @@ class Cmd(cmd.Cmd):
allow_cli_args = True # Should arguments passed on the command-line be processed as commands?
allow_redirection = True # Should output redirection and pipes be allowed
default_to_shell = False # Attempt to run unrecognized commands as shell commands
+ quit_on_sigint = True # Quit the loop on interrupt instead of just resetting prompt
reserved_words = []
# Attributes which ARE dynamically settable at runtime
@@ -1897,7 +1898,14 @@ class Cmd(cmd.Cmd):
self.poutput('{}{}'.format(self.prompt, line))
else:
# Otherwise, read a command from stdin
- line = self.pseudo_raw_input(self.prompt)
+ if not self.quit_on_sigint:
+ try:
+ line = self.pseudo_raw_input(self.prompt)
+ except KeyboardInterrupt:
+ self.poutput('^C')
+ line = ''
+ else:
+ line = self.pseudo_raw_input(self.prompt)
# Run the command along with all associated pre and post hooks
stop = self.onecmd_plus_hooks(line)
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 92f21757..e22212d4 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -900,6 +900,47 @@ def test_precmd_hook_failure(hook_failure):
assert out == True
+class SayApp(cmd2.Cmd):
+ def __init__(self, *args, **kwargs):
+ # Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x
+ cmd2.Cmd.__init__(self, *args, **kwargs)
+
+ def do_say(self, arg):
+ self.poutput(arg)
+
+@pytest.fixture
+def say_app():
+ app = SayApp()
+ app.stdout = StdOut()
+ return app
+
+def test_interrupt_quit(say_app):
+ # Mock out the input call so we don't actually wait for a user's response on stdin
+ m = mock.MagicMock(name='input')
+ m.side_effect = ['say hello', KeyboardInterrupt(), 'say goodbye', 'eof']
+ sm.input = m
+
+ say_app.cmdloop()
+
+ # And verify the expected output to stdout
+ out = say_app.stdout.buffer
+ assert out == 'hello\n'
+
+def test_interrupt_noquit(say_app):
+ say_app.quit_on_sigint = False
+
+ # Mock out the input call so we don't actually wait for a user's response on stdin
+ m = mock.MagicMock(name='input')
+ m.side_effect = ['say hello', KeyboardInterrupt(), 'say goodbye', 'eof']
+ sm.input = m
+
+ say_app.cmdloop()
+
+ # And verify the expected output to stdout
+ out = say_app.stdout.buffer
+ assert out == 'hello\n^C\ngoodbye\n'
+
+
class ShellApp(cmd2.Cmd):
def __init__(self, *args, **kwargs):
# Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x
diff --git a/tests/test_submenu.py b/tests/test_submenu.py
index 9b42f38f..0b5330a8 100644
--- a/tests/test_submenu.py
+++ b/tests/test_submenu.py
@@ -8,8 +8,17 @@ import cmd2
from conftest import run_cmd, StdOut, normalize
+class SecondLevelB(cmd2.Cmd):
+ """To be used as a second level command class. """
+
+ def __init__(self, *args, **kwargs):
+ cmd2.Cmd.__init__(self, *args, **kwargs)
+ self.prompt = '2ndLevel B '
+
+
class SecondLevel(cmd2.Cmd):
"""To be used as a second level command class. """
+
def __init__(self, *args, **kwargs):
cmd2.Cmd.__init__(self, *args, **kwargs)
self.prompt = '2ndLevel '
@@ -32,8 +41,10 @@ class SecondLevel(cmd2.Cmd):
second_level_cmd = SecondLevel()
+second_level_b_cmd = SecondLevelB()
+@cmd2.AddSubmenu(second_level_b_cmd, command='secondb')
@cmd2.AddSubmenu(second_level_cmd,
command='second',
aliases=('second_alias',),
@@ -63,6 +74,7 @@ def submenu_app():
second_level_cmd.stdout = StdOut()
return app
+
@pytest.fixture
def secondlevel_app():
app = SecondLevel()
@@ -89,6 +101,7 @@ def test_submenu_say_from_top_level(submenu_app):
assert len(out2) == 0
assert out1[0] == "You called a command in TopLevel with {!r}.".format(line)
+
def test_submenu_second_say_from_top_level(submenu_app):
line = 'testing'
out1, out2 = run_submenu_cmd(submenu_app, 'second say ' + line)
@@ -100,6 +113,7 @@ def test_submenu_second_say_from_top_level(submenu_app):
assert len(out2) == 1
assert out2[0] == "You called a command in SecondLevel with {!r}.".format(line)
+
def test_submenu_say_from_second_level(secondlevel_app):
line = 'testing'
out = run_cmd(secondlevel_app, 'say ' + line)
@@ -135,4 +149,3 @@ def test_submenu_from_top_help_second_say(submenu_app):
def test_submenu_shared_attribute(submenu_app):
out1, out2 = run_submenu_cmd(submenu_app, 'second get_top_level_attr')
assert out2 == [str(submenu_app.top_level_attr)]
-