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