root/galaxy-central/eggs/WebError-0.8a-py2.6.egg/weberror/exceptions/formatter.py @ 3

リビジョン 3, 21.7 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3
4"""
5Formatters for the exception data that comes from ExceptionCollector.
6"""
7# @@: TODO:
8# Use this: http://www.zope.org/Members/tino/VisualTraceback/VisualTracebackNews
9
10import cgi
11import re
12import sys
13from weberror.util import PySourceColor
14from xml.dom.minidom import getDOMImplementation
15
16def html_quote(s):
17    return cgi.escape(str(s), True)
18
19class AbstractFormatter(object):
20
21    general_data_order = ['object', 'source_url']
22
23    def __init__(self, show_hidden_frames=False,
24                 include_reusable=True,
25                 show_extra_data=True,
26                 trim_source_paths=()):
27        self.show_hidden_frames = show_hidden_frames
28        self.trim_source_paths = trim_source_paths
29        self.include_reusable = include_reusable
30        self.show_extra_data = show_extra_data
31
32    def format_collected_data(self, exc_data):
33        general_data = {}
34        if self.show_extra_data:
35            for name, value_list in exc_data.extra_data.items():
36                if isinstance(name, tuple):
37                    importance, title = name
38                else:
39                    importance, title = 'normal', name
40                for value in value_list:
41                    general_data[(importance, name)] = self.format_extra_data(
42                        importance, title, value)
43        lines = []
44        frames = self.filter_frames(exc_data.frames)
45        for frame in frames:
46            res = self.format_frame_start(frame)
47            if res:
48                lines.append(res)
49            sup = frame.supplement
50            if sup:
51                if sup.object:
52                    general_data[('important', 'object')] = self.format_sup_object(
53                        sup.object)
54                if sup.source_url:
55                    general_data[('important', 'source_url')] = self.format_sup_url(
56                        sup.source_url)
57                if sup.line:
58                    lines.append(self.format_sup_line_pos(sup.line, sup.column))
59                if sup.expression:
60                    lines.append(self.format_sup_expression(sup.expression))
61                if sup.warnings:
62                    for warning in sup.warnings:
63                        lines.append(self.format_sup_warning(warning))
64                if sup.info:
65                    lines.extend(self.format_sup_info(sup.info))
66            if frame.supplement_exception:
67                lines.append('Exception in supplement:')
68                lines.append(self.quote_long(frame.supplement_exception))
69            if frame.traceback_info:
70                lines.append(self.format_traceback_info(frame.traceback_info))
71            filename = frame.filename
72            if filename and self.trim_source_paths:
73                for path, repl in self.trim_source_paths:
74                    if filename.startswith(path):
75                        filename = repl + filename[len(path):]
76                        break
77            lines.append(self.format_source_line(filename or '?', frame))
78            source = frame.get_source_line()
79            long_source = frame.get_source_line(2)
80            if source:
81                lines.append(self.format_long_source(
82                    source, long_source))
83            res = self.format_frame_end(frame)
84            if res:
85                lines.append(res)
86        etype = exc_data.exception_type
87        if not isinstance(etype, basestring):
88            etype = etype.__name__
89        exc_info = self.format_exception_info(
90            etype,
91            exc_data.exception_value)
92        data_by_importance = {'important': [], 'normal': [],
93                              'supplemental': [], 'extra': []}
94        for (importance, name), value in general_data.items():
95            data_by_importance[importance].append(
96                (name, value))
97        for value in data_by_importance.values():
98            value.sort()
99        return self.format_combine(data_by_importance, lines, exc_info)
100
101    def filter_frames(self, frames):
102        """
103        Removes any frames that should be hidden, according to the
104        values of traceback_hide, self.show_hidden_frames, and the
105        hidden status of the final frame.
106        """
107        if self.show_hidden_frames:
108            return frames
109        new_frames = []
110        hidden = False
111        for frame in frames:
112            hide = frame.traceback_hide
113            # @@: It would be nice to signal a warning if an unknown
114            # hide string was used, but I'm not sure where to put
115            # that warning.
116            if hide == 'before':
117                new_frames = []
118                hidden = False
119            elif hide == 'before_and_this':
120                new_frames = []
121                hidden = False
122                continue
123            elif hide == 'reset':
124                hidden = False
125            elif hide == 'reset_and_this':
126                hidden = False
127                continue
128            elif hide == 'after':
129                hidden = True
130            elif hide == 'after_and_this':
131                hidden = True
132                continue
133            elif hide:
134                continue
135            elif hidden:
136                continue
137            new_frames.append(frame)
138        if frames[-1] not in new_frames:
139            # We must include the last frame; that we don't indicates
140            # that the error happened where something was "hidden",
141            # so we just have to show everything
142            return frames
143        return new_frames
144
145    def format_frame_start(self, frame):
146        """
147        Called before each frame starts; may return None to output no text.
148        """
149        return None
150
151    def format_frame_end(self, frame):
152        """
153        Called after each frame ends; may return None to output no text.
154        """
155        return None
156
157    def pretty_string_repr(self, s):
158        """
159        Formats the string as a triple-quoted string when it contains
160        newlines.
161        """
162        if '\n' in s:
163            s = repr(s)
164            s = s[0]*3 + s[1:-1] + s[-1]*3
165            s = s.replace('\\n', '\n')
166            return s
167        else:
168            return repr(s)
169
170    def long_item_list(self, lst):
171        """
172        Returns true if the list contains items that are long, and should
173        be more nicely formatted.
174        """
175        how_many = 0
176        for item in lst:
177            if len(repr(item)) > 40:
178                how_many += 1
179                if how_many >= 3:
180                    return True
181        return False
182
183class TextFormatter(AbstractFormatter):
184
185    def quote(self, s):
186        return s
187    def quote_long(self, s):
188        return s
189    def emphasize(self, s):
190        return s
191    def format_sup_object(self, obj):
192        return 'In object: %s' % self.emphasize(self.quote(repr(obj)))
193    def format_sup_url(self, url):
194        return 'URL: %s' % self.quote(url)
195    def format_sup_line_pos(self, line, column):
196        if column:
197            return self.emphasize('Line %i, Column %i' % (line, column))
198        else:
199            return self.emphasize('Line %i' % line)
200    def format_sup_expression(self, expr):
201        return self.emphasize('In expression: %s' % self.quote(expr))
202    def format_sup_warning(self, warning):
203        return 'Warning: %s' % self.quote(warning)
204    def format_sup_info(self, info):
205        return [self.quote_long(info)]
206    def format_source_line(self, filename, frame):
207        return 'File %r, line %s in %s' % (
208            filename, frame.lineno or '?', frame.name or '?')
209    def format_long_source(self, source, long_source):
210        return self.format_source(source)
211    def format_source(self, source_line):
212        return '  ' + self.quote(source_line.strip())
213    def format_exception_info(self, etype, evalue):
214        return self.emphasize(
215            '%s: %s' % (self.quote(etype), self.quote(evalue)))
216    def format_traceback_info(self, info):
217        return info
218       
219    def format_combine(self, data_by_importance, lines, exc_info):
220        lines[:0] = [value for n, value in data_by_importance['important']]
221        lines.append(exc_info)
222        for name in 'normal', 'supplemental', 'extra':
223            lines.extend([value for n, value in data_by_importance[name]])
224        return self.format_combine_lines(lines), ''
225
226    def format_combine_lines(self, lines):
227        return '\n'.join(lines)
228
229    def format_extra_data(self, importance, title, value):
230        if isinstance(value, str):
231            s = self.pretty_string_repr(value)
232            if '\n' in s:
233                return '%s:\n%s' % (title, s)
234            else:
235                return '%s: %s' % (title, s)
236        elif isinstance(value, dict):
237            lines = ['\n', title, '-'*len(title)]
238            items = value.items()
239            items.sort()
240            for n, v in items:
241                try:
242                    v = repr(v)
243                except Exception, e:
244                    v = 'Cannot display: %s' % e
245                v = truncate(v)
246                lines.append('  %s: %s' % (n, v))
247            return '\n'.join(lines)
248        elif (isinstance(value, (list, tuple))
249              and self.long_item_list(value)):
250            parts = [truncate(repr(v)) for v in value]
251            return '%s: [\n    %s]' % (
252                title, ',\n    '.join(parts))
253        else:
254            return '%s: %s' % (title, truncate(repr(value)))
255
256class HTMLFormatter(TextFormatter):
257
258    def quote(self, s):
259        return html_quote(s)
260    def quote_long(self, s):
261        return '<pre>%s</pre>' % self.quote(s)
262    def emphasize(self, s):
263        return '<b>%s</b>' % s
264    def format_sup_url(self, url):
265        return 'URL: <a href="%s">%s</a>' % (url, url)
266    def format_combine_lines(self, lines):
267        ## FIXME: this is horrible:
268        new_lines = []
269        for line in lines:
270            if not line.startswith('<div') and not line.endswith('</div>'):
271                line += '<br>'
272            new_lines.append(line)
273        return '\n'.join(new_lines)
274    def format_source_line(self, filename, frame):
275        name = self.quote(frame.name or '?')
276        return 'Module <span class="module" title="%s">%s</span>:<b>%s</b> in <code>%s</code>' % (
277            filename, frame.modname or '?', frame.lineno or '?',
278            name)
279        return 'File %r, line %s in <tt>%s</tt>' % (
280            filename, frame.lineno, name)
281    def format_long_source(self, source, long_source):
282        q_long_source = str2html(long_source, False, 4, True)
283        q_source = str2html(source, True, 0, False)
284        return ('<code style="display: none" class="source" source-type="long"><a class="switch_source" onclick="return switch_source(this, \'long\')" href="#">&lt;&lt;&nbsp; </a>%s</code>'
285                '<code class="source" source-type="short"><a onclick="return switch_source(this, \'short\')" class="switch_source" href="#">&gt;&gt;&nbsp; </a>%s</code>'
286                % (q_long_source,
287                   q_source))
288    def format_source(self, source_line):
289        return '&nbsp;&nbsp;<code class="source">%s</code>' % self.quote(source_line.strip())
290    def format_traceback_info(self, info):
291        return '<pre>%s</pre>' % self.quote(info)
292    def format_frame_start(self, frame):
293        ## FIXME: make it zebra?
294        return '<div class="frame" style="padding: 0; margin: 0">'
295    def format_frame_end(self, frame):
296        return '</div>'
297
298    def format_extra_data(self, importance, title, value):
299        if isinstance(value, str):
300            s = self.pretty_string_repr(value)
301            if '\n' in s:
302                return '%s:<br><pre>%s</pre>' % (title, self.quote(s))
303            else:
304                return '%s: <tt>%s</tt>' % (title, self.quote(s))
305        elif isinstance(value, dict):
306            return self.zebra_table(title, value)
307        elif (isinstance(value, (list, tuple))
308              and self.long_item_list(value)):
309            return '%s: <tt>[<br>\n&nbsp; &nbsp; %s]</tt>' % (
310                title, ',<br>&nbsp; &nbsp; '.join(map(self.quote, map(repr, value))))
311        else:
312            return '%s: <tt>%s</tt>' % (title, self.quote(repr(value)))
313
314    def format_combine(self, data_by_importance, lines, exc_info):
315        lines[:0] = [value for n, value in data_by_importance['important']]
316        lines.append(exc_info)
317        for name in 'normal', 'supplemental':
318            lines.extend([value for n, value in data_by_importance[name]])
319       
320        extra_data = []
321        if data_by_importance['extra']:
322            extra_data.extend([value for n, value in data_by_importance['extra']])
323        text = self.format_combine_lines(lines)
324        if self.include_reusable:
325            return text, extra_data
326        else:
327            # Usually because another error is already on this page,
328            # and so the js & CSS are unneeded
329            return text, extra_data
330
331    def zebra_table(self, title, rows, table_class="variables"):
332        if isinstance(rows, dict):
333            rows = rows.items()
334            rows.sort()
335        table = ['<table class="%s">' % table_class,
336                 '<tr class="header"><th colspan="2">%s</th></tr>'
337                 % self.quote(title)]
338        odd = False
339        for name, value in rows:
340            try:
341                value = repr(value)
342            except Exception, e:
343                value = 'Cannot print: %s' % e
344            odd = not odd
345            table.append(
346                '<tr class="%s"><td>%s</td>'
347                % (odd and 'odd' or 'even', self.quote(name)))
348            table.append(
349                '<td><tt>%s</tt></td></tr>'
350                % make_wrappable(self.quote(truncate(value))))
351        table.append('</table>')
352        return '\n'.join(table)
353
354def create_text_node(doc, elem, text):
355    if not isinstance(text, basestring):
356        try:
357            text = repr(text)
358        except:
359            text = 'UNABLE TO GET TEXT REPRESENTATION'
360    new_elem = doc.createElement(elem)
361    new_elem.appendChild(doc.createTextNode(text))
362    return new_elem
363
364class XMLFormatter(AbstractFormatter):
365    def format_collected_data(self, exc_data):
366        impl = getDOMImplementation()
367        newdoc = impl.createDocument(None, "traceback", None)
368        top_element = newdoc.documentElement
369       
370        sysinfo = newdoc.createElement('sysinfo')
371        language = create_text_node(newdoc, 'language', 'Python')
372        language.attributes['version'] = sys.version.split(' ')[0]
373        sysinfo.appendChild(language)
374        top_element.appendChild(sysinfo)
375       
376        frames = self.filter_frames(exc_data.frames)
377        stack = newdoc.createElement('stack')
378        top_element.appendChild(stack)
379        for frame in frames:
380            xml_frame = newdoc.createElement('frame')
381            stack.appendChild(xml_frame)
382           
383            filename = frame.filename
384            if filename and self.trim_source_paths:
385                for path, repl in self.trim_source_paths:
386                    if filename.startswith(path):
387                        filename = repl + filename[len(path):]
388                        break
389            self.format_source_line(filename or '?', frame, newdoc, xml_frame)
390           
391            source = frame.get_source_line()
392            long_source = frame.get_source_line(2)
393            if source:
394                self.format_long_source(source, long_source, newdoc, xml_frame)
395           
396            # @@@ TODO: Put in a way to optionally toggle including variables
397            # variables = newdoc.createElement('variables')
398            # xml_frame.appendChild(variables)
399            # for name, value in frame.locals.iteritems():
400            #     if isinstance(value, unicode):
401            #         value = value.encode('ascii', 'xmlcharrefreplace')
402            #     variable = newdoc.createElement('variable')
403            #     variable.appendChild(create_text_node(newdoc, 'name', name))
404            #     variable.appendChild(create_text_node(newdoc, 'value', value))
405            #     variables.appendChild(variable)
406       
407        etype = exc_data.exception_type
408        if not isinstance(etype, basestring):
409            etype = etype.__name__
410       
411        top_element.appendChild(self.format_exception_info(
412            etype, exc_data.exception_value, newdoc))
413        return newdoc.toprettyxml(), ''
414   
415    def format_source_line(self, filename, frame, newdoc, xml_frame):
416        name = frame.name or '?'
417        xml_frame.appendChild(create_text_node(newdoc, 'module', frame.modname or '?'))
418        xml_frame.appendChild(create_text_node(newdoc, 'filename', filename))
419        xml_frame.appendChild(create_text_node(newdoc, 'line', frame.lineno or '?'))
420        xml_frame.appendChild(create_text_node(newdoc, 'function', name))
421   
422    def format_long_source(self, source, long_source, newdoc, xml_frame):
423        source = source.encode('ascii', 'xmlcharrefreplace')
424        long_source = long_source.encode('ascii', 'xmlcharrefreplace')
425        xml_frame.appendChild(create_text_node(newdoc, 'operation', source.strip()))
426        xml_frame.appendChild(create_text_node(newdoc, 'operation_context', long_source))
427
428    def format_exception_info(self, etype, evalue, newdoc):
429        exception = newdoc.createElement('exception')
430        evalue = evalue.encode('ascii', 'xmlcharrefreplace')
431        exception.appendChild(create_text_node(newdoc, 'type', etype))
432        exception.appendChild(create_text_node(newdoc, 'value', evalue))
433        return exception
434
435   
436def format_html(exc_data, include_hidden_frames=False, **ops):
437    if not include_hidden_frames:
438        return HTMLFormatter(**ops).format_collected_data(exc_data)
439    short_er = format_html(exc_data, show_hidden_frames=False, **ops)
440    # @@: This should have a way of seeing if the previous traceback
441    # was actually trimmed at all
442    ops['include_reusable'] = False
443    ops['show_extra_data'] = False
444    long_er, head_html = format_html(exc_data, show_hidden_frames=True, **ops)
445    text_er, head_text = format_text(exc_data, show_hidden_frames=True, **ops)
446    xml_er, head_xml = format_xml(exc_data, show_hidden_frames=True, **ops)
447    return """
448    %s
449    <div id="short_traceback">
450    %s
451    </div>
452    <div id="full_traceback" class="hidden-data">
453    %s
454    </div>
455    <div id="text_version" class="hidden-data">
456    <textarea style="width: 100%%" rows=10 cols=60>%s</textarea>
457    </div>
458    <div id="xml_version" class="hidden-data">
459    <textarea style="width: 100%%" rows=10 cols=60>%s</textarea>
460    </div>
461    """ % (head_html, short_er, long_er, cgi.escape(text_er), cgi.escape(xml_er))
462
463       
464def format_text(exc_data, **ops):
465    return TextFormatter(**ops).format_collected_data(exc_data)
466
467
468def format_xml(exc_data, **ops):
469    return XMLFormatter(**ops).format_collected_data(exc_data)
470
471
472whitespace_re = re.compile(r'  +')
473pre_re = re.compile(r'</?pre.*?>')
474error_re = re.compile(r'<h3>ERROR: .*?</h3>')
475
476def str2html(src, strip=False, indent_subsequent=0,
477             highlight_inner=False):
478    """
479    Convert a string to HTML.  Try to be really safe about it,
480    returning a quoted version of the string if nothing else works.
481    """
482    try:
483        return _str2html(src, strip=strip,
484                         indent_subsequent=indent_subsequent,
485                         highlight_inner=highlight_inner)
486    except:
487        return html_quote(src)
488
489def _str2html(src, strip=False, indent_subsequent=0,
490              highlight_inner=False):
491    if strip:
492        src = src.strip()
493    orig_src = src
494    try:
495        src = PySourceColor.str2html(src, form='snip')
496        src = error_re.sub('', src)
497        src = pre_re.sub('', src)
498        src = re.sub(r'^[\n\r]{0,1}', '', src)
499        src = re.sub(r'[\n\r]{0,1}$', '', src)
500    except:
501        src = html_quote(orig_src)
502    lines = src.splitlines()
503    if len(lines) == 1:
504        return lines[0]
505    indent = ' '*indent_subsequent
506    for i in range(1, len(lines)):
507        lines[i] = indent+lines[i]
508        if highlight_inner and i == len(lines)/2:
509            lines[i] = '<span class="source-highlight">%s</span>' % lines[i]
510    src = '<br>\n'.join(lines)
511    src = whitespace_re.sub(
512        lambda m: '&nbsp;'*(len(m.group(0))-1) + ' ', src)
513    return src
514
515def truncate(string, limit=1000):
516    """
517    Truncate the string to the limit number of
518    characters
519    """
520    if len(string) > limit:
521        return string[:limit-20]+'...'+string[-17:]
522    else:
523        return string
524
525def make_wrappable(html, wrap_limit=60,
526                   split_on=';?&@!$#-/\\"\''):
527    # Currently using <wbr>, maybe should use &#8203;
528    #   http://www.cs.tut.fi/~jkorpela/html/nobr.html
529    if len(html) <= wrap_limit:
530        return html
531    words = html.split()
532    new_words = []
533    for word in words:
534        wrapped_word = ''
535        while len(word) > wrap_limit:
536            for char in split_on:
537                if char in word:
538                    first, rest = word.split(char, 1)
539                    wrapped_word += first+char+'<wbr>'
540                    word = rest
541                    break
542            else:
543                for i in range(0, len(word), wrap_limit):
544                    wrapped_word += word[i:i+wrap_limit]+'<wbr>'
545                word = ''
546        wrapped_word += word
547        new_words.append(wrapped_word)
548    return ' '.join(new_words)
549
550def make_pre_wrappable(html, wrap_limit=60,
551                       split_on=';?&@!$#-/\\"\''):
552    """
553    Like ``make_wrappable()`` but intended for text that will
554    go in a ``<pre>`` block, so wrap on a line-by-line basis.
555    """
556    lines = html.splitlines()
557    new_lines = []
558    for line in lines:
559        if len(line) > wrap_limit:
560            for char in split_on:
561                if char in line:
562                    parts = line.split(char)
563                    line = '<wbr>'.join(parts)
564                    break
565        new_lines.append(line)
566    return '\n'.join(lines)
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。