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