Issue #27573 make the exit message configurable.

This commit is contained in:
Steven D'Aprano 2016-08-24 01:42:15 +10:00
parent f4d28d4385
commit 6877ed3560
3 changed files with 46 additions and 9 deletions

View File

@ -30,15 +30,19 @@ build applications which provide an interactive interpreter prompt.
``sys.ps1`` and ``sys.ps2``, and input buffering. ``sys.ps1`` and ``sys.ps2``, and input buffering.
.. function:: interact(banner=None, readfunc=None, local=None) .. function:: interact(banner=None, readfunc=None, local=None, exitmsg=None)
Convenience function to run a read-eval-print loop. This creates a new Convenience function to run a read-eval-print loop. This creates a new
instance of :class:`InteractiveConsole` and sets *readfunc* to be used as instance of :class:`InteractiveConsole` and sets *readfunc* to be used as
the :meth:`InteractiveConsole.raw_input` method, if provided. If *local* is the :meth:`InteractiveConsole.raw_input` method, if provided. If *local* is
provided, it is passed to the :class:`InteractiveConsole` constructor for provided, it is passed to the :class:`InteractiveConsole` constructor for
use as the default namespace for the interpreter loop. The :meth:`interact` use as the default namespace for the interpreter loop. The :meth:`interact`
method of the instance is then run with *banner* passed as the banner to method of the instance is then run with *banner* and *exitmsg* passed as the
use, if provided. The console object is discarded after use. banner and exit message to use, if provided. The console object is discarded
after use.
.. versionchanged:: 3.6
Added *exitmsg* parameter.
.. function:: compile_command(source, filename="<input>", symbol="single") .. function:: compile_command(source, filename="<input>", symbol="single")
@ -136,7 +140,7 @@ The :class:`InteractiveConsole` class is a subclass of
interpreter objects as well as the following additions. interpreter objects as well as the following additions.
.. method:: InteractiveConsole.interact(banner=None) .. method:: InteractiveConsole.interact(banner=None, exitmsg=None)
Closely emulate the interactive Python console. The optional *banner* argument Closely emulate the interactive Python console. The optional *banner* argument
specify the banner to print before the first interaction; by default it prints a specify the banner to print before the first interaction; by default it prints a
@ -144,11 +148,15 @@ interpreter objects as well as the following additions.
by the class name of the console object in parentheses (so as not to confuse by the class name of the console object in parentheses (so as not to confuse
this with the real interpreter -- since it's so close!). this with the real interpreter -- since it's so close!).
The optional *exitmsg* argument specifies an exit message printed when exiting.
Pass the empty string to suppress the exit message. If *exitmsg* is not given or
None, a default message is printed.
.. versionchanged:: 3.4 .. versionchanged:: 3.4
To suppress printing any banner, pass an empty string. To suppress printing any banner, pass an empty string.
.. versionchanged:: 3.6 .. versionchanged:: 3.6
Now prints a brief message when exiting. Print an exit message when exiting.
.. method:: InteractiveConsole.push(line) .. method:: InteractiveConsole.push(line)

View File

@ -186,7 +186,7 @@ class InteractiveConsole(InteractiveInterpreter):
"""Reset the input buffer.""" """Reset the input buffer."""
self.buffer = [] self.buffer = []
def interact(self, banner=None): def interact(self, banner=None, exitmsg=None):
"""Closely emulate the interactive Python console. """Closely emulate the interactive Python console.
The optional banner argument specifies the banner to print The optional banner argument specifies the banner to print
@ -196,6 +196,11 @@ class InteractiveConsole(InteractiveInterpreter):
to confuse this with the real interpreter -- since it's so to confuse this with the real interpreter -- since it's so
close!). close!).
The optional exitmsg argument specifies the exit message
printed when exiting. Pass the empty string to suppress
printing an exit message. If exitmsg is not given or None,
a default message is printed.
""" """
try: try:
sys.ps1 sys.ps1
@ -230,7 +235,10 @@ class InteractiveConsole(InteractiveInterpreter):
self.write("\nKeyboardInterrupt\n") self.write("\nKeyboardInterrupt\n")
self.resetbuffer() self.resetbuffer()
more = 0 more = 0
self.write('now exiting %s...\n' % self.__class__.__name__) if exitmsg is None:
self.write('now exiting %s...\n' % self.__class__.__name__)
elif exitmsg != '':
self.write('%s\n' % exitmsg)
def push(self, line): def push(self, line):
"""Push a line to the interpreter. """Push a line to the interpreter.
@ -268,7 +276,7 @@ class InteractiveConsole(InteractiveInterpreter):
def interact(banner=None, readfunc=None, local=None): def interact(banner=None, readfunc=None, local=None, exitmsg=None):
"""Closely emulate the interactive Python interpreter. """Closely emulate the interactive Python interpreter.
This is a backwards compatible interface to the InteractiveConsole This is a backwards compatible interface to the InteractiveConsole
@ -280,6 +288,7 @@ def interact(banner=None, readfunc=None, local=None):
banner -- passed to InteractiveConsole.interact() banner -- passed to InteractiveConsole.interact()
readfunc -- if not None, replaces InteractiveConsole.raw_input() readfunc -- if not None, replaces InteractiveConsole.raw_input()
local -- passed to InteractiveInterpreter.__init__() local -- passed to InteractiveInterpreter.__init__()
exitmsg -- passed to InteractiveConsole.interact()
""" """
console = InteractiveConsole(local) console = InteractiveConsole(local)
@ -290,7 +299,7 @@ def interact(banner=None, readfunc=None, local=None):
import readline import readline
except ImportError: except ImportError:
pass pass
console.interact(banner) console.interact(banner, exitmsg)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -80,6 +80,7 @@ class TestInteractiveConsole(unittest.TestCase):
self.assertEqual(len(self.stderr.method_calls), 2) self.assertEqual(len(self.stderr.method_calls), 2)
def test_exit_msg(self): def test_exit_msg(self):
# default exit message
self.infunc.side_effect = EOFError('Finished') self.infunc.side_effect = EOFError('Finished')
self.console.interact(banner='') self.console.interact(banner='')
self.assertEqual(len(self.stderr.method_calls), 2) self.assertEqual(len(self.stderr.method_calls), 2)
@ -87,6 +88,25 @@ class TestInteractiveConsole(unittest.TestCase):
expected = 'now exiting InteractiveConsole...\n' expected = 'now exiting InteractiveConsole...\n'
self.assertEqual(err_msg, ['write', (expected,), {}]) self.assertEqual(err_msg, ['write', (expected,), {}])
# no exit message
self.stderr.reset_mock()
self.infunc.side_effect = EOFError('Finished')
self.console.interact(banner='', exitmsg='')
self.assertEqual(len(self.stderr.method_calls), 1)
# custom exit message
self.stderr.reset_mock()
message = (
'bye! \N{GREEK SMALL LETTER ZETA}\N{CYRILLIC SMALL LETTER ZHE}'
)
self.infunc.side_effect = EOFError('Finished')
self.console.interact(banner='', exitmsg=message)
self.assertEqual(len(self.stderr.method_calls), 2)
err_msg = self.stderr.method_calls[1]
expected = message + '\n'
self.assertEqual(err_msg, ['write', (expected,), {}])
def test_cause_tb(self): def test_cause_tb(self):
self.infunc.side_effect = ["raise ValueError('') from AttributeError", self.infunc.side_effect = ["raise ValueError('') from AttributeError",
EOFError('Finished')] EOFError('Finished')]