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