# -*- coding: utf-8 -*- """ werkzeug.debug.repr ~~~~~~~~~~~~~~~~~~~ This module implements object representations for debugging purposes. Unlike the default repr these reprs expose a lot more information and produce HTML instead of ASCII. Together with the CSS and JavaScript files of the debugger this gives a colorful and more compact output. :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details. :license: BSD. """ import sys import re import codecs from traceback import format_exception_only try: from collections import deque except ImportError: # pragma: no cover deque = None from werkzeug.utils import escape from werkzeug._compat import iteritems, PY2, text_type, integer_types, \ string_types missing = object() _paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}') RegexType = type(_paragraph_re) HELP_HTML = '''\

%(title)s

%(text)s
\ ''' OBJECT_DUMP_HTML = '''\

%(title)s

%(repr)s %(items)s
\ ''' def debug_repr(obj): """Creates a debug repr of an object as HTML unicode string.""" return DebugReprGenerator().repr(obj) def dump(obj=missing): """Print the object details to stdout._write (for the interactive console of the web debugger. """ gen = DebugReprGenerator() if obj is missing: rv = gen.dump_locals(sys._getframe(1).f_locals) else: rv = gen.dump_object(obj) sys.stdout._write(rv) class _Helper(object): """Displays an HTML version of the normal help, for the interactive debugger only because it requires a patched sys.stdout. """ def __repr__(self): return 'Type help(object) for help about object.' def __call__(self, topic=None): if topic is None: sys.stdout._write('%s' % repr(self)) return import pydoc pydoc.help(topic) rv = sys.stdout.reset() if isinstance(rv, bytes): rv = rv.decode('utf-8', 'ignore') paragraphs = _paragraph_re.split(rv) if len(paragraphs) > 1: title = paragraphs[0] text = '\n\n'.join(paragraphs[1:]) else: # pragma: no cover title = 'Help' text = paragraphs[0] sys.stdout._write(HELP_HTML % {'title': title, 'text': text}) helper = _Helper() def _add_subclass_info(inner, obj, base): if isinstance(base, tuple): for base in base: if type(obj) is base: return inner elif type(obj) is base: return inner module = '' if obj.__class__.__module__ not in ('__builtin__', 'exceptions'): module = '%s.' % obj.__class__.__module__ return '%s%s(%s)' % (module, obj.__class__.__name__, inner) class DebugReprGenerator(object): def __init__(self): self._stack = [] def _sequence_repr_maker(left, right, base=object(), limit=8): def proxy(self, obj, recursive): if recursive: return _add_subclass_info(left + '...' + right, obj, base) buf = [left] have_extended_section = False for idx, item in enumerate(obj): if idx: buf.append(', ') if idx == limit: buf.append('') have_extended_section = True buf.append(self.repr(item)) if have_extended_section: buf.append('') buf.append(right) return _add_subclass_info(u''.join(buf), obj, base) return proxy list_repr = _sequence_repr_maker('[', ']', list) tuple_repr = _sequence_repr_maker('(', ')', tuple) set_repr = _sequence_repr_maker('set([', '])', set) frozenset_repr = _sequence_repr_maker('frozenset([', '])', frozenset) if deque is not None: deque_repr = _sequence_repr_maker('collections.' 'deque([', '])', deque) del _sequence_repr_maker def regex_repr(self, obj): pattern = repr(obj.pattern) if PY2: pattern = pattern.decode('string-escape', 'ignore') else: pattern = codecs.decode(pattern, 'unicode-escape', 'ignore') if pattern[:1] == 'u': pattern = 'ur' + pattern[1:] else: pattern = 'r' + pattern return u're.compile(%s)' % pattern def string_repr(self, obj, limit=70): buf = [''] a = repr(obj[:limit]) b = repr(obj[limit:]) if isinstance(obj, text_type) and PY2: buf.append('u') a = a[1:] b = b[1:] if b != "''": buf.extend((escape(a[:-1]), '', escape(b[1:]), '')) else: buf.append(escape(a)) buf.append('') return _add_subclass_info(u''.join(buf), obj, (bytes, text_type)) def dict_repr(self, d, recursive, limit=5): if recursive: return _add_subclass_info(u'{...}', d, dict) buf = ['{'] have_extended_section = False for idx, (key, value) in enumerate(iteritems(d)): if idx: buf.append(', ') if idx == limit - 1: buf.append('') have_extended_section = True buf.append('%s: ' '%s' % (self.repr(key), self.repr(value))) if have_extended_section: buf.append('') buf.append('}') return _add_subclass_info(u''.join(buf), d, dict) def object_repr(self, obj): r = repr(obj) if PY2: r = r.decode('utf-8', 'replace') return u'%s' % escape(r) def dispatch_repr(self, obj, recursive): if obj is helper: return u'%r' % helper if isinstance(obj, (integer_types, float, complex)): return u'%r' % obj if isinstance(obj, string_types): return self.string_repr(obj) if isinstance(obj, RegexType): return self.regex_repr(obj) if isinstance(obj, list): return self.list_repr(obj, recursive) if isinstance(obj, tuple): return self.tuple_repr(obj, recursive) if isinstance(obj, set): return self.set_repr(obj, recursive) if isinstance(obj, frozenset): return self.frozenset_repr(obj, recursive) if isinstance(obj, dict): return self.dict_repr(obj, recursive) if deque is not None and isinstance(obj, deque): return self.deque_repr(obj, recursive) return self.object_repr(obj) def fallback_repr(self): try: info = ''.join(format_exception_only(*sys.exc_info()[:2])) except Exception: # pragma: no cover info = '?' if PY2: info = info.decode('utf-8', 'ignore') return u'<broken repr (%s)>' \ u'' % escape(info.strip()) def repr(self, obj): recursive = False for item in self._stack: if item is obj: recursive = True break self._stack.append(obj) try: try: return self.dispatch_repr(obj, recursive) except Exception: return self.fallback_repr() finally: self._stack.pop() def dump_object(self, obj): repr = items = None if isinstance(obj, dict): title = 'Contents of' items = [] for key, value in iteritems(obj): if not isinstance(key, string_types): items = None break items.append((key, self.repr(value))) if items is None: items = [] repr = self.repr(obj) for key in dir(obj): try: items.append((key, self.repr(getattr(obj, key)))) except Exception: pass title = 'Details for' title += ' ' + object.__repr__(obj)[1:-1] return self.render_object_dump(items, title, repr) def dump_locals(self, d): items = [(key, self.repr(value)) for key, value in d.items()] return self.render_object_dump(items, 'Local variables in frame') def render_object_dump(self, items, title, repr=None): html_items = [] for key, value in items: html_items.append('%s
%s
' % (escape(key), value)) if not html_items: html_items.append('Nothing') return OBJECT_DUMP_HTML % { 'title': escape(title), 'repr': repr and '
%s
' % repr or '', 'items': '\n'.join(html_items) }