Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 136 additions & 67 deletions Lib/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
# Inspired by similar code by Jeff Epler and Fredrik Lundh.


import builtins
import sys
import traceback
from codeop import CommandCompiler, compile_command

__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact",
"compile_command"]


class InteractiveInterpreter:
"""Base class for InteractiveConsole.

Expand All @@ -24,10 +26,10 @@ class InteractiveInterpreter:
def __init__(self, locals=None):
"""Constructor.

The optional 'locals' argument specifies the dictionary in
which code will be executed; it defaults to a newly created
dictionary with key "__name__" set to "__console__" and key
"__doc__" set to None.
The optional 'locals' argument specifies a mapping to use as the
namespace in which code will be executed; it defaults to a newly
created dictionary with key "__name__" set to "__console__" and
key "__doc__" set to None.

"""
if locals is None:
Expand Down Expand Up @@ -63,7 +65,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
code = self.compile(source, filename, symbol)
except (OverflowError, SyntaxError, ValueError):
# Case 1
self.showsyntaxerror(filename)
self.showsyntaxerror(filename, source=source)
return False

if code is None:
Expand Down Expand Up @@ -93,7 +95,7 @@ def runcode(self, code):
except:
self.showtraceback()

def showsyntaxerror(self, filename=None):
def showsyntaxerror(self, filename=None, **kwargs):
"""Display the syntax error that just occurred.

This doesn't display a stack trace because there isn't one.
Expand All @@ -105,29 +107,14 @@ def showsyntaxerror(self, filename=None):
The output is written by self.write(), below.

"""
type, value, tb = sys.exc_info()
sys.last_exc = value
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
if filename and type is SyntaxError:
# Work hard to stuff the correct filename in the exception
try:
msg, (dummy_filename, lineno, offset, line) = value.args
except ValueError:
# Not the format we expect; leave it alone
pass
else:
# Stuff in the right filename
value = SyntaxError(msg, (filename, lineno, offset, line))
sys.last_exc = sys.last_value = value
if sys.excepthook is sys.__excepthook__:
lines = traceback.format_exception_only(type, value)
self.write(''.join(lines))
else:
# If someone has set sys.excepthook, we let that take precedence
# over self.write
sys.excepthook(type, value, tb)
try:
typ, value, tb = sys.exc_info()
if filename and issubclass(typ, SyntaxError):
value.filename = filename
source = kwargs.pop('source', "")
self._showtraceback(typ, value, None, source)
finally:
typ = value = tb = None

def showtraceback(self):
"""Display the exception that just occurred.
Expand All @@ -137,19 +124,46 @@ def showtraceback(self):
The output is written by self.write(), below.

"""
sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
sys.last_traceback = last_tb
sys.last_exc = ei[1]
try:
lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
if sys.excepthook is sys.__excepthook__:
self.write(''.join(lines))
else:
# If someone has set sys.excepthook, we let that take precedence
# over self.write
sys.excepthook(ei[0], ei[1], last_tb)
typ, value, tb = sys.exc_info()
self._showtraceback(typ, value, tb.tb_next, '')
finally:
last_tb = ei = None
typ = value = tb = None

def _showtraceback(self, typ, value, tb, source):
sys.last_type = typ
sys.last_traceback = tb
value = value.with_traceback(tb)
# Set the line of text that the exception refers to
lines = source.splitlines()
if (source and typ is SyntaxError
and not value.text and value.lineno is not None
and len(lines) >= value.lineno):
value.text = lines[value.lineno - 1]
sys.last_exc = sys.last_value = value = value.with_traceback(tb)
if sys.excepthook is sys.__excepthook__:
self._excepthook(typ, value, tb)
else:
# If someone has set sys.excepthook, we let that take precedence
# over self.write
try:
sys.excepthook(typ, value, tb)
except SystemExit:
raise
except BaseException as e:
e.__context__ = None
e = e.with_traceback(e.__traceback__.tb_next)
print('Error in sys.excepthook:', file=sys.stderr)
sys.__excepthook__(type(e), e, e.__traceback__)
print(file=sys.stderr)
print('Original exception was:', file=sys.stderr)
sys.__excepthook__(typ, value, tb)

def _excepthook(self, typ, value, tb):
# This method is being overwritten in
# _pyrepl.console.InteractiveColoredConsole
lines = traceback.format_exception(typ, value, tb)
self.write(''.join(lines))

def write(self, data):
"""Write a string.
Expand All @@ -169,7 +183,7 @@ class InteractiveConsole(InteractiveInterpreter):

