• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 builtins
9import sys
10import traceback
11from codeop import CommandCompiler, compile_command
12
13__all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact",
14           "compile_command"]
15
16
17class InteractiveInterpreter:
18    """Base class for InteractiveConsole.
19
20    This class deals with parsing and interpreter state (the user's
21    namespace); it doesn't deal with input buffering or prompting or
22    input file naming (the filename is always passed in explicitly).
23
24    """
25
26    def __init__(self, locals=None):
27        """Constructor.
28
29        The optional 'locals' argument specifies a mapping to use as the
30        namespace in which code will be executed; it defaults to a newly
31        created dictionary with key "__name__" set to "__console__" and
32        key "__doc__" set to None.
33
34        """
35        if locals is None:
36            locals = {"__name__": "__console__", "__doc__": None}
37        self.locals = locals
38        self.compile = CommandCompiler()
39
40    def runsource(self, source, filename="<input>", symbol="single"):
41        """Compile and run some source in the interpreter.
42
43        Arguments are as for compile_command().
44
45        One of several things can happen:
46
47        1) The input is incorrect; compile_command() raised an
48        exception (SyntaxError or OverflowError).  A syntax traceback
49        will be printed by calling the showsyntaxerror() method.
50
51        2) The input is incomplete, and more input is required;
52        compile_command() returned None.  Nothing happens.
53
54        3) The input is complete; compile_command() returned a code
55        object.  The code is executed by calling self.runcode() (which
56        also handles run-time exceptions, except for SystemExit).
57
58        The return value is True in case 2, False in the other cases (unless
59        an exception is raised).  The return value can be used to
60        decide whether to use sys.ps1 or sys.ps2 to prompt the next
61        line.
62
63        """
64        try:
65            code = self.compile(source, filename, symbol)
66        except (OverflowError, SyntaxError, ValueError):
67            # Case 1
68            self.showsyntaxerror(filename, source=source)
69            return False
70
71        if code is None:
72            # Case 2
73            return True
74
75        # Case 3
76        self.runcode(code)
77        return False
78
79    def runcode(self, code):
80        """Execute a code object.
81
82        When an exception occurs, self.showtraceback() is called to
83        display a traceback.  All exceptions are caught except
84        SystemExit, which is reraised.
85
86        A note about KeyboardInterrupt: this exception may occur
87        elsewhere in this code, and may not always be caught.  The
88        caller should be prepared to deal with it.
89
90        """
91        try:
92            exec(code, self.locals)
93        except SystemExit:
94            raise
95        except:
96            self.showtraceback()
97
98    def showsyntaxerror(self, filename=None, **kwargs):
99        """Display the syntax error that just occurred.
100
101        This doesn't display a stack trace because there isn't one.
102
103        If a filename is given, it is stuffed in the exception instead
104        of what was there before (because Python's parser always uses
105        "<string>" when reading from a string).
106
107        The output is written by self.write(), below.
108
109        """
110        try:
111            typ, value, tb = sys.exc_info()
112            if filename and issubclass(typ, SyntaxError):
113                value.filename = filename
114            source = kwargs.pop('source', "")
115            self._showtraceback(typ, value, None, source)
116        finally:
117            typ = value = tb = None
118
119    def showtraceback(self):
120        """Display the exception that just occurred.
121
122        We remove the first stack item because it is our own code.
123
124        The output is written by self.write(), below.
125
126        """
127        try:
128            typ, value, tb = sys.exc_info()
129            self._showtraceback(typ, value, tb.tb_next, '')
130        finally:
131            typ = value = tb = None
132
133    def _showtraceback(self, typ, value, tb, source):
134        sys.last_type = typ
135        sys.last_traceback = tb
136        value = value.with_traceback(tb)
137        # Set the line of text that the exception refers to
138        lines = source.splitlines()
139        if (source and typ is SyntaxError
140                and not value.text and value.lineno is not None
141                and len(lines) >= value.lineno):
142            value.text = lines[value.lineno - 1]
143        sys.last_exc = sys.last_value = value = value.with_traceback(tb)
144        if sys.excepthook is sys.__excepthook__:
145            self._excepthook(typ, value, tb)
146        else:
147            # If someone has set sys.excepthook, we let that take precedence
148            # over self.write
149            try:
150                sys.excepthook(typ, value, tb)
151            except SystemExit:
152                raise
153            except BaseException as e:
154                e.__context__ = None
155                e = e.with_traceback(e.__traceback__.tb_next)
156                print('Error in sys.excepthook:', file=sys.stderr)
157                sys.__excepthook__(type(e), e, e.__traceback__)
158                print(file=sys.stderr)
159                print('Original exception was:', file=sys.stderr)
160                sys.__excepthook__(typ, value, tb)
161
162    def _excepthook(self, typ, value, tb):
163        # This method is being overwritten in
164        # _pyrepl.console.InteractiveColoredConsole
165        lines = traceback.format_exception(typ, value, tb)
166        self.write(''.join(lines))
167
168    def write(self, data):
169        """Write a string.
170
171        The base implementation writes to sys.stderr; a subclass may
172        replace this with a different implementation.
173
174        """
175        sys.stderr.write(data)
176
177
178class InteractiveConsole(InteractiveInterpreter):
179    """Closely emulate the behavior of the interactive Python interpreter.
180
181    This class builds on InteractiveInterpreter and adds prompting
182    using the familiar sys.ps1 and sys.ps2, and input buffering.
183
184    """
185
186    def __init__(self, locals=None, filename="<console>", *, local_exit=False):
187        """Constructor.
188
189        The optional locals argument will be passed to the
190        InteractiveInterpreter base class.
191
192        The optional filename argument should specify the (file)name
193        of the input stream; it will show up in tracebacks.
194
195        """
196        InteractiveInterpreter.__init__(self, locals)
197        self.filename = filename
198        self.local_exit = local_exit
199        self.resetbuffer()
200
201    def resetbuffer(self):
202        """Reset the input buffer."""
203        self.buffer = []
204
205    def interact(self, banner=None, exitmsg=None):
206        """Closely emulate the interactive Python console.
207
208        The optional banner argument specifies the banner to print
209        before the first interaction; by default it prints a banner
210        similar to the one printed by the real Python interpreter,
211        followed by the current class name in parentheses (so as not
212        to confuse this with the real interpreter -- since it's so
213        close!).
214
215        The optional exitmsg argument specifies the exit message
216        printed when exiting. Pass the empty string to suppress
217        printing an exit message. If exitmsg is not given or None,
218        a default message is printed.
219
220        """
221        try:
222            sys.ps1
223        except AttributeError:
224            sys.ps1 = ">>> "
225        try:
226            sys.ps2
227        except AttributeError:
228            sys.ps2 = "... "
229        cprt = 'Type "help", "copyright", "credits" or "license" for more information.'
230        if banner is None:
231            self.write("Python %s on %s\n%s\n(%s)\n" %
232                       (sys.version, sys.platform, cprt,
233                        self.__class__.__name__))
234        elif banner:
235            self.write("%s\n" % str(banner))
236        more = 0
237
238        # When the user uses exit() or quit() in their interactive shell
239        # they probably just want to exit the created shell, not the whole
240        # process. exit and quit in builtins closes sys.stdin which makes
241        # it super difficult to restore
242        #
243        # When self.local_exit is True, we overwrite the builtins so
244        # exit() and quit() only raises SystemExit and we can catch that
245        # to only exit the interactive shell
246
247        _exit = None
248        _quit = None
249
250        if self.local_exit:
251            if hasattr(builtins, "exit"):
252                _exit = builtins.exit
253                builtins.exit = Quitter("exit")
254
255            if hasattr(builtins, "quit"):
256                _quit = builtins.quit
257                builtins.quit = Quitter("quit")
258
259        try:
260            while True:
261                try:
262                    if more:
263                        prompt = sys.ps2
264                    else:
265                        prompt = sys.ps1
266                    try:
267                        line = self.raw_input(prompt)
268                    except EOFError:
269                        self.write("\n")
270                        break
271                    else:
272                        more = self.push(line)
273                except KeyboardInterrupt:
274                    self.write("\nKeyboardInterrupt\n")
275                    self.resetbuffer()
276                    more = 0
277                except SystemExit as e:
278                    if self.local_exit:
279                        self.write("\n")
280                        break
281                    else:
282                        raise e
283        finally:
284            # restore exit and quit in builtins if they were modified
285            if _exit is not None:
286                builtins.exit = _exit
287
288            if _quit is not None:
289                builtins.quit = _quit
290
291            if exitmsg is None:
292                self.write('now exiting %s...\n' % self.__class__.__name__)
293            elif exitmsg != '':
294                self.write('%s\n' % exitmsg)
295
296    def push(self, line, filename=None, _symbol="single"):
297        """Push a line to the interpreter.
298
299        The line should not have a trailing newline; it may have
300        internal newlines.  The line is appended to a buffer and the
301        interpreter's runsource() method is called with the
302        concatenated contents of the buffer as source.  If this
303        indicates that the command was executed or invalid, the buffer
304        is reset; otherwise, the command is incomplete, and the buffer
305        is left as it was after the line was appended.  The return
306        value is 1 if more input is required, 0 if the line was dealt
307        with in some way (this is the same as runsource()).
308
309        """
310        self.buffer.append(line)
311        source = "\n".join(self.buffer)
312        if filename is None:
313            filename = self.filename
314        more = self.runsource(source, filename, symbol=_symbol)
315        if not more:
316            self.resetbuffer()
317        return more
318
319    def raw_input(self, prompt=""):
320        """Write a prompt and read a line.
321
322        The returned line does not include the trailing newline.
323        When the user enters the EOF key sequence, EOFError is raised.
324
325        The base implementation uses the built-in function
326        input(); a subclass may replace this with a different
327        implementation.
328
329        """
330        return input(prompt)
331
332
333class Quitter:
334    def __init__(self, name):
335        self.name = name
336        if sys.platform == "win32":
337            self.eof = 'Ctrl-Z plus Return'
338        else:
339            self.eof = 'Ctrl-D (i.e. EOF)'
340
341    def __repr__(self):
342        return f'Use {self.name} or {self.eof} to exit'
343
344    def __call__(self, code=None):
345        raise SystemExit(code)
346
347
348def interact(banner=None, readfunc=None, local=None, exitmsg=None, local_exit=False):
349    """Closely emulate the interactive Python interpreter.
350
351    This is a backwards compatible interface to the InteractiveConsole
352    class.  When readfunc is not specified, it attempts to import the
353    readline module to enable GNU readline if it is available.
354
355    Arguments (all optional, all default to None):
356
357    banner -- passed to InteractiveConsole.interact()
358    readfunc -- if not None, replaces InteractiveConsole.raw_input()
359    local -- passed to InteractiveInterpreter.__init__()
360    exitmsg -- passed to InteractiveConsole.interact()
361    local_exit -- passed to InteractiveConsole.__init__()
362
363    """
364    console = InteractiveConsole(local, local_exit=local_exit)
365    if readfunc is not None:
366        console.raw_input = readfunc
367    else:
368        try:
369            import readline
370        except ImportError:
371            pass
372    console.interact(banner, exitmsg)
373
374
375if __name__ == "__main__":
376    import argparse
377
378    parser = argparse.ArgumentParser()
379    parser.add_argument('-q', action='store_true',
380                        help="don't print version and copyright messages")
381    args = parser.parse_args()
382    if args.q or sys.flags.quiet:
383        banner = ''
384    else:
385        banner = None
386    interact(banner)
387