From cc83e06efff71b81ca5a3ac6df65775971181295 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 7 Mar 2016 18:52:36 -0800 Subject: tox.ini: Measure test coverage --- paste/exceptions/formatter.py | 565 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 paste/exceptions/formatter.py (limited to 'paste/exceptions/formatter.py') diff --git a/paste/exceptions/formatter.py b/paste/exceptions/formatter.py new file mode 100644 index 0000000..09309de --- /dev/null +++ b/paste/exceptions/formatter.py @@ -0,0 +1,565 @@ +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +""" +Formatters for the exception data that comes from ExceptionCollector. +""" +# @@: TODO: +# Use this: http://www.zope.org/Members/tino/VisualTraceback/VisualTracebackNews + +import cgi +import six +import re +from paste.util import PySourceColor + +def html_quote(s): + return cgi.escape(str(s), True) + +class AbstractFormatter(object): + + general_data_order = ['object', 'source_url'] + + def __init__(self, show_hidden_frames=False, + include_reusable=True, + show_extra_data=True, + trim_source_paths=()): + self.show_hidden_frames = show_hidden_frames + self.trim_source_paths = trim_source_paths + self.include_reusable = include_reusable + self.show_extra_data = show_extra_data + + def format_collected_data(self, exc_data): + general_data = {} + if self.show_extra_data: + for name, value_list in exc_data.extra_data.items(): + if isinstance(name, tuple): + importance, title = name + else: + importance, title = 'normal', name + for value in value_list: + general_data[(importance, name)] = self.format_extra_data( + importance, title, value) + lines = [] + frames = self.filter_frames(exc_data.frames) + for frame in frames: + sup = frame.supplement + if sup: + if sup.object: + general_data[('important', 'object')] = self.format_sup_object( + sup.object) + if sup.source_url: + general_data[('important', 'source_url')] = self.format_sup_url( + sup.source_url) + if sup.line: + lines.append(self.format_sup_line_pos(sup.line, sup.column)) + if sup.expression: + lines.append(self.format_sup_expression(sup.expression)) + if sup.warnings: + for warning in sup.warnings: + lines.append(self.format_sup_warning(warning)) + if sup.info: + lines.extend(self.format_sup_info(sup.info)) + if frame.supplement_exception: + lines.append('Exception in supplement:') + lines.append(self.quote_long(frame.supplement_exception)) + if frame.traceback_info: + lines.append(self.format_traceback_info(frame.traceback_info)) + filename = frame.filename + if filename and self.trim_source_paths: + for path, repl in self.trim_source_paths: + if filename.startswith(path): + filename = repl + filename[len(path):] + break + lines.append(self.format_source_line(filename or '?', frame)) + source = frame.get_source_line() + long_source = frame.get_source_line(2) + if source: + lines.append(self.format_long_source( + source, long_source)) + etype = exc_data.exception_type + if not isinstance(etype, six.string_types): + etype = etype.__name__ + exc_info = self.format_exception_info( + etype, + exc_data.exception_value) + data_by_importance = {'important': [], 'normal': [], + 'supplemental': [], 'extra': []} + for (importance, name), value in general_data.items(): + data_by_importance[importance].append( + (name, value)) + for value in data_by_importance.values(): + value.sort() + return self.format_combine(data_by_importance, lines, exc_info) + + def filter_frames(self, frames): + """ + Removes any frames that should be hidden, according to the + values of traceback_hide, self.show_hidden_frames, and the + hidden status of the final frame. + """ + if self.show_hidden_frames: + return frames + new_frames = [] + hidden = False + for frame in frames: + hide = frame.traceback_hide + # @@: It would be nice to signal a warning if an unknown + # hide string was used, but I'm not sure where to put + # that warning. + if hide == 'before': + new_frames = [] + hidden = False + elif hide == 'before_and_this': + new_frames = [] + hidden = False + continue + elif hide == 'reset': + hidden = False + elif hide == 'reset_and_this': + hidden = False + continue + elif hide == 'after': + hidden = True + elif hide == 'after_and_this': + hidden = True + continue + elif hide: + continue + elif hidden: + continue + new_frames.append(frame) + if frames[-1] not in new_frames: + # We must include the last frame; that we don't indicates + # that the error happened where something was "hidden", + # so we just have to show everything + return frames + return new_frames + + def pretty_string_repr(self, s): + """ + Formats the string as a triple-quoted string when it contains + newlines. + """ + if '\n' in s: + s = repr(s) + s = s[0]*3 + s[1:-1] + s[-1]*3 + s = s.replace('\\n', '\n') + return s + else: + return repr(s) + + def long_item_list(self, lst): + """ + Returns true if the list contains items that are long, and should + be more nicely formatted. + """ + how_many = 0 + for item in lst: + if len(repr(item)) > 40: + how_many += 1 + if how_many >= 3: + return True + return False + +class TextFormatter(AbstractFormatter): + + def quote(self, s): + return s + def quote_long(self, s): + return s + def emphasize(self, s): + return s + def format_sup_object(self, obj): + return 'In object: %s' % self.emphasize(self.quote(repr(obj))) + def format_sup_url(self, url): + return 'URL: %s' % self.quote(url) + def format_sup_line_pos(self, line, column): + if column: + return self.emphasize('Line %i, Column %i' % (line, column)) + else: + return self.emphasize('Line %i' % line) + def format_sup_expression(self, expr): + return self.emphasize('In expression: %s' % self.quote(expr)) + def format_sup_warning(self, warning): + return 'Warning: %s' % self.quote(warning) + def format_sup_info(self, info): + return [self.quote_long(info)] + def format_source_line(self, filename, frame): + return 'File %r, line %s in %s' % ( + filename, frame.lineno or '?', frame.name or '?') + def format_long_source(self, source, long_source): + return self.format_source(source) + def format_source(self, source_line): + return ' ' + self.quote(source_line.strip()) + def format_exception_info(self, etype, evalue): + return self.emphasize( + '%s: %s' % (self.quote(etype), self.quote(evalue))) + def format_traceback_info(self, info): + return info + + def format_combine(self, data_by_importance, lines, exc_info): + lines[:0] = [value for n, value in data_by_importance['important']] + lines.append(exc_info) + for name in 'normal', 'supplemental', 'extra': + lines.extend([value for n, value in data_by_importance[name]]) + return self.format_combine_lines(lines) + + def format_combine_lines(self, lines): + return '\n'.join(lines) + + def format_extra_data(self, importance, title, value): + if isinstance(value, str): + s = self.pretty_string_repr(value) + if '\n' in s: + return '%s:\n%s' % (title, s) + else: + return '%s: %s' % (title, s) + elif isinstance(value, dict): + lines = ['\n', title, '-'*len(title)] + items = value.items() + items = sorted(items) + for n, v in items: + try: + v = repr(v) + except Exception as e: + v = 'Cannot display: %s' % e + v = truncate(v) + lines.append(' %s: %s' % (n, v)) + return '\n'.join(lines) + elif (isinstance(value, (list, tuple)) + and self.long_item_list(value)): + parts = [truncate(repr(v)) for v in value] + return '%s: [\n %s]' % ( + title, ',\n '.join(parts)) + else: + return '%s: %s' % (title, truncate(repr(value))) + +class HTMLFormatter(TextFormatter): + + def quote(self, s): + return html_quote(s) + def quote_long(self, s): + return '
%s
' % self.quote(s) + def emphasize(self, s): + return '%s' % s + def format_sup_url(self, url): + return 'URL: %s' % (url, url) + def format_combine_lines(self, lines): + return '
\n'.join(lines) + def format_source_line(self, filename, frame): + name = self.quote(frame.name or '?') + return 'Module %s:%s in %s' % ( + filename, frame.modname or '?', frame.lineno or '?', + name) + return 'File %r, line %s in %s' % ( + filename, frame.lineno, name) + def format_long_source(self, source, long_source): + q_long_source = str2html(long_source, False, 4, True) + q_source = str2html(source, True, 0, False) + return ('' + '>>  %s' + % (q_long_source, + q_source)) + def format_source(self, source_line): + return '  %s' % self.quote(source_line.strip()) + def format_traceback_info(self, info): + return '
%s
' % self.quote(info) + + def format_extra_data(self, importance, title, value): + if isinstance(value, str): + s = self.pretty_string_repr(value) + if '\n' in s: + return '%s:
%s
' % (title, self.quote(s)) + else: + return '%s: %s' % (title, self.quote(s)) + elif isinstance(value, dict): + return self.zebra_table(title, value) + elif (isinstance(value, (list, tuple)) + and self.long_item_list(value)): + return '%s: [
\n    %s]
' % ( + title, ',
    '.join(map(self.quote, map(repr, value)))) + else: + return '%s: %s' % (title, self.quote(repr(value))) + + def format_combine(self, data_by_importance, lines, exc_info): + lines[:0] = [value for n, value in data_by_importance['important']] + lines.append(exc_info) + for name in 'normal', 'supplemental': + lines.extend([value for n, value in data_by_importance[name]]) + if data_by_importance['extra']: + lines.append( + '\n' + + '
