codekingpro/portable-devtools
114k
1"""Utilities needed to emulate Python's interactive interpreter.2 3"""4 5# Inspired by similar code by Jeff Epler and Fredrik Lundh.6 7 8import builtins9import sys10import traceback11from codeop import CommandCompiler, compile_command12 13__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact",14 "compile_command"]15 16class InteractiveInterpreter:17 """Base class for InteractiveConsole.18 19 This class deals with parsing and interpreter state (the user's20 namespace); it doesn't deal with input buffering or prompting or21 input file naming (the filename is always passed in explicitly).22 23 """24 25 def __init__(self, locals=None):26 """Constructor.27 28 The optional 'locals' argument specifies a mapping to use as the29 namespace in which code will be executed; it defaults to a newly30 created dictionary with key "__name__" set to "__console__" and31 key "__doc__" set to None.32 33 """34 if locals is None:35 locals = {"__name__": "__console__", "__doc__": None}36 self.locals = locals37 self.compile = CommandCompiler()38 39 def runsource(self, source, filename="<input>", symbol="single"):40 """Compile and run some source in the interpreter.41 42 Arguments are as for compile_command().43 44 One of several things can happen:45 46 1) The input is incorrect; compile_command() raised an47 exception (SyntaxError or OverflowError). A syntax traceback48 will be printed by calling the showsyntaxerror() method.49 50 2) The input is incomplete, and more input is required;51 compile_command() returned None. Nothing happens.52 53 3) The input is complete; compile_command() returned a code54 object. The code is executed by calling self.runcode() (which55 also handles run-time exceptions, except for SystemExit).56 57 The return value is True in case 2, False in the other cases (unless58 an exception is raised). The return value can be used to59 decide whether to use sys.ps1 or sys.ps2 to prompt the next60 line.61 62 """63 try:64 code = self.compile(source, filename, symbol)65 except (OverflowError, SyntaxError, ValueError):66 # Case 167 self.showsyntaxerror(filename, source=source)68 return False69 70 if code is None:71 # Case 272 return True73 74 # Case 375 self.runcode(code)76 return False77 78 def runcode(self, code):79 """Execute a code object.80 81 When an exception occurs, self.showtraceback() is called to82 display a traceback. All exceptions are caught except83 SystemExit, which is reraised.84 85 A note about KeyboardInterrupt: this exception may occur86 elsewhere in this code, and may not always be caught. The87 caller should be prepared to deal with it.88 89 """90 try:91 exec(code, self.locals)92 except SystemExit:93 raise94 except:95 self.showtraceback()96 97 def showsyntaxerror(self, filename=None, **kwargs):98 """Display the syntax error that just occurred.99 100 This doesn't display a stack trace because there isn't one.101 102 If a filename is given, it is stuffed in the exception instead103 of what was there before (because Python's parser always uses104 "<string>" when reading from a string).105 106 The output is written by self.write(), below.107 108 """109 try:110 typ, value, tb = sys.exc_info()111 if filename and issubclass(typ, SyntaxError):112 value.filename = filename113 source = kwargs.pop('source', "")114 self._showtraceback(typ, value, None, source)115 finally:116 typ = value = tb = None117 118 def showtraceback(self):119 """Display the exception that just occurred.120 121 We remove the first stack item because it is our own code.122 123 The output is written by self.write(), below.124 125 """126 try:127 typ, value, tb = sys.exc_info()128 self._showtraceback(typ, value, tb.tb_next, "")129 finally:130 typ = value = tb = None131 132 def _showtraceback(self, typ, value, tb, source):133 sys.last_type = typ134 sys.last_traceback = tb135 value = value.with_traceback(tb)136 # Set the line of text that the exception refers to137 lines = source.splitlines()138 if (source and typ is SyntaxError139 and not value.text and value.lineno is not None140 and len(lines) >= value.lineno):141 value.text = lines[value.lineno - 1]142 sys.last_exc = sys.last_value = value143 if sys.excepthook is sys.__excepthook__:144 self._excepthook(typ, value, tb)145 else:146 # If someone has set sys.excepthook, we let that take precedence147 # over self.write148 try:149 sys.excepthook(typ, value, tb)150 except SystemExit:151 raise152 except BaseException as e:153 e.__context__ = None154 e = e.with_traceback(e.__traceback__.tb_next)155 print('Error in sys.excepthook:', file=sys.stderr)156 sys.__excepthook__(type(e), e, e.__traceback__)157 print(file=sys.stderr)158 print('Original exception was:', file=sys.stderr)159 sys.__excepthook__(typ, value, tb)160 161 def _excepthook(self, typ, value, tb):162 # This method is being overwritten in163 # _pyrepl.console.InteractiveColoredConsole164 lines = traceback.format_exception(typ, value, tb)165 self.write(''.join(lines))166 167 def write(self, data):168 """Write a string.169 170 The base implementation writes to sys.stderr; a subclass may171 replace this with a different implementation.172 173 """174 sys.stderr.write(data)175 176 177class InteractiveConsole(InteractiveInterpreter):178 """Closely emulate the behavior of the interactive Python interpreter.179 180 This class builds on InteractiveInterpreter and adds prompting181 using the familiar sys.ps1 and sys.ps2, and input buffering.182 183 """184 185 def __init__(self, locals=None, filename="<console>", *, local_exit=False):186 """Constructor.187 188 The optional locals argument will be passed to the189 InteractiveInterpreter base class.190 191 The optional filename argument should specify the (file)name192 of the input stream; it will show up in tracebacks.193 194 """195 InteractiveInterpreter.__init__(self, locals)196 self.filename = filename197 self.local_exit = local_exit198 self.resetbuffer()199 200 def resetbuffer(self):201 """Reset the input buffer."""202 self.buffer = []203 204 def interact(self, banner=None, exitmsg=None):205 """Closely emulate the interactive Python console.206 207 The optional banner argument specifies the banner to print208 before the first interaction; by default it prints a banner209 similar to the one printed by the real Python interpreter,210 followed by the current class name in parentheses (so as not211 to confuse this with the real interpreter -- since it's so212 close!).213 214 The optional exitmsg argument specifies the exit message215 printed when exiting. Pass the empty string to suppress216 printing an exit message. If exitmsg is not given or None,217 a default message is printed.218 219 """220 try:221 sys.ps1222 delete_ps1_after = False223 except AttributeError:224 sys.ps1 = ">>> "225 delete_ps1_after = True226 try:227 _ps2 = sys.ps2228 delete_ps2_after = False229 except AttributeError:230 sys.ps2 = "... "231 delete_ps2_after = True232 233 cprt = 'Type "help", "copyright", "credits" or "license" for more information.'234 if banner is None:235 self.write("Python %s on %s\n%s\n(%s)\n" %236 (sys.version, sys.platform, cprt,237 self.__class__.__name__))238 elif banner:239 self.write("%s\n" % str(banner))240 more = 0241 242 # When the user uses exit() or quit() in their interactive shell243 # they probably just want to exit the created shell, not the whole244 # process. exit and quit in builtins closes sys.stdin which makes245 # it super difficult to restore246 #247 # When self.local_exit is True, we overwrite the builtins so248 # exit() and quit() only raises SystemExit and we can catch that249 # to only exit the interactive shell250 251 _exit = None252 _quit = None253 254 if self.local_exit:255 if hasattr(builtins, "exit"):256 _exit = builtins.exit257 builtins.exit = Quitter("exit")258 259 if hasattr(builtins, "quit"):260 _quit = builtins.quit261 builtins.quit = Quitter("quit")262 263 try:264 while True:265 try:266 if more:267 prompt = sys.ps2268 else:269 prompt = sys.ps1270 try:271 line = self.raw_input(prompt)272 except EOFError:273 self.write("\n")274 break275 else:276 more = self.push(line)277 except KeyboardInterrupt:278 self.write("\nKeyboardInterrupt\n")279 self.resetbuffer()280 more = 0281 except SystemExit as e:282 if self.local_exit:283 self.write("\n")284 break285 else:286 raise e287 finally:288 # restore exit and quit in builtins if they were modified289 if _exit is not None:290 builtins.exit = _exit291 292 if _quit is not None:293 builtins.quit = _quit294 295 if delete_ps1_after:296 del sys.ps1297 298 if delete_ps2_after:299 del sys.ps2300 301 if exitmsg is None:302 self.write('now exiting %s...\n' % self.__class__.__name__)303 elif exitmsg != '':304 self.write('%s\n' % exitmsg)305 306 def push(self, line, filename=None, _symbol="single"):307 """Push a line to the interpreter.308 309 The line should not have a trailing newline; it may have310 internal newlines. The line is appended to a buffer and the311 interpreter's runsource() method is called with the312 concatenated contents of the buffer as source. If this313 indicates that the command was executed or invalid, the buffer314 is reset; otherwise, the command is incomplete, and the buffer315 is left as it was after the line was appended. The return316 value is 1 if more input is required, 0 if the line was dealt317 with in some way (this is the same as runsource()).318 319 """320 self.buffer.append(line)321 source = "\n".join(self.buffer)322 if filename is None:323 filename = self.filename324 more = self.runsource(source, filename, symbol=_symbol)325 if not more:326 self.resetbuffer()327 return more328 329 def raw_input(self, prompt=""):330 """Write a prompt and read a line.331 332 The returned line does not include the trailing newline.333 When the user enters the EOF key sequence, EOFError is raised.334 335 The base implementation uses the built-in function336 input(); a subclass may replace this with a different337 implementation.338 339 """340 return input(prompt)341 342 343class Quitter:344 def __init__(self, name):345 self.name = name346 if sys.platform == "win32":347 self.eof = 'Ctrl-Z plus Return'348 else:349 self.eof = 'Ctrl-D (i.e. EOF)'350 351 def __repr__(self):352 return f'Use {self.name} or {self.eof} to exit'353 354 def __call__(self, code=None):355 raise SystemExit(code)356 357 358def interact(banner=None, readfunc=None, local=None, exitmsg=None, local_exit=False):359 """Closely emulate the interactive Python interpreter.360 361 This is a backwards compatible interface to the InteractiveConsole362 class. When readfunc is not specified, it attempts to import the363 readline module to enable GNU readline if it is available.364 365 Arguments (all optional, all default to None):366 367 banner -- passed to InteractiveConsole.interact()368 readfunc -- if not None, replaces InteractiveConsole.raw_input()369 local -- passed to InteractiveInterpreter.__init__()370 exitmsg -- passed to InteractiveConsole.interact()371 local_exit -- passed to InteractiveConsole.__init__()372 373 """374 console = InteractiveConsole(local, local_exit=local_exit)375 if readfunc is not None:376 console.raw_input = readfunc377 else:378 try:379 import readline # noqa: F401380 except ImportError:381 pass382 console.interact(banner, exitmsg)383 384 385if __name__ == "__main__":386 import argparse387 388 parser = argparse.ArgumentParser(color=True)389 parser.add_argument('-q', action='store_true',390 help="don't print version and copyright messages")391 args = parser.parse_args()392 if args.q or sys.flags.quiet:393 banner = ''394 else:395 banner = None396 interact(banner)397 