console.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. # -*- coding: utf-8 -*-
  2. """
  3. werkzeug.debug.console
  4. ~~~~~~~~~~~~~~~~~~~~~~
  5. Interactive console support.
  6. :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
  7. :license: BSD.
  8. """
  9. import sys
  10. import code
  11. from types import CodeType
  12. from werkzeug.utils import escape
  13. from werkzeug.local import Local
  14. from werkzeug.debug.repr import debug_repr, dump, helper
  15. _local = Local()
  16. class HTMLStringO(object):
  17. """A StringO version that HTML escapes on write."""
  18. def __init__(self):
  19. self._buffer = []
  20. def isatty(self):
  21. return False
  22. def close(self):
  23. pass
  24. def flush(self):
  25. pass
  26. def seek(self, n, mode=0):
  27. pass
  28. def readline(self):
  29. if len(self._buffer) == 0:
  30. return ''
  31. ret = self._buffer[0]
  32. del self._buffer[0]
  33. return ret
  34. def reset(self):
  35. val = ''.join(self._buffer)
  36. del self._buffer[:]
  37. return val
  38. def _write(self, x):
  39. if isinstance(x, bytes):
  40. x = x.decode('utf-8', 'replace')
  41. self._buffer.append(x)
  42. def write(self, x):
  43. self._write(escape(x))
  44. def writelines(self, x):
  45. self._write(escape(''.join(x)))
  46. class ThreadedStream(object):
  47. """Thread-local wrapper for sys.stdout for the interactive console."""
  48. def push():
  49. if not isinstance(sys.stdout, ThreadedStream):
  50. sys.stdout = ThreadedStream()
  51. _local.stream = HTMLStringO()
  52. push = staticmethod(push)
  53. def fetch():
  54. try:
  55. stream = _local.stream
  56. except AttributeError:
  57. return ''
  58. return stream.reset()
  59. fetch = staticmethod(fetch)
  60. def displayhook(obj):
  61. try:
  62. stream = _local.stream
  63. except AttributeError:
  64. return _displayhook(obj)
  65. # stream._write bypasses escaping as debug_repr is
  66. # already generating HTML for us.
  67. if obj is not None:
  68. _local._current_ipy.locals['_'] = obj
  69. stream._write(debug_repr(obj))
  70. displayhook = staticmethod(displayhook)
  71. def __setattr__(self, name, value):
  72. raise AttributeError('read only attribute %s' % name)
  73. def __dir__(self):
  74. return dir(sys.__stdout__)
  75. def __getattribute__(self, name):
  76. if name == '__members__':
  77. return dir(sys.__stdout__)
  78. try:
  79. stream = _local.stream
  80. except AttributeError:
  81. stream = sys.__stdout__
  82. return getattr(stream, name)
  83. def __repr__(self):
  84. return repr(sys.__stdout__)
  85. # add the threaded stream as display hook
  86. _displayhook = sys.displayhook
  87. sys.displayhook = ThreadedStream.displayhook
  88. class _ConsoleLoader(object):
  89. def __init__(self):
  90. self._storage = {}
  91. def register(self, code, source):
  92. self._storage[id(code)] = source
  93. # register code objects of wrapped functions too.
  94. for var in code.co_consts:
  95. if isinstance(var, CodeType):
  96. self._storage[id(var)] = source
  97. def get_source_by_code(self, code):
  98. try:
  99. return self._storage[id(code)]
  100. except KeyError:
  101. pass
  102. def _wrap_compiler(console):
  103. compile = console.compile
  104. def func(source, filename, symbol):
  105. code = compile(source, filename, symbol)
  106. console.loader.register(code, source)
  107. return code
  108. console.compile = func
  109. class _InteractiveConsole(code.InteractiveInterpreter):
  110. def __init__(self, globals, locals):
  111. code.InteractiveInterpreter.__init__(self, locals)
  112. self.globals = dict(globals)
  113. self.globals['dump'] = dump
  114. self.globals['help'] = helper
  115. self.globals['__loader__'] = self.loader = _ConsoleLoader()
  116. self.more = False
  117. self.buffer = []
  118. _wrap_compiler(self)
  119. def runsource(self, source):
  120. source = source.rstrip() + '\n'
  121. ThreadedStream.push()
  122. prompt = self.more and '... ' or '>>> '
  123. try:
  124. source_to_eval = ''.join(self.buffer + [source])
  125. if code.InteractiveInterpreter.runsource(self,
  126. source_to_eval, '<debugger>', 'single'):
  127. self.more = True
  128. self.buffer.append(source)
  129. else:
  130. self.more = False
  131. del self.buffer[:]
  132. finally:
  133. output = ThreadedStream.fetch()
  134. return prompt + source + output
  135. def runcode(self, code):
  136. try:
  137. eval(code, self.globals, self.locals)
  138. except Exception:
  139. self.showtraceback()
  140. def showtraceback(self):
  141. from werkzeug.debug.tbtools import get_current_traceback
  142. tb = get_current_traceback(skip=1)
  143. sys.stdout._write(tb.render_summary())
  144. def showsyntaxerror(self, filename=None):
  145. from werkzeug.debug.tbtools import get_current_traceback
  146. tb = get_current_traceback(skip=4)
  147. sys.stdout._write(tb.render_summary())
  148. def write(self, data):
  149. sys.stdout.write(data)
  150. class Console(object):
  151. """An interactive console."""
  152. def __init__(self, globals=None, locals=None):
  153. if locals is None:
  154. locals = {}
  155. if globals is None:
  156. globals = {}
  157. self._ipy = _InteractiveConsole(globals, locals)
  158. def eval(self, code):
  159. _local._current_ipy = self._ipy
  160. old_sys_stdout = sys.stdout
  161. try:
  162. return self._ipy.runsource(code)
  163. finally:
  164. sys.stdout = old_sys_stdout