diff options
Diffstat (limited to 'websdk/werkzeug/debug/tbtools.py')
-rw-r--r-- | websdk/werkzeug/debug/tbtools.py | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/websdk/werkzeug/debug/tbtools.py b/websdk/werkzeug/debug/tbtools.py new file mode 100644 index 0000000..4bcba21 --- /dev/null +++ b/websdk/werkzeug/debug/tbtools.py @@ -0,0 +1,483 @@ +# -*- coding: utf-8 -*- +""" + werkzeug.debug.tbtools + ~~~~~~~~~~~~~~~~~~~~~~ + + This module provides various traceback related utility functions. + + :copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details. + :license: BSD. +""" +import re +import os +import sys +import inspect +import traceback +import codecs +from tokenize import TokenError +from werkzeug.utils import cached_property, escape +from werkzeug.debug.console import Console + +_coding_re = re.compile(r'coding[:=]\s*([-\w.]+)') +_line_re = re.compile(r'^(.*?)$(?m)') +_funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') +UTF8_COOKIE = '\xef\xbb\xbf' + +system_exceptions = (SystemExit, KeyboardInterrupt) +try: + system_exceptions += (GeneratorExit,) +except NameError: + pass + + +HEADER = u'''\ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> + <head> + <title>%(title)s // Werkzeug Debugger</title> + <link rel="stylesheet" href="?__debugger__=yes&cmd=resource&f=style.css" type="text/css"> + <script type="text/javascript" src="?__debugger__=yes&cmd=resource&f=jquery.js"></script> + <script type="text/javascript" src="?__debugger__=yes&cmd=resource&f=debugger.js"></script> + <script type="text/javascript"> + var TRACEBACK = %(traceback_id)d, + CONSOLE_MODE = %(console)s, + EVALEX = %(evalex)s, + SECRET = "%(secret)s"; + </script> + </head> + <body> + <div class="debugger"> +''' +FOOTER = u'''\ + <div class="footer"> + Brought to you by <strong class="arthur">DON'T PANIC</strong>, your + friendly Werkzeug powered traceback interpreter. + </div> + </div> + </body> +</html> +''' + +PAGE_HTML = HEADER + u'''\ +<h1>%(exception_type)s</h1> +<div class="detail"> + <p class="errormsg">%(exception)s</p> +</div> +<h2 class="traceback">Traceback <em>(most recent call last)</em></h2> +%(summary)s +<div class="plain"> + <form action="%(lodgeit_url)s" method="post"> + <p> + <input type="hidden" name="language" value="pytb"> + This is the Copy/Paste friendly version of the traceback. <span + class="pastemessage">You can also paste this traceback into LodgeIt: + <input type="submit" value="create paste"></span> + </p> + <textarea cols="50" rows="10" name="code" readonly>%(plaintext)s</textarea> + </form> +</div> +<div class="explanation"> + The debugger caught an exception in your WSGI application. You can now + look at the traceback which led to the error. <span class="nojavascript"> + If you enable JavaScript you can also use additional features such as code + execution (if the evalex feature is enabled), automatic pasting of the + exceptions and much more.</span> +</div> +''' + FOOTER + ''' +<!-- + +%(plaintext_cs)s + +--> +''' + +CONSOLE_HTML = HEADER + u'''\ +<h1>Interactive Console</h1> +<div class="explanation"> +In this console you can execute Python expressions in the context of the +application. The initial namespace was created by the debugger automatically. +</div> +<div class="console"><div class="inner">The Console requires JavaScript.</div></div> +''' + FOOTER + +SUMMARY_HTML = u'''\ +<div class="%(classes)s"> + %(title)s + <ul>%(frames)s</ul> + %(description)s +</div> +''' + +FRAME_HTML = u'''\ +<div class="frame" id="frame-%(id)d"> + <h4>File <cite class="filename">"%(filename)s"</cite>, + line <em class="line">%(lineno)s</em>, + in <code class="function">%(function_name)s</code></h4> + <pre>%(current_line)s</pre> +</div> +''' + +SOURCE_TABLE_HTML = u'<table class=source>%s</table>' + +SOURCE_LINE_HTML = u'''\ +<tr class="%(classes)s"> + <td class=lineno>%(lineno)s</td> + <td>%(code)s</td> +</tr> +''' + + +def render_console_html(secret): + return CONSOLE_HTML % { + 'evalex': 'true', + 'console': 'true', + 'title': 'Console', + 'secret': secret, + 'traceback_id': -1 + } + + +def get_current_traceback(ignore_system_exceptions=False, + show_hidden_frames=False, skip=0): + """Get the current exception info as `Traceback` object. Per default + calling this method will reraise system exceptions such as generator exit, + system exit or others. This behavior can be disabled by passing `False` + to the function as first parameter. + """ + exc_type, exc_value, tb = sys.exc_info() + if ignore_system_exceptions and exc_type in system_exceptions: + raise + for x in xrange(skip): + if tb.tb_next is None: + break + tb = tb.tb_next + tb = Traceback(exc_type, exc_value, tb) + if not show_hidden_frames: + tb.filter_hidden_frames() + return tb + + +class Line(object): + """Helper for the source renderer.""" + __slots__ = ('lineno', 'code', 'in_frame', 'current') + + def __init__(self, lineno, code): + self.lineno = lineno + self.code = code + self.in_frame = False + self.current = False + + def classes(self): + rv = ['line'] + if self.in_frame: + rv.append('in-frame') + if self.current: + rv.append('current') + return rv + classes = property(classes) + + def render(self): + return SOURCE_LINE_HTML % { + 'classes': u' '.join(self.classes), + 'lineno': self.lineno, + 'code': escape(self.code) + } + + +class Traceback(object): + """Wraps a traceback.""" + + def __init__(self, exc_type, exc_value, tb): + self.exc_type = exc_type + self.exc_value = exc_value + if not isinstance(exc_type, str): + exception_type = exc_type.__name__ + if exc_type.__module__ not in ('__builtin__', 'exceptions'): + exception_type = exc_type.__module__ + '.' + exception_type + else: + exception_type = exc_type + self.exception_type = exception_type + + # we only add frames to the list that are not hidden. This follows + # the the magic variables as defined by paste.exceptions.collector + self.frames = [] + while tb: + self.frames.append(Frame(exc_type, exc_value, tb)) + tb = tb.tb_next + + def filter_hidden_frames(self): + """Remove the frames according to the paste spec.""" + if not self.frames: + return + + new_frames = [] + hidden = False + for frame in self.frames: + hide = frame.hide + if hide in ('before', 'before_and_this'): + new_frames = [] + hidden = False + if hide == 'before_and_this': + continue + elif hide in ('reset', 'reset_and_this'): + hidden = False + if hide == 'reset_and_this': + continue + elif hide in ('after', 'after_and_this'): + hidden = True + if hide == 'after_and_this': + continue + elif hide or hidden: + continue + new_frames.append(frame) + + # if we only have one frame and that frame is from the codeop + # module, remove it. + if len(new_frames) == 1 and self.frames[0].module == 'codeop': + del self.frames[:] + + # if the last frame is missing something went terrible wrong :( + elif self.frames[-1] in new_frames: + self.frames[:] = new_frames + + def is_syntax_error(self): + """Is it a syntax error?""" + return isinstance(self.exc_value, SyntaxError) + is_syntax_error = property(is_syntax_error) + + def exception(self): + """String representation of the exception.""" + buf = traceback.format_exception_only(self.exc_type, self.exc_value) + return ''.join(buf).strip().decode('utf-8', 'replace') + exception = property(exception) + + def log(self, logfile=None): + """Log the ASCII traceback into a file object.""" + if logfile is None: + logfile = sys.stderr + tb = self.plaintext.encode('utf-8', 'replace').rstrip() + '\n' + logfile.write(tb) + + def paste(self, lodgeit_url): + """Create a paste and return the paste id.""" + from xmlrpclib import ServerProxy + srv = ServerProxy('%sxmlrpc/' % lodgeit_url) + return srv.pastes.newPaste('pytb', self.plaintext, '', '', '', True) + + def render_summary(self, include_title=True): + """Render the traceback for the interactive console.""" + title = '' + description = '' + frames = [] + classes = ['traceback'] + if not self.frames: + classes.append('noframe-traceback') + + if include_title: + if self.is_syntax_error: + title = u'Syntax Error' + else: + title = u'Traceback <em>(most recent call last)</em>:' + + for frame in self.frames: + frames.append(u'<li%s>%s' % ( + frame.info and u' title="%s"' % escape(frame.info) or u'', + frame.render() + )) + + if self.is_syntax_error: + description_wrapper = u'<pre class=syntaxerror>%s</pre>' + else: + description_wrapper = u'<blockquote>%s</blockquote>' + + return SUMMARY_HTML % { + 'classes': u' '.join(classes), + 'title': title and u'<h3>%s</h3>' % title or u'', + 'frames': u'\n'.join(frames), + 'description': description_wrapper % escape(self.exception) + } + + def render_full(self, evalex=False, lodgeit_url=None, + secret=None): + """Render the Full HTML page with the traceback info.""" + exc = escape(self.exception) + return PAGE_HTML % { + 'evalex': evalex and 'true' or 'false', + 'console': 'false', + 'lodgeit_url': escape(lodgeit_url), + 'title': exc, + 'exception': exc, + 'exception_type': escape(self.exception_type), + 'summary': self.render_summary(include_title=False), + 'plaintext': self.plaintext, + 'plaintext_cs': re.sub('-{2,}', '-', self.plaintext), + 'traceback_id': self.id, + 'secret': secret + } + + def generate_plaintext_traceback(self): + """Like the plaintext attribute but returns a generator""" + yield u'Traceback (most recent call last):' + for frame in self.frames: + yield u' File "%s", line %s, in %s' % ( + frame.filename, + frame.lineno, + frame.function_name + ) + yield u' ' + frame.current_line.strip() + yield self.exception + + def plaintext(self): + return u'\n'.join(self.generate_plaintext_traceback()) + plaintext = cached_property(plaintext) + + id = property(lambda x: id(x)) + + +class Frame(object): + """A single frame in a traceback.""" + + def __init__(self, exc_type, exc_value, tb): + self.lineno = tb.tb_lineno + self.function_name = tb.tb_frame.f_code.co_name + self.locals = tb.tb_frame.f_locals + self.globals = tb.tb_frame.f_globals + + fn = inspect.getsourcefile(tb) or inspect.getfile(tb) + if fn[-4:] in ('.pyo', '.pyc'): + fn = fn[:-1] + # if it's a file on the file system resolve the real filename. + if os.path.isfile(fn): + fn = os.path.realpath(fn) + self.filename = fn + self.module = self.globals.get('__name__') + self.loader = self.globals.get('__loader__') + self.code = tb.tb_frame.f_code + + # support for paste's traceback extensions + self.hide = self.locals.get('__traceback_hide__', False) + info = self.locals.get('__traceback_info__') + if info is not None: + try: + info = unicode(info) + except UnicodeError: + info = str(info).decode('utf-8', 'replace') + self.info = info + + def render(self): + """Render a single frame in a traceback.""" + return FRAME_HTML % { + 'id': self.id, + 'filename': escape(self.filename), + 'lineno': self.lineno, + 'function_name': escape(self.function_name), + 'current_line': escape(self.current_line.strip()) + } + + def get_annotated_lines(self): + """Helper function that returns lines with extra information.""" + lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)] + + # find function definition and mark lines + if hasattr(self.code, 'co_firstlineno'): + lineno = self.code.co_firstlineno - 1 + while lineno > 0: + if _funcdef_re.match(lines[lineno].code): + break + lineno -= 1 + try: + offset = len(inspect.getblock([x.code + '\n' for x + in lines[lineno:]])) + except TokenError: + offset = 0 + for line in lines[lineno:lineno + offset]: + line.in_frame = True + + # mark current line + try: + lines[self.lineno - 1].current = True + except IndexError: + pass + + return lines + + def render_source(self): + """Render the sourcecode.""" + return SOURCE_TABLE_HTML % u'\n'.join(line.render() for line in + self.get_annotated_lines()) + + def eval(self, code, mode='single'): + """Evaluate code in the context of the frame.""" + if isinstance(code, basestring): + if isinstance(code, unicode): + code = UTF8_COOKIE + code.encode('utf-8') + code = compile(code, '<interactive>', mode) + if mode != 'exec': + return eval(code, self.globals, self.locals) + exec code in self.globals, self.locals + + @cached_property + def sourcelines(self): + """The sourcecode of the file as list of unicode strings.""" + # get sourcecode from loader or file + source = None + if self.loader is not None: + try: + if hasattr(self.loader, 'get_source'): + source = self.loader.get_source(self.module) + elif hasattr(self.loader, 'get_source_by_code'): + source = self.loader.get_source_by_code(self.code) + except Exception: + # we munch the exception so that we don't cause troubles + # if the loader is broken. + pass + + if source is None: + try: + f = file(self.filename) + except IOError: + return [] + try: + source = f.read() + finally: + f.close() + + # already unicode? return right away + if isinstance(source, unicode): + return source.splitlines() + + # yes. it should be ascii, but we don't want to reject too many + # characters in the debugger if something breaks + charset = 'utf-8' + if source.startswith(UTF8_COOKIE): + source = source[3:] + else: + for idx, match in enumerate(_line_re.finditer(source)): + match = _line_re.search(match.group()) + if match is not None: + charset = match.group(1) + break + if idx > 1: + break + + # on broken cookies we fall back to utf-8 too + try: + codecs.lookup(charset) + except LookupError: + charset = 'utf-8' + + return source.decode(charset, 'replace').splitlines() + + @property + def current_line(self): + try: + return self.sourcelines[self.lineno - 1] + except IndexError: + return u'' + + @cached_property + def console(self): + return Console(self.globals, self.locals) + + id = property(lambda x: id(x)) |