| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 21b9a54 commit 38f1ed6
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,7 @@ | |||
| 25 | 25 | ||
| 26 | 26 | from __future__ import unicode_literals | |
| 27 | 27 | import io | |
| 28 | + import linecache | ||
| 28 | 29 | import os | |
| 29 | 30 | import stat | |
| 30 | 31 | from itertools import islice | |
@@ -232,3 +233,74 @@ def append_reload_and_write(self, s, filename, encoding): | |||
| 232 | 233 | # Make sure that entries contains at least one element. If the | |
| 233 | 234 | # file and s are empty, this can occur. | |
| 234 | 235 | self.entries = [''] | |
| 236 | + | ||
| 237 | + class BPythonLinecache(dict): | ||
| 238 | + """Replaces the cache dict in the standard-library linecache module, | ||
| 239 | + to also remember (in an unerasable way) bpython console input.""" | ||
| 240 | + | ||
| 241 | + def __init__(self, *args, **kwargs): | ||
| 242 | + dict.__init__(self, *args, **kwargs) | ||
| 243 | + self.bpython_history = [] | ||
| 244 | + | ||
| 245 | + def is_bpython_filename(self, fname): | ||
| 246 | + try: | ||
| 247 | + return fname.startswith('<bpython-input-') | ||
| 248 | + except AttributeError: | ||
| 249 | + # In case the key isn't a string | ||
| 250 | + return False | ||
| 251 | + | ||
| 252 | + def get_bpython_history(self, key): | ||
| 253 | + """Given a filename provided by remember_bpython_input, | ||
| 254 | + returns the associated source string.""" | ||
| 255 | + try: | ||
| 256 | + idx = int(key.split('-')[2][:-1]) | ||
| 257 | + return self.bpython_history[idx] | ||
| 258 | + except (IndexError, ValueError): | ||
| 259 | + raise KeyError | ||
| 260 | + | ||
| 261 | + def remember_bpython_input(self, source): | ||
| 262 | + """Remembers a string of source code, and returns | ||
| 263 | + a fake filename to use to retrieve it later.""" | ||
| 264 | + filename = '<bpython-input-%s>' % len(self.bpython_history) | ||
| 265 | + self.bpython_history.append((len(source), None, | ||
| 266 | + source.splitlines(True), filename)) | ||
| 267 | + return filename | ||
| 268 | + | ||
| 269 | + def __getitem__(self, key): | ||
| 270 | + if self.is_bpython_filename(key): | ||
| 271 | + return self.get_bpython_history(key) | ||
| 272 | + return dict.__getitem__(self, key) | ||
| 273 | + | ||
| 274 | + def __contains__(self, key): | ||
| 275 | + if self.is_bpython_filename(key): | ||
| 276 | + try: | ||
| 277 | + self.get_bpython_history(key) | ||
| 278 | + return True | ||
| 279 | + except KeyError: | ||
| 280 | + return False | ||
| 281 | + return dict.__contains__(self, key) | ||
| 282 | + | ||
| 283 | + def __delitem__(self, key): | ||
| 284 | + if not self.is_bpython_filename(key): | ||
| 285 | + return dict.__delitem__(self, key) | ||
| 286 | + | ||
| 287 | + def _bpython_clear_linecache(): | ||
| 288 | + try: | ||
| 289 | + bpython_history = linecache.cache.bpython_history | ||
| 290 | + except AttributeError: | ||
| 291 | + bpython_history = [] | ||
| 292 | + linecache.cache = BPythonLinecache() | ||
| 293 | + linecache.cache.bpython_history = bpython_history | ||
| 294 | + | ||
| 295 | + # Monkey-patch the linecache module so that we're able | ||
| 296 | + # to hold our command history there and have it persist | ||
| 297 | + linecache.cache = BPythonLinecache(linecache.cache) | ||
| 298 | + linecache.clearcache = _bpython_clear_linecache | ||
| 299 | + | ||
| 300 | + def filename_for_console_input(code_string): | ||
| 301 | + """Remembers a string of source code, and returns | ||
| 302 | + a fake filename to use to retrieve it later.""" | ||
| 303 | + try: | ||
| 304 | + return linecache.cache.remember_bpython_input(code_string) | ||
| 305 | + except AttributeError: | ||
| 306 | + return '<input>' | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -47,7 +47,7 @@ | |||
| 47 | 47 | from bpython.clipboard import get_clipboard, CopyFailed | |
| 48 | 48 | from bpython.config import getpreferredencoding | |
| 49 | 49 | from bpython.formatter import Parenthesis | |
| 50 | - from bpython.history import History | ||
| 50 | + from bpython.history import History, filename_for_console_input | ||
| 51 | 51 | from bpython.paste import PasteHelper, PastePinnwand, PasteFailed | |
| 52 | 52 | from bpython.translations import _, ngettext | |
| 53 | 53 | ||
@@ -94,7 +94,7 @@ def __init__(self, locals=None, encoding=None): | |||
| 94 | 94 | def reset_running_time(self): | |
| 95 | 95 | self.running_time = 0 | |
| 96 | 96 | ||
| 97 | - def runsource(self, source, filename='<input>', symbol='single', | ||
| 97 | + def runsource(self, source, filename=None, symbol='single', | ||
| 98 | 98 | encode=True): | |
| 99 | 99 | """Execute Python code. | |
| 100 | 100 | ||
@@ -104,6 +104,8 @@ def runsource(self, source, filename='<input>', symbol='single', | |||
| 104 | 104 | if not py3 and encode: | |
| 105 | 105 | source = u'# coding: %s\n%s' % (self.encoding, source) | |
| 106 | 106 | source = source.encode(self.encoding) | |
| 107 | + if filename is None: | ||
| 108 | + filename = filename_for_console_input(source) | ||
| 107 | 109 | with self.timer: | |
| 108 | 110 | return code.InteractiveInterpreter.runsource(self, source, | |
| 109 | 111 | filename, symbol) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -17,6 +17,14 @@ | |||
| 17 | 17 | ||
| 18 | 18 | pypy = 'PyPy' in sys.version | |
| 19 | 19 | ||
| 20 | + def _last_console_filename(): | ||
| 21 | + """Returns the last 'filename' used for console input | ||
| 22 | + (as will be displayed in a traceback).""" | ||
| 23 | + import linecache | ||
| 24 | + try: | ||
| 25 | + return '<bpython-input-%s>' % (len(linecache.cache.bpython_history) - 1) | ||
| 26 | + except AttributeError: | ||
| 27 | + return '<input>' | ||
| 20 | 28 | ||
| 21 | 29 | class TestInterpreter(unittest.TestCase): | |
| 22 | 30 | def test_syntaxerror(self): | |
@@ -30,11 +38,11 @@ def append_to_a(message): | |||
| 30 | 38 | i.runsource('1.1.1.1') | |
| 31 | 39 | ||
| 32 | 40 | if pypy: | |
| 33 | - expected = ' File ' + green('"<input>"') + ', line ' + \ | ||
| 41 | + expected = ' File ' + green('"%s"' % _last_console_filename()) + ', line ' + \ | ||
| 34 | 42 | bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ | |
| 35 | 43 | bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' | |
| 36 | 44 | else: | |
| 37 | - expected = ' File ' + green('"<input>"') + ', line ' + \ | ||
| 45 | + expected = ' File ' + green('"%s"' % _last_console_filename()) + ', line ' + \ | ||
| 38 | 46 | bold(magenta('1')) + '\n 1.1.1.1\n ^\n' + \ | |
| 39 | 47 | bold(red('SyntaxError')) + ': ' + cyan('invalid syntax') + '\n' | |
| 40 | 48 | ||
@@ -56,16 +64,16 @@ def f(): | |||
| 56 | 64 | def g(): | |
| 57 | 65 | return f() | |
| 58 | 66 | ||
| 59 | - i.runsource('g()') | ||
| 67 | + i.runsource('g()', encode=False) | ||
| 60 | 68 | ||
| 61 | 69 | if pypy: | |
| 62 | 70 | global_not_found = "global name 'g' is not defined" | |
| 63 | 71 | else: | |
| 64 | 72 | global_not_found = "name 'g' is not defined" | |
| 65 | 73 | ||
| 66 | 74 | expected = 'Traceback (most recent call last):\n File ' + \ | |
| 67 | - green('"<input>"') + ', line ' + bold(magenta('1')) + ', in ' + \ | ||
| 68 | - cyan('<module>') + '\n' + bold(red('NameError')) + ': ' + \ | ||
| 75 | + green('"%s"' % _last_console_filename()) + ', line ' + bold(magenta('1')) + ', in ' + \ | ||
| 76 | + cyan('<module>') + '\n g()\n' + bold(red('NameError')) + ': ' + \ | ||
| 69 | 77 | cyan(global_not_found) + '\n' | |
| 70 | 78 | ||
| 71 | 79 | self.assertMultiLineEqual(str(plain('').join(a)), str(expected)) | |
@@ -106,3 +114,11 @@ def test_runsource_unicode(self): | |||
| 106 | 114 | i.runsource("a = u'\xfe'", encode=True) | |
| 107 | 115 | self.assertIsInstance(i.locals['a'], type(u'')) | |
| 108 | 116 | self.assertEqual(i.locals['a'], u"\xfe") | |
| 117 | + | ||
| 118 | + def test_getsource_works_on_interactively_defined_functions(self): | ||
| 119 | + source = 'def foo(x):\n return x + 1\n' | ||
| 120 | + i = interpreter.Interp() | ||
| 121 | + i.runsource(source) | ||
| 122 | + import inspect | ||
| 123 | + inspected_source = inspect.getsource(i.locals['foo']) | ||
| 124 | + self.assertEquals(inspected_source, source) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments