Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/websdk/werkzeug/debug/tbtools.py
diff options
context:
space:
mode:
Diffstat (limited to 'websdk/werkzeug/debug/tbtools.py')
-rw-r--r--websdk/werkzeug/debug/tbtools.py483
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&amp;cmd=resource&amp;f=style.css" type="text/css">
+ <script type="text/javascript" src="?__debugger__=yes&amp;cmd=resource&amp;f=jquery.js"></script>
+ <script type="text/javascript" src="?__debugger__=yes&amp;cmd=resource&amp;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))