\n') + lines.extend([value for n, value in data_by_importance['extra']]) + lines.append('
') + text = self.format_combine_lines(lines) + if self.include_reusable: + return error_css + hide_display_js + text + else: + # Usually because another error is already on this page, + # and so the js & CSS are unneeded + return text + + def zebra_table(self, title, rows, table_class="variables"): + if isinstance(rows, dict): + rows = rows.items() + rows = sorted(rows) + table = ['' % table_class, + '' + % self.quote(title)] + odd = False + for name, value in rows: + try: + value = repr(value) + except Exception as e: + value = 'Cannot print: %s' % e + odd = not odd + table.append( + '' + % (odd and 'odd' or 'even', self.quote(name))) + table.append( + '' + % make_wrappable(self.quote(truncate(value)))) + table.append('
%s
%s%s
') + return '\n'.join(table) + +hide_display_js = r''' +''' + + +error_css = """ + +""" + +def format_html(exc_data, include_hidden_frames=False, **ops): + if not include_hidden_frames: + return HTMLFormatter(**ops).format_collected_data(exc_data) + short_er = format_html(exc_data, show_hidden_frames=False, **ops) + # @@: This should have a way of seeing if the previous traceback + # was actually trimmed at all + ops['include_reusable'] = False + ops['show_extra_data'] = False + long_er = format_html(exc_data, show_hidden_frames=True, **ops) + text_er = format_text(exc_data, show_hidden_frames=True, **ops) + return """ + %s +
+ +
+ %s +
+
+ +
+ +
+ """ % (short_er, long_er, cgi.escape(text_er)) + +def format_text(exc_data, **ops): + return TextFormatter(**ops).format_collected_data(exc_data) + +whitespace_re = re.compile(r' +') +pre_re = re.compile(r'') +error_re = re.compile(r'

ERROR: .*?

') + +def str2html(src, strip=False, indent_subsequent=0, + highlight_inner=False): + """ + Convert a string to HTML. Try to be really safe about it, + returning a quoted version of the string if nothing else works. + """ + try: + return _str2html(src, strip=strip, + indent_subsequent=indent_subsequent, + highlight_inner=highlight_inner) + except: + return html_quote(src) + +def _str2html(src, strip=False, indent_subsequent=0, + highlight_inner=False): + if strip: + src = src.strip() + orig_src = src + try: + src = PySourceColor.str2html(src, form='snip') + src = error_re.sub('', src) + src = pre_re.sub('', src) + src = re.sub(r'^[\n\r]{0,1}', '', src) + src = re.sub(r'[\n\r]{0,1}$', '', src) + except: + src = html_quote(orig_src) + lines = src.splitlines() + if len(lines) == 1: + return lines[0] + indent = ' '*indent_subsequent + for i in range(1, len(lines)): + lines[i] = indent+lines[i] + if highlight_inner and i == len(lines)/2: + lines[i] = '%s' % lines[i] + src = '
\n'.join(lines) + src = whitespace_re.sub( + lambda m: ' '*(len(m.group(0))-1) + ' ', src) + return src + +def truncate(string, limit=1000): + """ + Truncate the string to the limit number of + characters + """ + if len(string) > limit: + return string[:limit-20]+'...'+string[-17:] + else: + return string + +def make_wrappable(html, wrap_limit=60, + split_on=';?&@!$#-/\\"\''): + # Currently using , maybe should use ​ + # http://www.cs.tut.fi/~jkorpela/html/nobr.html + if len(html) <= wrap_limit: + return html + words = html.split() + new_words = [] + for word in words: + wrapped_word = '' + while len(word) > wrap_limit: + for char in split_on: + if char in word: + first, rest = word.split(char, 1) + wrapped_word += first+char+'' + word = rest + break + else: + for i in range(0, len(word), wrap_limit): + wrapped_word += word[i:i+wrap_limit]+'' + word = '' + wrapped_word += word + new_words.append(wrapped_word) + return ' '.join(new_words) + +def make_pre_wrappable(html, wrap_limit=60, + split_on=';?&@!$#-/\\"\''): + """ + Like ``make_wrappable()`` but intended for text that will + go in a ``
`` block, so wrap on a line-by-line basis.
+    """
+    lines = html.splitlines()
+    new_lines = []
+    for line in lines:
+        if len(line) > wrap_limit:
+            for char in split_on:
+                if char in line:
+                    parts = line.split(char)
+                    line = ''.join(parts)
+                    break
+        new_lines.append(line)
+    return '\n'.join(lines)
-- 
cgit v1.2.1