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