Home | History | Annotate | Download | only in Lib
      1 #!/usr/bin/env python3
      2 """Generate Python documentation in HTML or text for interactive use.
      3 
      4 At the Python interactive prompt, calling help(thing) on a Python object
      5 documents the object, and calling help() starts up an interactive
      6 help session.
      7 
      8 Or, at the shell command line outside of Python:
      9 
     10 Run "pydoc <name>" to show documentation on something.  <name> may be
     11 the name of a function, module, package, or a dotted reference to a
     12 class or function within a module or module in a package.  If the
     13 argument contains a path segment delimiter (e.g. slash on Unix,
     14 backslash on Windows) it is treated as the path to a Python source file.
     15 
     16 Run "pydoc -k <keyword>" to search for a keyword in the synopsis lines
     17 of all available modules.
     18 
     19 Run "pydoc -p <port>" to start an HTTP server on the given port on the
     20 local machine.  Port number 0 can be used to get an arbitrary unused port.
     21 
     22 Run "pydoc -b" to start an HTTP server on an arbitrary unused port and
     23 open a Web browser to interactively browse documentation.  The -p option
     24 can be used with the -b option to explicitly specify the server port.
     25 
     26 Run "pydoc -w <name>" to write out the HTML documentation for a module
     27 to a file named "<name>.html".
     28 
     29 Module docs for core modules are assumed to be in
     30 
     31     https://docs.python.org/X.Y/library/
     32 
     33 This can be overridden by setting the PYTHONDOCS environment variable
     34 to a different URL or to a local directory containing the Library
     35 Reference Manual pages.
     36 """
     37 __all__ = ['help']
     38 __author__ = "Ka-Ping Yee <ping (at] lfw.org>"
     39 __date__ = "26 February 2001"
     40 
     41 __credits__ = """Guido van Rossum, for an excellent programming language.
     42 Tommy Burnette, the original creator of manpy.
     43 Paul Prescod, for all his work on onlinehelp.
     44 Richard Chamberlain, for the first implementation of textdoc.
     45 """
     46 
     47 # Known bugs that can't be fixed here:
     48 #   - synopsis() cannot be prevented from clobbering existing
     49 #     loaded modules.
     50 #   - If the __file__ attribute on a module is a relative path and
     51 #     the current directory is changed with os.chdir(), an incorrect
     52 #     path will be displayed.
     53 
     54 import builtins
     55 import importlib._bootstrap
     56 import importlib._bootstrap_external
     57 import importlib.machinery
     58 import importlib.util
     59 import inspect
     60 import io
     61 import os
     62 import pkgutil
     63 import platform
     64 import re
     65 import sys
     66 import time
     67 import tokenize
     68 import urllib.parse
     69 import warnings
     70 from collections import deque
     71 from reprlib import Repr
     72 from traceback import format_exception_only
     73 
     74 
     75 # --------------------------------------------------------- common routines
     76 
     77 def pathdirs():
     78     """Convert sys.path into a list of absolute, existing, unique paths."""
     79     dirs = []
     80     normdirs = []
     81     for dir in sys.path:
     82         dir = os.path.abspath(dir or '.')
     83         normdir = os.path.normcase(dir)
     84         if normdir not in normdirs and os.path.isdir(dir):
     85             dirs.append(dir)
     86             normdirs.append(normdir)
     87     return dirs
     88 
     89 def getdoc(object):
     90     """Get the doc string or comments for an object."""
     91     result = inspect.getdoc(object) or inspect.getcomments(object)
     92     return result and re.sub('^ *\n', '', result.rstrip()) or ''
     93 
     94 def splitdoc(doc):
     95     """Split a doc string into a synopsis line (if any) and the rest."""
     96     lines = doc.strip().split('\n')
     97     if len(lines) == 1:
     98         return lines[0], ''
     99     elif len(lines) >= 2 and not lines[1].rstrip():
    100         return lines[0], '\n'.join(lines[2:])
    101     return '', '\n'.join(lines)
    102 
    103 def classname(object, modname):
    104     """Get a class name and qualify it with a module name if necessary."""
    105     name = object.__name__
    106     if object.__module__ != modname:
    107         name = object.__module__ + '.' + name
    108     return name
    109 
    110 def isdata(object):
    111     """Check if an object is of a type that probably means it's data."""
    112     return not (inspect.ismodule(object) or inspect.isclass(object) or
    113                 inspect.isroutine(object) or inspect.isframe(object) or
    114                 inspect.istraceback(object) or inspect.iscode(object))
    115 
    116 def replace(text, *pairs):
    117     """Do a series of global replacements on a string."""
    118     while pairs:
    119         text = pairs[1].join(text.split(pairs[0]))
    120         pairs = pairs[2:]
    121     return text
    122 
    123 def cram(text, maxlen):
    124     """Omit part of a string if needed to make it fit in a maximum length."""
    125     if len(text) > maxlen:
    126         pre = max(0, (maxlen-3)//2)
    127         post = max(0, maxlen-3-pre)
    128         return text[:pre] + '...' + text[len(text)-post:]
    129     return text
    130 
    131 _re_stripid = re.compile(r' at 0x[0-9a-f]{6,16}(>+)$', re.IGNORECASE)
    132 def stripid(text):
    133     """Remove the hexadecimal id from a Python object representation."""
    134     # The behaviour of %p is implementation-dependent in terms of case.
    135     return _re_stripid.sub(r'\1', text)
    136 
    137 def _is_some_method(obj):
    138     return (inspect.isfunction(obj) or
    139             inspect.ismethod(obj) or
    140             inspect.isbuiltin(obj) or
    141             inspect.ismethoddescriptor(obj))
    142 
    143 def _is_bound_method(fn):
    144     """
    145     Returns True if fn is a bound method, regardless of whether
    146     fn was implemented in Python or in C.
    147     """
    148     if inspect.ismethod(fn):
    149         return True
    150     if inspect.isbuiltin(fn):
    151         self = getattr(fn, '__self__', None)
    152         return not (inspect.ismodule(self) or (self is None))
    153     return False
    154 
    155 
    156 def allmethods(cl):
    157     methods = {}
    158     for key, value in inspect.getmembers(cl, _is_some_method):
    159         methods[key] = 1
    160     for base in cl.__bases__:
    161         methods.update(allmethods(base)) # all your base are belong to us
    162     for key in methods.keys():
    163         methods[key] = getattr(cl, key)
    164     return methods
    165 
    166 def _split_list(s, predicate):
    167     """Split sequence s via predicate, and return pair ([true], [false]).
    168 
    169     The return value is a 2-tuple of lists,
    170         ([x for x in s if predicate(x)],
    171          [x for x in s if not predicate(x)])
    172     """
    173 
    174     yes = []
    175     no = []
    176     for x in s:
    177         if predicate(x):
    178             yes.append(x)
    179         else:
    180             no.append(x)
    181     return yes, no
    182 
    183 def visiblename(name, all=None, obj=None):
    184     """Decide whether to show documentation on a variable."""
    185     # Certain special names are redundant or internal.
    186     # XXX Remove __initializing__?
    187     if name in {'__author__', '__builtins__', '__cached__', '__credits__',
    188                 '__date__', '__doc__', '__file__', '__spec__',
    189                 '__loader__', '__module__', '__name__', '__package__',
    190                 '__path__', '__qualname__', '__slots__', '__version__'}:
    191         return 0
    192     # Private names are hidden, but special names are displayed.
    193     if name.startswith('__') and name.endswith('__'): return 1
    194     # Namedtuples have public fields and methods with a single leading underscore
    195     if name.startswith('_') and hasattr(obj, '_fields'):
    196         return True
    197     if all is not None:
    198         # only document that which the programmer exported in __all__
    199         return name in all
    200     else:
    201         return not name.startswith('_')
    202 
    203 def classify_class_attrs(object):
    204     """Wrap inspect.classify_class_attrs, with fixup for data descriptors."""
    205     results = []
    206     for (name, kind, cls, value) in inspect.classify_class_attrs(object):
    207         if inspect.isdatadescriptor(value):
    208             kind = 'data descriptor'
    209         results.append((name, kind, cls, value))
    210     return results
    211 
    212 def sort_attributes(attrs, object):
    213     'Sort the attrs list in-place by _fields and then alphabetically by name'
    214     # This allows data descriptors to be ordered according
    215     # to a _fields attribute if present.
    216     fields = getattr(object, '_fields', [])
    217     try:
    218         field_order = {name : i-len(fields) for (i, name) in enumerate(fields)}
    219     except TypeError:
    220         field_order = {}
    221     keyfunc = lambda attr: (field_order.get(attr[0], 0), attr[0])
    222     attrs.sort(key=keyfunc)
    223 
    224 # ----------------------------------------------------- module manipulation
    225 
    226 def ispackage(path):
    227     """Guess whether a path refers to a package directory."""
    228     if os.path.isdir(path):
    229         for ext in ('.py', '.pyc'):
    230             if os.path.isfile(os.path.join(path, '__init__' + ext)):
    231                 return True
    232     return False
    233 
    234 def source_synopsis(file):
    235     line = file.readline()
    236     while line[:1] == '#' or not line.strip():
    237         line = file.readline()
    238         if not line: break
    239     line = line.strip()
    240     if line[:4] == 'r"""': line = line[1:]
    241     if line[:3] == '"""':
    242         line = line[3:]
    243         if line[-1:] == '\\': line = line[:-1]
    244         while not line.strip():
    245             line = file.readline()
    246             if not line: break
    247         result = line.split('"""')[0].strip()
    248     else: result = None
    249     return result
    250 
    251 def synopsis(filename, cache={}):
    252     """Get the one-line summary out of a module file."""
    253     mtime = os.stat(filename).st_mtime
    254     lastupdate, result = cache.get(filename, (None, None))
    255     if lastupdate is None or lastupdate < mtime:
    256         # Look for binary suffixes first, falling back to source.
    257         if filename.endswith(tuple(importlib.machinery.BYTECODE_SUFFIXES)):
    258             loader_cls = importlib.machinery.SourcelessFileLoader
    259         elif filename.endswith(tuple(importlib.machinery.EXTENSION_SUFFIXES)):
    260             loader_cls = importlib.machinery.ExtensionFileLoader
    261         else:
    262             loader_cls = None
    263         # Now handle the choice.
    264         if loader_cls is None:
    265             # Must be a source file.
    266             try:
    267                 file = tokenize.open(filename)
    268             except OSError:
    269                 # module can't be opened, so skip it
    270                 return None
    271             # text modules can be directly examined
    272             with file:
    273                 result = source_synopsis(file)
    274         else:
    275             # Must be a binary module, which has to be imported.
    276             loader = loader_cls('__temp__', filename)
    277             # XXX We probably don't need to pass in the loader here.
    278             spec = importlib.util.spec_from_file_location('__temp__', filename,
    279                                                           loader=loader)
    280             try:
    281                 module = importlib._bootstrap._load(spec)
    282             except:
    283                 return None
    284             del sys.modules['__temp__']
    285             result = module.__doc__.splitlines()[0] if module.__doc__ else None
    286         # Cache the result.
    287         cache[filename] = (mtime, result)
    288     return result
    289 
    290 class ErrorDuringImport(Exception):
    291     """Errors that occurred while trying to import something to document it."""
    292     def __init__(self, filename, exc_info):
    293         self.filename = filename
    294         self.exc, self.value, self.tb = exc_info
    295 
    296     def __str__(self):
    297         exc = self.exc.__name__
    298         return 'problem in %s - %s: %s' % (self.filename, exc, self.value)
    299 
    300 def importfile(path):
    301     """Import a Python source file or compiled file given its path."""
    302     magic = importlib.util.MAGIC_NUMBER
    303     with open(path, 'rb') as file:
    304         is_bytecode = magic == file.read(len(magic))
    305     filename = os.path.basename(path)
    306     name, ext = os.path.splitext(filename)
    307     if is_bytecode:
    308         loader = importlib._bootstrap_external.SourcelessFileLoader(name, path)
    309     else:
    310         loader = importlib._bootstrap_external.SourceFileLoader(name, path)
    311     # XXX We probably don't need to pass in the loader here.
    312     spec = importlib.util.spec_from_file_location(name, path, loader=loader)
    313     try:
    314         return importlib._bootstrap._load(spec)
    315     except:
    316         raise ErrorDuringImport(path, sys.exc_info())
    317 
    318 def safeimport(path, forceload=0, cache={}):
    319     """Import a module; handle errors; return None if the module isn't found.
    320 
    321     If the module *is* found but an exception occurs, it's wrapped in an
    322     ErrorDuringImport exception and reraised.  Unlike __import__, if a
    323     package path is specified, the module at the end of the path is returned,
    324     not the package at the beginning.  If the optional 'forceload' argument
    325     is 1, we reload the module from disk (unless it's a dynamic extension)."""
    326     try:
    327         # If forceload is 1 and the module has been previously loaded from
    328         # disk, we always have to reload the module.  Checking the file's
    329         # mtime isn't good enough (e.g. the module could contain a class
    330         # that inherits from another module that has changed).
    331         if forceload and path in sys.modules:
    332             if path not in sys.builtin_module_names:
    333                 # Remove the module from sys.modules and re-import to try
    334                 # and avoid problems with partially loaded modules.
    335                 # Also remove any submodules because they won't appear
    336                 # in the newly loaded module's namespace if they're already
    337                 # in sys.modules.
    338                 subs = [m for m in sys.modules if m.startswith(path + '.')]
    339                 for key in [path] + subs:
    340                     # Prevent garbage collection.
    341                     cache[key] = sys.modules[key]
    342                     del sys.modules[key]
    343         module = __import__(path)
    344     except:
    345         # Did the error occur before or after the module was found?
    346         (exc, value, tb) = info = sys.exc_info()
    347         if path in sys.modules:
    348             # An error occurred while executing the imported module.
    349             raise ErrorDuringImport(sys.modules[path].__file__, info)
    350         elif exc is SyntaxError:
    351             # A SyntaxError occurred before we could execute the module.
    352             raise ErrorDuringImport(value.filename, info)
    353         elif issubclass(exc, ImportError) and value.name == path:
    354             # No such module in the path.
    355             return None
    356         else:
    357             # Some other error occurred during the importing process.
    358             raise ErrorDuringImport(path, sys.exc_info())
    359     for part in path.split('.')[1:]:
    360         try: module = getattr(module, part)
    361         except AttributeError: return None
    362     return module
    363 
    364 # ---------------------------------------------------- formatter base class
    365 
    366 class Doc:
    367 
    368     PYTHONDOCS = os.environ.get("PYTHONDOCS",
    369                                 "https://docs.python.org/%d.%d/library"
    370                                 % sys.version_info[:2])
    371 
    372     def document(self, object, name=None, *args):
    373         """Generate documentation for an object."""
    374         args = (object, name) + args
    375         # 'try' clause is to attempt to handle the possibility that inspect
    376         # identifies something in a way that pydoc itself has issues handling;
    377         # think 'super' and how it is a descriptor (which raises the exception
    378         # by lacking a __name__ attribute) and an instance.
    379         if inspect.isgetsetdescriptor(object): return self.docdata(*args)
    380         if inspect.ismemberdescriptor(object): return self.docdata(*args)
    381         try:
    382             if inspect.ismodule(object): return self.docmodule(*args)
    383             if inspect.isclass(object): return self.docclass(*args)
    384             if inspect.isroutine(object): return self.docroutine(*args)
    385         except AttributeError:
    386             pass
    387         if isinstance(object, property): return self.docproperty(*args)
    388         return self.docother(*args)
    389 
    390     def fail(self, object, name=None, *args):
    391         """Raise an exception for unimplemented types."""
    392         message = "don't know how to document object%s of type %s" % (
    393             name and ' ' + repr(name), type(object).__name__)
    394         raise TypeError(message)
    395 
    396     docmodule = docclass = docroutine = docother = docproperty = docdata = fail
    397 
    398     def getdocloc(self, object,
    399                   basedir=os.path.join(sys.base_exec_prefix, "lib",
    400                                        "python%d.%d" %  sys.version_info[:2])):
    401         """Return the location of module docs or None"""
    402 
    403         try:
    404             file = inspect.getabsfile(object)
    405         except TypeError:
    406             file = '(built-in)'
    407 
    408         docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS)
    409 
    410         basedir = os.path.normcase(basedir)
    411         if (isinstance(object, type(os)) and
    412             (object.__name__ in ('errno', 'exceptions', 'gc', 'imp',
    413                                  'marshal', 'posix', 'signal', 'sys',
    414                                  '_thread', 'zipimport') or
    415              (file.startswith(basedir) and
    416               not file.startswith(os.path.join(basedir, 'site-packages')))) and
    417             object.__name__ not in ('xml.etree', 'test.pydoc_mod')):
    418             if docloc.startswith(("http://", "https://")):
    419                 docloc = "%s/%s" % (docloc.rstrip("/"), object.__name__.lower())
    420             else:
    421                 docloc = os.path.join(docloc, object.__name__.lower() + ".html")
    422         else:
    423             docloc = None
    424         return docloc
    425 
    426 # -------------------------------------------- HTML documentation generator
    427 
    428 class HTMLRepr(Repr):
    429     """Class for safely making an HTML representation of a Python object."""
    430     def __init__(self):
    431         Repr.__init__(self)
    432         self.maxlist = self.maxtuple = 20
    433         self.maxdict = 10
    434         self.maxstring = self.maxother = 100
    435 
    436     def escape(self, text):
    437         return replace(text, '&', '&amp;', '<', '&lt;', '>', '&gt;')
    438 
    439     def repr(self, object):
    440         return Repr.repr(self, object)
    441 
    442     def repr1(self, x, level):
    443         if hasattr(type(x), '__name__'):
    444             methodname = 'repr_' + '_'.join(type(x).__name__.split())
    445             if hasattr(self, methodname):
    446                 return getattr(self, methodname)(x, level)
    447         return self.escape(cram(stripid(repr(x)), self.maxother))
    448 
    449     def repr_string(self, x, level):
    450         test = cram(x, self.maxstring)
    451         testrepr = repr(test)
    452         if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
    453             # Backslashes are only literal in the string and are never
    454             # needed to make any special characters, so show a raw string.
    455             return 'r' + testrepr[0] + self.escape(test) + testrepr[0]
    456         return re.sub(r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',
    457                       r'<font color="#c040c0">\1</font>',
    458                       self.escape(testrepr))
    459 
    460     repr_str = repr_string
    461 
    462     def repr_instance(self, x, level):
    463         try:
    464             return self.escape(cram(stripid(repr(x)), self.maxstring))
    465         except:
    466             return self.escape('<%s instance>' % x.__class__.__name__)
    467 
    468     repr_unicode = repr_string
    469 
    470 class HTMLDoc(Doc):
    471     """Formatter class for HTML documentation."""
    472 
    473     # ------------------------------------------- HTML formatting utilities
    474 
    475     _repr_instance = HTMLRepr()
    476     repr = _repr_instance.repr
    477     escape = _repr_instance.escape
    478 
    479     def page(self, title, contents):
    480         """Format an HTML page."""
    481         return '''\
    482 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
    483 <html><head><title>Python: %s</title>
    484 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    485 </head><body bgcolor="#f0f0f8">
    486 %s
    487 </body></html>''' % (title, contents)
    488 
    489     def heading(self, title, fgcol, bgcol, extras=''):
    490         """Format a page heading."""
    491         return '''
    492 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="heading">
    493 <tr bgcolor="%s">
    494 <td valign=bottom>&nbsp;<br>
    495 <font color="%s" face="helvetica, arial">&nbsp;<br>%s</font></td
    496 ><td align=right valign=bottom
    497 ><font color="%s" face="helvetica, arial">%s</font></td></tr></table>
    498     ''' % (bgcol, fgcol, title, fgcol, extras or '&nbsp;')
    499 
    500     def section(self, title, fgcol, bgcol, contents, width=6,
    501                 prelude='', marginalia=None, gap='&nbsp;'):
    502         """Format a section with a heading."""
    503         if marginalia is None:
    504             marginalia = '<tt>' + '&nbsp;' * width + '</tt>'
    505         result = '''<p>
    506 <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
    507 <tr bgcolor="%s">
    508 <td colspan=3 valign=bottom>&nbsp;<br>
    509 <font color="%s" face="helvetica, arial">%s</font></td></tr>
    510     ''' % (bgcol, fgcol, title)
    511         if prelude:
    512             result = result + '''
    513 <tr bgcolor="%s"><td rowspan=2>%s</td>
    514 <td colspan=2>%s</td></tr>
    515 <tr><td>%s</td>''' % (bgcol, marginalia, prelude, gap)
    516         else:
    517             result = result + '''
    518 <tr><td bgcolor="%s">%s</td><td>%s</td>''' % (bgcol, marginalia, gap)
    519 
    520         return result + '\n<td width="100%%">%s</td></tr></table>' % contents
    521 
    522     def bigsection(self, title, *args):
    523         """Format a section with a big heading."""
    524         title = '<big><strong>%s</strong></big>' % title
    525         return self.section(title, *args)
    526 
    527     def preformat(self, text):
    528         """Format literal preformatted text."""
    529         text = self.escape(text.expandtabs())
    530         return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',
    531                              ' ', '&nbsp;', '\n', '<br>\n')
    532 
    533     def multicolumn(self, list, format, cols=4):
    534         """Format a list of items into a multi-column list."""
    535         result = ''
    536         rows = (len(list)+cols-1)//cols
    537         for col in range(cols):
    538             result = result + '<td width="%d%%" valign=top>' % (100//cols)
    539             for i in range(rows*col, rows*col+rows):
    540                 if i < len(list):
    541                     result = result + format(list[i]) + '<br>\n'
    542             result = result + '</td>'
    543         return '<table width="100%%" summary="list"><tr>%s</tr></table>' % result
    544 
    545     def grey(self, text): return '<font color="#909090">%s</font>' % text
    546 
    547     def namelink(self, name, *dicts):
    548         """Make a link for an identifier, given name-to-URL mappings."""
    549         for dict in dicts:
    550             if name in dict:
    551                 return '<a href="%s">%s</a>' % (dict[name], name)
    552         return name
    553 
    554     def classlink(self, object, modname):
    555         """Make a link for a class."""
    556         name, module = object.__name__, sys.modules.get(object.__module__)
    557         if hasattr(module, name) and getattr(module, name) is object:
    558             return '<a href="%s.html#%s">%s</a>' % (
    559                 module.__name__, name, classname(object, modname))
    560         return classname(object, modname)
    561 
    562     def modulelink(self, object):
    563         """Make a link for a module."""
    564         return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)
    565 
    566     def modpkglink(self, modpkginfo):
    567         """Make a link for a module or package to display in an index."""
    568         name, path, ispackage, shadowed = modpkginfo
    569         if shadowed:
    570             return self.grey(name)
    571         if path:
    572             url = '%s.%s.html' % (path, name)
    573         else:
    574             url = '%s.html' % name
    575         if ispackage:
    576             text = '<strong>%s</strong>&nbsp;(package)' % name
    577         else:
    578             text = name
    579         return '<a href="%s">%s</a>' % (url, text)
    580 
    581     def filelink(self, url, path):
    582         """Make a link to source file."""
    583         return '<a href="file:%s">%s</a>' % (url, path)
    584 
    585     def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
    586         """Mark up some plain text, given a context of symbols to look for.
    587         Each context dictionary maps object names to anchor names."""
    588         escape = escape or self.escape
    589         results = []
    590         here = 0
    591         pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
    592                                 r'RFC[- ]?(\d+)|'
    593                                 r'PEP[- ]?(\d+)|'
    594                                 r'(self\.)?(\w+))')
    595         while True:
    596             match = pattern.search(text, here)
    597             if not match: break
    598             start, end = match.span()
    599             results.append(escape(text[here:start]))
    600 
    601             all, scheme, rfc, pep, selfdot, name = match.groups()
    602             if scheme:
    603                 url = escape(all).replace('"', '&quot;')
    604                 results.append('<a href="%s">%s</a>' % (url, url))
    605             elif rfc:
    606                 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
    607                 results.append('<a href="%s">%s</a>' % (url, escape(all)))
    608             elif pep:
    609                 url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
    610                 results.append('<a href="%s">%s</a>' % (url, escape(all)))
    611             elif selfdot:
    612                 # Create a link for methods like 'self.method(...)'
    613                 # and use <strong> for attributes like 'self.attr'
    614                 if text[end:end+1] == '(':
    615                     results.append('self.' + self.namelink(name, methods))
    616                 else:
    617                     results.append('self.<strong>%s</strong>' % name)
    618             elif text[end:end+1] == '(':
    619                 results.append(self.namelink(name, methods, funcs, classes))
    620             else:
    621                 results.append(self.namelink(name, classes))
    622             here = end
    623         results.append(escape(text[here:]))
    624         return ''.join(results)
    625 
    626     # ---------------------------------------------- type-specific routines
    627 
    628     def formattree(self, tree, modname, parent=None):
    629         """Produce HTML for a class tree as given by inspect.getclasstree()."""
    630         result = ''
    631         for entry in tree:
    632             if type(entry) is type(()):
    633                 c, bases = entry
    634                 result = result + '<dt><font face="helvetica, arial">'
    635                 result = result + self.classlink(c, modname)
    636                 if bases and bases != (parent,):
    637                     parents = []
    638                     for base in bases:
    639                         parents.append(self.classlink(base, modname))
    640                     result = result + '(' + ', '.join(parents) + ')'
    641                 result = result + '\n</font></dt>'
    642             elif type(entry) is type([]):
    643                 result = result + '<dd>\n%s</dd>\n' % self.formattree(
    644                     entry, modname, c)
    645         return '<dl>\n%s</dl>\n' % result
    646 
    647     def docmodule(self, object, name=None, mod=None, *ignored):
    648         """Produce HTML documentation for a module object."""
    649         name = object.__name__ # ignore the passed-in name
    650         try:
    651             all = object.__all__
    652         except AttributeError:
    653             all = None
    654         parts = name.split('.')
    655         links = []
    656         for i in range(len(parts)-1):
    657             links.append(
    658                 '<a href="%s.html"><font color="#ffffff">%s</font></a>' %
    659                 ('.'.join(parts[:i+1]), parts[i]))
    660         linkedname = '.'.join(links + parts[-1:])
    661         head = '<big><big><strong>%s</strong></big></big>' % linkedname
    662         try:
    663             path = inspect.getabsfile(object)
    664             url = urllib.parse.quote(path)
    665             filelink = self.filelink(url, path)
    666         except TypeError:
    667             filelink = '(built-in)'
    668         info = []
    669         if hasattr(object, '__version__'):
    670             version = str(object.__version__)
    671             if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
    672                 version = version[11:-1].strip()
    673             info.append('version %s' % self.escape(version))
    674         if hasattr(object, '__date__'):
    675             info.append(self.escape(str(object.__date__)))
    676         if info:
    677             head = head + ' (%s)' % ', '.join(info)
    678         docloc = self.getdocloc(object)
    679         if docloc is not None:
    680             docloc = '<br><a href="%(docloc)s">Module Reference</a>' % locals()
    681         else:
    682             docloc = ''
    683         result = self.heading(
    684             head, '#ffffff', '#7799ee',
    685             '<a href=".">index</a><br>' + filelink + docloc)
    686 
    687         modules = inspect.getmembers(object, inspect.ismodule)
    688 
    689         classes, cdict = [], {}
    690         for key, value in inspect.getmembers(object, inspect.isclass):
    691             # if __all__ exists, believe it.  Otherwise use old heuristic.
    692             if (all is not None or
    693                 (inspect.getmodule(value) or object) is object):
    694                 if visiblename(key, all, object):
    695                     classes.append((key, value))
    696                     cdict[key] = cdict[value] = '#' + key
    697         for key, value in classes:
    698             for base in value.__bases__:
    699                 key, modname = base.__name__, base.__module__
    700                 module = sys.modules.get(modname)
    701                 if modname != name and module and hasattr(module, key):
    702                     if getattr(module, key) is base:
    703                         if not key in cdict:
    704                             cdict[key] = cdict[base] = modname + '.html#' + key
    705         funcs, fdict = [], {}
    706         for key, value in inspect.getmembers(object, inspect.isroutine):
    707             # if __all__ exists, believe it.  Otherwise use old heuristic.
    708             if (all is not None or
    709                 inspect.isbuiltin(value) or inspect.getmodule(value) is object):
    710                 if visiblename(key, all, object):
    711                     funcs.append((key, value))
    712                     fdict[key] = '#-' + key
    713                     if inspect.isfunction(value): fdict[value] = fdict[key]
    714         data = []
    715         for key, value in inspect.getmembers(object, isdata):
    716             if visiblename(key, all, object):
    717                 data.append((key, value))
    718 
    719         doc = self.markup(getdoc(object), self.preformat, fdict, cdict)
    720         doc = doc and '<tt>%s</tt>' % doc
    721         result = result + '<p>%s</p>\n' % doc
    722 
    723         if hasattr(object, '__path__'):
    724             modpkgs = []
    725             for importer, modname, ispkg in pkgutil.iter_modules(object.__path__):
    726                 modpkgs.append((modname, name, ispkg, 0))
    727             modpkgs.sort()
    728             contents = self.multicolumn(modpkgs, self.modpkglink)
    729             result = result + self.bigsection(
    730                 'Package Contents', '#ffffff', '#aa55cc', contents)
    731         elif modules:
    732             contents = self.multicolumn(
    733                 modules, lambda t: self.modulelink(t[1]))
    734             result = result + self.bigsection(
    735                 'Modules', '#ffffff', '#aa55cc', contents)
    736 
    737         if classes:
    738             classlist = [value for (key, value) in classes]
    739             contents = [
    740                 self.formattree(inspect.getclasstree(classlist, 1), name)]
    741             for key, value in classes:
    742                 contents.append(self.document(value, key, name, fdict, cdict))
    743             result = result + self.bigsection(
    744                 'Classes', '#ffffff', '#ee77aa', ' '.join(contents))
    745         if funcs:
    746             contents = []
    747             for key, value in funcs:
    748                 contents.append(self.document(value, key, name, fdict, cdict))
    749             result = result + self.bigsection(
    750                 'Functions', '#ffffff', '#eeaa77', ' '.join(contents))
    751         if data:
    752             contents = []
    753             for key, value in data:
    754                 contents.append(self.document(value, key))
    755             result = result + self.bigsection(
    756                 'Data', '#ffffff', '#55aa55', '<br>\n'.join(contents))
    757         if hasattr(object, '__author__'):
    758             contents = self.markup(str(object.__author__), self.preformat)
    759             result = result + self.bigsection(
    760                 'Author', '#ffffff', '#7799ee', contents)
    761         if hasattr(object, '__credits__'):
    762             contents = self.markup(str(object.__credits__), self.preformat)
    763             result = result + self.bigsection(
    764                 'Credits', '#ffffff', '#7799ee', contents)
    765 
    766         return result
    767 
    768     def docclass(self, object, name=None, mod=None, funcs={}, classes={},
    769                  *ignored):
    770         """Produce HTML documentation for a class object."""
    771         realname = object.__name__
    772         name = name or realname
    773         bases = object.__bases__
    774 
    775         contents = []
    776         push = contents.append
    777 
    778         # Cute little class to pump out a horizontal rule between sections.
    779         class HorizontalRule:
    780             def __init__(self):
    781                 self.needone = 0
    782             def maybe(self):
    783                 if self.needone:
    784                     push('<hr>\n')
    785                 self.needone = 1
    786         hr = HorizontalRule()
    787 
    788         # List the mro, if non-trivial.
    789         mro = deque(inspect.getmro(object))
    790         if len(mro) > 2:
    791             hr.maybe()
    792             push('<dl><dt>Method resolution order:</dt>\n')
    793             for base in mro:
    794                 push('<dd>%s</dd>\n' % self.classlink(base,
    795                                                       object.__module__))
    796             push('</dl>\n')
    797 
    798         def spill(msg, attrs, predicate):
    799             ok, attrs = _split_list(attrs, predicate)
    800             if ok:
    801                 hr.maybe()
    802                 push(msg)
    803                 for name, kind, homecls, value in ok:
    804                     try:
    805                         value = getattr(object, name)
    806                     except Exception:
    807                         # Some descriptors may meet a failure in their __get__.
    808                         # (bug #1785)
    809                         push(self._docdescriptor(name, value, mod))
    810                     else:
    811                         push(self.document(value, name, mod,
    812                                         funcs, classes, mdict, object))
    813                     push('\n')
    814             return attrs
    815 
    816         def spilldescriptors(msg, attrs, predicate):
    817             ok, attrs = _split_list(attrs, predicate)
    818             if ok:
    819                 hr.maybe()
    820                 push(msg)
    821                 for name, kind, homecls, value in ok:
    822                     push(self._docdescriptor(name, value, mod))
    823             return attrs
    824 
    825         def spilldata(msg, attrs, predicate):
    826             ok, attrs = _split_list(attrs, predicate)
    827             if ok:
    828                 hr.maybe()
    829                 push(msg)
    830                 for name, kind, homecls, value in ok:
    831                     base = self.docother(getattr(object, name), name, mod)
    832                     if callable(value) or inspect.isdatadescriptor(value):
    833                         doc = getattr(value, "__doc__", None)
    834                     else:
    835                         doc = None
    836                     if doc is None:
    837                         push('<dl><dt>%s</dl>\n' % base)
    838                     else:
    839                         doc = self.markup(getdoc(value), self.preformat,
    840                                           funcs, classes, mdict)
    841                         doc = '<dd><tt>%s</tt>' % doc
    842                         push('<dl><dt>%s%s</dl>\n' % (base, doc))
    843                     push('\n')
    844             return attrs
    845 
    846         attrs = [(name, kind, cls, value)
    847                  for name, kind, cls, value in classify_class_attrs(object)
    848                  if visiblename(name, obj=object)]
    849 
    850         mdict = {}
    851         for key, kind, homecls, value in attrs:
    852             mdict[key] = anchor = '#' + name + '-' + key
    853             try:
    854                 value = getattr(object, name)
    855             except Exception:
    856                 # Some descriptors may meet a failure in their __get__.
    857                 # (bug #1785)
    858                 pass
    859             try:
    860                 # The value may not be hashable (e.g., a data attr with
    861                 # a dict or list value).
    862                 mdict[value] = anchor
    863             except TypeError:
    864                 pass
    865 
    866         while attrs:
    867             if mro:
    868                 thisclass = mro.popleft()
    869             else:
    870                 thisclass = attrs[0][2]
    871             attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
    872 
    873             if thisclass is builtins.object:
    874                 attrs = inherited
    875                 continue
    876             elif thisclass is object:
    877                 tag = 'defined here'
    878             else:
    879                 tag = 'inherited from %s' % self.classlink(thisclass,
    880                                                            object.__module__)
    881             tag += ':<br>\n'
    882 
    883             sort_attributes(attrs, object)
    884 
    885             # Pump out the attrs, segregated by kind.
    886             attrs = spill('Methods %s' % tag, attrs,
    887                           lambda t: t[1] == 'method')
    888             attrs = spill('Class methods %s' % tag, attrs,
    889                           lambda t: t[1] == 'class method')
    890             attrs = spill('Static methods %s' % tag, attrs,
    891                           lambda t: t[1] == 'static method')
    892             attrs = spilldescriptors('Data descriptors %s' % tag, attrs,
    893                                      lambda t: t[1] == 'data descriptor')
    894             attrs = spilldata('Data and other attributes %s' % tag, attrs,
    895                               lambda t: t[1] == 'data')
    896             assert attrs == []
    897             attrs = inherited
    898 
    899         contents = ''.join(contents)
    900 
    901         if name == realname:
    902             title = '<a name="%s">class <strong>%s</strong></a>' % (
    903                 name, realname)
    904         else:
    905             title = '<strong>%s</strong> = <a name="%s">class %s</a>' % (
    906                 name, name, realname)
    907         if bases:
    908             parents = []
    909             for base in bases:
    910                 parents.append(self.classlink(base, object.__module__))
    911             title = title + '(%s)' % ', '.join(parents)
    912         doc = self.markup(getdoc(object), self.preformat, funcs, classes, mdict)
    913         doc = doc and '<tt>%s<br>&nbsp;</tt>' % doc
    914 
    915         return self.section(title, '#000000', '#ffc8d8', contents, 3, doc)
    916 
    917     def formatvalue(self, object):
    918         """Format an argument default value as text."""
    919         return self.grey('=' + self.repr(object))
    920 
    921     def docroutine(self, object, name=None, mod=None,
    922                    funcs={}, classes={}, methods={}, cl=None):
    923         """Produce HTML documentation for a function or method object."""
    924         realname = object.__name__
    925         name = name or realname
    926         anchor = (cl and cl.__name__ or '') + '-' + name
    927         note = ''
    928         skipdocs = 0
    929         if _is_bound_method(object):
    930             imclass = object.__self__.__class__
    931             if cl:
    932                 if imclass is not cl:
    933                     note = ' from ' + self.classlink(imclass, mod)
    934             else:
    935                 if object.__self__ is not None:
    936                     note = ' method of %s instance' % self.classlink(
    937                         object.__self__.__class__, mod)
    938                 else:
    939                     note = ' unbound %s method' % self.classlink(imclass,mod)
    940 
    941         if name == realname:
    942             title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)
    943         else:
    944             if (cl and realname in cl.__dict__ and
    945                 cl.__dict__[realname] is object):
    946                 reallink = '<a href="#%s">%s</a>' % (
    947                     cl.__name__ + '-' + realname, realname)
    948                 skipdocs = 1
    949             else:
    950                 reallink = realname
    951             title = '<a name="%s"><strong>%s</strong></a> = %s' % (
    952                 anchor, name, reallink)
    953         argspec = None
    954         if inspect.isroutine(object):
    955             try:
    956                 signature = inspect.signature(object)
    957             except (ValueError, TypeError):
    958                 signature = None
    959             if signature:
    960                 argspec = str(signature)
    961                 if realname == '<lambda>':
    962                     title = '<strong>%s</strong> <em>lambda</em> ' % name
    963                     # XXX lambda's won't usually have func_annotations['return']
    964                     # since the syntax doesn't support but it is possible.
    965                     # So removing parentheses isn't truly safe.
    966                     argspec = argspec[1:-1] # remove parentheses
    967         if not argspec:
    968             argspec = '(...)'
    969 
    970         decl = title + self.escape(argspec) + (note and self.grey(
    971                '<font face="helvetica, arial">%s</font>' % note))
    972 
    973         if skipdocs:
    974             return '<dl><dt>%s</dt></dl>\n' % decl
    975         else:
    976             doc = self.markup(
    977                 getdoc(object), self.preformat, funcs, classes, methods)
    978             doc = doc and '<dd><tt>%s</tt></dd>' % doc
    979             return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
    980 
    981     def _docdescriptor(self, name, value, mod):
    982         results = []
    983         push = results.append
    984 
    985         if name:
    986             push('<dl><dt><strong>%s</strong></dt>\n' % name)
    987         if value.__doc__ is not None:
    988             doc = self.markup(getdoc(value), self.preformat)
    989             push('<dd><tt>%s</tt></dd>\n' % doc)
    990         push('</dl>\n')
    991 
    992         return ''.join(results)
    993 
    994     def docproperty(self, object, name=None, mod=None, cl=None):
    995         """Produce html documentation for a property."""
    996         return self._docdescriptor(name, object, mod)
    997 
    998     def docother(self, object, name=None, mod=None, *ignored):
    999         """Produce HTML documentation for a data object."""
   1000         lhs = name and '<strong>%s</strong> = ' % name or ''
   1001         return lhs + self.repr(object)
   1002 
   1003     def docdata(self, object, name=None, mod=None, cl=None):
   1004         """Produce html documentation for a data descriptor."""
   1005         return self._docdescriptor(name, object, mod)
   1006 
   1007     def index(self, dir, shadowed=None):
   1008         """Generate an HTML index for a directory of modules."""
   1009         modpkgs = []
   1010         if shadowed is None: shadowed = {}
   1011         for importer, name, ispkg in pkgutil.iter_modules([dir]):
   1012             if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name):
   1013                 # ignore a module if its name contains a surrogate character
   1014                 continue
   1015             modpkgs.append((name, '', ispkg, name in shadowed))
   1016             shadowed[name] = 1
   1017 
   1018         modpkgs.sort()
   1019         contents = self.multicolumn(modpkgs, self.modpkglink)
   1020         return self.bigsection(dir, '#ffffff', '#ee77aa', contents)
   1021 
   1022 # -------------------------------------------- text documentation generator
   1023 
   1024 class TextRepr(Repr):
   1025     """Class for safely making a text representation of a Python object."""
   1026     def __init__(self):
   1027         Repr.__init__(self)
   1028         self.maxlist = self.maxtuple = 20
   1029         self.maxdict = 10
   1030         self.maxstring = self.maxother = 100
   1031 
   1032     def repr1(self, x, level):
   1033         if hasattr(type(x), '__name__'):
   1034             methodname = 'repr_' + '_'.join(type(x).__name__.split())
   1035             if hasattr(self, methodname):
   1036                 return getattr(self, methodname)(x, level)
   1037         return cram(stripid(repr(x)), self.maxother)
   1038 
   1039     def repr_string(self, x, level):
   1040         test = cram(x, self.maxstring)
   1041         testrepr = repr(test)
   1042         if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
   1043             # Backslashes are only literal in the string and are never
   1044             # needed to make any special characters, so show a raw string.
   1045             return 'r' + testrepr[0] + test + testrepr[0]
   1046         return testrepr
   1047 
   1048     repr_str = repr_string
   1049 
   1050     def repr_instance(self, x, level):
   1051         try:
   1052             return cram(stripid(repr(x)), self.maxstring)
   1053         except:
   1054             return '<%s instance>' % x.__class__.__name__
   1055 
   1056 class TextDoc(Doc):
   1057     """Formatter class for text documentation."""
   1058 
   1059     # ------------------------------------------- text formatting utilities
   1060 
   1061     _repr_instance = TextRepr()
   1062     repr = _repr_instance.repr
   1063 
   1064     def bold(self, text):
   1065         """Format a string in bold by overstriking."""
   1066         return ''.join(ch + '\b' + ch for ch in text)
   1067 
   1068     def indent(self, text, prefix='    '):
   1069         """Indent text by prepending a given prefix to each line."""
   1070         if not text: return ''
   1071         lines = [prefix + line for line in text.split('\n')]
   1072         if lines: lines[-1] = lines[-1].rstrip()
   1073         return '\n'.join(lines)
   1074 
   1075     def section(self, title, contents):
   1076         """Format a section with a given heading."""
   1077         clean_contents = self.indent(contents).rstrip()
   1078         return self.bold(title) + '\n' + clean_contents + '\n\n'
   1079 
   1080     # ---------------------------------------------- type-specific routines
   1081 
   1082     def formattree(self, tree, modname, parent=None, prefix=''):
   1083         """Render in text a class tree as returned by inspect.getclasstree()."""
   1084         result = ''
   1085         for entry in tree:
   1086             if type(entry) is type(()):
   1087                 c, bases = entry
   1088                 result = result + prefix + classname(c, modname)
   1089                 if bases and bases != (parent,):
   1090                     parents = (classname(c, modname) for c in bases)
   1091                     result = result + '(%s)' % ', '.join(parents)
   1092                 result = result + '\n'
   1093             elif type(entry) is type([]):
   1094                 result = result + self.formattree(
   1095                     entry, modname, c, prefix + '    ')
   1096         return result
   1097 
   1098     def docmodule(self, object, name=None, mod=None):
   1099         """Produce text documentation for a given module object."""
   1100         name = object.__name__ # ignore the passed-in name
   1101         synop, desc = splitdoc(getdoc(object))
   1102         result = self.section('NAME', name + (synop and ' - ' + synop))
   1103         all = getattr(object, '__all__', None)
   1104         docloc = self.getdocloc(object)
   1105         if docloc is not None:
   1106             result = result + self.section('MODULE REFERENCE', docloc + """
   1107 
   1108 The following documentation is automatically generated from the Python
   1109 source files.  It may be incomplete, incorrect or include features that
   1110 are considered implementation detail and may vary between Python
   1111 implementations.  When in doubt, consult the module reference at the
   1112 location listed above.
   1113 """)
   1114 
   1115         if desc:
   1116             result = result + self.section('DESCRIPTION', desc)
   1117 
   1118         classes = []
   1119         for key, value in inspect.getmembers(object, inspect.isclass):
   1120             # if __all__ exists, believe it.  Otherwise use old heuristic.
   1121             if (all is not None
   1122                 or (inspect.getmodule(value) or object) is object):
   1123                 if visiblename(key, all, object):
   1124                     classes.append((key, value))
   1125         funcs = []
   1126         for key, value in inspect.getmembers(object, inspect.isroutine):
   1127             # if __all__ exists, believe it.  Otherwise use old heuristic.
   1128             if (all is not None or
   1129                 inspect.isbuiltin(value) or inspect.getmodule(value) is object):
   1130                 if visiblename(key, all, object):
   1131                     funcs.append((key, value))
   1132         data = []
   1133         for key, value in inspect.getmembers(object, isdata):
   1134             if visiblename(key, all, object):
   1135                 data.append((key, value))
   1136 
   1137         modpkgs = []
   1138         modpkgs_names = set()
   1139         if hasattr(object, '__path__'):
   1140             for importer, modname, ispkg in pkgutil.iter_modules(object.__path__):
   1141                 modpkgs_names.add(modname)
   1142                 if ispkg:
   1143                     modpkgs.append(modname + ' (package)')
   1144                 else:
   1145                     modpkgs.append(modname)
   1146 
   1147             modpkgs.sort()
   1148             result = result + self.section(
   1149                 'PACKAGE CONTENTS', '\n'.join(modpkgs))
   1150 
   1151         # Detect submodules as sometimes created by C extensions
   1152         submodules = []
   1153         for key, value in inspect.getmembers(object, inspect.ismodule):
   1154             if value.__name__.startswith(name + '.') and key not in modpkgs_names:
   1155                 submodules.append(key)
   1156         if submodules:
   1157             submodules.sort()
   1158             result = result + self.section(
   1159                 'SUBMODULES', '\n'.join(submodules))
   1160 
   1161         if classes:
   1162             classlist = [value for key, value in classes]
   1163             contents = [self.formattree(
   1164                 inspect.getclasstree(classlist, 1), name)]
   1165             for key, value in classes:
   1166                 contents.append(self.document(value, key, name))
   1167             result = result + self.section('CLASSES', '\n'.join(contents))
   1168 
   1169         if funcs:
   1170             contents = []
   1171             for key, value in funcs:
   1172                 contents.append(self.document(value, key, name))
   1173             result = result + self.section('FUNCTIONS', '\n'.join(contents))
   1174 
   1175         if data:
   1176             contents = []
   1177             for key, value in data:
   1178                 contents.append(self.docother(value, key, name, maxlen=70))
   1179             result = result + self.section('DATA', '\n'.join(contents))
   1180 
   1181         if hasattr(object, '__version__'):
   1182             version = str(object.__version__)
   1183             if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
   1184                 version = version[11:-1].strip()
   1185             result = result + self.section('VERSION', version)
   1186         if hasattr(object, '__date__'):
   1187             result = result + self.section('DATE', str(object.__date__))
   1188         if hasattr(object, '__author__'):
   1189             result = result + self.section('AUTHOR', str(object.__author__))
   1190         if hasattr(object, '__credits__'):
   1191             result = result + self.section('CREDITS', str(object.__credits__))
   1192         try:
   1193             file = inspect.getabsfile(object)
   1194         except TypeError:
   1195             file = '(built-in)'
   1196         result = result + self.section('FILE', file)
   1197         return result
   1198 
   1199     def docclass(self, object, name=None, mod=None, *ignored):
   1200         """Produce text documentation for a given class object."""
   1201         realname = object.__name__
   1202         name = name or realname
   1203         bases = object.__bases__
   1204 
   1205         def makename(c, m=object.__module__):
   1206             return classname(c, m)
   1207 
   1208         if name == realname:
   1209             title = 'class ' + self.bold(realname)
   1210         else:
   1211             title = self.bold(name) + ' = class ' + realname
   1212         if bases:
   1213             parents = map(makename, bases)
   1214             title = title + '(%s)' % ', '.join(parents)
   1215 
   1216         doc = getdoc(object)
   1217         contents = doc and [doc + '\n'] or []
   1218         push = contents.append
   1219 
   1220         # List the mro, if non-trivial.
   1221         mro = deque(inspect.getmro(object))
   1222         if len(mro) > 2:
   1223             push("Method resolution order:")
   1224             for base in mro:
   1225                 push('    ' + makename(base))
   1226             push('')
   1227 
   1228         # Cute little class to pump out a horizontal rule between sections.
   1229         class HorizontalRule:
   1230             def __init__(self):
   1231                 self.needone = 0
   1232             def maybe(self):
   1233                 if self.needone:
   1234                     push('-' * 70)
   1235                 self.needone = 1
   1236         hr = HorizontalRule()
   1237 
   1238         def spill(msg, attrs, predicate):
   1239             ok, attrs = _split_list(attrs, predicate)
   1240             if ok:
   1241                 hr.maybe()
   1242                 push(msg)
   1243                 for name, kind, homecls, value in ok:
   1244                     try:
   1245                         value = getattr(object, name)
   1246                     except Exception:
   1247                         # Some descriptors may meet a failure in their __get__.
   1248                         # (bug #1785)
   1249                         push(self._docdescriptor(name, value, mod))
   1250                     else:
   1251                         push(self.document(value,
   1252                                         name, mod, object))
   1253             return attrs
   1254 
   1255         def spilldescriptors(msg, attrs, predicate):
   1256             ok, attrs = _split_list(attrs, predicate)
   1257             if ok:
   1258                 hr.maybe()
   1259                 push(msg)
   1260                 for name, kind, homecls, value in ok:
   1261                     push(self._docdescriptor(name, value, mod))
   1262             return attrs
   1263 
   1264         def spilldata(msg, attrs, predicate):
   1265             ok, attrs = _split_list(attrs, predicate)
   1266             if ok:
   1267                 hr.maybe()
   1268                 push(msg)
   1269                 for name, kind, homecls, value in ok:
   1270                     if callable(value) or inspect.isdatadescriptor(value):
   1271                         doc = getdoc(value)
   1272                     else:
   1273                         doc = None
   1274                     try:
   1275                         obj = getattr(object, name)
   1276                     except AttributeError:
   1277                         obj = homecls.__dict__[name]
   1278                     push(self.docother(obj, name, mod, maxlen=70, doc=doc) +
   1279                          '\n')
   1280             return attrs
   1281 
   1282         attrs = [(name, kind, cls, value)
   1283                  for name, kind, cls, value in classify_class_attrs(object)
   1284                  if visiblename(name, obj=object)]
   1285 
   1286         while attrs:
   1287             if mro:
   1288                 thisclass = mro.popleft()
   1289             else:
   1290                 thisclass = attrs[0][2]
   1291             attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
   1292 
   1293             if thisclass is builtins.object:
   1294                 attrs = inherited
   1295                 continue
   1296             elif thisclass is object:
   1297                 tag = "defined here"
   1298             else:
   1299                 tag = "inherited from %s" % classname(thisclass,
   1300                                                       object.__module__)
   1301 
   1302             sort_attributes(attrs, object)
   1303 
   1304             # Pump out the attrs, segregated by kind.
   1305             attrs = spill("Methods %s:\n" % tag, attrs,
   1306                           lambda t: t[1] == 'method')
   1307             attrs = spill("Class methods %s:\n" % tag, attrs,
   1308                           lambda t: t[1] == 'class method')
   1309             attrs = spill("Static methods %s:\n" % tag, attrs,
   1310                           lambda t: t[1] == 'static method')
   1311             attrs = spilldescriptors("Data descriptors %s:\n" % tag, attrs,
   1312                                      lambda t: t[1] == 'data descriptor')
   1313             attrs = spilldata("Data and other attributes %s:\n" % tag, attrs,
   1314                               lambda t: t[1] == 'data')
   1315 
   1316             assert attrs == []
   1317             attrs = inherited
   1318 
   1319         contents = '\n'.join(contents)
   1320         if not contents:
   1321             return title + '\n'
   1322         return title + '\n' + self.indent(contents.rstrip(), ' |  ') + '\n'
   1323 
   1324     def formatvalue(self, object):
   1325         """Format an argument default value as text."""
   1326         return '=' + self.repr(object)
   1327 
   1328     def docroutine(self, object, name=None, mod=None, cl=None):
   1329         """Produce text documentation for a function or method object."""
   1330         realname = object.__name__
   1331         name = name or realname
   1332         note = ''
   1333         skipdocs = 0
   1334         if _is_bound_method(object):
   1335             imclass = object.__self__.__class__
   1336             if cl:
   1337                 if imclass is not cl:
   1338                     note = ' from ' + classname(imclass, mod)
   1339             else:
   1340                 if object.__self__ is not None:
   1341                     note = ' method of %s instance' % classname(
   1342                         object.__self__.__class__, mod)
   1343                 else:
   1344                     note = ' unbound %s method' % classname(imclass,mod)
   1345 
   1346         if name == realname:
   1347             title = self.bold(realname)
   1348         else:
   1349             if (cl and realname in cl.__dict__ and
   1350                 cl.__dict__[realname] is object):
   1351                 skipdocs = 1
   1352             title = self.bold(name) + ' = ' + realname
   1353         argspec = None
   1354 
   1355         if inspect.isroutine(object):
   1356             try:
   1357                 signature = inspect.signature(object)
   1358             except (ValueError, TypeError):
   1359                 signature = None
   1360             if signature:
   1361                 argspec = str(signature)
   1362                 if realname == '<lambda>':
   1363                     title = self.bold(name) + ' lambda '
   1364                     # XXX lambda's won't usually have func_annotations['return']
   1365                     # since the syntax doesn't support but it is possible.
   1366                     # So removing parentheses isn't truly safe.
   1367                     argspec = argspec[1:-1] # remove parentheses
   1368         if not argspec:
   1369             argspec = '(...)'
   1370         decl = title + argspec + note
   1371 
   1372         if skipdocs:
   1373             return decl + '\n'
   1374         else:
   1375             doc = getdoc(object) or ''
   1376             return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')
   1377 
   1378     def _docdescriptor(self, name, value, mod):
   1379         results = []
   1380         push = results.append
   1381 
   1382         if name:
   1383             push(self.bold(name))
   1384             push('\n')
   1385         doc = getdoc(value) or ''
   1386         if doc:
   1387             push(self.indent(doc))
   1388             push('\n')
   1389         return ''.join(results)
   1390 
   1391     def docproperty(self, object, name=None, mod=None, cl=None):
   1392         """Produce text documentation for a property."""
   1393         return self._docdescriptor(name, object, mod)
   1394 
   1395     def docdata(self, object, name=None, mod=None, cl=None):
   1396         """Produce text documentation for a data descriptor."""
   1397         return self._docdescriptor(name, object, mod)
   1398 
   1399     def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):
   1400         """Produce text documentation for a data object."""
   1401         repr = self.repr(object)
   1402         if maxlen:
   1403             line = (name and name + ' = ' or '') + repr
   1404             chop = maxlen - len(line)
   1405             if chop < 0: repr = repr[:chop] + '...'
   1406         line = (name and self.bold(name) + ' = ' or '') + repr
   1407         if doc is not None:
   1408             line += '\n' + self.indent(str(doc))
   1409         return line
   1410 
   1411 class _PlainTextDoc(TextDoc):
   1412     """Subclass of TextDoc which overrides string styling"""
   1413     def bold(self, text):
   1414         return text
   1415 
   1416 # --------------------------------------------------------- user interfaces
   1417 
   1418 def pager(text):
   1419     """The first time this is called, determine what kind of pager to use."""
   1420     global pager
   1421     pager = getpager()
   1422     pager(text)
   1423 
   1424 def getpager():
   1425     """Decide what method to use for paging through text."""
   1426     if not hasattr(sys.stdin, "isatty"):
   1427         return plainpager
   1428     if not hasattr(sys.stdout, "isatty"):
   1429         return plainpager
   1430     if not sys.stdin.isatty() or not sys.stdout.isatty():
   1431         return plainpager
   1432     use_pager = os.environ.get('MANPAGER') or os.environ.get('PAGER')
   1433     if use_pager:
   1434         if sys.platform == 'win32': # pipes completely broken in Windows
   1435             return lambda text: tempfilepager(plain(text), use_pager)
   1436         elif os.environ.get('TERM') in ('dumb', 'emacs'):
   1437             return lambda text: pipepager(plain(text), use_pager)
   1438         else:
   1439             return lambda text: pipepager(text, use_pager)
   1440     if os.environ.get('TERM') in ('dumb', 'emacs'):
   1441         return plainpager
   1442     if sys.platform == 'win32':
   1443         return lambda text: tempfilepager(plain(text), 'more <')
   1444     if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
   1445         return lambda text: pipepager(text, 'less')
   1446 
   1447     import tempfile
   1448     (fd, filename) = tempfile.mkstemp()
   1449     os.close(fd)
   1450     try:
   1451         if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
   1452             return lambda text: pipepager(text, 'more')
   1453         else:
   1454             return ttypager
   1455     finally:
   1456         os.unlink(filename)
   1457 
   1458 def plain(text):
   1459     """Remove boldface formatting from text."""
   1460     return re.sub('.\b', '', text)
   1461 
   1462 def pipepager(text, cmd):
   1463     """Page through text by feeding it to another program."""
   1464     import subprocess
   1465     proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE)
   1466     try:
   1467         with io.TextIOWrapper(proc.stdin, errors='backslashreplace') as pipe:
   1468             try:
   1469                 pipe.write(text)
   1470             except KeyboardInterrupt:
   1471                 # We've hereby abandoned whatever text hasn't been written,
   1472                 # but the pager is still in control of the terminal.
   1473                 pass
   1474     except OSError:
   1475         pass # Ignore broken pipes caused by quitting the pager program.
   1476     while True:
   1477         try:
   1478             proc.wait()
   1479             break
   1480         except KeyboardInterrupt:
   1481             # Ignore ctl-c like the pager itself does.  Otherwise the pager is
   1482             # left running and the terminal is in raw mode and unusable.
   1483             pass
   1484 
   1485 def tempfilepager(text, cmd):
   1486     """Page through text by invoking a program on a temporary file."""
   1487     import tempfile
   1488     filename = tempfile.mktemp()
   1489     with open(filename, 'w', errors='backslashreplace') as file:
   1490         file.write(text)
   1491     try:
   1492         os.system(cmd + ' "' + filename + '"')
   1493     finally:
   1494         os.unlink(filename)
   1495 
   1496 def _escape_stdout(text):
   1497     # Escape non-encodable characters to avoid encoding errors later
   1498     encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
   1499     return text.encode(encoding, 'backslashreplace').decode(encoding)
   1500 
   1501 def ttypager(text):
   1502     """Page through text on a text terminal."""
   1503     lines = plain(_escape_stdout(text)).split('\n')
   1504     try:
   1505         import tty
   1506         fd = sys.stdin.fileno()
   1507         old = tty.tcgetattr(fd)
   1508         tty.setcbreak(fd)
   1509         getchar = lambda: sys.stdin.read(1)
   1510     except (ImportError, AttributeError, io.UnsupportedOperation):
   1511         tty = None
   1512         getchar = lambda: sys.stdin.readline()[:-1][:1]
   1513 
   1514     try:
   1515         try:
   1516             h = int(os.environ.get('LINES', 0))
   1517         except ValueError:
   1518             h = 0
   1519         if h <= 1:
   1520             h = 25
   1521         r = inc = h - 1
   1522         sys.stdout.write('\n'.join(lines[:inc]) + '\n')
   1523         while lines[r:]:
   1524             sys.stdout.write('-- more --')
   1525             sys.stdout.flush()
   1526             c = getchar()
   1527 
   1528             if c in ('q', 'Q'):
   1529                 sys.stdout.write('\r          \r')
   1530                 break
   1531             elif c in ('\r', '\n'):
   1532                 sys.stdout.write('\r          \r' + lines[r] + '\n')
   1533                 r = r + 1
   1534                 continue
   1535             if c in ('b', 'B', '\x1b'):
   1536                 r = r - inc - inc
   1537                 if r < 0: r = 0
   1538             sys.stdout.write('\n' + '\n'.join(lines[r:r+inc]) + '\n')
   1539             r = r + inc
   1540 
   1541     finally:
   1542         if tty:
   1543             tty.tcsetattr(fd, tty.TCSAFLUSH, old)
   1544 
   1545 def plainpager(text):
   1546     """Simply print unformatted text.  This is the ultimate fallback."""
   1547     sys.stdout.write(plain(_escape_stdout(text)))
   1548 
   1549 def describe(thing):
   1550     """Produce a short description of the given thing."""
   1551     if inspect.ismodule(thing):
   1552         if thing.__name__ in sys.builtin_module_names:
   1553             return 'built-in module ' + thing.__name__
   1554         if hasattr(thing, '__path__'):
   1555             return 'package ' + thing.__name__
   1556         else:
   1557             return 'module ' + thing.__name__
   1558     if inspect.isbuiltin(thing):
   1559         return 'built-in function ' + thing.__name__
   1560     if inspect.isgetsetdescriptor(thing):
   1561         return 'getset descriptor %s.%s.%s' % (
   1562             thing.__objclass__.__module__, thing.__objclass__.__name__,
   1563             thing.__name__)
   1564     if inspect.ismemberdescriptor(thing):
   1565         return 'member descriptor %s.%s.%s' % (
   1566             thing.__objclass__.__module__, thing.__objclass__.__name__,
   1567             thing.__name__)
   1568     if inspect.isclass(thing):
   1569         return 'class ' + thing.__name__
   1570     if inspect.isfunction(thing):
   1571         return 'function ' + thing.__name__
   1572     if inspect.ismethod(thing):
   1573         return 'method ' + thing.__name__
   1574     return type(thing).__name__
   1575 
   1576 def locate(path, forceload=0):
   1577     """Locate an object by name or dotted path, importing as necessary."""
   1578     parts = [part for part in path.split('.') if part]
   1579     module, n = None, 0
   1580     while n < len(parts):
   1581         nextmodule = safeimport('.'.join(parts[:n+1]), forceload)
   1582         if nextmodule: module, n = nextmodule, n + 1
   1583         else: break
   1584     if module:
   1585         object = module
   1586     else:
   1587         object = builtins
   1588     for part in parts[n:]:
   1589         try:
   1590             object = getattr(object, part)
   1591         except AttributeError:
   1592             return None
   1593     return object
   1594 
   1595 # --------------------------------------- interactive interpreter interface
   1596 
   1597 text = TextDoc()
   1598 plaintext = _PlainTextDoc()
   1599 html = HTMLDoc()
   1600 
   1601 def resolve(thing, forceload=0):
   1602     """Given an object or a path to an object, get the object and its name."""
   1603     if isinstance(thing, str):
   1604         object = locate(thing, forceload)
   1605         if object is None:
   1606             raise ImportError('''\
   1607 No Python documentation found for %r.
   1608 Use help() to get the interactive help utility.
   1609 Use help(str) for help on the str class.''' % thing)
   1610         return object, thing
   1611     else:
   1612         name = getattr(thing, '__name__', None)
   1613         return thing, name if isinstance(name, str) else None
   1614 
   1615 def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
   1616         renderer=None):
   1617     """Render text documentation, given an object or a path to an object."""
   1618     if renderer is None:
   1619         renderer = text
   1620     object, name = resolve(thing, forceload)
   1621     desc = describe(object)
   1622     module = inspect.getmodule(object)
   1623     if name and '.' in name:
   1624         desc += ' in ' + name[:name.rfind('.')]
   1625     elif module and module is not object:
   1626         desc += ' in module ' + module.__name__
   1627 
   1628     if not (inspect.ismodule(object) or
   1629               inspect.isclass(object) or
   1630               inspect.isroutine(object) or
   1631               inspect.isgetsetdescriptor(object) or
   1632               inspect.ismemberdescriptor(object) or
   1633               isinstance(object, property)):
   1634         # If the passed object is a piece of data or an instance,
   1635         # document its available methods instead of its value.
   1636         object = type(object)
   1637         desc += ' object'
   1638     return title % desc + '\n\n' + renderer.document(object, name)
   1639 
   1640 def doc(thing, title='Python Library Documentation: %s', forceload=0,
   1641         output=None):
   1642     """Display text documentation, given an object or a path to an object."""
   1643     try:
   1644         if output is None:
   1645             pager(render_doc(thing, title, forceload))
   1646         else:
   1647             output.write(render_doc(thing, title, forceload, plaintext))
   1648     except (ImportError, ErrorDuringImport) as value:
   1649         print(value)
   1650 
   1651 def writedoc(thing, forceload=0):
   1652     """Write HTML documentation to a file in the current directory."""
   1653     try:
   1654         object, name = resolve(thing, forceload)
   1655         page = html.page(describe(object), html.document(object, name))
   1656         with open(name + '.html', 'w', encoding='utf-8') as file:
   1657             file.write(page)
   1658         print('wrote', name + '.html')
   1659     except (ImportError, ErrorDuringImport) as value:
   1660         print(value)
   1661 
   1662 def writedocs(dir, pkgpath='', done=None):
   1663     """Write out HTML documentation for all modules in a directory tree."""
   1664     if done is None: done = {}
   1665     for importer, modname, ispkg in pkgutil.walk_packages([dir], pkgpath):
   1666         writedoc(modname)
   1667     return
   1668 
   1669 class Helper:
   1670 
   1671     # These dictionaries map a topic name to either an alias, or a tuple
   1672     # (label, seealso-items).  The "label" is the label of the corresponding
   1673     # section in the .rst file under Doc/ and an index into the dictionary
   1674     # in pydoc_data/topics.py.
   1675     #
   1676     # CAUTION: if you change one of these dictionaries, be sure to adapt the
   1677     #          list of needed labels in Doc/tools/pyspecific.py and
   1678     #          regenerate the pydoc_data/topics.py file by running
   1679     #              make pydoc-topics
   1680     #          in Doc/ and copying the output file into the Lib/ directory.
   1681 
   1682     keywords = {
   1683         'False': '',
   1684         'None': '',
   1685         'True': '',
   1686         'and': 'BOOLEAN',
   1687         'as': 'with',
   1688         'assert': ('assert', ''),
   1689         'break': ('break', 'while for'),
   1690         'class': ('class', 'CLASSES SPECIALMETHODS'),
   1691         'continue': ('continue', 'while for'),
   1692         'def': ('function', ''),
   1693         'del': ('del', 'BASICMETHODS'),
   1694         'elif': 'if',
   1695         'else': ('else', 'while for'),
   1696         'except': 'try',
   1697         'finally': 'try',
   1698         'for': ('for', 'break continue while'),
   1699         'from': 'import',
   1700         'global': ('global', 'nonlocal NAMESPACES'),
   1701         'if': ('if', 'TRUTHVALUE'),
   1702         'import': ('import', 'MODULES'),
   1703         'in': ('in', 'SEQUENCEMETHODS'),
   1704         'is': 'COMPARISON',
   1705         'lambda': ('lambda', 'FUNCTIONS'),
   1706         'nonlocal': ('nonlocal', 'global NAMESPACES'),
   1707         'not': 'BOOLEAN',
   1708         'or': 'BOOLEAN',
   1709         'pass': ('pass', ''),
   1710         'raise': ('raise', 'EXCEPTIONS'),
   1711         'return': ('return', 'FUNCTIONS'),
   1712         'try': ('try', 'EXCEPTIONS'),
   1713         'while': ('while', 'break continue if TRUTHVALUE'),
   1714         'with': ('with', 'CONTEXTMANAGERS EXCEPTIONS yield'),
   1715         'yield': ('yield', ''),
   1716     }
   1717     # Either add symbols to this dictionary or to the symbols dictionary
   1718     # directly: Whichever is easier. They are merged later.
   1719     _symbols_inverse = {
   1720         'STRINGS' : ("'", "'''", "r'", "b'", '"""', '"', 'r"', 'b"'),
   1721         'OPERATORS' : ('+', '-', '*', '**', '/', '//', '%', '<<', '>>', '&',
   1722                        '|', '^', '~', '<', '>', '<=', '>=', '==', '!=', '<>'),
   1723         'COMPARISON' : ('<', '>', '<=', '>=', '==', '!=', '<>'),
   1724         'UNARY' : ('-', '~'),
   1725         'AUGMENTEDASSIGNMENT' : ('+=', '-=', '*=', '/=', '%=', '&=', '|=',
   1726                                 '^=', '<<=', '>>=', '**=', '//='),
   1727         'BITWISE' : ('<<', '>>', '&', '|', '^', '~'),
   1728         'COMPLEX' : ('j', 'J')
   1729     }
   1730     symbols = {
   1731         '%': 'OPERATORS FORMATTING',
   1732         '**': 'POWER',
   1733         ',': 'TUPLES LISTS FUNCTIONS',
   1734         '.': 'ATTRIBUTES FLOAT MODULES OBJECTS',
   1735         '...': 'ELLIPSIS',
   1736         ':': 'SLICINGS DICTIONARYLITERALS',
   1737         '@': 'def class',
   1738         '\\': 'STRINGS',
   1739         '_': 'PRIVATENAMES',
   1740         '__': 'PRIVATENAMES SPECIALMETHODS',
   1741         '`': 'BACKQUOTES',
   1742         '(': 'TUPLES FUNCTIONS CALLS',
   1743         ')': 'TUPLES FUNCTIONS CALLS',
   1744         '[': 'LISTS SUBSCRIPTS SLICINGS',
   1745         ']': 'LISTS SUBSCRIPTS SLICINGS'
   1746     }
   1747     for topic, symbols_ in _symbols_inverse.items():
   1748         for symbol in symbols_:
   1749             topics = symbols.get(symbol, topic)
   1750             if topic not in topics:
   1751                 topics = topics + ' ' + topic
   1752             symbols[symbol] = topics
   1753 
   1754     topics = {
   1755         'TYPES': ('types', 'STRINGS UNICODE NUMBERS SEQUENCES MAPPINGS '
   1756                   'FUNCTIONS CLASSES MODULES FILES inspect'),
   1757         'STRINGS': ('strings', 'str UNICODE SEQUENCES STRINGMETHODS '
   1758                     'FORMATTING TYPES'),
   1759         'STRINGMETHODS': ('string-methods', 'STRINGS FORMATTING'),
   1760         'FORMATTING': ('formatstrings', 'OPERATORS'),
   1761         'UNICODE': ('strings', 'encodings unicode SEQUENCES STRINGMETHODS '
   1762                     'FORMATTING TYPES'),
   1763         'NUMBERS': ('numbers', 'INTEGER FLOAT COMPLEX TYPES'),
   1764         'INTEGER': ('integers', 'int range'),
   1765         'FLOAT': ('floating', 'float math'),
   1766         'COMPLEX': ('imaginary', 'complex cmath'),
   1767         'SEQUENCES': ('typesseq', 'STRINGMETHODS FORMATTING range LISTS'),
   1768         'MAPPINGS': 'DICTIONARIES',
   1769         'FUNCTIONS': ('typesfunctions', 'def TYPES'),
   1770         'METHODS': ('typesmethods', 'class def CLASSES TYPES'),
   1771         'CODEOBJECTS': ('bltin-code-objects', 'compile FUNCTIONS TYPES'),
   1772         'TYPEOBJECTS': ('bltin-type-objects', 'types TYPES'),
   1773         'FRAMEOBJECTS': 'TYPES',
   1774         'TRACEBACKS': 'TYPES',
   1775         'NONE': ('bltin-null-object', ''),
   1776         'ELLIPSIS': ('bltin-ellipsis-object', 'SLICINGS'),
   1777         'SPECIALATTRIBUTES': ('specialattrs', ''),
   1778         'CLASSES': ('types', 'class SPECIALMETHODS PRIVATENAMES'),
   1779         'MODULES': ('typesmodules', 'import'),
   1780         'PACKAGES': 'import',
   1781         'EXPRESSIONS': ('operator-summary', 'lambda or and not in is BOOLEAN '
   1782                         'COMPARISON BITWISE SHIFTING BINARY FORMATTING POWER '
   1783                         'UNARY ATTRIBUTES SUBSCRIPTS SLICINGS CALLS TUPLES '
   1784                         'LISTS DICTIONARIES'),
   1785         'OPERATORS': 'EXPRESSIONS',
   1786         'PRECEDENCE': 'EXPRESSIONS',
   1787         'OBJECTS': ('objects', 'TYPES'),
   1788         'SPECIALMETHODS': ('specialnames', 'BASICMETHODS ATTRIBUTEMETHODS '
   1789                            'CALLABLEMETHODS SEQUENCEMETHODS MAPPINGMETHODS '
   1790                            'NUMBERMETHODS CLASSES'),
   1791         'BASICMETHODS': ('customization', 'hash repr str SPECIALMETHODS'),
   1792         'ATTRIBUTEMETHODS': ('attribute-access', 'ATTRIBUTES SPECIALMETHODS'),
   1793         'CALLABLEMETHODS': ('callable-types', 'CALLS SPECIALMETHODS'),
   1794         'SEQUENCEMETHODS': ('sequence-types', 'SEQUENCES SEQUENCEMETHODS '
   1795                              'SPECIALMETHODS'),
   1796         'MAPPINGMETHODS': ('sequence-types', 'MAPPINGS SPECIALMETHODS'),
   1797         'NUMBERMETHODS': ('numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT '
   1798                           'SPECIALMETHODS'),
   1799         'EXECUTION': ('execmodel', 'NAMESPACES DYNAMICFEATURES EXCEPTIONS'),
   1800         'NAMESPACES': ('naming', 'global nonlocal ASSIGNMENT DELETION DYNAMICFEATURES'),
   1801         'DYNAMICFEATURES': ('dynamic-features', ''),
   1802         'SCOPING': 'NAMESPACES',
   1803         'FRAMES': 'NAMESPACES',
   1804         'EXCEPTIONS': ('exceptions', 'try except finally raise'),
   1805         'CONVERSIONS': ('conversions', ''),
   1806         'IDENTIFIERS': ('identifiers', 'keywords SPECIALIDENTIFIERS'),
   1807         'SPECIALIDENTIFIERS': ('id-classes', ''),
   1808         'PRIVATENAMES': ('atom-identifiers', ''),
   1809         'LITERALS': ('atom-literals', 'STRINGS NUMBERS TUPLELITERALS '
   1810                      'LISTLITERALS DICTIONARYLITERALS'),
   1811         'TUPLES': 'SEQUENCES',
   1812         'TUPLELITERALS': ('exprlists', 'TUPLES LITERALS'),
   1813         'LISTS': ('typesseq-mutable', 'LISTLITERALS'),
   1814         'LISTLITERALS': ('lists', 'LISTS LITERALS'),
   1815         'DICTIONARIES': ('typesmapping', 'DICTIONARYLITERALS'),
   1816         'DICTIONARYLITERALS': ('dict', 'DICTIONARIES LITERALS'),
   1817         'ATTRIBUTES': ('attribute-references', 'getattr hasattr setattr ATTRIBUTEMETHODS'),
   1818         'SUBSCRIPTS': ('subscriptions', 'SEQUENCEMETHODS'),
   1819         'SLICINGS': ('slicings', 'SEQUENCEMETHODS'),
   1820         'CALLS': ('calls', 'EXPRESSIONS'),
   1821         'POWER': ('power', 'EXPRESSIONS'),
   1822         'UNARY': ('unary', 'EXPRESSIONS'),
   1823         'BINARY': ('binary', 'EXPRESSIONS'),
   1824         'SHIFTING': ('shifting', 'EXPRESSIONS'),
   1825         'BITWISE': ('bitwise', 'EXPRESSIONS'),
   1826         'COMPARISON': ('comparisons', 'EXPRESSIONS BASICMETHODS'),
   1827         'BOOLEAN': ('booleans', 'EXPRESSIONS TRUTHVALUE'),
   1828         'ASSERTION': 'assert',
   1829         'ASSIGNMENT': ('assignment', 'AUGMENTEDASSIGNMENT'),
   1830         'AUGMENTEDASSIGNMENT': ('augassign', 'NUMBERMETHODS'),
   1831         'DELETION': 'del',
   1832         'RETURNING': 'return',
   1833         'IMPORTING': 'import',
   1834         'CONDITIONAL': 'if',
   1835         'LOOPING': ('compound', 'for while break continue'),
   1836         'TRUTHVALUE': ('truth', 'if while and or not BASICMETHODS'),
   1837         'DEBUGGING': ('debugger', 'pdb'),
   1838         'CONTEXTMANAGERS': ('context-managers', 'with'),
   1839     }
   1840 
   1841     def __init__(self, input=None, output=None):
   1842         self._input = input
   1843         self._output = output
   1844 
   1845     input  = property(lambda self: self._input or sys.stdin)
   1846     output = property(lambda self: self._output or sys.stdout)
   1847 
   1848     def __repr__(self):
   1849         if inspect.stack()[1][3] == '?':
   1850             self()
   1851             return ''
   1852         return '<%s.%s instance>' % (self.__class__.__module__,
   1853                                      self.__class__.__qualname__)
   1854 
   1855     _GoInteractive = object()
   1856     def __call__(self, request=_GoInteractive):
   1857         if request is not self._GoInteractive:
   1858             self.help(request)
   1859         else:
   1860             self.intro()
   1861             self.interact()
   1862             self.output.write('''
   1863 You are now leaving help and returning to the Python interpreter.
   1864 If you want to ask for help on a particular object directly from the
   1865 interpreter, you can type "help(object)".  Executing "help('string')"
   1866 has the same effect as typing a particular string at the help> prompt.
   1867 ''')
   1868 
   1869     def interact(self):
   1870         self.output.write('\n')
   1871         while True:
   1872             try:
   1873                 request = self.getline('help> ')
   1874                 if not request: break
   1875             except (KeyboardInterrupt, EOFError):
   1876                 break
   1877             request = replace(request, '"', '', "'", '').strip()
   1878             if request.lower() in ('q', 'quit'): break
   1879             if request == 'help':
   1880                 self.intro()
   1881             else:
   1882                 self.help(request)
   1883 
   1884     def getline(self, prompt):
   1885         """Read one line, using input() when appropriate."""
   1886         if self.input is sys.stdin:
   1887             return input(prompt)
   1888         else:
   1889             self.output.write(prompt)
   1890             self.output.flush()
   1891             return self.input.readline()
   1892 
   1893     def help(self, request):
   1894         if type(request) is type(''):
   1895             request = request.strip()
   1896             if request == 'keywords': self.listkeywords()
   1897             elif request == 'symbols': self.listsymbols()
   1898             elif request == 'topics': self.listtopics()
   1899             elif request == 'modules': self.listmodules()
   1900             elif request[:8] == 'modules ':
   1901                 self.listmodules(request.split()[1])
   1902             elif request in self.symbols: self.showsymbol(request)
   1903             elif request in ['True', 'False', 'None']:
   1904                 # special case these keywords since they are objects too
   1905                 doc(eval(request), 'Help on %s:')
   1906             elif request in self.keywords: self.showtopic(request)
   1907             elif request in self.topics: self.showtopic(request)
   1908             elif request: doc(request, 'Help on %s:', output=self._output)
   1909             else: doc(str, 'Help on %s:', output=self._output)
   1910         elif isinstance(request, Helper): self()
   1911         else: doc(request, 'Help on %s:', output=self._output)
   1912         self.output.write('\n')
   1913 
   1914     def intro(self):
   1915         self.output.write('''
   1916 Welcome to Python {0}'s help utility!
   1917 
   1918 If this is your first time using Python, you should definitely check out
   1919 the tutorial on the Internet at http://docs.python.org/{0}/tutorial/.
   1920 
   1921 Enter the name of any module, keyword, or topic to get help on writing
   1922 Python programs and using Python modules.  To quit this help utility and
   1923 return to the interpreter, just type "quit".
   1924 
   1925 To get a list of available modules, keywords, symbols, or topics, type
   1926 "modules", "keywords", "symbols", or "topics".  Each module also comes
   1927 with a one-line summary of what it does; to list the modules whose name
   1928 or summary contain a given string such as "spam", type "modules spam".
   1929 '''.format('%d.%d' % sys.version_info[:2]))
   1930 
   1931     def list(self, items, columns=4, width=80):
   1932         items = list(sorted(items))
   1933         colw = width // columns
   1934         rows = (len(items) + columns - 1) // columns
   1935         for row in range(rows):
   1936             for col in range(columns):
   1937                 i = col * rows + row
   1938                 if i < len(items):
   1939                     self.output.write(items[i])
   1940                     if col < columns - 1:
   1941                         self.output.write(' ' + ' ' * (colw - 1 - len(items[i])))
   1942             self.output.write('\n')
   1943 
   1944     def listkeywords(self):
   1945         self.output.write('''
   1946 Here is a list of the Python keywords.  Enter any keyword to get more help.
   1947 
   1948 ''')
   1949         self.list(self.keywords.keys())
   1950 
   1951     def listsymbols(self):
   1952         self.output.write('''
   1953 Here is a list of the punctuation symbols which Python assigns special meaning
   1954 to. Enter any symbol to get more help.
   1955 
   1956 ''')
   1957         self.list(self.symbols.keys())
   1958 
   1959     def listtopics(self):
   1960         self.output.write('''
   1961 Here is a list of available topics.  Enter any topic name to get more help.
   1962 
   1963 ''')
   1964         self.list(self.topics.keys())
   1965 
   1966     def showtopic(self, topic, more_xrefs=''):
   1967         try:
   1968             import pydoc_data.topics
   1969         except ImportError:
   1970             self.output.write('''
   1971 Sorry, topic and keyword documentation is not available because the
   1972 module "pydoc_data.topics" could not be found.
   1973 ''')
   1974             return
   1975         target = self.topics.get(topic, self.keywords.get(topic))
   1976         if not target:
   1977             self.output.write('no documentation found for %s\n' % repr(topic))
   1978             return
   1979         if type(target) is type(''):
   1980             return self.showtopic(target, more_xrefs)
   1981 
   1982         label, xrefs = target
   1983         try:
   1984             doc = pydoc_data.topics.topics[label]
   1985         except KeyError:
   1986             self.output.write('no documentation found for %s\n' % repr(topic))
   1987             return
   1988         pager(doc.strip() + '\n')
   1989         if more_xrefs:
   1990             xrefs = (xrefs or '') + ' ' + more_xrefs
   1991         if xrefs:
   1992             import textwrap
   1993             text = 'Related help topics: ' + ', '.join(xrefs.split()) + '\n'
   1994             wrapped_text = textwrap.wrap(text, 72)
   1995             self.output.write('\n%s\n' % ''.join(wrapped_text))
   1996 
   1997     def _gettopic(self, topic, more_xrefs=''):
   1998         """Return unbuffered tuple of (topic, xrefs).
   1999 
   2000         If an error occurs here, the exception is caught and displayed by
   2001         the url handler.
   2002 
   2003         This function duplicates the showtopic method but returns its
   2004         result directly so it can be formatted for display in an html page.
   2005         """
   2006         try:
   2007             import pydoc_data.topics
   2008         except ImportError:
   2009             return('''
   2010 Sorry, topic and keyword documentation is not available because the
   2011 module "pydoc_data.topics" could not be found.
   2012 ''' , '')
   2013         target = self.topics.get(topic, self.keywords.get(topic))
   2014         if not target:
   2015             raise ValueError('could not find topic')
   2016         if isinstance(target, str):
   2017             return self._gettopic(target, more_xrefs)
   2018         label, xrefs = target
   2019         doc = pydoc_data.topics.topics[label]
   2020         if more_xrefs:
   2021             xrefs = (xrefs or '') + ' ' + more_xrefs
   2022         return doc, xrefs
   2023 
   2024     def showsymbol(self, symbol):
   2025         target = self.symbols[symbol]
   2026         topic, _, xrefs = target.partition(' ')
   2027         self.showtopic(topic, xrefs)
   2028 
   2029     def listmodules(self, key=''):
   2030         if key:
   2031             self.output.write('''
   2032 Here is a list of modules whose name or summary contains '{}'.
   2033 If there are any, enter a module name to get more help.
   2034 
   2035 '''.format(key))
   2036             apropos(key)
   2037         else:
   2038             self.output.write('''
   2039 Please wait a moment while I gather a list of all available modules...
   2040 
   2041 ''')
   2042             modules = {}
   2043             def callback(path, modname, desc, modules=modules):
   2044                 if modname and modname[-9:] == '.__init__':
   2045                     modname = modname[:-9] + ' (package)'
   2046                 if modname.find('.') < 0:
   2047                     modules[modname] = 1
   2048             def onerror(modname):
   2049                 callback(None, modname, None)
   2050             ModuleScanner().run(callback, onerror=onerror)
   2051             self.list(modules.keys())
   2052             self.output.write('''
   2053 Enter any module name to get more help.  Or, type "modules spam" to search
   2054 for modules whose name or summary contain the string "spam".
   2055 ''')
   2056 
   2057 help = Helper()
   2058 
   2059 class ModuleScanner:
   2060     """An interruptible scanner that searches module synopses."""
   2061 
   2062     def run(self, callback, key=None, completer=None, onerror=None):
   2063         if key: key = key.lower()
   2064         self.quit = False
   2065         seen = {}
   2066 
   2067         for modname in sys.builtin_module_names:
   2068             if modname != '__main__':
   2069                 seen[modname] = 1
   2070                 if key is None:
   2071                     callback(None, modname, '')
   2072                 else:
   2073                     name = __import__(modname).__doc__ or ''
   2074                     desc = name.split('\n')[0]
   2075                     name = modname + ' - ' + desc
   2076                     if name.lower().find(key) >= 0:
   2077                         callback(None, modname, desc)
   2078 
   2079         for importer, modname, ispkg in pkgutil.walk_packages(onerror=onerror):
   2080             if self.quit:
   2081                 break
   2082 
   2083             if key is None:
   2084                 callback(None, modname, '')
   2085             else:
   2086                 try:
   2087                     spec = pkgutil._get_spec(importer, modname)
   2088                 except SyntaxError:
   2089                     # raised by tests for bad coding cookies or BOM
   2090                     continue
   2091                 loader = spec.loader
   2092                 if hasattr(loader, 'get_source'):
   2093                     try:
   2094                         source = loader.get_source(modname)
   2095                     except Exception:
   2096                         if onerror:
   2097                             onerror(modname)
   2098                         continue
   2099                     desc = source_synopsis(io.StringIO(source)) or ''
   2100                     if hasattr(loader, 'get_filename'):
   2101                         path = loader.get_filename(modname)
   2102                     else:
   2103                         path = None
   2104                 else:
   2105                     try:
   2106                         module = importlib._bootstrap._load(spec)
   2107                     except ImportError:
   2108                         if onerror:
   2109                             onerror(modname)
   2110                         continue
   2111                     desc = module.__doc__.splitlines()[0] if module.__doc__ else ''
   2112                     path = getattr(module,'__file__',None)
   2113                 name = modname + ' - ' + desc
   2114                 if name.lower().find(key) >= 0:
   2115                     callback(path, modname, desc)
   2116 
   2117         if completer:
   2118             completer()
   2119 
   2120 def apropos(key):
   2121     """Print all the one-line module summaries that contain a substring."""
   2122     def callback(path, modname, desc):
   2123         if modname[-9:] == '.__init__':
   2124             modname = modname[:-9] + ' (package)'
   2125         print(modname, desc and '- ' + desc)
   2126     def onerror(modname):
   2127         pass
   2128     with warnings.catch_warnings():
   2129         warnings.filterwarnings('ignore') # ignore problems during import
   2130         ModuleScanner().run(callback, key, onerror=onerror)
   2131 
   2132 # --------------------------------------- enhanced Web browser interface
   2133 
   2134 def _start_server(urlhandler, port):
   2135     """Start an HTTP server thread on a specific port.
   2136 
   2137     Start an HTML/text server thread, so HTML or text documents can be
   2138     browsed dynamically and interactively with a Web browser.  Example use:
   2139 
   2140         >>> import time
   2141         >>> import pydoc
   2142 
   2143         Define a URL handler.  To determine what the client is asking
   2144         for, check the URL and content_type.
   2145 
   2146         Then get or generate some text or HTML code and return it.
   2147 
   2148         >>> def my_url_handler(url, content_type):
   2149         ...     text = 'the URL sent was: (%s, %s)' % (url, content_type)
   2150         ...     return text
   2151 
   2152         Start server thread on port 0.
   2153         If you use port 0, the server will pick a random port number.
   2154         You can then use serverthread.port to get the port number.
   2155 
   2156         >>> port = 0
   2157         >>> serverthread = pydoc._start_server(my_url_handler, port)
   2158 
   2159         Check that the server is really started.  If it is, open browser
   2160         and get first page.  Use serverthread.url as the starting page.
   2161 
   2162         >>> if serverthread.serving:
   2163         ...    import webbrowser
   2164 
   2165         The next two lines are commented out so a browser doesn't open if
   2166         doctest is run on this module.
   2167 
   2168         #...    webbrowser.open(serverthread.url)
   2169         #True
   2170 
   2171         Let the server do its thing. We just need to monitor its status.
   2172         Use time.sleep so the loop doesn't hog the CPU.
   2173 
   2174         >>> starttime = time.time()
   2175         >>> timeout = 1                    #seconds
   2176 
   2177         This is a short timeout for testing purposes.
   2178 
   2179         >>> while serverthread.serving:
   2180         ...     time.sleep(.01)
   2181         ...     if serverthread.serving and time.time() - starttime > timeout:
   2182         ...          serverthread.stop()
   2183         ...          break
   2184 
   2185         Print any errors that may have occurred.
   2186 
   2187         >>> print(serverthread.error)
   2188         None
   2189    """
   2190     import http.server
   2191     import email.message
   2192     import select
   2193     import threading
   2194 
   2195     class DocHandler(http.server.BaseHTTPRequestHandler):
   2196 
   2197         def do_GET(self):
   2198             """Process a request from an HTML browser.
   2199 
   2200             The URL received is in self.path.
   2201             Get an HTML page from self.urlhandler and send it.
   2202             """
   2203             if self.path.endswith('.css'):
   2204                 content_type = 'text/css'
   2205             else:
   2206                 content_type = 'text/html'
   2207             self.send_response(200)
   2208             self.send_header('Content-Type', '%s; charset=UTF-8' % content_type)
   2209             self.end_headers()
   2210             self.wfile.write(self.urlhandler(
   2211                 self.path, content_type).encode('utf-8'))
   2212 
   2213         def log_message(self, *args):
   2214             # Don't log messages.
   2215             pass
   2216 
   2217     class DocServer(http.server.HTTPServer):
   2218 
   2219         def __init__(self, port, callback):
   2220             self.host = 'localhost'
   2221             self.address = (self.host, port)
   2222             self.callback = callback
   2223             self.base.__init__(self, self.address, self.handler)
   2224             self.quit = False
   2225 
   2226         def serve_until_quit(self):
   2227             while not self.quit:
   2228                 rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
   2229                 if rd:
   2230                     self.handle_request()
   2231             self.server_close()
   2232 
   2233         def server_activate(self):
   2234             self.base.server_activate(self)
   2235             if self.callback:
   2236                 self.callback(self)
   2237 
   2238     class ServerThread(threading.Thread):
   2239 
   2240         def __init__(self, urlhandler, port):
   2241             self.urlhandler = urlhandler
   2242             self.port = int(port)
   2243             threading.Thread.__init__(self)
   2244             self.serving = False
   2245             self.error = None
   2246 
   2247         def run(self):
   2248             """Start the server."""
   2249             try:
   2250                 DocServer.base = http.server.HTTPServer
   2251                 DocServer.handler = DocHandler
   2252                 DocHandler.MessageClass = email.message.Message
   2253                 DocHandler.urlhandler = staticmethod(self.urlhandler)
   2254                 docsvr = DocServer(self.port, self.ready)
   2255                 self.docserver = docsvr
   2256                 docsvr.serve_until_quit()
   2257             except Exception as e:
   2258                 self.error = e
   2259 
   2260         def ready(self, server):
   2261             self.serving = True
   2262             self.host = server.host
   2263             self.port = server.server_port
   2264             self.url = 'http://%s:%d/' % (self.host, self.port)
   2265 
   2266         def stop(self):
   2267             """Stop the server and this thread nicely"""
   2268             self.docserver.quit = True
   2269             self.serving = False
   2270             self.url = None
   2271 
   2272     thread = ServerThread(urlhandler, port)
   2273     thread.start()
   2274     # Wait until thread.serving is True to make sure we are
   2275     # really up before returning.
   2276     while not thread.error and not thread.serving:
   2277         time.sleep(.01)
   2278     return thread
   2279 
   2280 
   2281 def _url_handler(url, content_type="text/html"):
   2282     """The pydoc url handler for use with the pydoc server.
   2283 
   2284     If the content_type is 'text/css', the _pydoc.css style
   2285     sheet is read and returned if it exits.
   2286 
   2287     If the content_type is 'text/html', then the result of
   2288     get_html_page(url) is returned.
   2289     """
   2290     class _HTMLDoc(HTMLDoc):
   2291 
   2292         def page(self, title, contents):
   2293             """Format an HTML page."""
   2294             css_path = "pydoc_data/_pydoc.css"
   2295             css_link = (
   2296                 '<link rel="stylesheet" type="text/css" href="%s">' %
   2297                 css_path)
   2298             return '''\
   2299 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
   2300 <html><head><title>Pydoc: %s</title>
   2301 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
   2302 %s</head><body bgcolor="#f0f0f8">%s<div style="clear:both;padding-top:.5em;">%s</div>
   2303 </body></html>''' % (title, css_link, html_navbar(), contents)
   2304 
   2305         def filelink(self, url, path):
   2306             return '<a href="getfile?key=%s">%s</a>' % (url, path)
   2307 
   2308 
   2309     html = _HTMLDoc()
   2310 
   2311     def html_navbar():
   2312         version = html.escape("%s [%s, %s]" % (platform.python_version(),
   2313                                                platform.python_build()[0],
   2314                                                platform.python_compiler()))
   2315         return """
   2316             <div style='float:left'>
   2317                 Python %s<br>%s
   2318             </div>
   2319             <div style='float:right'>
   2320                 <div style='text-align:center'>
   2321                   <a href="index.html">Module Index</a>
   2322                   : <a href="topics.html">Topics</a>
   2323                   : <a href="keywords.html">Keywords</a>
   2324                 </div>
   2325                 <div>
   2326                     <form action="get" style='display:inline;'>
   2327                       <input type=text name=key size=15>
   2328                       <input type=submit value="Get">
   2329                     </form>&nbsp;
   2330                     <form action="search" style='display:inline;'>
   2331                       <input type=text name=key size=15>
   2332                       <input type=submit value="Search">
   2333                     </form>
   2334                 </div>
   2335             </div>
   2336             """ % (version, html.escape(platform.platform(terse=True)))
   2337 
   2338     def html_index():
   2339         """Module Index page."""
   2340 
   2341         def bltinlink(name):
   2342             return '<a href="%s.html">%s</a>' % (name, name)
   2343 
   2344         heading = html.heading(
   2345             '<big><big><strong>Index of Modules</strong></big></big>',
   2346             '#ffffff', '#7799ee')
   2347         names = [name for name in sys.builtin_module_names
   2348                  if name != '__main__']
   2349         contents = html.multicolumn(names, bltinlink)
   2350         contents = [heading, '<p>' + html.bigsection(
   2351             'Built-in Modules', '#ffffff', '#ee77aa', contents)]
   2352 
   2353         seen = {}
   2354         for dir in sys.path:
   2355             contents.append(html.index(dir, seen))
   2356 
   2357         contents.append(
   2358             '<p align=right><font color="#909090" face="helvetica,'
   2359             'arial"><strong>pydoc</strong> by Ka-Ping Yee'
   2360             '&lt;ping (at] lfw.org&gt;</font>')
   2361         return 'Index of Modules', ''.join(contents)
   2362 
   2363     def html_search(key):
   2364         """Search results page."""
   2365         # scan for modules
   2366         search_result = []
   2367 
   2368         def callback(path, modname, desc):
   2369             if modname[-9:] == '.__init__':
   2370                 modname = modname[:-9] + ' (package)'
   2371             search_result.append((modname, desc and '- ' + desc))
   2372 
   2373         with warnings.catch_warnings():
   2374             warnings.filterwarnings('ignore') # ignore problems during import
   2375             def onerror(modname):
   2376                 pass
   2377             ModuleScanner().run(callback, key, onerror=onerror)
   2378 
   2379         # format page
   2380         def bltinlink(name):
   2381             return '<a href="%s.html">%s</a>' % (name, name)
   2382 
   2383         results = []
   2384         heading = html.heading(
   2385             '<big><big><strong>Search Results</strong></big></big>',
   2386             '#ffffff', '#7799ee')
   2387         for name, desc in search_result:
   2388             results.append(bltinlink(name) + desc)
   2389         contents = heading + html.bigsection(
   2390             'key = %s' % key, '#ffffff', '#ee77aa', '<br>'.join(results))
   2391         return 'Search Results', contents
   2392 
   2393     def html_getfile(path):
   2394         """Get and display a source file listing safely."""
   2395         path = urllib.parse.unquote(path)
   2396         with tokenize.open(path) as fp:
   2397             lines = html.escape(fp.read())
   2398         body = '<pre>%s</pre>' % lines
   2399         heading = html.heading(
   2400             '<big><big><strong>File Listing</strong></big></big>',
   2401             '#ffffff', '#7799ee')
   2402         contents = heading + html.bigsection(
   2403             'File: %s' % path, '#ffffff', '#ee77aa', body)
   2404         return 'getfile %s' % path, contents
   2405 
   2406     def html_topics():
   2407         """Index of topic texts available."""
   2408 
   2409         def bltinlink(name):
   2410             return '<a href="topic?key=%s">%s</a>' % (name, name)
   2411 
   2412         heading = html.heading(
   2413             '<big><big><strong>INDEX</strong></big></big>',
   2414             '#ffffff', '#7799ee')
   2415         names = sorted(Helper.topics.keys())
   2416 
   2417         contents = html.multicolumn(names, bltinlink)
   2418         contents = heading + html.bigsection(
   2419             'Topics', '#ffffff', '#ee77aa', contents)
   2420         return 'Topics', contents
   2421 
   2422     def html_keywords():
   2423         """Index of keywords."""
   2424         heading = html.heading(
   2425             '<big><big><strong>INDEX</strong></big></big>',
   2426             '#ffffff', '#7799ee')
   2427         names = sorted(Helper.keywords.keys())
   2428 
   2429         def bltinlink(name):
   2430             return '<a href="topic?key=%s">%s</a>' % (name, name)
   2431 
   2432         contents = html.multicolumn(names, bltinlink)
   2433         contents = heading + html.bigsection(
   2434             'Keywords', '#ffffff', '#ee77aa', contents)
   2435         return 'Keywords', contents
   2436 
   2437     def html_topicpage(topic):
   2438         """Topic or keyword help page."""
   2439         buf = io.StringIO()
   2440         htmlhelp = Helper(buf, buf)
   2441         contents, xrefs = htmlhelp._gettopic(topic)
   2442         if topic in htmlhelp.keywords:
   2443             title = 'KEYWORD'
   2444         else:
   2445             title = 'TOPIC'
   2446         heading = html.heading(
   2447             '<big><big><strong>%s</strong></big></big>' % title,
   2448             '#ffffff', '#7799ee')
   2449         contents = '<pre>%s</pre>' % html.markup(contents)
   2450         contents = html.bigsection(topic , '#ffffff','#ee77aa', contents)
   2451         if xrefs:
   2452             xrefs = sorted(xrefs.split())
   2453 
   2454             def bltinlink(name):
   2455                 return '<a href="topic?key=%s">%s</a>' % (name, name)
   2456 
   2457             xrefs = html.multicolumn(xrefs, bltinlink)
   2458             xrefs = html.section('Related help topics: ',
   2459                                  '#ffffff', '#ee77aa', xrefs)
   2460         return ('%s %s' % (title, topic),
   2461                 ''.join((heading, contents, xrefs)))
   2462 
   2463     def html_getobj(url):
   2464         obj = locate(url, forceload=1)
   2465         if obj is None and url != 'None':
   2466             raise ValueError('could not find object')
   2467         title = describe(obj)
   2468         content = html.document(obj, url)
   2469         return title, content
   2470 
   2471     def html_error(url, exc):
   2472         heading = html.heading(
   2473             '<big><big><strong>Error</strong></big></big>',
   2474             '#ffffff', '#7799ee')
   2475         contents = '<br>'.join(html.escape(line) for line in
   2476                                format_exception_only(type(exc), exc))
   2477         contents = heading + html.bigsection(url, '#ffffff', '#bb0000',
   2478                                              contents)
   2479         return "Error - %s" % url, contents
   2480 
   2481     def get_html_page(url):
   2482         """Generate an HTML page for url."""
   2483         complete_url = url
   2484         if url.endswith('.html'):
   2485             url = url[:-5]
   2486         try:
   2487             if url in ("", "index"):
   2488                 title, content = html_index()
   2489             elif url == "topics":
   2490                 title, content = html_topics()
   2491             elif url == "keywords":
   2492                 title, content = html_keywords()
   2493             elif '=' in url:
   2494                 op, _, url = url.partition('=')
   2495                 if op == "search?key":
   2496                     title, content = html_search(url)
   2497                 elif op == "getfile?key":
   2498                     title, content = html_getfile(url)
   2499                 elif op == "topic?key":
   2500                     # try topics first, then objects.
   2501                     try:
   2502                         title, content = html_topicpage(url)
   2503                     except ValueError:
   2504                         title, content = html_getobj(url)
   2505                 elif op == "get?key":
   2506                     # try objects first, then topics.
   2507                     if url in ("", "index"):
   2508                         title, content = html_index()
   2509                     else:
   2510                         try:
   2511                             title, content = html_getobj(url)
   2512                         except ValueError:
   2513                             title, content = html_topicpage(url)
   2514                 else:
   2515                     raise ValueError('bad pydoc url')
   2516             else:
   2517                 title, content = html_getobj(url)
   2518         except Exception as exc:
   2519             # Catch any errors and display them in an error page.
   2520             title, content = html_error(complete_url, exc)
   2521         return html.page(title, content)
   2522 
   2523     if url.startswith('/'):
   2524         url = url[1:]
   2525     if content_type == 'text/css':
   2526         path_here = os.path.dirname(os.path.realpath(__file__))
   2527         css_path = os.path.join(path_here, url)
   2528         with open(css_path) as fp:
   2529             return ''.join(fp.readlines())
   2530     elif content_type == 'text/html':
   2531         return get_html_page(url)
   2532     # Errors outside the url handler are caught by the server.
   2533     raise TypeError('unknown content type %r for url %s' % (content_type, url))
   2534 
   2535 
   2536 def browse(port=0, *, open_browser=True):
   2537     """Start the enhanced pydoc Web server and open a Web browser.
   2538 
   2539     Use port '0' to start the server on an arbitrary port.
   2540     Set open_browser to False to suppress opening a browser.
   2541     """
   2542     import webbrowser
   2543     serverthread = _start_server(_url_handler, port)
   2544     if serverthread.error:
   2545         print(serverthread.error)
   2546         return
   2547     if serverthread.serving:
   2548         server_help_msg = 'Server commands: [b]rowser, [q]uit'
   2549         if open_browser:
   2550             webbrowser.open(serverthread.url)
   2551         try:
   2552             print('Server ready at', serverthread.url)
   2553             print(server_help_msg)
   2554             while serverthread.serving:
   2555                 cmd = input('server> ')
   2556                 cmd = cmd.lower()
   2557                 if cmd == 'q':
   2558                     break
   2559                 elif cmd == 'b':
   2560                     webbrowser.open(serverthread.url)
   2561                 else:
   2562                     print(server_help_msg)
   2563         except (KeyboardInterrupt, EOFError):
   2564             print()
   2565         finally:
   2566             if serverthread.serving:
   2567                 serverthread.stop()
   2568                 print('Server stopped')
   2569 
   2570 
   2571 # -------------------------------------------------- command-line interface
   2572 
   2573 def ispath(x):
   2574     return isinstance(x, str) and x.find(os.sep) >= 0
   2575 
   2576 def cli():
   2577     """Command-line interface (looks at sys.argv to decide what to do)."""
   2578     import getopt
   2579     class BadUsage(Exception): pass
   2580 
   2581     # Scripts don't get the current directory in their path by default
   2582     # unless they are run with the '-m' switch
   2583     if '' not in sys.path:
   2584         scriptdir = os.path.dirname(sys.argv[0])
   2585         if scriptdir in sys.path:
   2586             sys.path.remove(scriptdir)
   2587         sys.path.insert(0, '.')
   2588 
   2589     try:
   2590         opts, args = getopt.getopt(sys.argv[1:], 'bk:p:w')
   2591         writing = False
   2592         start_server = False
   2593         open_browser = False
   2594         port = None
   2595         for opt, val in opts:
   2596             if opt == '-b':
   2597                 start_server = True
   2598                 open_browser = True
   2599             if opt == '-k':
   2600                 apropos(val)
   2601                 return
   2602             if opt == '-p':
   2603                 start_server = True
   2604                 port = val
   2605             if opt == '-w':
   2606                 writing = True
   2607 
   2608         if start_server:
   2609             if port is None:
   2610                 port = 0
   2611             browse(port, open_browser=open_browser)
   2612             return
   2613 
   2614         if not args: raise BadUsage
   2615         for arg in args:
   2616             if ispath(arg) and not os.path.exists(arg):
   2617                 print('file %r does not exist' % arg)
   2618                 break
   2619             try:
   2620                 if ispath(arg) and os.path.isfile(arg):
   2621                     arg = importfile(arg)
   2622                 if writing:
   2623                     if ispath(arg) and os.path.isdir(arg):
   2624                         writedocs(arg)
   2625                     else:
   2626                         writedoc(arg)
   2627                 else:
   2628                     help.help(arg)
   2629             except ErrorDuringImport as value:
   2630                 print(value)
   2631 
   2632     except (getopt.error, BadUsage):
   2633         cmd = os.path.splitext(os.path.basename(sys.argv[0]))[0]
   2634         print("""pydoc - the Python documentation tool
   2635 
   2636 {cmd} <name> ...
   2637     Show text documentation on something.  <name> may be the name of a
   2638     Python keyword, topic, function, module, or package, or a dotted
   2639     reference to a class or function within a module or module in a
   2640     package.  If <name> contains a '{sep}', it is used as the path to a
   2641     Python source file to document. If name is 'keywords', 'topics',
   2642     or 'modules', a listing of these things is displayed.
   2643 
   2644 {cmd} -k <keyword>
   2645     Search for a keyword in the synopsis lines of all available modules.
   2646 
   2647 {cmd} -p <port>
   2648     Start an HTTP server on the given port on the local machine.  Port
   2649     number 0 can be used to get an arbitrary unused port.
   2650 
   2651 {cmd} -b
   2652     Start an HTTP server on an arbitrary unused port and open a Web browser
   2653     to interactively browse documentation.  The -p option can be used with
   2654     the -b option to explicitly specify the server port.
   2655 
   2656 {cmd} -w <name> ...
   2657     Write out the HTML documentation for a module to a file in the current
   2658     directory.  If <name> contains a '{sep}', it is treated as a filename; if
   2659     it names a directory, documentation is written for all the contents.
   2660 """.format(cmd=cmd, sep=os.sep))
   2661 
   2662 if __name__ == '__main__':
   2663     cli()
   2664