diff options
| author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2018-03-20 20:32:23 -0400 |
|---|---|---|
| committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2018-03-20 20:32:23 -0400 |
| commit | 0e927cc650c37be9a7028d72ebb26b1ff820f8c8 (patch) | |
| tree | 0fdcd8db6d5192d1d05d5a36cc002a904d90c0eb | |
| parent | 361774895ac825b5f12a38b5bd0ab784cc9a3e95 (diff) | |
| parent | 450466e560b312034d4a4cf0a256e6051954d1f4 (diff) | |
| download | cmd2-git-0e927cc650c37be9a7028d72ebb26b1ff820f8c8.tar.gz | |
Merge branch 'master' into quoted_completion
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rwxr-xr-x | cmd2.py | 43 | ||||
| -rwxr-xr-x | examples/paged_output.py | 4 | ||||
| -rwxr-xr-x | examples/subcommands.py | 6 | ||||
| -rw-r--r-- | tests/test_cmd2.py | 2 |
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) @@ -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'] |
