diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2018-08-24 11:24:51 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-08-24 11:24:51 -0400 |
commit | ce9121de599dfaa17dd2216447084f0a92630985 (patch) | |
tree | fd05373fcfc464b738d05f93d4f33713ab47f607 | |
parent | d953fb28d9afc82098512b0bd5f99104a9c193b8 (diff) | |
parent | 8c0abd3c1adb204737851c06cb9eeba16791f2c4 (diff) | |
download | cmd2-git-ce9121de599dfaa17dd2216447084f0a92630985.tar.gz |
Merge branch 'master' into ac_parser
-rw-r--r-- | CHANGELOG.md | 3 | ||||
-rw-r--r-- | cmd2/cmd2.py | 6 | ||||
-rw-r--r-- | docs/unfreefeatures.rst | 7 | ||||
-rwxr-xr-x | examples/exit_code.py | 43 | ||||
-rw-r--r-- | tests/test_cmd2.py | 75 |
5 files changed, 134 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e08e5a75..7fbc7594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## 0.9.5 (TBD, 2018) * Bug Fixes * Fixed bug where ``get_all_commands`` could return non-callable attributes +* Enhancements + * Added ``exit_code`` attribute of ``cmd2.Cmd`` class + * Enables applications to return a non-zero exit code when exiting from ``cmdloop`` ## 0.9.4 (August 21, 2018) * Bug Fixes diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 2303e86c..c49ec0cc 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -523,6 +523,9 @@ class Cmd(cmd.Cmd): # This boolean flag determines whether or not the cmd2 application can interact with the clipboard self.can_clip = can_clip + # This determines if a non-zero exit code should be used when exiting the application + self.exit_code = None + # ----- Methods related to presenting output to the user ----- @property @@ -3228,6 +3231,9 @@ Script should contain one command per line, just like command would be typed in func() self.postloop() + if self.exit_code is not None: + sys.exit(self.exit_code) + ### # # plugin related functions diff --git a/docs/unfreefeatures.rst b/docs/unfreefeatures.rst index 41144c8f..cd27745d 100644 --- a/docs/unfreefeatures.rst +++ b/docs/unfreefeatures.rst @@ -182,3 +182,10 @@ Presents numbered options to user, as bash ``select``. 2. salty Sauce? 2 wheaties with salty sauce, yum! + + +Exit code to shell +================== +The ``self.exit_code`` attribute of your ``cmd2`` application controls +what exit code is sent to the shell when your application exits from +``cmdloop()``. diff --git a/examples/exit_code.py b/examples/exit_code.py new file mode 100755 index 00000000..8ae2d310 --- /dev/null +++ b/examples/exit_code.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# coding=utf-8 +"""A simple example demonstrating the following how to emit a non-zero exit code in your cmd2 application. +""" +import cmd2 +import sys +from typing import List + + +class ReplWithExitCode(cmd2.Cmd): + """ Example cmd2 application where we can specify an exit code when existing.""" + + def __init__(self): + super().__init__() + + @cmd2.with_argument_list + def do_exit(self, arg_list: List[str]) -> bool: + """Exit the application with an optional exit code. + +Usage: exit [exit_code] + Where: + * exit_code - integer exit code to return to the shell +""" + # If an argument was provided + if arg_list: + try: + self.exit_code = int(arg_list[0]) + except ValueError: + self.perror("{} isn't a valid integer exit code".format(arg_list[0])) + self.exit_code = -1 + + self._should_quit = True + return self._STOP_AND_EXIT + + def postloop(self) -> None: + """Hook method executed once when the cmdloop() method is about to return.""" + code = self.exit_code if self.exit_code is not None else 0 + self.poutput('{!r} exiting with code: {}'.format(sys.argv[0], code)) + + +if __name__ == '__main__': + app = ReplWithExitCode() + app.cmdloop() diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 7f97a795..b75cd102 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1928,3 +1928,78 @@ def test_get_help_topics(base_app): # Verify that the base app has no additional help_foo methods custom_help = base_app.get_help_topics() assert len(custom_help) == 0 + + +class ReplWithExitCode(cmd2.Cmd): + """ Example cmd2 application where we can specify an exit code when existing.""" + + def __init__(self): + super().__init__() + + @cmd2.with_argument_list + def do_exit(self, arg_list) -> bool: + """Exit the application with an optional exit code. + +Usage: exit [exit_code] + Where: + * exit_code - integer exit code to return to the shell +""" + # If an argument was provided + if arg_list: + try: + self.exit_code = int(arg_list[0]) + except ValueError: + self.perror("{} isn't a valid integer exit code".format(arg_list[0])) + self.exit_code = -1 + + self._should_quit = True + return self._STOP_AND_EXIT + + def postloop(self) -> None: + """Hook method executed once when the cmdloop() method is about to return.""" + code = self.exit_code if self.exit_code is not None else 0 + self.poutput('exiting with code: {}'.format(code)) + +@pytest.fixture +def exit_code_repl(): + app = ReplWithExitCode() + return app + +def test_exit_code_default(exit_code_repl): + # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test + app = exit_code_repl + app.use_rawinput = True + app.stdout = StdOut() + + # Mock out the input call so we don't actually wait for a user's response on stdin + m = mock.MagicMock(name='input', return_value='exit') + builtins.input = m + + # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args + testargs = ["prog"] + expected = 'exiting with code: 0\n' + with mock.patch.object(sys, 'argv', testargs): + # Run the command loop + app.cmdloop() + out = app.stdout.buffer + assert out == expected + +def test_exit_code_nonzero(exit_code_repl): + # Create a cmd2.Cmd() instance and make sure basic settings are like we want for test + app = exit_code_repl + app.use_rawinput = True + app.stdout = StdOut() + + # Mock out the input call so we don't actually wait for a user's response on stdin + m = mock.MagicMock(name='input', return_value='exit 23') + builtins.input = m + + # Need to patch sys.argv so cmd2 doesn't think it was called with arguments equal to the py.test args + testargs = ["prog"] + expected = 'exiting with code: 23\n' + with mock.patch.object(sys, 'argv', testargs): + # Run the command loop + with pytest.raises(SystemExit): + app.cmdloop() + out = app.stdout.buffer + assert out == expected |