summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2018-08-24 11:24:51 -0400
committerGitHub <noreply@github.com>2018-08-24 11:24:51 -0400
commitce9121de599dfaa17dd2216447084f0a92630985 (patch)
treefd05373fcfc464b738d05f93d4f33713ab47f607
parentd953fb28d9afc82098512b0bd5f99104a9c193b8 (diff)
parent8c0abd3c1adb204737851c06cb9eeba16791f2c4 (diff)
downloadcmd2-git-ce9121de599dfaa17dd2216447084f0a92630985.tar.gz
Merge branch 'master' into ac_parser
-rw-r--r--CHANGELOG.md3
-rw-r--r--cmd2/cmd2.py6
-rw-r--r--docs/unfreefeatures.rst7
-rwxr-xr-xexamples/exit_code.py43
-rw-r--r--tests/test_cmd2.py75
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