summaryrefslogtreecommitdiff
path: root/paste/exceptions/formatter.py
blob: 97da7ab9520e652757c70e15b3f4d30c52cadb81 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
"""
Formatters for the exception data that comes from ExceptionCollector.
"""

import cgi

def html_quote(s):
    return cgi.escape(s, True)

class AbstractFormatter:

    general_data_order = ['object', 'source_url']

    def __init__(self, show_hidden_frames=False,
                 trim_source_paths=()):
        self.show_hidden_frames = show_hidden_frames
        self.trim_source_paths = trim_source_paths

    def format_collected_data(self, exc_data):
        general_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 = []
        show_hidden_frames = self.show_hidden_frames
        last = exc_data.frames[-1]
        if last.traceback_hide or last.traceback_stop:
            # If the last frame was supposed to have been hidden,
            # there's clearly a problem in the hidden portion of
            # the framework itself
            show_hidden_frames = True
        for frame in exc_data.frames:
            if frame.traceback_hide and not show_hidden_frames:
                continue
            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))
            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.lineno or '?',
                frame.name or '?'))
            source = frame.get_source_line()
            if source:
                lines.append(self.format_source(source))
        exc_info = self.format_exception_info(
            exc_data.exception_type,
            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 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.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 'Line %i, Column %i' % (line, column)
        else:
            return 'Line %i' % line
    def format_sup_expression(self, expr):
        return '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(info)]
    def format_source_line(self, filename, lineno, name):
        return 'File %r, line %s in %s' % (filename, lineno, name)
    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_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 = [title, '-'*len(title)]
            items = value.items()
            items.sort()
            for n, v in items:
                lines.append('%s: %s' % (n, repr(v)))
            return '\n'.join(lines)
        elif (isinstance(value, (list, tuple))
              and self.long_item_list(value)):
            return '%s: [,\n    %s]' % (
                title, '\n    '.join(map(repr, value)))
        else:
            return '%s: %r' % (title, value)

class HTMLFormatter(TextFormatter):

    def quote(self, s):
        return html_quote(s)
    def quote_long(self, s):
        return '<pre>%s</pre>' % self.quote(s)
    def emphasize(self, s):
        return '<b>%s</b>' % s
    def format_sup_url(self, url):
        return 'URL: <a href="%s">%s</a>' % (url, url)
    def format_combine_lines(self, lines):
        return '<br>\n'.join(lines)
    def format_source_line(self, filename, lineno, name):
        return 'File %r, line %s in <tt>%s</tt>' % (filename, lineno, name)
    def format_source(self, source_line):
        return '&nbsp;&nbsp;<tt>%s</tt>' % self.quote(source_line.strip())

    def format_extra_data(self, importance, title, value):
        if isinstance(value, str):
            s = self.pretty_string_repr(value)
            if '\n' in s:
                return '%s:<br><pre>%s</pre>' % (title, self.quote(s))
            else:
                return '%s: <tt>%s</tt>' % (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: <tt>[<br>\n&nbsp; &nbsp; %s]</tt>' % (
                title, ',<br>&nbsp; &nbsp; '.join(map(self.quote, map(repr, value))))
        else:
            return '%s: <tt>%s</tt>' % (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(
                hide_display_js +
                '<a href="#extra_data" '
                'onclick="javascript:hide_display(\'extra_data\')" '
                'class="button">show extra data</a><br>'
                '<div id="extra_data" style="display: none">')
            lines.extend([value for n, value in data_by_importance['extra']])
            lines.append('</div>')
        return self.format_combine_lines(lines)

    def zebra_table(self, title, rows, table_class="variables"):
        if isinstance(rows, dict):
            rows = rows.items()
            rows.sort()
        table = ['<table class="%s">' % table_class,
                 '<tr class="header"><th colspan="2">%s</th></tr>'
                 % self.quote(title)]
        odd = False
        for name, value in rows:
            odd = not odd
            table.append(
                '<tr class="%s"><td>%s</td>'
                % (odd and 'odd' or 'even', self.quote(name)))
            table.append(
                '<td><tt>%s</tt></td></tr>'
                % self.make_wrappable(self.quote(repr(value))))
        table.append('</table>')
        return '\n'.join(table)

    def make_wrappable(self, html, wrap_limit=60,
                       split_on=';?&@!$#-/\\"\''):
        # Currently using <wbr>, maybe should use &#8203;
        #   http://www.cs.tut.fi/~jkorpela/html/nobr.html
        words = html.split()
        new_words = []
        for word in words:
            if len(word) > wrap_limit:
                for char in split_on:
                    if char in word:
                        words = [
                            self.make_wrappable(w, wrap_limit=wrap_limit,
                                                split_on=split_on)
                            for w in word.split(char, 1)]
                        new_words.append('<wbr>'.join(words))
                        break
            else:
                new_words.append(word)
        return ' '.join(new_words)

hide_display_js = '''\
<script type="text/javascript">
function hide_display(id) {
  var el = document.getElementById(id);
  if (el.style.display == "none") {
    el.style.display = "";
    return true;
  } else {
    el.style.display = "none";
    return false;
  }
}
</script>'''
    

def format_html(exc_data, **ops):
    return HTMLFormatter(**ops).format_collected_data(exc_data)
def format_text(exc_data, **ops):
    return TextFormatter(**ops).format_collected_data(exc_data)