summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2018-03-20 20:32:23 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2018-03-20 20:32:23 -0400
commit0e927cc650c37be9a7028d72ebb26b1ff820f8c8 (patch)
tree0fdcd8db6d5192d1d05d5a36cc002a904d90c0eb
parent361774895ac825b5f12a38b5bd0ab784cc9a3e95 (diff)
parent450466e560b312034d4a4cf0a256e6051954d1f4 (diff)
downloadcmd2-git-0e927cc650c37be9a7028d72ebb26b1ff820f8c8.tar.gz
Merge branch 'master' into quoted_completion
-rw-r--r--CHANGELOG.md2
-rwxr-xr-xcmd2.py43
-rwxr-xr-xexamples/paged_output.py4
-rwxr-xr-xexamples/subcommands.py6
-rw-r--r--tests/test_cmd2.py2
5 files changed, 51 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7359ea08..0082a067 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
* Fixed a bug in tab-completion of command names within sub-menus
* Fixed a bug when using persistent readline history in Python 2.7
* Fixed a bug where the ``AddSubmenu`` decorator didn't work with a default value for ``shared_attributes``
+ * Added a check to ``ppaged()`` to only use a pager when running in a real fully functional terminal
* Enhancements
* Added [quit_on_sigint](http://cmd2.readthedocs.io/en/latest/settingchanges.html#quit-on-sigint) attribute to enable canceling current line instead of quitting when Ctrl+C is typed
* Added possibility of having readline history preservation in a SubMenu
@@ -11,6 +12,7 @@
* Added command aliasing with ``alias`` and ``unalias`` commands
* Added the ability to load an initialization script at startup
* See [alias_startup.py](https://github.com/python-cmd2/cmd2/blob/master/examples/alias_startup.py) for an example
+ * Added a default SIGINT handler which terminates any open pipe subprocesses and re-raises a KeyboardInterrupt
## 0.8.1 (March 9, 2018)
diff --git a/cmd2.py b/cmd2.py
index a8171603..6bbfc94e 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -37,6 +37,7 @@ import platform
import re
import shlex
import shutil
+import signal
import six
import sys
import tempfile
@@ -1243,7 +1244,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
+ quit_on_sigint = False # Quit the loop on interrupt instead of just resetting prompt
reserved_words = []
# Attributes which ARE dynamically settable at runtime
@@ -1453,7 +1454,8 @@ class Cmd(cmd.Cmd):
def ppaged(self, msg, end='\n'):
"""Print output using a pager if it would go off screen and stdout isn't currently being redirected.
- Never uses a pager inside of a script (Python or text) or when output is being redirected or piped.
+ Never uses a pager inside of a script (Python or text) or when output is being redirected or piped or when
+ stdout or stdin are not a fully functional terminal.
:param msg: str - message to print to current stdout - anything convertible to a str with '{}'.format() is OK
:param end: str - string appended after the end of the message if not already present, default a newline
@@ -1464,8 +1466,18 @@ class Cmd(cmd.Cmd):
if not msg_str.endswith(end):
msg_str += end
+ # Attempt to detect if we are not running within a fully functional terminal.
+ # Don't try to use the pager when being run by a continuous integration system like Jenkins + pexpect.
+ functional_terminal = False
+
+ if self.stdin.isatty() and self.stdout.isatty():
+ if sys.platform.startswith('win') or os.environ.get('TERM') is not None:
+ functional_terminal = True
+
# Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
- if not self.redirecting and not self._in_py and not self._script_dir:
+ # Also only attempt to use a pager if actually running in a real fully functional terminal
+ if functional_terminal and not self.redirecting and not self._in_py and not self._script_dir:
+
if sys.platform.startswith('win'):
pager_cmd = 'more'
else:
@@ -1842,6 +1854,31 @@ class Cmd(cmd.Cmd):
completions.sort()
return completions
+ # noinspection PyUnusedLocal
+ def sigint_handler(self, signum, frame):
+ """Signal handler for SIGINTs which typically come from Ctrl-C events.
+
+ If you need custom SIGINT behavior, then override this function.
+
+ :param signum: int - signal number
+ :param frame
+ """
+
+ # Save copy of pipe_proc since it could theoretically change while this is running
+ pipe_proc = self.pipe_proc
+
+ if pipe_proc is not None:
+ pipe_proc.terminate()
+
+ # Re-raise a KeyboardInterrupt so other parts of the code can catch it
+ raise KeyboardInterrupt("Got a keyboard interrupt")
+
+ def preloop(self):
+ """"Hook method executed once when the cmdloop() method is called."""
+
+ # Register a default SIGINT signal handler for Ctrl+C
+ signal.signal(signal.SIGINT, self.sigint_handler)
+
def precmd(self, statement):
"""Hook method executed just before the command is processed by ``onecmd()`` and after adding it to the history.
diff --git a/examples/paged_output.py b/examples/paged_output.py
index 171c1b3e..9005a4da 100755
--- a/examples/paged_output.py
+++ b/examples/paged_output.py
@@ -17,6 +17,10 @@ class PagedOutput(cmd2.Cmd):
@with_argument_list
def do_page_file(self, args):
"""Read in a text file and display its output in a pager."""
+ if not args:
+ self.perror('page_file requires a path to a file as an argument', traceback_war=False)
+ return
+
with open(args[0], 'r') as f:
text = f.read()
self.ppaged(text)
diff --git a/examples/subcommands.py b/examples/subcommands.py
index fa99f6b4..2a7e0afa 100755
--- a/examples/subcommands.py
+++ b/examples/subcommands.py
@@ -63,11 +63,11 @@ class SubcommandsExample(cmd2.Cmd):
@with_argparser(base_parser)
def do_base(self, args):
"""Base command help"""
- try:
+ if args.func is not None:
# Call whatever subcommand function was selected
args.func(self, args)
- except AttributeError:
- # No subcommand was provided, so as called
+ else:
+ # No subcommand was provided, so call help
self.do_help('base')
# functools.partialmethod was added in Python 3.4
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index d69bf343..20d477ba 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -915,6 +915,8 @@ def say_app():
return app
def test_interrupt_quit(say_app):
+ say_app.quit_on_sigint = True
+
# 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']