"""

def __init__(self, locals=None, filename="<console>"):
def __init__(self, locals=None, filename="<console>", *, local_exit=False):
"""Constructor.

The optional locals argument will be passed to the
Expand All @@ -181,6 +195,7 @@ def __init__(self, locals=None, filename="<console>"):
"""
InteractiveInterpreter.__init__(self, locals)
self.filename = filename
self.local_exit = local_exit
self.resetbuffer()

def resetbuffer(self):
Expand Down Expand Up @@ -219,29 +234,66 @@ def interact(self, banner=None, exitmsg=None):
elif banner:
self.write("%s\n" % str(banner))
more = 0
while 1:
try:
if more:
prompt = sys.ps2
else:
prompt = sys.ps1

# When the user uses exit() or quit() in their interactive shell
# they probably just want to exit the created shell, not the whole
# process. exit and quit in builtins closes sys.stdin which makes
# it super difficult to restore
#
# When self.local_exit is True, we overwrite the builtins so
# exit() and quit() only raises SystemExit and we can catch that
# to only exit the interactive shell

_exit = None
_quit = None

if self.local_exit:
if hasattr(builtins, "exit"):
_exit = builtins.exit
builtins.exit = Quitter("exit")

if hasattr(builtins, "quit"):
_quit = builtins.quit
builtins.quit = Quitter("quit")

try:
while True:
try:
line = self.raw_input(prompt)
except EOFError:
self.write("\n")
break
else:
more = self.push(line)
except KeyboardInterrupt:
self.write("\nKeyboardInterrupt\n")
self.resetbuffer()
more = 0
if exitmsg is None:
self.write('now exiting %s...\n' % self.__class__.__name__)
elif exitmsg != '':
self.write('%s\n' % exitmsg)

def push(self, line):
if more:
prompt = sys.ps2
else:
prompt = sys.ps1
try:
line = self.raw_input(prompt)
except EOFError:
self.write("\n")
break
else:
more = self.push(line)
except KeyboardInterrupt:
self.write("\nKeyboardInterrupt\n")
self.resetbuffer()
more = 0
except SystemExit as e:
if self.local_exit:
self.write("\n")
break
else:
raise e
finally:
# restore exit and quit in builtins if they were modified
if _exit is not None:
builtins.exit = _exit

if _quit is not None:
builtins.quit = _quit

if exitmsg is None:
self.write('now exiting %s...\n' % self.__class__.__name__)
elif exitmsg != '':
self.write('%s\n' % exitmsg)

def push(self, line, filename=None, _symbol="single"):
"""Push a line to the interpreter.

The line should not have a trailing newline; it may have
Expand All @@ -257,7 +309,9 @@ def push(self, line):
"""
self.buffer.append(line)
source = "\n".join(self.buffer)
more = self.runsource(source, self.filename)
if filename is None:
filename = self.filename
more = self.runsource(source, filename, symbol=_symbol)
if not more:
self.resetbuffer()
return more
Expand All @@ -276,8 +330,22 @@ def raw_input(self, prompt=""):
return input(prompt)


class Quitter:
def __init__(self, name):
self.name = name
if sys.platform == "win32":
self.eof = 'Ctrl-Z plus Return'
else:
self.eof = 'Ctrl-D (i.e. EOF)'

def __repr__(self):
return f'Use {self.name} or {self.eof} to exit'

def __call__(self, code=None):
raise SystemExit(code)


def interact(banner=None, readfunc=None, local=None, exitmsg=None):
def interact(banner=None, readfunc=None, local=None, exitmsg=None, local_exit=False):
"""Closely emulate the interactive Python interpreter.

This is a backwards compatible interface to the InteractiveConsole
Expand All @@ -290,9 +358,10 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None):
readfunc -- if not None, replaces InteractiveConsole.raw_input()
local -- passed to InteractiveInterpreter.__init__()
exitmsg -- passed to InteractiveConsole.interact()
local_exit -- passed to InteractiveConsole.__init__()

"""
console = InteractiveConsole(local)
console = InteractiveConsole(local, local_exit=local_exit)
if readfunc is not None:
console.raw_input = readfunc
else:
Expand All @@ -308,7 +377,7 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None):

parser = argparse.ArgumentParser()
parser.add_argument('-q', action='store_true',
help="don't print version and copyright messages")
help="don't print version and copyright messages")
args = parser.parse_args()
if args.q or sys.flags.quiet:
banner = ''
Expand Down
Loading
Loading