class Q(object): __doc__ = __doc__ # from the module's __doc__ above import ast import code import inspect import os import pydoc import sys import random import re import time import functools # The debugging log will go to this file; temporary files will also have # this path as a prefix, followed by a random number. OUTPUT_PATH = os.path.join( os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp', 'q') NORMAL, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN = ESCAPE_SEQUENCES TEXT_REPR = pydoc.TextRepr() # For portably converting strings between python2 and python3 BASESTRING_TYPES = BASESTRING_TYPES TEXT_TYPES = TEXT_TYPES class FileWriter(object): """An object that appends to or overwrites a single file.""" import sys # For portably converting strings between python2 and python3 BASESTRING_TYPES = BASESTRING_TYPES TEXT_TYPES = TEXT_TYPES def __init__(self, path): self.path = path self.open = open # App Engine's dev_appserver patches 'open' to simulate security # restrictions in production; we circumvent this to write output. if open.__name__ == 'FakeFile': # dev_appserver's patched 'file' self.open = open.__bases__[0] # the original built-in 'file' def write(self, mode, content): if 'b' not in mode: mode = '%sb' % mode if (isinstance(content, self.BASESTRING_TYPES) and isinstance(content, self.TEXT_TYPES)): content = content.encode('utf-8') try: f = self.open(self.path, mode) f.write(content) f.close() except IOError: pass class Writer: """Abstract away the output pipe, timestamping, and color support.""" NORMAL, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN = ESCAPE_SEQUENCES def __init__(self, file_writer, time): self.color = True self.file_writer = file_writer self.gap_seconds = 2 self.time = time # the 'time' module (needed because no globals) self.start_time = self.time.time() self.last_write = 0 def write(self, chunks): """Writes out a list of strings as a single timestamped unit.""" if not self.color: chunks = [x for x in chunks if not x.startswith('\x1b')] content = ''.join(chunks) now = self.time.time() prefix = '%4.1fs ' % ((now - self.start_time) % 100) indent = ' ' * len(prefix) if self.color: prefix = self.YELLOW + prefix + self.NORMAL if now - self.last_write >= self.gap_seconds: prefix = '\n' + prefix self.last_write = now output = prefix + content.replace('\n', '\n' + indent) self.file_writer.write('a', output + '\n') class Stanza: """Abstract away indentation and line-wrapping.""" def __init__(self, indent=0, width=80 - 7): self.chunks = [' ' * indent] self.indent = indent self.column = indent self.width = width def newline(self): if len(self.chunks) > 1: self.column = self.width def add(self, items, sep='', wrap=True): """Adds a list of strings that are to be printed on one line.""" items = list(map(str, items)) size = sum([len(x) for x in items if not x.startswith('\x1b')]) if (wrap and self.column > self.indent and self.column + len(sep) + size > self.width): self.chunks.append(sep.rstrip() + '\n' + ' ' * self.indent) self.column = self.indent else: self.chunks.append(sep) self.column += len(sep) self.chunks.extend(items) self.column += size def __init__(self): self.writer = self.Writer(self.FileWriter(self.OUTPUT_PATH), self.time) self.indent = 0 # in_console tracks whether we're in an interactive console. # We use it to display the caller as "<console>" instead of "<module>". self.in_console = False def unindent(self, lines): """Removes any indentation that is common to all of the given lines.""" indent = min( len(self.re.match(r'^ *', line).group()) for line in lines) return [line[indent:].rstrip() for line in lines] def safe_repr(self, value): # TODO: Use colour to distinguish '...' elision from actual '...' # TODO: Show a nicer repr for SRE.Match objects. # TODO: Show a nicer repr for big multiline strings. result = self.TEXT_REPR.repr(value) if isinstance(value, self.BASESTRING_TYPES) and len(value) > 80: # If the string is big, save it to a file for later examination. if isinstance(value, self.TEXT_TYPES): value = value.encode('utf-8') path = self.OUTPUT_PATH + '%08d.txt' % self.random.randrange(1e8) self.FileWriter(path).write('w', value) result += ' (file://' + path + ')' return result def get_call_exprs(self, line): """Gets the argument expressions from the source of a function call.""" line = line.lstrip() try: tree = self.ast.parse(line) except SyntaxError: return None for node in self.ast.walk(tree): if isinstance(node, self.ast.Call): offsets = [] for arg in node.args: # In Python 3.4 the col_offset is calculated wrong. See # https://bugs.python.org/issue21295 if isinstance(arg, self.ast.Attribute) and ( (3, 4, 0) <= self.sys.version_info <= (3, 4, 3)): offsets.append(arg.col_offset - len(arg.value.id) - 1) else: offsets.append(arg.col_offset) if node.keywords: line = line[:node.keywords[0].value.col_offset] line = self.re.sub(r'\w+\s*=\s*$', '', line) else: line = self.re.sub(r'\s*\)\s*$', '', line) offsets.append(len(line)) args = [] for i in range(len(node.args)): args.append(line[offsets[i]:offsets[i + 1]].rstrip(', ')) return args def show(self, func_name, values, labels=None): """Prints out nice representations of the given values.""" s = self.Stanza(self.indent) if func_name == '<module>' and self.in_console: func_name = '<console>' s.add([func_name + ': ']) reprs = map(self.safe_repr, values) if labels: sep = '' for label, repr in zip(labels, reprs): s.add([label + '=', self.CYAN, repr, self.NORMAL], sep) sep = ', ' else: sep = '' for repr in reprs: s.add([self.CYAN, repr, self.NORMAL], sep) sep = ', ' self.writer.write(s.chunks) def tb(self, max_depth=None): if max_depth is not None: max_depth -= 1 s = self.Stanza(self.indent) for frame, filename, lineno, module, code_context, i in reversed( self.inspect.stack()[1:max_depth]): s.add(['In ', self.RED, frame.f_code.co_name, self.NORMAL, '():']) s.newline() s.add([ self.GREEN, filename, self.NORMAL, ':', self.BLUE, lineno, self.NORMAL ]) s.newline() if code_context: s.add([self.NORMAL, code_context[i]]) self.writer.write(s.chunks) def __enter__(self): pass def __exit__(self, etype, evalue, etb): if evalue is None: return info = self.inspect.getframeinfo(etb.tb_next or etb, context=3) s = self.Stanza(self.indent) s.add([self.RED, '!> ', self.safe_repr(evalue), self.NORMAL]) s.add(['at ', info.filename, ':', info.lineno], ' ') lines = self.unindent(info.code_context) firstlineno = info.lineno - info.index fmt = '%' + str(len(str(firstlineno + len(lines)))) + 'd' for i, line in enumerate(lines): s.newline() s.add([ i == info.index and self.MAGENTA or '', fmt % (i + firstlineno), i == info.index and '> ' or ': ', line, self.NORMAL ]) self.writer.write(s.chunks) def trace(self, func): """Decorator to print out a function's arguments and return value.""" def wrapper(*args, **kwargs): # Print out the call to the function with its arguments. s = self.Stanza(self.indent) s.add([self.GREEN, func.__name__, self.NORMAL, '(']) s.indent += 4 sep = '' for arg in args: s.add([self.CYAN, self.safe_repr(arg), self.NORMAL], sep) sep = ', ' for name, value in sorted(kwargs.items()): s.add([ name + '=', self.CYAN, self.safe_repr(value), self.NORMAL ], sep) sep = ', ' s.add(')', wrap=False) self.writer.write(s.chunks) # Call the function. self.indent += 2 try: result = func(*args, **kwargs) except: # Display an exception. self.indent -= 2 etype, evalue, etb = self.sys.exc_info() info = self.inspect.getframeinfo(etb.tb_next, context=3) s = self.Stanza(self.indent) s.add([self.RED, '!> ', self.safe_repr(evalue), self.NORMAL]) s.add(['at ', info.filename, ':', info.lineno], ' ') lines = self.unindent(info.code_context) firstlineno = info.lineno - info.index fmt = '%' + str(len(str(firstlineno + len(lines)))) + 'd' for i, line in enumerate(lines): s.newline() s.add([ i == info.index and self.MAGENTA or '', fmt % (i + firstlineno), i == info.index and '> ' or ': ', line, self.NORMAL ]) self.writer.write(s.chunks) raise # Display the return value. self.indent -= 2 s = self.Stanza(self.indent) s.add([ self.GREEN, '-> ', self.CYAN, self.safe_repr(result), self.NORMAL ]) self.writer.write(s.chunks) return result return self.functools.update_wrapper(wrapper, func) def __call__(self, *args): """If invoked as a decorator on a function, adds tracing output to the function; otherwise immediately prints out the arguments.""" info = self.inspect.getframeinfo(self.sys._getframe(1), context=9) # info.index is the index of the line containing the end of the call # expression, so this gets a few lines up to the end of the expression. lines = [''] if info.code_context: lines = info.code_context[:info.index + 1] # If we see "@q" on a single line, behave like a trace decorator. for line in lines: if line.strip() in ('@q', '@q()') and args: return self.trace(args[0]) # Otherwise, search for the beginning of the call expression; once it # parses, use the expressions in the call to label the debugging # output. for i in range(1, len(lines) + 1): labels = self.get_call_exprs(''.join(lines[-i:]).replace('\n', '')) if labels: break self.show(info.function, args, labels) return args and args[0] def __truediv__(self, arg): # a tight-binding operator """Prints out and returns the argument.""" info = self.inspect.getframeinfo(self.sys._getframe(1)) self.show(info.function, [arg]) return arg # Compat for Python 2 without from future import __division__ turned on __div__ = __truediv__ __or__ = __div__ # a loose-binding operator q = __call__ # backward compatibility with @q.q t = trace # backward compatibility with @q.t __name__ = 'Q' # App Engine's import hook dies if this isn't present def d(self, depth=1): """Launches an interactive console at the point where it's called.""" info = self.inspect.getframeinfo(self.sys._getframe(1)) s = self.Stanza(self.indent) s.add([info.function + ': ']) s.add([self.MAGENTA, 'Interactive console opened', self.NORMAL]) self.writer.write(s.chunks) frame = self.sys._getframe(depth) env = frame.f_globals.copy() env.update(frame.f_locals) self.indent += 2 self.in_console = True self.code.interact('Python console opened by q.d() in ' + info.function, local=env) self.in_console = False self.indent -= 2 s = self.Stanza(self.indent) s.add([info.function + ': ']) s.add([self.MAGENTA, 'Interactive console closed', self.NORMAL]) self.writer.write(s.chunks)
class DLibraryDoc(pydoc.Doc): """Formatter class to output DLibrary docs. """ __tab = '.' * 8 # Html spaces doesn't work for Bitbucket markdown! __newline = ' \n' # MD needs two extra spaces to see it as an actual new line. __rule = '-' * 80 + '\n' @classmethod def __heading(cls, text: str, level: int = 1) -> str: return '%s %s%s' % ('#' * level, text, cls.__newline) @classmethod def __block_quote(cls, text: str) -> str: return cls.__newline.join( ['> %s' % line for line in (text or '').split('\n')]) @classmethod def __get_clazz_attributes(cls, clazz) -> list: return [(name, kind, clz, value) for name, kind, clz, value in pydoc.classify_class_attrs(clazz) if DLibraryDoc.__show_clazz_attribute(clazz, name, clz)] @classmethod def __show_clazz_attribute(cls, clazz, name: str, clz) -> bool: specials = ['__init__'] return pydoc.visiblename(name, obj=clazz) and ( (name in specials and clz == clazz) or not name.startswith('__')) @classmethod def __doc_class_mro(cls, clazz, mro: deque) -> str: mro = [base for base in mro ][1:] # First remove own class name, as we see that. return cls.__heading( '\> ' + ' > '.join( [pydoc.classname(base, clazz.__module__) for base in mro]), 3) def docproperty(self, prop, name=None, mod=None, cl=None) -> str: """Produces text documentation for a property. """ return self.__heading( ('%s `GET%s%s` `%s`' % (self.__bold(self.__escape(name)), '/SET' if prop.fset is not None else '', '/DEL' if prop.fdel is not None else '', pydoc.getdoc(prop))).rstrip(), 3) def docdata(self, data, name=None, mod=None, cl=None) -> str: """Produces text documentation for data. """ return '- %s = %s' % (self.__bold(self.__escape(name)), data) @staticmethod def __escape(text: str) -> str: return text.replace('*', '\*').replace('_', '\_') if text else '' @staticmethod def __clean(text: str) -> str: return text.strip() @staticmethod def __bold(text: str) -> str: return '**%s**' % text # We use **, as __ has special meaning to Python in names! @staticmethod def __header_link(link: str, anchor: str) -> str: # Bitbucket markdown doesn't support normal html anchor links, so we'll use header links! return '[%s](#%s)' % (link, 'markdown-header-%s' % anchor.replace(' ', '-').lower()) def __indent(self, text: str) -> str: # Indent text by prepending the indent sequence to each line. return self.__newline.join( [self.__tab + line for line in (text or '').split('\n')]) def _section(self, title: str, contents: str, level: int = 1) -> str: return '%s%s%s%s' % (self.__heading( title, level), self.__newline, contents, self.__newline * 2) def _formattree(self, tree, module, modname, clazz_names: list, parent=None, level=0): """Render in text a class tree as returned by inspect.getclasstree(). """ result = '' for entry in tree: clazz = None if type(entry) is type(()): clazz, bases = entry clazz_name = pydoc.classname(clazz, modname) if clazz_name in clazz_names: clazz_name = self.__header_link(clazz_name, clazz_name) result += self.__tab * level + clazz_name if bases and bases != (parent, ): parents = (pydoc.classname(c, modname) for c in bases) result += '(%s)' % ', '.join(parents) result += self.__newline elif type(entry) is type([]): result += self._formattree(entry, module, modname, clazz_names, clazz, level + 1) return result def _create_module_name_heading(self, module_name: str) -> str: return self.__bold('MODULE %s' % module_name) @staticmethod def _create_module_name_content(synopsis: str) -> str: return synopsis @staticmethod def _create_module_doc_loc_heading() -> str: return 'MODULE REFERENCE' @staticmethod def _create_module_doc_loc_content(doc_loc: str) -> str: return doc_loc + """ The following documentation is automatically generated from the Python source files. It may be incomplete, incorrect or include features that are considered implementation detail and may vary between Python implementations. When in doubt, consult the module reference at the location listed above. """ @staticmethod def _create_module_description_heading() -> str: return 'DESCRIPTION' @staticmethod def _create_module_description_content(description: str) -> str: return description @staticmethod def _create_module_packages_heading() -> str: return 'PACKAGE CONTENTS' def _create_module_packages_content(self, packages: list) -> str: return self.__newline.join(packages) @staticmethod def _create_module_submodules_heading() -> str: return 'SUBMODULES' def _create_module_submodules_content(self, submodules: list) -> str: return self.__newline.join(submodules) @staticmethod def _create_module_classes_heading() -> str: return 'CLASSES' def _create_module_classes_content(self, module, name: str, classes: list) -> str: contents = [ self._formattree( inspect.getclasstree([value for key, value in classes], 1), module, name, [item[0] for item in classes]) ] for key, value in classes: contents.append(self.__rule) contents.append(self.document(value, key, name, 2)) return self.__newline.join(contents) @staticmethod def _create_module_functions_heading() -> str: return 'FUNCTIONS' def _create_module_functions_content(self, name: str, functions: list) -> str: return self.__newline.join( [self.document(value, key, name) for key, value in functions]) @staticmethod def _create_module_data_heading() -> str: return 'DATA' def _create_module_data_content(self, name: str, data: list) -> str: return self.__newline.join([ self.docother(value, key, name, maxlen=80) for key, value in data ]) @staticmethod def _create_module_version_heading() -> str: return 'VERSION' @staticmethod def _create_module_version_content(module) -> str: version = str(module.__version__) if version[:11] == '$' + 'Revision: ' and version[-1:] == '$': version = version[11:-1].strip() return version @staticmethod def _create_module_date_heading() -> str: return 'DATE' @staticmethod def _create_module_date_content(module) -> str: return str(module.__date__) @staticmethod def _create_module_author_heading() -> str: return 'AUTHOR' @staticmethod def _create_module_author_content(module) -> str: return str(module.__author__) @staticmethod def _create_module_credits_heading() -> str: return 'CREDITS' @staticmethod def _create_module_credits_content(module) -> str: return str(module.__credits__) @staticmethod def _create_module_file_heading() -> str: return 'FILE' @staticmethod def _create_module_file_content(module) -> str: try: file = inspect.getabsfile(module) except TypeError: file = '(built-in)' return file # noinspection PyUnusedLocal def docmodule(self, module, name=None, *args) -> str: """Produce text documentation for a given module object. """ name = name or module.__name__ synopsis, description = pydoc.splitdoc(pydoc.getdoc(module)) synopsis = self.__clean(self.__escape(synopsis)) description = self.__clean(self.__escape(description)) doc_loc = self.getdocloc(module) packages, package_names = self.__get_module_packages(module) submodules = self.__get_module_submodules(module, name, package_names) classes = self.__get_module_classes(module) functions = self.__get_module_functions(module) data = self.__get_module_data(module) return '%s%s%s%s%s%s%s%s%s%s%s%s%s' % ( self._section(self._create_module_name_heading(name), self._create_module_name_content(synopsis)), self._section(self._create_module_doc_loc_heading(), self._create_module_doc_loc_content(doc_loc)) if doc_loc is not None else '', self._section(self._create_module_description_heading(), self._create_module_description_content(description)) if description else '', self._section(self._create_module_packages_heading(), self._create_module_packages_content(packages)) if packages else '', self._section(self._create_module_submodules_heading(), self._create_module_submodules_content(submodules)) if submodules else '', self._section( self._create_module_classes_heading(), self._create_module_classes_content(module, name, classes)) if classes else '', self._section( self._create_module_functions_heading(), self._create_module_functions_content(name, functions)) if functions else '', self._section(self._create_module_data_heading( ), self._create_module_data_content(name, data)) if data else '', self._section(self._create_module_version_heading(), self._create_module_version_content(module)) if hasattr(module, '__version__') else '', self._section(self._create_module_date_heading(), self._create_module_date_content(module)) if hasattr( module, '__date__') else '', self._section(self._create_module_author_heading(), self._create_module_author_content(module)) if hasattr(module, '__author__') else '', self._section(self._create_module_credits_heading(), self._create_module_credits_content(module)) if hasattr(module, '__credits__') else '', self._section(self._create_module_file_heading(), self._create_module_file_content(module))) @staticmethod def __get_module_packages(module) -> (list, set): """Returns both the packages and the package names. """ packages = [] package_names = set() if hasattr(module, '__path__'): for importer, modname, is_package in pkgutil.iter_modules( module.__path__): package_names.add(modname) packages.append('%s (package)' % modname if is_package else modname) packages.sort() return packages, package_names @staticmethod def __get_module_submodules(module, name, package_names) -> list: """Detect submodules as sometimes created by C extensions! """ submodules = [] for key, value in inspect.getmembers(module, inspect.ismodule): if value.__name__.startswith(name + '.') and key not in package_names: submodules.append(key) submodules.sort() return submodules @staticmethod def __get_module_classes(module) -> list: all_things = getattr(module, '__all__', None) classes = [] for key, value in inspect.getmembers(module, inspect.isclass): # if __all__ exists, believe it. Otherwise use old heuristic. if all_things is not None or (inspect.getmodule(value) or module) is module: if pydoc.visiblename(key, all_things, module): classes.append((key, value)) return classes @staticmethod def __get_module_functions(module) -> list: all_things = getattr(module, '__all__', None) functions = [] for key, value in inspect.getmembers(module, inspect.isroutine): # if __all__ exists, believe it. Otherwise use old heuristic. if all_things is not None or inspect.isbuiltin( value) or inspect.getmodule(value) is module: if pydoc.visiblename(key, all_things, module): functions.append((key, value)) return functions @staticmethod def __get_module_data(module) -> list: all_things = getattr(module, '__all__', None) data = [] for key, value in inspect.getmembers(module, pydoc.isdata): if pydoc.visiblename(key, all_things, module): data.append((key, value)) return data def _create_class_name_heading(self, clazz, name: str) -> str: name = self.__escape(name) real = self.__escape(clazz.__name__) bases = clazz.__bases__ return '%s%s' % ( ('class ' + self.__bold(real)) if name == real else (self.__bold(name) + ' = class ' + real), '(%s)' % ', '.join( map(lambda c, m=clazz.__module__: pydoc.classname(c, m), bases)) if bases else '') def _create_class_name_content(self, synopsis: str, description: str) -> str: return synopsis + (self.__newline if description else '') + description def docclass(self, clazz, name=None, mod=None, level=1, *args) -> str: """Produce text documentation for a given class object. """ name = name or clazz.__name__ synopsis, description = pydoc.splitdoc(pydoc.getdoc(clazz)) synopsis = self.__clean(self.__escape(synopsis)) description = self.__clean(self.__escape(description)) mro = deque(inspect.getmro(clazz)) attrs = self.__get_clazz_attributes(clazz) return '%s%s%s' % ( self._section( self._create_class_name_heading(clazz, name), self._create_class_name_content(synopsis, description), level), # List the mro, if non-trivial. self.__doc_class_mro(clazz, mro) if len(mro) > 2 else '', self.__newline.join([ self.__docclass_attribute(clazz, mod, attr) for attr in attrs ])) def _create_class_methods_content(self, clazz, mod, attribute) -> str: name = attribute[0] value = attribute[3] try: value = getattr(clazz, name) except Exception: # Some descriptors may meet a failure in their __get__. (bug #1785) return self._docdescriptor(name, value, mod) + self.__newline else: return self.document(value, name, mod, clazz) + self.__newline def _create_class_data_descriptors_content(self, clazz, mod, attribute) -> str: if isinstance(attribute[3], property): return self.docproperty(attribute[3], attribute[0], mod, clazz) + self.__newline name = attribute[0] value = attribute[3] return self._docdescriptor(name, value, mod) + self.__newline def _create_class_data_and_other_attributes_content( self, clazz, mod, attribute) -> str: return self.docdata(attribute[3], attribute[0], mod, clazz) def __docclass_attribute(self, clazz, mod, attribute) -> str: """Produces text documentation for given attributes for the clazz or an inherited class. """ return { 'method': self._create_class_methods_content, 'class method': self._create_class_methods_content, 'static method': self._create_class_methods_content, 'data descriptor': self._create_class_data_descriptors_content, 'data': self._create_class_data_and_other_attributes_content }.get(attribute[1])(clazz, mod, attribute) repr = pydoc.TextRepr().repr def formatvalue(self, object): """Format an argument default value as text.""" return '=' + self.repr(object) def docroutine(self, object, name=None, mod=None, cl=None): """Produce text documentation for a function or method object.""" realname = object.__name__ name = name or realname note = '' skipdocs = 0 if inspect.ismethod(object): imclass = object.__self__.__class__ if cl: if imclass is not cl: note = ' from ' + pydoc.classname(imclass, mod) else: if object.__self__ is not None: note = ' method of %s instance' % pydoc.classname( object.__self__.__class__, mod) else: note = ' unbound %s method' % pydoc.classname(imclass, mod) object = object.__func__ if name == realname: title = self.__bold(self.__escape(realname)) else: if (cl and realname in cl.__dict__ and cl.__dict__[realname] is object): skipdocs = 1 title = self.__bold(name) + ' = ' + realname if inspect.isfunction(object): args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann = \ inspect.getfullargspec(object) argspec = inspect.formatargspec( args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann, formatvalue=self.formatvalue, formatannotation=inspect.formatannotationrelativeto(object)) if realname == '<lambda>': title = self.__bold(name) + ' lambda ' # XXX lambda's won't usually have func_annotations['return'] # since the syntax doesn't support but it is possible. # So removing parentheses isn't truly safe. argspec = argspec[1:-1] # remove parentheses else: argspec = '(...)' decl = self.__heading(title + argspec + note, 3) if skipdocs: return decl + self.__newline else: doc = pydoc.getdoc(object) or '' return '%s%s%s%s%s' % (self.__newline, decl, self.__newline, doc and self.__block_quote( self.__clean(self.__escape(doc))), self.__newline) def _docdescriptor(self, name, value, mod): results = [] push = results.append if name: push(self.__bold(self.__escape(name))) push(self.__newline) doc = pydoc.getdoc(value) or '' if doc: push(self.__indent(doc)) push(self.__newline) return ''.join(results) def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None): """Produce text documentation for a data object.""" repr = self.repr(object) if maxlen: line = (name and self.__escape(name) + ' = ' or '') + repr chop = maxlen - len(line) if chop < 0: repr = repr[:chop] + '...' line = (name and self.__bold(self.__escape(name)) + ' = ' or '') + repr if doc is not None: line += self.__newline + self.__indent(str(doc)) return line