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