push('<dl><dt><strong>%s</strong></dt>\n' % name)
doc = self.markup(getdoc(object), self.preformat)
push('<dd><tt>%s</tt></dd>\n' % doc)
def docother(self, object, name=None, mod=None, *ignored):
"""Produce HTML documentation for a data object."""
lhs = name and '<strong>%s</strong> = ' % name or ''
return lhs + self.repr(object)
def index(self, dir, shadowed=None):
"""Generate an HTML index for a directory of modules."""
if shadowed is None: shadowed = {}
for importer, name, ispkg in pkgutil.iter_modules([dir]):
if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name):
# ignore a module if its name contains a surrogate character
modpkgs.append((name, '', ispkg, name in shadowed))
contents = self.multicolumn(modpkgs, self.modpkglink)
return self.bigsection(dir, '#ffffff', '#ee77aa', contents)
# -------------------------------------------- text documentation generator
"""Class for safely making a text representation of a Python object."""
self.maxlist = self.maxtuple = 20
self.maxstring = self.maxother = 100
def repr1(self, x, level):
if hasattr(type(x), '__name__'):
methodname = 'repr_' + '_'.join(type(x).__name__.split())
if hasattr(self, methodname):
return getattr(self, methodname)(x, level)
return cram(stripid(repr(x)), self.maxother)
def repr_string(self, x, level):
test = cram(x, self.maxstring)
if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
# Backslashes are only literal in the string and are never
# needed to make any special characters, so show a raw string.
return 'r' + testrepr[0] + test + testrepr[0]
def repr_instance(self, x, level):
return cram(stripid(repr(x)), self.maxstring)
return '<%s instance>' % x.__class__.__name__
"""Formatter class for text documentation."""
# ------------------------------------------- text formatting utilities
_repr_instance = TextRepr()
repr = _repr_instance.repr
"""Format a string in bold by overstriking."""
return ''.join(ch + '\b' + ch for ch in text)
def indent(self, text, prefix=' '):
"""Indent text by prepending a given prefix to each line."""
lines = [prefix + line for line in text.split('\n')]
if lines: lines[-1] = lines[-1].rstrip()
def section(self, title, contents):
"""Format a section with a given heading."""
clean_contents = self.indent(contents).rstrip()
return self.bold(title) + '\n' + clean_contents + '\n\n'
# ---------------------------------------------- type-specific routines
def formattree(self, tree, modname, parent=None, prefix=''):
"""Render in text a class tree as returned by inspect.getclasstree()."""
if type(entry) is type(()):
result = result + prefix + classname(c, modname)
if bases and bases != (parent,):
parents = (classname(c, modname) for c in bases)
result = result + '(%s)' % ', '.join(parents)
elif type(entry) is type([]):
result = result + self.formattree(
entry, modname, c, prefix + ' ')
def docmodule(self, object, name=None, mod=None):
"""Produce text documentation for a given module object."""
name = object.__name__ # ignore the passed-in name
synop, desc = splitdoc(getdoc(object))
result = self.section('NAME', name + (synop and ' - ' + synop))
all = getattr(object, '__all__', None)
docloc = self.getdocloc(object)
result = result + self.section('MODULE REFERENCE', docloc + """
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
result = result + self.section('DESCRIPTION', desc)
for key, value in inspect.getmembers(object, inspect.isclass):
# if __all__ exists, believe it. Otherwise use old heuristic.
or (inspect.getmodule(value) or object) is object):
if visiblename(key, all, object):
classes.append((key, value))
for key, value in inspect.getmembers(object, inspect.isroutine):
# if __all__ exists, believe it. Otherwise use old heuristic.
inspect.isbuiltin(value) or inspect.getmodule(value) is object):
if visiblename(key, all, object):
funcs.append((key, value))
for key, value in inspect.getmembers(object, isdata):
if visiblename(key, all, object):
data.append((key, value))
if hasattr(object, '__path__'):
for importer, modname, ispkg in pkgutil.iter_modules(object.__path__):
modpkgs_names.add(modname)
modpkgs.append(modname + ' (package)')
result = result + self.section(
'PACKAGE CONTENTS', '\n'.join(modpkgs))
# Detect submodules as sometimes created by C extensions
for key, value in inspect.getmembers(object, inspect.ismodule):
if value.__name__.startswith(name + '.') and key not in modpkgs_names:
result = result + self.section(
'SUBMODULES', '\n'.join(submodules))
classlist = [value for key, value in classes]
contents = [self.formattree(
inspect.getclasstree(classlist, 1), name)]
for key, value in classes:
contents.append(self.document(value, key, name))
result = result + self.section('CLASSES', '\n'.join(contents))
contents.append(self.document(value, key, name))
result = result + self.section('FUNCTIONS', '\n'.join(contents))
contents.append(self.docother(value, key, name, maxlen=70))
result = result + self.section('DATA', '\n'.join(contents))
if hasattr(object, '__version__'):
version = str(object.__version__)
if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
version = version[11:-1].strip()
result = result + self.section('VERSION', version)
if hasattr(object, '__date__'):
result = result + self.section('DATE', str(object.__date__))
if hasattr(object, '__author__'):
result = result + self.section('AUTHOR', str(object.__author__))
if hasattr(object, '__credits__'):
result = result + self.section('CREDITS', str(object.__credits__))
file = inspect.getabsfile(object)
result = result + self.section('FILE', file)
def docclass(self, object, name=None, mod=None, *ignored):
"""Produce text documentation for a given class object."""
realname = object.__name__
def makename(c, m=object.__module__):
title = 'class ' + self.bold(realname)
title = self.bold(name) + ' = class ' + realname
parents = map(makename, bases)
title = title + '(%s)' % ', '.join(parents)
signature = inspect.signature(object)
except (ValueError, TypeError):
if argspec and argspec != '()':
push(name + argspec + '\n')
# List the mro, if non-trivial.
mro = deque(inspect.getmro(object))
push("Method resolution order:")
push(' ' + makename(base))
# List the built-in subclasses, if any:
(str(cls.__name__) for cls in type.__subclasses__(object)
if not cls.__name__.startswith("_") and cls.__module__ == "builtins"),
no_of_subclasses = len(subclasses)
MAX_SUBCLASSES_TO_DISPLAY = 4
push("Built-in subclasses:")
for subclassname in subclasses[:MAX_SUBCLASSES_TO_DISPLAY]:
if no_of_subclasses > MAX_SUBCLASSES_TO_DISPLAY:
str(no_of_subclasses - MAX_SUBCLASSES_TO_DISPLAY) +
# Cute little class to pump out a horizontal rule between sections.
def spill(msg, attrs, predicate):
ok, attrs = _split_list(attrs, predicate)
for name, kind, homecls, value in ok:
value = getattr(object, name)
# Some descriptors may meet a failure in their __get__.
push(self.docdata(value, name, mod))
push(self.document(value,
def spilldescriptors(msg, attrs, predicate):
ok, attrs = _split_list(attrs, predicate)
for name, kind, homecls, value in ok:
push(self.docdata(value, name, mod))
def spilldata(msg, attrs, predicate):
ok, attrs = _split_list(attrs, predicate)
for name, kind, homecls, value in ok:
if callable(value) or inspect.isdatadescriptor(value):
obj = getattr(object, name)
obj = homecls.__dict__[name]
push(self.docother(obj, name, mod, maxlen=70, doc=doc) +
attrs = [(name, kind, cls, value)
for name, kind, cls, value in classify_class_attrs(object)
if visiblename(name, obj=object)]
thisclass = mro.popleft()
attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
if object is not builtins.object and thisclass is builtins.object:
elif thisclass is object:
tag = "inherited from %s" % classname(thisclass,
sort_attributes(attrs, object)
# Pump out the attrs, segregated by kind.
attrs = spill("Methods %s:\n" % tag, attrs,
lambda t: t[1] == 'method')
attrs = spill("Class methods %s:\n" % tag, attrs,
lambda t: t[1] == 'class method')
attrs = spill("Static methods %s:\n" % tag, attrs,
lambda t: t[1] == 'static method')
attrs = spilldescriptors("Readonly properties %s:\n" % tag, attrs,
lambda t: t[1] == 'readonly property')
attrs = spilldescriptors("Data descriptors %s:\n" % tag, attrs,
lambda t: t[1] == 'data descriptor')
attrs = spilldata("Data and other attributes %s:\n" % tag, attrs,
lambda t: t[1] == 'data')
contents = '\n'.join(contents)
return title + '\n' + self.indent(contents.rstrip(), ' | ') + '\n'
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__
if _is_bound_method(object):
imclass = object.__self__.__class__
note = ' from ' + classname(imclass, mod)
if object.__self__ is not None:
note = ' method of %s instance' % classname(
object.__self__.__class__, mod)
note = ' unbound %s method' % classname(imclass,mod)
if (inspect.iscoroutinefunction(object) or
inspect.isasyncgenfunction(object)):
asyncqualifier = 'async '
title = self.bold(realname)
if cl and inspect.getattr_static(cl, realname, []) is object:
title = self.bold(name) + ' = ' + realname
if inspect.isroutine(object):
signature = inspect.signature(object)
except (ValueError, TypeError):
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
decl = asyncqualifier + title + argspec + note
doc = getdoc(object) or ''
return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')
def docdata(self, object, name=None, mod=None, cl=None):
"""Produce text documentation for a data descriptor."""
doc = getdoc(object) or ''
def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):
"""Produce text documentation for a data object."""
line = (name and name + ' = ' or '') + repr
chop = maxlen - len(line)
if chop < 0: repr = repr[:chop] + '...'
line = (name and self.bold(name) + ' = ' or '') + repr
line += '\n' + self.indent(str(doc))
class _PlainTextDoc(TextDoc):
"""Subclass of TextDoc which overrides string styling"""
# --------------------------------------------------------- user interfaces
"""The first time this is called, determine what kind of pager to use."""
"""Decide what method to use for paging through text."""
if not hasattr(sys.stdin, "isatty"):
if not hasattr(sys.stdout, "isatty"):
if not sys.stdin.isatty() or not sys.stdout.isatty():
use_pager = os.environ.get('MANPAGER') or os.environ.get('PAGER')
if sys.platform == 'win32': # pipes completely broken in Windows
return lambda text: tempfilepager(plain(text), use_pager)
elif os.environ.get('TERM') in ('dumb', 'emacs'):
return lambda text: pipepager(plain(text), use_pager)
return lambda text: pipepager(text, use_pager)
if os.environ.get('TERM') in ('dumb', 'emacs'):
if sys.platform == 'win32':
return lambda text: tempfilepager(plain(text), 'more <')
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
return lambda text: pipepager(text, 'less')
(fd, filename) = tempfile.mkstemp()
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
return lambda text: pipepager(text, 'more')