pydoc.py 98.1 KB
Newer Older
1
#!/usr/bin/env python3
2
"""Generate Python documentation in HTML or text for interactive use.
3

Ka-Ping Yee's avatar
Ka-Ping Yee committed
4 5
In the Python interpreter, do "from pydoc import help" to provide online
help.  Calling help(thing) on a Python object documents the object.
6

7
Or, at the shell command line outside of Python:
8

9 10 11 12 13
Run "pydoc <name>" to show documentation on something.  <name> may be
the name of a function, module, package, or a dotted reference to a
class or function within a module or module in a package.  If the
argument contains a path segment delimiter (e.g. slash on Unix,
backslash on Windows) it is treated as the path to a Python source file.
14

15 16
Run "pydoc -k <keyword>" to search for a keyword in the synopsis lines
of all available modules.
17

18 19 20 21 22 23
Run "pydoc -p <port>" to start an HTTP server on the given port on the
local machine.  Port number 0 can be used to get an arbitrary unused port.

Run "pydoc -b" to start an HTTP server on an arbitrary unused port and
open a Web browser to interactively browse documentation.  The -p option
can be used with the -b option to explicitly specify the server port.
Ka-Ping Yee's avatar
Ka-Ping Yee committed
24

25 26
Run "pydoc -w <name>" to write out the HTML documentation for a module
to a file named "<name>.html".
27 28 29

Module docs for core modules are assumed to be in

30
    http://docs.python.org/X.Y/library/
31 32 33 34

This can be overridden by setting the PYTHONDOCS environment variable
to a different URL or to a local directory containing the Library
Reference Manual pages.
Ka-Ping Yee's avatar
Ka-Ping Yee committed
35
"""
36
__all__ = ['help']
37
__author__ = "Ka-Ping Yee <ping@lfw.org>"
38
__date__ = "26 February 2001"
Jeremy Hylton's avatar
Jeremy Hylton committed
39

40
__credits__ = """Guido van Rossum, for an excellent programming language.
Ka-Ping Yee's avatar
Ka-Ping Yee committed
41
Tommy Burnette, the original creator of manpy.
42 43
Paul Prescod, for all his work on onlinehelp.
Richard Chamberlain, for the first implementation of textdoc.
44
"""
45

46 47 48 49 50 51 52
# Known bugs that can't be fixed here:
#   - imp.load_module() cannot be prevented from clobbering existing
#     loaded modules, so calling synopsis() on a binary module file
#     changes the contents of any existing module with the same name.
#   - If the __file__ attribute on a module is a relative path and
#     the current directory is changed with os.chdir(), an incorrect
#     path will be displayed.
Ka-Ping Yee's avatar
Ka-Ping Yee committed
53

54 55 56
import builtins
import imp
import inspect
57 58
import io
import os
59 60 61
import pkgutil
import platform
import re
62
import sys
63
import time
64
import tokenize
65
import warnings
66
from collections import deque
67
from reprlib import Repr
68
from traceback import extract_tb, format_exception_only
69 70


71 72 73 74 75
# --------------------------------------------------------- common routines

def pathdirs():
    """Convert sys.path into a list of absolute, existing, unique paths."""
    dirs = []
76
    normdirs = []
77 78
    for dir in sys.path:
        dir = os.path.abspath(dir or '.')
79 80
        normdir = os.path.normcase(dir)
        if normdir not in normdirs and os.path.isdir(dir):
81
            dirs.append(dir)
82
            normdirs.append(normdir)
83 84 85 86
    return dirs

def getdoc(object):
    """Get the doc string or comments for an object."""
87
    result = inspect.getdoc(object) or inspect.getcomments(object)
88
    return result and re.sub('^ *\n', '', result.rstrip()) or ''
89

90 91
def splitdoc(doc):
    """Split a doc string into a synopsis line (if any) and the rest."""
92
    lines = doc.strip().split('\n')
93 94
    if len(lines) == 1:
        return lines[0], ''
95 96 97
    elif len(lines) >= 2 and not lines[1].rstrip():
        return lines[0], '\n'.join(lines[2:])
    return '', '\n'.join(lines)
98

99 100 101 102 103 104 105
def classname(object, modname):
    """Get a class name and qualify it with a module name if necessary."""
    name = object.__name__
    if object.__module__ != modname:
        name = object.__module__ + '.' + name
    return name

106
def isdata(object):
Georg Brandl's avatar
Georg Brandl committed
107
    """Check if an object is of a type that probably means it's data."""
108 109 110
    return not (inspect.ismodule(object) or inspect.isclass(object) or
                inspect.isroutine(object) or inspect.isframe(object) or
                inspect.istraceback(object) or inspect.iscode(object))
111 112 113

def replace(text, *pairs):
    """Do a series of global replacements on a string."""
114
    while pairs:
115
        text = pairs[1].join(text.split(pairs[0]))
116
        pairs = pairs[2:]
117 118 119 120 121
    return text

def cram(text, maxlen):
    """Omit part of a string if needed to make it fit in a maximum length."""
    if len(text) > maxlen:
122
        pre = max(0, (maxlen-3)//2)
123 124 125 126
        post = max(0, maxlen-3-pre)
        return text[:pre] + '...' + text[len(text)-post:]
    return text

127
_re_stripid = re.compile(r' at 0x[0-9a-f]{6,16}(>+)$', re.IGNORECASE)
128
def stripid(text):
129
    """Remove the hexadecimal id from a Python object representation."""
130
    # The behaviour of %p is implementation-dependent in terms of case.
131
    return _re_stripid.sub(r'\1', text)
132

133 134
def _is_some_method(obj):
    return inspect.ismethod(obj) or inspect.ismethoddescriptor(obj)
135

136 137
def allmethods(cl):
    methods = {}
138
    for key, value in inspect.getmembers(cl, _is_some_method):
139 140 141 142 143 144 145
        methods[key] = 1
    for base in cl.__bases__:
        methods.update(allmethods(base)) # all your base are belong to us
    for key in methods.keys():
        methods[key] = getattr(cl, key)
    return methods

Tim Peters's avatar
Tim Peters committed
146 147 148 149 150 151 152 153
def _split_list(s, predicate):
    """Split sequence s via predicate, and return pair ([true], [false]).

    The return value is a 2-tuple of lists,
        ([x for x in s if predicate(x)],
         [x for x in s if not predicate(x)])
    """

154 155
    yes = []
    no = []
Tim Peters's avatar
Tim Peters committed
156 157 158
    for x in s:
        if predicate(x):
            yes.append(x)
159
        else:
Tim Peters's avatar
Tim Peters committed
160
            no.append(x)
161 162
    return yes, no

163
def visiblename(name, all=None, obj=None):
164 165
    """Decide whether to show documentation on a variable."""
    # Certain special names are redundant.
166
    if name in {'__builtins__', '__doc__', '__file__', '__path__',
Barry Warsaw's avatar
Barry Warsaw committed
167
                     '__module__', '__name__', '__slots__', '__package__',
168
                     '__cached__', '__author__', '__credits__', '__date__',
169
                     '__version__', '__qualname__'}:
170
        return 0
171 172
    # Private names are hidden, but special names are displayed.
    if name.startswith('__') and name.endswith('__'): return 1
173 174 175
    # Namedtuples have public fields and methods with a single leading underscore
    if name.startswith('_') and hasattr(obj, '_fields'):
        return True
176 177 178 179 180
    if all is not None:
        # only document that which the programmer exported in __all__
        return name in all
    else:
        return not name.startswith('_')
181

182 183
def classify_class_attrs(object):
    """Wrap inspect.classify_class_attrs, with fixup for data descriptors."""
184 185
    results = []
    for (name, kind, cls, value) in inspect.classify_class_attrs(object):
186 187
        if inspect.isdatadescriptor(value):
            kind = 'data descriptor'
188 189
        results.append((name, kind, cls, value))
    return results
190

191 192 193 194 195
# ----------------------------------------------------- module manipulation

def ispackage(path):
    """Guess whether a path refers to a package directory."""
    if os.path.isdir(path):
196
        for ext in ('.py', '.pyc', '.pyo'):
197
            if os.path.isfile(os.path.join(path, '__init__' + ext)):
198 199
                return True
    return False
200

201 202
def source_synopsis(file):
    line = file.readline()
203
    while line[:1] == '#' or not line.strip():
204 205
        line = file.readline()
        if not line: break
206
    line = line.strip()
207 208 209 210
    if line[:4] == 'r"""': line = line[1:]
    if line[:3] == '"""':
        line = line[3:]
        if line[-1:] == '\\': line = line[:-1]
211
        while not line.strip():
212 213
            line = file.readline()
            if not line: break
214
        result = line.split('"""')[0].strip()
215 216 217
    else: result = None
    return result

218 219
def synopsis(filename, cache={}):
    """Get the one-line summary out of a module file."""
220
    mtime = os.stat(filename).st_mtime
221 222
    lastupdate, result = cache.get(filename, (None, None))
    if lastupdate is None or lastupdate < mtime:
223
        info = inspect.getmoduleinfo(filename)
224
        try:
225
            file = tokenize.open(filename)
226 227 228
        except IOError:
            # module can't be opened, so skip it
            return None
229 230 231
        if info and 'b' in info[2]: # binary modules have to be imported
            try: module = imp.load_module('__temp__', file, filename, info[1:])
            except: return None
232
            result = (module.__doc__ or '').splitlines()[0]
233 234
            del sys.modules['__temp__']
        else: # text modules can be directly examined
235 236
            result = source_synopsis(file)
            file.close()
237 238 239
        cache[filename] = (mtime, result)
    return result

240 241
class ErrorDuringImport(Exception):
    """Errors that occurred while trying to import something to document it."""
242
    def __init__(self, filename, exc_info):
243
        self.filename = filename
244
        self.exc, self.value, self.tb = exc_info
245 246

    def __str__(self):
247
        exc = self.exc.__name__
248
        return 'problem in %s - %s: %s' % (self.filename, exc, self.value)
249 250 251 252

def importfile(path):
    """Import a Python source file or compiled file given its path."""
    magic = imp.get_magic()
253 254 255 256 257 258 259 260 261 262 263 264
    with open(path, 'rb') as file:
        if file.read(len(magic)) == magic:
            kind = imp.PY_COMPILED
        else:
            kind = imp.PY_SOURCE
        file.seek(0)
        filename = os.path.basename(path)
        name, ext = os.path.splitext(filename)
        try:
            module = imp.load_module(name, file, path, (ext, 'r', kind))
        except:
            raise ErrorDuringImport(path, sys.exc_info())
265 266
    return module

267 268 269 270 271 272 273 274 275
def safeimport(path, forceload=0, cache={}):
    """Import a module; handle errors; return None if the module isn't found.

    If the module *is* found but an exception occurs, it's wrapped in an
    ErrorDuringImport exception and reraised.  Unlike __import__, if a
    package path is specified, the module at the end of the path is returned,
    not the package at the beginning.  If the optional 'forceload' argument
    is 1, we reload the module from disk (unless it's a dynamic extension)."""
    try:
276 277 278 279 280 281
        # If forceload is 1 and the module has been previously loaded from
        # disk, we always have to reload the module.  Checking the file's
        # mtime isn't good enough (e.g. the module could contain a class
        # that inherits from another module that has changed).
        if forceload and path in sys.modules:
            if path not in sys.builtin_module_names:
282 283 284 285 286
                # Remove the module from sys.modules and re-import to try
                # and avoid problems with partially loaded modules.
                # Also remove any submodules because they won't appear
                # in the newly loaded module's namespace if they're already
                # in sys.modules.
287 288 289 290 291
                subs = [m for m in sys.modules if m.startswith(path + '.')]
                for key in [path] + subs:
                    # Prevent garbage collection.
                    cache[key] = sys.modules[key]
                    del sys.modules[key]
292 293 294 295
        module = __import__(path)
    except:
        # Did the error occur before or after the module was found?
        (exc, value, tb) = info = sys.exc_info()
296
        if path in sys.modules:
Fred Drake's avatar
Fred Drake committed
297
            # An error occurred while executing the imported module.
298 299 300 301
            raise ErrorDuringImport(sys.modules[path].__file__, info)
        elif exc is SyntaxError:
            # A SyntaxError occurred before we could execute the module.
            raise ErrorDuringImport(value.filename, info)
302
        elif exc is ImportError and extract_tb(tb)[-1][2]=='safeimport':
Benjamin Peterson's avatar
Benjamin Peterson committed
303 304
            # The import error occurred directly in this function,
            # which means there is no such module in the path.
305 306 307 308
            return None
        else:
            # Some other error occurred during the importing process.
            raise ErrorDuringImport(path, sys.exc_info())
309
    for part in path.split('.')[1:]:
310 311 312
        try: module = getattr(module, part)
        except AttributeError: return None
    return module
313 314 315 316

# ---------------------------------------------------- formatter base class

class Doc:
317 318 319 320 321

    PYTHONDOCS = os.environ.get("PYTHONDOCS",
                                "http://docs.python.org/%d.%d/library"
                                % sys.version_info[:2])

322
    def document(self, object, name=None, *args):
323
        """Generate documentation for an object."""
324
        args = (object, name) + args
325 326 327 328
        # 'try' clause is to attempt to handle the possibility that inspect
        # identifies something in a way that pydoc itself has issues handling;
        # think 'super' and how it is a descriptor (which raises the exception
        # by lacking a __name__ attribute) and an instance.
329 330
        if inspect.isgetsetdescriptor(object): return self.docdata(*args)
        if inspect.ismemberdescriptor(object): return self.docdata(*args)
331 332 333 334 335 336
        try:
            if inspect.ismodule(object): return self.docmodule(*args)
            if inspect.isclass(object): return self.docclass(*args)
            if inspect.isroutine(object): return self.docroutine(*args)
        except AttributeError:
            pass
337
        if isinstance(object, property): return self.docproperty(*args)
338
        return self.docother(*args)
339 340 341 342 343

    def fail(self, object, name=None, *args):
        """Raise an exception for unimplemented types."""
        message = "don't know how to document object%s of type %s" % (
            name and ' ' + repr(name), type(object).__name__)
344
        raise TypeError(message)
345

346
    docmodule = docclass = docroutine = docother = docproperty = docdata = fail
347

348 349 350 351 352 353 354 355
    def getdocloc(self, object):
        """Return the location of module docs or None"""

        try:
            file = inspect.getabsfile(object)
        except TypeError:
            file = '(built-in)'

356 357
        docloc = os.environ.get("PYTHONDOCS", self.PYTHONDOCS)

358
        basedir = os.path.join(sys.exec_prefix, "lib",
359
                               "python%d.%d" %  sys.version_info[:2])
360 361 362
        if (isinstance(object, type(os)) and
            (object.__name__ in ('errno', 'exceptions', 'gc', 'imp',
                                 'marshal', 'posix', 'signal', 'sys',
363
                                 '_thread', 'zipimport') or
364
             (file.startswith(basedir) and
365
              not file.startswith(os.path.join(basedir, 'site-packages')))) and
366
            object.__name__ not in ('xml.etree', 'test.pydoc_mod')):
367
            if docloc.startswith("http://"):
368
                docloc = "%s/%s" % (docloc.rstrip("/"), object.__name__)
369
            else:
370
                docloc = os.path.join(docloc, object.__name__ + ".html")
371 372 373 374
        else:
            docloc = None
        return docloc

375 376 377 378 379 380
# -------------------------------------------- HTML documentation generator

class HTMLRepr(Repr):
    """Class for safely making an HTML representation of a Python object."""
    def __init__(self):
        Repr.__init__(self)
381 382
        self.maxlist = self.maxtuple = 20
        self.maxdict = 10
383
        self.maxstring = self.maxother = 100
384 385

    def escape(self, text):
386
        return replace(text, '&', '&amp;', '<', '&lt;', '>', '&gt;')
387 388

    def repr(self, object):
389
        return Repr.repr(self, object)
390 391

    def repr1(self, x, level):
392
        if hasattr(type(x), '__name__'):
393
            methodname = 'repr_' + '_'.join(type(x).__name__.split())
394 395 396
            if hasattr(self, methodname):
                return getattr(self, methodname)(x, level)
        return self.escape(cram(stripid(repr(x)), self.maxother))
397 398

    def repr_string(self, x, level):
399 400
        test = cram(x, self.maxstring)
        testrepr = repr(test)
401
        if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
402 403 404
            # Backslashes are only literal in the string and are never
            # needed to make any special characters, so show a raw string.
            return 'r' + testrepr[0] + self.escape(test) + testrepr[0]
405
        return re.sub(r'((\\[\\abfnrtv\'"]|\\[0-9]..|\\x..|\\u....)+)',
406
                      r'<font color="#c040c0">\1</font>',
407
                      self.escape(testrepr))
408

409 410
    repr_str = repr_string

411 412
    def repr_instance(self, x, level):
        try:
413
            return self.escape(cram(stripid(repr(x)), self.maxstring))
414 415 416 417 418 419 420 421 422 423 424 425 426 427
        except:
            return self.escape('<%s instance>' % x.__class__.__name__)

    repr_unicode = repr_string

class HTMLDoc(Doc):
    """Formatter class for HTML documentation."""

    # ------------------------------------------- HTML formatting utilities

    _repr_instance = HTMLRepr()
    repr = _repr_instance.repr
    escape = _repr_instance.escape

428 429
    def page(self, title, contents):
        """Format an HTML page."""
430
        return '''\
431
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
432
<html><head><title>Python: %s</title>
433
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
434
</head><body bgcolor="#f0f0f8">
435 436 437 438 439 440
%s
</body></html>''' % (title, contents)

    def heading(self, title, fgcol, bgcol, extras=''):
        """Format a page heading."""
        return '''
441
<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="heading">
442
<tr bgcolor="%s">
443 444
<td valign=bottom>&nbsp;<br>
<font color="%s" face="helvetica, arial">&nbsp;<br>%s</font></td
445
><td align=right valign=bottom
Ka-Ping Yee's avatar
Ka-Ping Yee committed
446
><font color="%s" face="helvetica, arial">%s</font></td></tr></table>
447 448
    ''' % (bgcol, fgcol, title, fgcol, extras or '&nbsp;')

449 450
    def section(self, title, fgcol, bgcol, contents, width=6,
                prelude='', marginalia=None, gap='&nbsp;'):
451 452
        """Format a section with a heading."""
        if marginalia is None:
453
            marginalia = '<tt>' + '&nbsp;' * width + '</tt>'
454
        result = '''<p>
455
<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">
456
<tr bgcolor="%s">
457 458
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="%s" face="helvetica, arial">%s</font></td></tr>
459 460 461
    ''' % (bgcol, fgcol, title)
        if prelude:
            result = result + '''
Ka-Ping Yee's avatar
Ka-Ping Yee committed
462 463 464 465 466
<tr bgcolor="%s"><td rowspan=2>%s</td>
<td colspan=2>%s</td></tr>
<tr><td>%s</td>''' % (bgcol, marginalia, prelude, gap)
        else:
            result = result + '''
467
<tr><td bgcolor="%s">%s</td><td>%s</td>''' % (bgcol, marginalia, gap)
468

469
        return result + '\n<td width="100%%">%s</td></tr></table>' % contents
470 471 472

    def bigsection(self, title, *args):
        """Format a section with a big heading."""
473
        title = '<big><strong>%s</strong></big>' % title
474
        return self.section(title, *args)
475

476 477
    def preformat(self, text):
        """Format literal preformatted text."""
478
        text = self.escape(text.expandtabs())
479 480
        return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',
                             ' ', '&nbsp;', '\n', '<br>\n')
481 482 483 484

    def multicolumn(self, list, format, cols=4):
        """Format a list of items into a multi-column list."""
        result = ''
485
        rows = (len(list)+cols-1)//cols
486
        for col in range(cols):
487
            result = result + '<td width="%d%%" valign=top>' % (100//cols)
488 489
            for i in range(rows*col, rows*col+rows):
                if i < len(list):
490
                    result = result + format(list[i]) + '<br>\n'
491
            result = result + '</td>'
492
        return '<table width="100%%" summary="list"><tr>%s</tr></table>' % result
493

494
    def grey(self, text): return '<font color="#909090">%s</font>' % text
495 496 497 498

    def namelink(self, name, *dicts):
        """Make a link for an identifier, given name-to-URL mappings."""
        for dict in dicts:
499
            if name in dict:
500 501 502
                return '<a href="%s">%s</a>' % (dict[name], name)
        return name

503
    def classlink(self, object, modname):
504
        """Make a link for a class."""
505 506
        name, module = object.__name__, sys.modules.get(object.__module__)
        if hasattr(module, name) and getattr(module, name) is object:
507
            return '<a href="%s.html#%s">%s</a>' % (
508 509
                module.__name__, name, classname(object, modname))
        return classname(object, modname)
510 511 512 513 514

    def modulelink(self, object):
        """Make a link for a module."""
        return '<a href="%s.html">%s</a>' % (object.__name__, object.__name__)

515
    def modpkglink(self, modpkginfo):
516
        """Make a link for a module or package to display in an index."""
517
        name, path, ispackage, shadowed = modpkginfo
518
        if shadowed:
519
            return self.grey(name)
520 521 522 523 524
        if path:
            url = '%s.%s.html' % (path, name)
        else:
            url = '%s.html' % name
        if ispackage:
525
            text = '<strong>%s</strong>&nbsp;(package)' % name
526 527 528 529
        else:
            text = name
        return '<a href="%s">%s</a>' % (url, text)

530 531 532 533
    def filelink(self, url, path):
        """Make a link to source file."""
        return '<a href="file:%s">%s</a>' % (url, path)

534 535 536 537 538 539
    def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
        """Mark up some plain text, given a context of symbols to look for.
        Each context dictionary maps object names to anchor names."""
        escape = escape or self.escape
        results = []
        here = 0
540 541
        pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
                                r'RFC[- ]?(\d+)|'
542
                                r'PEP[- ]?(\d+)|'
543
                                r'(self\.)?(\w+))')
544
        while True:
545 546 547 548 549
            match = pattern.search(text, here)
            if not match: break
            start, end = match.span()
            results.append(escape(text[here:start]))

550
            all, scheme, rfc, pep, selfdot, name = match.groups()
551
            if scheme:
Neil Schemenauer's avatar
Neil Schemenauer committed
552 553
                url = escape(all).replace('"', '&quot;')
                results.append('<a href="%s">%s</a>' % (url, url))
554
            elif rfc:
555 556 557
                url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
                results.append('<a href="%s">%s</a>' % (url, escape(all)))
            elif pep:
Christian Heimes's avatar
Christian Heimes committed
558
                url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
559 560 561 562
                results.append('<a href="%s">%s</a>' % (url, escape(all)))
            elif text[end:end+1] == '(':
                results.append(self.namelink(name, methods, funcs, classes))
            elif selfdot:
563
                results.append('self.<strong>%s</strong>' % name)
564
            else:
565
                results.append(self.namelink(name, classes))
566 567
            here = end
        results.append(escape(text[here:]))
568
        return ''.join(results)
569 570 571

    # ---------------------------------------------- type-specific routines

572
    def formattree(self, tree, modname, parent=None):
573 574 575 576 577
        """Produce HTML for a class tree as given by inspect.getclasstree()."""
        result = ''
        for entry in tree:
            if type(entry) is type(()):
                c, bases = entry
578
                result = result + '<dt><font face="helvetica, arial">'
579
                result = result + self.classlink(c, modname)
580 581 582
                if bases and bases != (parent,):
                    parents = []
                    for base in bases:
583
                        parents.append(self.classlink(base, modname))
584
                    result = result + '(' + ', '.join(parents) + ')'
585
                result = result + '\n</font></dt>'
586
            elif type(entry) is type([]):
587
                result = result + '<dd>\n%s</dd>\n' % self.formattree(
588
                    entry, modname, c)
589 590
        return '<dl>\n%s</dl>\n' % result

591
    def docmodule(self, object, name=None, mod=None, *ignored):
592
        """Produce HTML documentation for a module object."""
593
        name = object.__name__ # ignore the passed-in name
594 595 596 597
        try:
            all = object.__all__
        except AttributeError:
            all = None
598
        parts = name.split('.')
Ka-Ping Yee's avatar
Ka-Ping Yee committed
599 600 601
        links = []
        for i in range(len(parts)-1):
            links.append(
602
                '<a href="%s.html"><font color="#ffffff">%s</font></a>' %
603 604
                ('.'.join(parts[:i+1]), parts[i]))
        linkedname = '.'.join(links + parts[-1:])
605
        head = '<big><big><strong>%s</strong></big></big>' % linkedname
606
        try:
607
            path = inspect.getabsfile(object)
608 609 610 611
            url = path
            if sys.platform == 'win32':
                import nturl2path
                url = nturl2path.pathname2url(path)
612
            filelink = self.filelink(url, path)
613 614
        except TypeError:
            filelink = '(built-in)'
615
        info = []
616
        if hasattr(object, '__version__'):
617
            version = str(object.__version__)
618
            if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
619
                version = version[11:-1].strip()
620
            info.append('version %s' % self.escape(version))
621 622 623
        if hasattr(object, '__date__'):
            info.append(self.escape(str(object.__date__)))
        if info:
624
            head = head + ' (%s)' % ', '.join(info)
625 626
        docloc = self.getdocloc(object)
        if docloc is not None:
627
            docloc = '<br><a href="%(docloc)s">Module Reference</a>' % locals()
628 629
        else:
            docloc = ''
Ka-Ping Yee's avatar
Ka-Ping Yee committed
630
        result = self.heading(
631 632
            head, '#ffffff', '#7799ee',
            '<a href=".">index</a><br>' + filelink + docloc)
633

634 635
        modules = inspect.getmembers(object, inspect.ismodule)

636 637
        classes, cdict = [], {}
        for key, value in inspect.getmembers(object, inspect.isclass):
638 639 640
            # if __all__ exists, believe it.  Otherwise use old heuristic.
            if (all is not None or
                (inspect.getmodule(value) or object) is object):
641
                if visiblename(key, all, object):
642 643
                    classes.append((key, value))
                    cdict[key] = cdict[value] = '#' + key
644 645 646 647 648 649
        for key, value in classes:
            for base in value.__bases__:
                key, modname = base.__name__, base.__module__
                module = sys.modules.get(modname)
                if modname != name and module and hasattr(module, key):
                    if getattr(module, key) is base:
650
                        if not key in cdict:
651
                            cdict[key] = cdict[base] = modname + '.html#' + key
652 653
        funcs, fdict = [], {}
        for key, value in inspect.getmembers(object, inspect.isroutine):
654 655 656
            # if __all__ exists, believe it.  Otherwise use old heuristic.
            if (all is not None or
                inspect.isbuiltin(value) or inspect.getmodule(value) is object):
657
                if visiblename(key, all, object):
658 659 660
                    funcs.append((key, value))
                    fdict[key] = '#-' + key
                    if inspect.isfunction(value): fdict[value] = fdict[key]
661 662
        data = []
        for key, value in inspect.getmembers(object, isdata):
663
            if visiblename(key, all, object):
664
                data.append((key, value))
665 666 667

        doc = self.markup(getdoc(object), self.preformat, fdict, cdict)
        doc = doc and '<tt>%s</tt>' % doc
668
        result = result + '<p>%s</p>\n' % doc
669 670 671

        if hasattr(object, '__path__'):
            modpkgs = []
672 673
            for importer, modname, ispkg in pkgutil.iter_modules(object.__path__):
                modpkgs.append((modname, name, ispkg, 0))
674 675 676 677 678
            modpkgs.sort()
            contents = self.multicolumn(modpkgs, self.modpkglink)
            result = result + self.bigsection(
                'Package Contents', '#ffffff', '#aa55cc', contents)
        elif modules:
679
            contents = self.multicolumn(
680
                modules, lambda t: self.modulelink(t[1]))
681
            result = result + self.bigsection(
Christian Heimes's avatar
Christian Heimes committed
682
                'Modules', '#ffffff', '#aa55cc', contents)
683 684

        if classes:
685
            classlist = [value for (key, value) in classes]
686 687
            contents = [
                self.formattree(inspect.getclasstree(classlist, 1), name)]
688
            for key, value in classes:
689
                contents.append(self.document(value, key, name, fdict, cdict))
690
            result = result + self.bigsection(
691
                'Classes', '#ffffff', '#ee77aa', ' '.join(contents))
692
        if funcs:
693 694
            contents = []
            for key, value in funcs:
695
                contents.append(self.document(value, key, name, fdict, cdict))
696
            result = result + self.bigsection(
697
                'Functions', '#ffffff', '#eeaa77', ' '.join(contents))
698
        if data:
699
            contents = []
700
            for key, value in data:
701
                contents.append(self.document(value, key))
702
            result = result + self.bigsection(
703
                'Data', '#ffffff', '#55aa55', '<br>\n'.join(contents))
704 705 706 707 708 709 710 711 712
        if hasattr(object, '__author__'):
            contents = self.markup(str(object.__author__), self.preformat)
            result = result + self.bigsection(
                'Author', '#ffffff', '#7799ee', contents)
        if hasattr(object, '__credits__'):
            contents = self.markup(str(object.__credits__), self.preformat)
            result = result + self.bigsection(
                'Credits', '#ffffff', '#7799ee', contents)

713 714
        return result

715 716
    def docclass(self, object, name=None, mod=None, funcs={}, classes={},
                 *ignored):
717
        """Produce HTML documentation for a class object."""
718 719
        realname = object.__name__
        name = name or realname
720 721
        bases = object.__bases__

722 723 724
        contents = []
        push = contents.append

Tim Peters's avatar
Tim Peters committed
725 726 727 728 729 730 731 732 733 734
        # Cute little class to pump out a horizontal rule between sections.
        class HorizontalRule:
            def __init__(self):
                self.needone = 0
            def maybe(self):
                if self.needone:
                    push('<hr>\n')
                self.needone = 1
        hr = HorizontalRule()

735
        # List the mro, if non-trivial.
736
        mro = deque(inspect.getmro(object))
737 738 739 740 741 742 743 744
        if len(mro) > 2:
            hr.maybe()
            push('<dl><dt>Method resolution order:</dt>\n')
            for base in mro:
                push('<dd>%s</dd>\n' % self.classlink(base,
                                                      object.__module__))
            push('</dl>\n')

745
        def spill(msg, attrs, predicate):
Tim Peters's avatar
Tim Peters committed
746
            ok, attrs = _split_list(attrs, predicate)
747
            if ok:
Tim Peters's avatar
Tim Peters committed
748
                hr.maybe()
749 750 751 752 753 754 755
                push(msg)
                for name, kind, homecls, value in ok:
                    push(self.document(getattr(object, name), name, mod,
                                       funcs, classes, mdict, object))
                    push('\n')
            return attrs

756
        def spilldescriptors(msg, attrs, predicate):
Tim Peters's avatar
Tim Peters committed
757
            ok, attrs = _split_list(attrs, predicate)
758
            if ok:
Tim Peters's avatar
Tim Peters committed
759
                hr.maybe()
760 761
                push(msg)
                for name, kind, homecls, value in ok:
762
                    push(self._docdescriptor(name, value, mod))
763 764
            return attrs

Tim Peters's avatar
Tim Peters committed
765 766
        def spilldata(msg, attrs, predicate):
            ok, attrs = _split_list(attrs, predicate)
767
            if ok:
Tim Peters's avatar
Tim Peters committed
768
                hr.maybe()
769 770 771
                push(msg)
                for name, kind, homecls, value in ok:
                    base = self.docother(getattr(object, name), name, mod)
772
                    if callable(value) or inspect.isdatadescriptor(value):
773 774 775
                        doc = getattr(value, "__doc__", None)
                    else:
                        doc = None
776 777 778 779 780
                    if doc is None:
                        push('<dl><dt>%s</dl>\n' % base)
                    else:
                        doc = self.markup(getdoc(value), self.preformat,
                                          funcs, classes, mdict)
781
                        doc = '<dd><tt>%s</tt>' % doc
782 783 784 785
                        push('<dl><dt>%s%s</dl>\n' % (base, doc))
                    push('\n')
            return attrs

786 787
        attrs = [(name, kind, cls, value)
                 for name, kind, cls, value in classify_class_attrs(object)
788
                 if visiblename(name, obj=object)]
789

790 791 792 793 794 795 796 797 798 799 800
        mdict = {}
        for key, kind, homecls, value in attrs:
            mdict[key] = anchor = '#' + name + '-' + key
            value = getattr(object, key)
            try:
                # The value may not be hashable (e.g., a data attr with
                # a dict or list value).
                mdict[value] = anchor
            except TypeError:
                pass

Tim Peters's avatar
Tim Peters committed
801
        while attrs:
802
            if mro:
803
                thisclass = mro.popleft()
804 805
            else:
                thisclass = attrs[0][2]
Tim Peters's avatar
Tim Peters committed
806
            attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
807

808
            if thisclass is builtins.object:
809 810 811 812
                attrs = inherited
                continue
            elif thisclass is object:
                tag = 'defined here'
813
            else:
814 815
                tag = 'inherited from %s' % self.classlink(thisclass,
                                                           object.__module__)
816 817 818
            tag += ':<br>\n'

            # Sort attrs by name.
819
            attrs.sort(key=lambda t: t[0])
820 821

            # Pump out the attrs, segregated by kind.
822
            attrs = spill('Methods %s' % tag, attrs,
823
                          lambda t: t[1] == 'method')
824
            attrs = spill('Class methods %s' % tag, attrs,
825
                          lambda t: t[1] == 'class method')
826
            attrs = spill('Static methods %s' % tag, attrs,
827
                          lambda t: t[1] == 'static method')
828 829
            attrs = spilldescriptors('Data descriptors %s' % tag, attrs,
                                     lambda t: t[1] == 'data descriptor')
830
            attrs = spilldata('Data and other attributes %s' % tag, attrs,
Tim Peters's avatar
Tim Peters committed
831
                              lambda t: t[1] == 'data')
832
            assert attrs == []
833
            attrs = inherited
834 835

        contents = ''.join(contents)
836 837 838 839 840 841 842

        if name == realname:
            title = '<a name="%s">class <strong>%s</strong></a>' % (
                name, realname)
        else:
            title = '<strong>%s</strong> = <a name="%s">class %s</a>' % (
                name, name, realname)
843 844 845
        if bases:
            parents = []
            for base in bases:
846
                parents.append(self.classlink(base, object.__module__))
847
            title = title + '(%s)' % ', '.join(parents)
848
        doc = self.markup(getdoc(object), self.preformat, funcs, classes, mdict)
849
        doc = doc and '<tt>%s<br>&nbsp;</tt>' % doc
850

851
        return self.section(title, '#000000', '#ffc8d8', contents, 3, doc)
852 853 854

    def formatvalue(self, object):
        """Format an argument default value as text."""
855
        return self.grey('=' + self.repr(object))
856

857
    def docroutine(self, object, name=None, mod=None,
858
                   funcs={}, classes={}, methods={}, cl=None):
Ka-Ping Yee's avatar
Ka-Ping Yee committed
859
        """Produce HTML documentation for a function or method object."""
860 861
        realname = object.__name__
        name = name or realname
862
        anchor = (cl and cl.__name__ or '') + '-' + name
863
        note = ''
864
        skipdocs = 0
865
        if inspect.ismethod(object):
866
            imclass = object.__self__.__class__
867
            if cl:
868
                if imclass is not cl:
869
                    note = ' from ' + self.classlink(imclass, mod)
870
            else:
871
                if object.__self__ is not None:
872
                    note = ' method of %s instance' % self.classlink(
873
                        object.__self__.__class__, mod)
874 875
                else:
                    note = ' unbound %s method' % self.classlink(imclass,mod)
876
            object = object.__func__
877 878 879 880

        if name == realname:
            title = '<a name="%s"><strong>%s</strong></a>' % (anchor, realname)
        else:
881
            if (cl and realname in cl.__dict__ and
882
                cl.__dict__[realname] is object):
Ka-Ping Yee's avatar
Ka-Ping Yee committed
883
                reallink = '<a href="#%s">%s</a>' % (
884 885 886 887 888 889
                    cl.__name__ + '-' + realname, realname)
                skipdocs = 1
            else:
                reallink = realname
            title = '<a name="%s"><strong>%s</strong></a> = %s' % (
                anchor, name, reallink)
890
        if inspect.isfunction(object):
891 892
            args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann = \
                inspect.getfullargspec(object)
Ka-Ping Yee's avatar
Ka-Ping Yee committed
893
            argspec = inspect.formatargspec(
894 895 896
                args, varargs, kwonlyargs, kwdefaults, varkw, defaults, ann,
                formatvalue=self.formatvalue,
                formatannotation=inspect.formatannotationrelativeto(object))
897
            if realname == '<lambda>':
898
                title = '<strong>%s</strong> <em>lambda</em> ' % name
899 900 901
                # XXX lambda's won't usually have func_annotations['return']
                # since the syntax doesn't support but it is possible.
                # So removing parentheses isn't truly safe.
902
                argspec = argspec[1:-1] # remove parentheses
903 904
        else:
            argspec = '(...)'
Ka-Ping Yee's avatar
Ka-Ping Yee committed
905

906 907
        decl = title + argspec + (note and self.grey(
               '<font face="helvetica, arial">%s</font>' % note))
908

909
        if skipdocs:
910
            return '<dl><dt>%s</dt></dl>\n' % decl
911 912 913
        else:
            doc = self.markup(
                getdoc(object), self.preformat, funcs, classes, methods)
914 915
            doc = doc and '<dd><tt>%s</tt></dd>' % doc
            return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
916

917
    def _docdescriptor(self, name, value, mod):
918 919 920 921 922 923
        results = []
        push = results.append

        if name:
            push('<dl><dt><strong>%s</strong></dt>\n' % name)
        if value.__doc__ is not None:
924
            doc = self.markup(getdoc(value), self.preformat)
925 926 927 928 929 930 931
            push('<dd><tt>%s</tt></dd>\n' % doc)
        push('</dl>\n')

        return ''.join(results)

    def docproperty(self, object, name=None, mod=None, cl=None):
        """Produce html documentation for a property."""
932
        return self._docdescriptor(name, object, mod)
933

934
    def docother(self, object, name=None, mod=None, *ignored):
935
        """Produce HTML documentation for a data object."""
936 937
        lhs = name and '<strong>%s</strong> = ' % name or ''
        return lhs + self.repr(object)
938

939 940 941 942
    def docdata(self, object, name=None, mod=None, cl=None):
        """Produce html documentation for a data descriptor."""
        return self._docdescriptor(name, object, mod)

943 944 945 946
    def index(self, dir, shadowed=None):
        """Generate an HTML index for a directory of modules."""
        modpkgs = []
        if shadowed is None: shadowed = {}
947
        for importer, name, ispkg in pkgutil.iter_modules([dir]):
948 949 950
            if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name):
                # ignore a module if its name contains a surrogate character
                continue
951 952
            modpkgs.append((name, '', ispkg, name in shadowed))
            shadowed[name] = 1
953 954 955 956 957 958 959 960 961 962 963

        modpkgs.sort()
        contents = self.multicolumn(modpkgs, self.modpkglink)
        return self.bigsection(dir, '#ffffff', '#ee77aa', contents)

# -------------------------------------------- text documentation generator

class TextRepr(Repr):
    """Class for safely making a text representation of a Python object."""
    def __init__(self):
        Repr.__init__(self)
964 965
        self.maxlist = self.maxtuple = 20
        self.maxdict = 10
966
        self.maxstring = self.maxother = 100
967 968

    def repr1(self, x, level):
969
        if hasattr(type(x), '__name__'):
970
            methodname = 'repr_' + '_'.join(type(x).__name__.split())
971 972 973
            if hasattr(self, methodname):
                return getattr(self, methodname)(x, level)
        return cram(stripid(repr(x)), self.maxother)
974

975 976 977
    def repr_string(self, x, level):
        test = cram(x, self.maxstring)
        testrepr = repr(test)
978
        if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
979 980 981 982 983
            # Backslashes are only literal in the string and are never
            # needed to make any special characters, so show a raw string.
            return 'r' + testrepr[0] + test + testrepr[0]
        return testrepr

984 985
    repr_str = repr_string

986 987
    def repr_instance(self, x, level):
        try:
988
            return cram(stripid(repr(x)), self.maxstring)
989 990 991 992 993 994 995 996 997 998 999 1000 1001
        except:
            return '<%s instance>' % x.__class__.__name__

class TextDoc(Doc):
    """Formatter class for text documentation."""

    # ------------------------------------------- text formatting utilities

    _repr_instance = TextRepr()
    repr = _repr_instance.repr

    def bold(self, text):
        """Format a string in bold by overstriking."""
1002
        return ''.join(ch + '\b' + ch for ch in text)
1003 1004 1005 1006

    def indent(self, text, prefix='    '):
        """Indent text by prepending a given prefix to each line."""
        if not text: return ''
1007
        lines = [prefix + line for line in text.split('\n')]
1008 1009
        if lines: lines[-1] = lines[-1].rstrip()
        return '\n'.join(lines)
1010 1011 1012

    def section(self, title, contents):
        """Format a section with a given heading."""
1013 1014
        clean_contents = self.indent(contents).rstrip()
        return self.bold(title) + '\n' + clean_contents + '\n\n'
1015 1016 1017

    # ---------------------------------------------- type-specific routines

1018
    def formattree(self, tree, modname, parent=None, prefix=''):
1019 1020 1021 1022
        """Render in text a class tree as returned by inspect.getclasstree()."""
        result = ''
        for entry in tree:
            if type(entry) is type(()):
1023 1024
                c, bases = entry
                result = result + prefix + classname(c, modname)
1025
                if bases and bases != (parent,):
1026
                    parents = (classname(c, modname) for c in bases)
1027
                    result = result + '(%s)' % ', '.join(parents)
1028 1029
                result = result + '\n'
            elif type(entry) is type([]):
1030 1031
                result = result + self.formattree(
                    entry, modname, c, prefix + '    ')
1032 1033
        return result

1034
    def docmodule(self, object, name=None, mod=None):
1035
        """Produce text documentation for a given module object."""
1036
        name = object.__name__ # ignore the passed-in name
1037 1038
        synop, desc = splitdoc(getdoc(object))
        result = self.section('NAME', name + (synop and ' - ' + synop))
1039
        all = getattr(object, '__all__', None)
1040 1041
        docloc = self.getdocloc(object)
        if docloc is not None:
1042 1043
            result = result + self.section('MODULE REFERENCE', docloc + """

1044 1045 1046 1047 1048
The following documentation is automatically generated from the Python
source files.  It may be incomplete, incorrect or include features that
are considered implementation detail and may vary between Python
implementations.  When in doubt, consult the module reference at the
location listed above.
1049
""")
1050

1051 1052
        if desc:
            result = result + self.section('DESCRIPTION', desc)
1053 1054 1055

        classes = []
        for key, value in inspect.getmembers(object, inspect.isclass):
1056 1057 1058
            # if __all__ exists, believe it.  Otherwise use old heuristic.
            if (all is not None
                or (inspect.getmodule(value) or object) is object):
1059
                if visiblename(key, all, object):
1060
                    classes.append((key, value))
1061 1062
        funcs = []
        for key, value in inspect.getmembers(object, inspect.isroutine):
1063 1064 1065
            # if __all__ exists, believe it.  Otherwise use old heuristic.
            if (all is not None or
                inspect.isbuiltin(value) or inspect.getmodule(value) is object):
1066
                if visiblename(key, all, object):
1067
                    funcs.append((key, value))
1068 1069
        data = []
        for key, value in inspect.getmembers(object, isdata):
1070
            if visiblename(key, all, object):
1071
                data.append((key, value))
1072

1073 1074
        modpkgs = []
        modpkgs_names = set()
1075
        if hasattr(object, '__path__'):
1076
            for importer, modname, ispkg in pkgutil.iter_modules(object.__path__):
1077
                modpkgs_names.add(modname)
1078 1079 1080 1081 1082
                if ispkg:
                    modpkgs.append(modname + ' (package)')
                else:
                    modpkgs.append(modname)

1083 1084
            modpkgs.sort()
            result = result + self.section(
1085
                'PACKAGE CONTENTS', '\n'.join(modpkgs))
1086

1087 1088 1089 1090 1091 1092 1093 1094
        # Detect submodules as sometimes created by C extensions
        submodules = []
        for key, value in inspect.getmembers(object, inspect.ismodule):
            if value.__name__.startswith(name + '.') and key not in modpkgs_names:
                submodules.append(key)
        if submodules:
            submodules.sort()
            result = result + self.section(
1095
                'SUBMODULES', '\n'.join(submodules))
1096

1097
        if classes:
1098
            classlist = [value for key, value in classes]
1099 1100 1101
            contents = [self.formattree(
                inspect.getclasstree(classlist, 1), name)]
            for key, value in classes:
1102
                contents.append(self.document(value, key, name))
1103
            result = result + self.section('CLASSES', '\n'.join(contents))
1104 1105

        if funcs:
1106 1107
            contents = []
            for key, value in funcs:
1108
                contents.append(self.document(value, key, name))
1109
            result = result + self.section('FUNCTIONS', '\n'.join(contents))
1110

1111
        if data:
1112
            contents = []
1113
            for key, value in data:
1114
                contents.append(self.docother(value, key, name, maxlen=70))
1115
            result = result + self.section('DATA', '\n'.join(contents))
1116 1117 1118

        if hasattr(object, '__version__'):
            version = str(object.__version__)
1119
            if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
1120
                version = version[11:-1].strip()
1121
            result = result + self.section('VERSION', version)
1122 1123
        if hasattr(object, '__date__'):
            result = result + self.section('DATE', str(object.__date__))
1124
        if hasattr(object, '__author__'):
1125 1126 1127
            result = result + self.section('AUTHOR', str(object.__author__))
        if hasattr(object, '__credits__'):
            result = result + self.section('CREDITS', str(object.__credits__))
1128 1129 1130 1131 1132
        try:
            file = inspect.getabsfile(object)
        except TypeError:
            file = '(built-in)'
        result = result + self.section('FILE', file)
1133 1134
        return result

1135
    def docclass(self, object, name=None, mod=None, *ignored):
1136
        """Produce text documentation for a given class object."""
1137 1138
        realname = object.__name__
        name = name or realname
1139 1140
        bases = object.__bases__

1141 1142 1143
        def makename(c, m=object.__module__):
            return classname(c, m)

1144 1145 1146 1147
        if name == realname:
            title = 'class ' + self.bold(realname)
        else:
            title = self.bold(name) + ' = class ' + realname
1148
        if bases:
1149
            parents = map(makename, bases)
1150
            title = title + '(%s)' % ', '.join(parents)
1151 1152

        doc = getdoc(object)
1153 1154 1155
        contents = doc and [doc + '\n'] or []
        push = contents.append

1156
        # List the mro, if non-trivial.
1157
        mro = deque(inspect.getmro(object))
1158 1159 1160 1161 1162 1163
        if len(mro) > 2:
            push("Method resolution order:")
            for base in mro:
                push('    ' + makename(base))
            push('')

1164 1165 1166 1167 1168 1169 1170 1171 1172 1173
        # Cute little class to pump out a horizontal rule between sections.
        class HorizontalRule:
            def __init__(self):
                self.needone = 0
            def maybe(self):
                if self.needone:
                    push('-' * 70)
                self.needone = 1
        hr = HorizontalRule()

1174
        def spill(msg, attrs, predicate):
Tim Peters's avatar
Tim Peters committed
1175
            ok, attrs = _split_list(attrs, predicate)
1176
            if ok:
1177
                hr.maybe()
1178 1179 1180 1181 1182 1183
                push(msg)
                for name, kind, homecls, value in ok:
                    push(self.document(getattr(object, name),
                                       name, mod, object))
            return attrs

1184
        def spilldescriptors(msg, attrs, predicate):
Tim Peters's avatar
Tim Peters committed
1185
            ok, attrs = _split_list(attrs, predicate)
1186
            if ok:
1187
                hr.maybe()
1188 1189
                push(msg)
                for name, kind, homecls, value in ok:
1190
                    push(self._docdescriptor(name, value, mod))
1191
            return attrs
1192

Tim Peters's avatar
Tim Peters committed
1193 1194
        def spilldata(msg, attrs, predicate):
            ok, attrs = _split_list(attrs, predicate)
1195
            if ok:
1196
                hr.maybe()
1197 1198
                push(msg)
                for name, kind, homecls, value in ok:
1199
                    if callable(value) or inspect.isdatadescriptor(value):
1200
                        doc = getdoc(value)
1201 1202
                    else:
                        doc = None
1203
                    push(self.docother(getattr(object, name),
1204
                                       name, mod, maxlen=70, doc=doc) + '\n')
1205 1206
            return attrs

1207 1208
        attrs = [(name, kind, cls, value)
                 for name, kind, cls, value in classify_class_attrs(object)
1209
                 if visiblename(name, obj=object)]
1210

Tim Peters's avatar
Tim Peters committed
1211
        while attrs:
1212
            if mro:
1213
                thisclass = mro.popleft()
1214 1215
            else:
                thisclass = attrs[0][2]
Tim Peters's avatar
Tim Peters committed
1216
            attrs, inherited = _split_list(attrs, lambda t: t[2] is thisclass)
1217

1218
            if thisclass is builtins.object:
1219 1220 1221
                attrs = inherited
                continue
            elif thisclass is object:
1222 1223
                tag = "defined here"
            else:
Tim Peters's avatar
Tim Peters committed
1224 1225
                tag = "inherited from %s" % classname(thisclass,
                                                      object.__module__)
1226 1227

            # Sort attrs by name.
1228
            attrs.sort()
1229 1230

            # Pump out the attrs, segregated by kind.
1231
            attrs = spill("Methods %s:\n" % tag, attrs,
1232
                          lambda t: t[1] == 'method')
1233
            attrs = spill("Class methods %s:\n" % tag, attrs,
1234
                          lambda t: t[1] == 'class method')
1235
            attrs = spill("Static methods %s:\n" % tag, attrs,
1236
                          lambda t: t[1] == 'static method')
1237 1238
            attrs = spilldescriptors("Data descriptors %s:\n" % tag, attrs,
                                     lambda t: t[1] == 'data descriptor')
1239 1240
            attrs = spilldata("Data and other attributes %s:\n" % tag, attrs,
                              lambda t: t[1] == 'data')
1241
            assert attrs == []
1242
            attrs = inherited
1243 1244 1245 1246

        contents = '\n'.join(contents)
        if not contents:
            return title + '\n'
1247
        return title + '\n' + self.indent(contents.rstrip(), ' |  ') + '\n'
1248 1249 1250 1251 1252

    def formatvalue(self, object):
        """Format an argument default value as text."""
        return '=' + self.repr(object)

1253
    def docroutine(self, object, name=None, mod=None, cl=None):
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1254
        """Produce text documentation for a function or method object."""
1255 1256 1257
        realname = object.__name__
        name = name or realname
        note = ''
1258
        skipdocs = 0
1259
        if inspect.ismethod(object):
1260
            imclass = object.__self__.__class__
1261
            if cl:
1262 1263
                if imclass is not cl:
                    note = ' from ' + classname(imclass, mod)
1264
            else:
1265
                if object.__self__ is not None:
1266
                    note = ' method of %s instance' % classname(
1267
                        object.__self__.__class__, mod)
1268 1269
                else:
                    note = ' unbound %s method' % classname(imclass,mod)
1270
            object = object.__func__
1271 1272 1273 1274

        if name == realname:
            title = self.bold(realname)
        else:
1275
            if (cl and realname in cl.__dict__ and
1276 1277
                cl.__dict__[realname] is object):
                skipdocs = 1
1278
            title = self.bold(name) + ' = ' + realname
1279
        if inspect.isfunction(object):
1280 1281
            args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann = \
              inspect.getfullargspec(object)
1282
            argspec = inspect.formatargspec(
1283 1284 1285
                args, varargs, varkw, defaults, kwonlyargs, kwdefaults, ann,
                formatvalue=self.formatvalue,
                formatannotation=inspect.formatannotationrelativeto(object))
1286
            if realname == '<lambda>':
1287
                title = self.bold(name) + ' lambda '
1288 1289 1290
                # XXX lambda's won't usually have func_annotations['return']
                # since the syntax doesn't support but it is possible.
                # So removing parentheses isn't truly safe.
1291
                argspec = argspec[1:-1] # remove parentheses
1292 1293
        else:
            argspec = '(...)'
1294 1295
        decl = title + argspec + note

1296
        if skipdocs:
1297
            return decl + '\n'
1298 1299
        else:
            doc = getdoc(object) or ''
1300
            return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n')
1301

1302
    def _docdescriptor(self, name, value, mod):
1303 1304 1305 1306
        results = []
        push = results.append

        if name:
1307 1308
            push(self.bold(name))
            push('\n')
1309 1310 1311
        doc = getdoc(value) or ''
        if doc:
            push(self.indent(doc))
1312 1313
            push('\n')
        return ''.join(results)
1314 1315 1316

    def docproperty(self, object, name=None, mod=None, cl=None):
        """Produce text documentation for a property."""
1317
        return self._docdescriptor(name, object, mod)
1318

1319 1320 1321 1322
    def docdata(self, object, name=None, mod=None, cl=None):
        """Produce text documentation for a data descriptor."""
        return self._docdescriptor(name, object, mod)

1323
    def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):
1324 1325 1326
        """Produce text documentation for a data object."""
        repr = self.repr(object)
        if maxlen:
1327
            line = (name and name + ' = ' or '') + repr
1328 1329
            chop = maxlen - len(line)
            if chop < 0: repr = repr[:chop] + '...'
1330
        line = (name and self.bold(name) + ' = ' or '') + repr
1331 1332
        if doc is not None:
            line += '\n' + self.indent(str(doc))
1333 1334
        return line

1335 1336 1337 1338 1339
class _PlainTextDoc(TextDoc):
    """Subclass of TextDoc which overrides string styling"""
    def bold(self, text):
        return text

1340 1341 1342 1343 1344 1345 1346 1347 1348 1349
# --------------------------------------------------------- user interfaces

def pager(text):
    """The first time this is called, determine what kind of pager to use."""
    global pager
    pager = getpager()
    pager(text)

def getpager():
    """Decide what method to use for paging through text."""
1350
    if not hasattr(sys.stdout, "isatty"):
1351 1352 1353
        return plainpager
    if not sys.stdin.isatty() or not sys.stdout.isatty():
        return plainpager
1354
    if 'PAGER' in os.environ:
1355
        if sys.platform == 'win32': # pipes completely broken in Windows
1356
            return lambda text: tempfilepager(plain(text), os.environ['PAGER'])
1357
        elif os.environ.get('TERM') in ('dumb', 'emacs'):
1358
            return lambda text: pipepager(plain(text), os.environ['PAGER'])
1359
        else:
1360
            return lambda text: pipepager(text, os.environ['PAGER'])
1361 1362
    if os.environ.get('TERM') in ('dumb', 'emacs'):
        return plainpager
1363
    if sys.platform == 'win32' or sys.platform.startswith('os2'):
1364
        return lambda text: tempfilepager(plain(text), 'more <')
1365
    if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
1366
        return lambda text: pipepager(text, 'less')
1367 1368

    import tempfile
1369 1370
    (fd, filename) = tempfile.mkstemp()
    os.close(fd)
1371
    try:
Georg Brandl's avatar
Georg Brandl committed
1372
        if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
1373 1374 1375 1376 1377 1378
            return lambda text: pipepager(text, 'more')
        else:
            return ttypager
    finally:
        os.unlink(filename)

1379 1380 1381 1382
def plain(text):
    """Remove boldface formatting from text."""
    return re.sub('.\b', '', text)

1383 1384 1385 1386 1387 1388 1389
def pipepager(text, cmd):
    """Page through text by feeding it to another program."""
    pipe = os.popen(cmd, 'w')
    try:
        pipe.write(text)
        pipe.close()
    except IOError:
1390
        pass # Ignore broken pipes caused by quitting the pager program.
1391 1392 1393 1394

def tempfilepager(text, cmd):
    """Page through text by invoking a program on a temporary file."""
    import tempfile
1395 1396
    filename = tempfile.mktemp()
    file = open(filename, 'w')
1397 1398 1399
    file.write(text)
    file.close()
    try:
Georg Brandl's avatar
Georg Brandl committed
1400
        os.system(cmd + ' "' + filename + '"')
1401 1402 1403 1404 1405
    finally:
        os.unlink(filename)

def ttypager(text):
    """Page through text on a text terminal."""
1406
    lines = plain(text).split('\n')
1407 1408 1409 1410 1411 1412
    try:
        import tty
        fd = sys.stdin.fileno()
        old = tty.tcgetattr(fd)
        tty.setcbreak(fd)
        getchar = lambda: sys.stdin.read(1)
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1413
    except (ImportError, AttributeError):
1414 1415 1416 1417 1418
        tty = None
        getchar = lambda: sys.stdin.readline()[:-1][:1]

    try:
        r = inc = os.environ.get('LINES', 25) - 1
1419
        sys.stdout.write('\n'.join(lines[:inc]) + '\n')
1420 1421 1422 1423 1424
        while lines[r:]:
            sys.stdout.write('-- more --')
            sys.stdout.flush()
            c = getchar()

1425
            if c in ('q', 'Q'):
1426 1427
                sys.stdout.write('\r          \r')
                break
1428
            elif c in ('\r', '\n'):
1429 1430 1431
                sys.stdout.write('\r          \r' + lines[r] + '\n')
                r = r + 1
                continue
1432
            if c in ('b', 'B', '\x1b'):
1433 1434
                r = r - inc - inc
                if r < 0: r = 0
1435
            sys.stdout.write('\n' + '\n'.join(lines[r:r+inc]) + '\n')
1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446
            r = r + inc

    finally:
        if tty:
            tty.tcsetattr(fd, tty.TCSAFLUSH, old)

def plainpager(text):
    """Simply print unformatted text.  This is the ultimate fallback."""
    sys.stdout.write(plain(text))

def describe(thing):
1447
    """Produce a short description of the given thing."""
1448 1449 1450 1451 1452 1453 1454 1455 1456
    if inspect.ismodule(thing):
        if thing.__name__ in sys.builtin_module_names:
            return 'built-in module ' + thing.__name__
        if hasattr(thing, '__path__'):
            return 'package ' + thing.__name__
        else:
            return 'module ' + thing.__name__
    if inspect.isbuiltin(thing):
        return 'built-in function ' + thing.__name__
1457 1458 1459 1460 1461 1462 1463 1464
    if inspect.isgetsetdescriptor(thing):
        return 'getset descriptor %s.%s.%s' % (
            thing.__objclass__.__module__, thing.__objclass__.__name__,
            thing.__name__)
    if inspect.ismemberdescriptor(thing):
        return 'member descriptor %s.%s.%s' % (
            thing.__objclass__.__module__, thing.__objclass__.__name__,
            thing.__name__)
1465 1466 1467 1468 1469 1470
    if inspect.isclass(thing):
        return 'class ' + thing.__name__
    if inspect.isfunction(thing):
        return 'function ' + thing.__name__
    if inspect.ismethod(thing):
        return 'method ' + thing.__name__
1471 1472
    return type(thing).__name__

1473
def locate(path, forceload=0):
1474
    """Locate an object by name or dotted path, importing as necessary."""
1475
    parts = [part for part in path.split('.') if part]
1476 1477
    module, n = None, 0
    while n < len(parts):
1478
        nextmodule = safeimport('.'.join(parts[:n+1]), forceload)
1479 1480 1481 1482 1483
        if nextmodule: module, n = nextmodule, n + 1
        else: break
    if module:
        object = module
    else:
1484 1485 1486 1487 1488 1489 1490
        object = builtins
    for part in parts[n:]:
        try:
            object = getattr(object, part)
        except AttributeError:
            return None
    return object
1491 1492 1493 1494

# --------------------------------------- interactive interpreter interface

text = TextDoc()
1495
plaintext = _PlainTextDoc()
1496 1497
html = HTMLDoc()

1498 1499 1500 1501 1502
def resolve(thing, forceload=0):
    """Given an object or a path to an object, get the object and its name."""
    if isinstance(thing, str):
        object = locate(thing, forceload)
        if not object:
1503
            raise ImportError('no Python documentation found for %r' % thing)
1504 1505 1506 1507
        return object, thing
    else:
        return thing, getattr(thing, '__name__', None)

1508 1509
def render_doc(thing, title='Python Library Documentation: %s', forceload=0,
        renderer=None):
1510
    """Render text documentation, given an object or a path to an object."""
1511 1512
    if renderer is None:
        renderer = text
1513 1514 1515 1516 1517 1518 1519
    object, name = resolve(thing, forceload)
    desc = describe(object)
    module = inspect.getmodule(object)
    if name and '.' in name:
        desc += ' in ' + name[:name.rfind('.')]
    elif module and module is not object:
        desc += ' in module ' + module.__name__
1520 1521

    if not (inspect.ismodule(object) or
1522 1523 1524 1525 1526 1527 1528 1529 1530
              inspect.isclass(object) or
              inspect.isroutine(object) or
              inspect.isgetsetdescriptor(object) or
              inspect.ismemberdescriptor(object) or
              isinstance(object, property)):
        # If the passed object is a piece of data or an instance,
        # document its available methods instead of its value.
        object = type(object)
        desc += ' object'
1531
    return title % desc + '\n\n' + renderer.document(object, name)
1532

1533 1534
def doc(thing, title='Python Library Documentation: %s', forceload=0,
        output=None):
1535
    """Display text documentation, given an object or a path to an object."""
1536
    try:
1537 1538 1539 1540
        if output is None:
            pager(render_doc(thing, title, forceload))
        else:
            output.write(render_doc(thing, title, forceload, plaintext))
1541
    except (ImportError, ErrorDuringImport) as value:
1542
        print(value)
1543 1544

def writedoc(thing, forceload=0):
1545
    """Write HTML documentation to a file in the current directory."""
1546
    try:
1547 1548
        object, name = resolve(thing, forceload)
        page = html.page(describe(object), html.document(object, name))
1549
        file = open(name + '.html', 'w', encoding='utf-8')
1550 1551
        file.write(page)
        file.close()
1552
        print('wrote', name + '.html')
1553
    except (ImportError, ErrorDuringImport) as value:
1554
        print(value)
1555

1556
def writedocs(dir, pkgpath='', done=None):
1557
    """Write out HTML documentation for all modules in a directory tree."""
1558
    if done is None: done = {}
1559 1560 1561
    for importer, modname, ispkg in pkgutil.walk_packages([dir], pkgpath):
        writedoc(modname)
    return
1562 1563

class Helper:
1564 1565 1566 1567

    # These dictionaries map a topic name to either an alias, or a tuple
    # (label, seealso-items).  The "label" is the label of the corresponding
    # section in the .rst file under Doc/ and an index into the dictionary
1568
    # in pydoc_data/topics.py.
1569 1570 1571
    #
    # CAUTION: if you change one of these dictionaries, be sure to adapt the
    #          list of needed labels in Doc/tools/sphinxext/pyspecific.py and
1572
    #          regenerate the pydoc_data/topics.py file by running
1573 1574 1575
    #              make pydoc-topics
    #          in Doc/ and copying the output file into the Lib/ directory.

1576
    keywords = {
1577 1578 1579
        'False': '',
        'None': '',
        'True': '',
1580
        'and': 'BOOLEAN',
1581
        'as': 'with',
1582 1583 1584 1585 1586 1587
        'assert': ('assert', ''),
        'break': ('break', 'while for'),
        'class': ('class', 'CLASSES SPECIALMETHODS'),
        'continue': ('continue', 'while for'),
        'def': ('function', ''),
        'del': ('del', 'BASICMETHODS'),
1588
        'elif': 'if',
1589
        'else': ('else', 'while for'),
1590 1591
        'except': 'try',
        'finally': 'try',
1592
        'for': ('for', 'break continue while'),
1593
        'from': 'import',
1594
        'global': ('global', 'nonlocal NAMESPACES'),
1595 1596
        'if': ('if', 'TRUTHVALUE'),
        'import': ('import', 'MODULES'),
1597
        'in': ('in', 'SEQUENCEMETHODS'),
1598
        'is': 'COMPARISON',
1599
        'lambda': ('lambda', 'FUNCTIONS'),
1600
        'nonlocal': ('nonlocal', 'global NAMESPACES'),
1601 1602
        'not': 'BOOLEAN',
        'or': 'BOOLEAN',
1603 1604 1605 1606 1607 1608 1609
        'pass': ('pass', ''),
        'raise': ('raise', 'EXCEPTIONS'),
        'return': ('return', 'FUNCTIONS'),
        'try': ('try', 'EXCEPTIONS'),
        'while': ('while', 'break continue if TRUTHVALUE'),
        'with': ('with', 'CONTEXTMANAGERS EXCEPTIONS yield'),
        'yield': ('yield', ''),
1610
    }
1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646
    # Either add symbols to this dictionary or to the symbols dictionary
    # directly: Whichever is easier. They are merged later.
    _symbols_inverse = {
        'STRINGS' : ("'", "'''", "r'", "b'", '"""', '"', 'r"', 'b"'),
        'OPERATORS' : ('+', '-', '*', '**', '/', '//', '%', '<<', '>>', '&',
                       '|', '^', '~', '<', '>', '<=', '>=', '==', '!=', '<>'),
        'COMPARISON' : ('<', '>', '<=', '>=', '==', '!=', '<>'),
        'UNARY' : ('-', '~'),
        'AUGMENTEDASSIGNMENT' : ('+=', '-=', '*=', '/=', '%=', '&=', '|=',
                                '^=', '<<=', '>>=', '**=', '//='),
        'BITWISE' : ('<<', '>>', '&', '|', '^', '~'),
        'COMPLEX' : ('j', 'J')
    }
    symbols = {
        '%': 'OPERATORS FORMATTING',
        '**': 'POWER',
        ',': 'TUPLES LISTS FUNCTIONS',
        '.': 'ATTRIBUTES FLOAT MODULES OBJECTS',
        '...': 'ELLIPSIS',
        ':': 'SLICINGS DICTIONARYLITERALS',
        '@': 'def class',
        '\\': 'STRINGS',
        '_': 'PRIVATENAMES',
        '__': 'PRIVATENAMES SPECIALMETHODS',
        '`': 'BACKQUOTES',
        '(': 'TUPLES FUNCTIONS CALLS',
        ')': 'TUPLES FUNCTIONS CALLS',
        '[': 'LISTS SUBSCRIPTS SLICINGS',
        ']': 'LISTS SUBSCRIPTS SLICINGS'
    }
    for topic, symbols_ in _symbols_inverse.items():
        for symbol in symbols_:
            topics = symbols.get(symbol, topic)
            if topic not in topics:
                topics = topics + ' ' + topic
            symbols[symbol] = topics
1647 1648

    topics = {
1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
        'TYPES': ('types', 'STRINGS UNICODE NUMBERS SEQUENCES MAPPINGS '
                  'FUNCTIONS CLASSES MODULES FILES inspect'),
        'STRINGS': ('strings', 'str UNICODE SEQUENCES STRINGMETHODS '
                    'FORMATTING TYPES'),
        'STRINGMETHODS': ('string-methods', 'STRINGS FORMATTING'),
        'FORMATTING': ('formatstrings', 'OPERATORS'),
        'UNICODE': ('strings', 'encodings unicode SEQUENCES STRINGMETHODS '
                    'FORMATTING TYPES'),
        'NUMBERS': ('numbers', 'INTEGER FLOAT COMPLEX TYPES'),
        'INTEGER': ('integers', 'int range'),
        'FLOAT': ('floating', 'float math'),
        'COMPLEX': ('imaginary', 'complex cmath'),
        'SEQUENCES': ('typesseq', 'STRINGMETHODS FORMATTING range LISTS'),
1662
        'MAPPINGS': 'DICTIONARIES',
1663 1664 1665 1666
        'FUNCTIONS': ('typesfunctions', 'def TYPES'),
        'METHODS': ('typesmethods', 'class def CLASSES TYPES'),
        'CODEOBJECTS': ('bltin-code-objects', 'compile FUNCTIONS TYPES'),
        'TYPEOBJECTS': ('bltin-type-objects', 'types TYPES'),
1667 1668
        'FRAMEOBJECTS': 'TYPES',
        'TRACEBACKS': 'TYPES',
1669 1670 1671 1672 1673 1674
        'NONE': ('bltin-null-object', ''),
        'ELLIPSIS': ('bltin-ellipsis-object', 'SLICINGS'),
        'FILES': ('bltin-file-objects', ''),
        'SPECIALATTRIBUTES': ('specialattrs', ''),
        'CLASSES': ('types', 'class SPECIALMETHODS PRIVATENAMES'),
        'MODULES': ('typesmodules', 'import'),
1675
        'PACKAGES': 'import',
1676 1677 1678 1679
        'EXPRESSIONS': ('operator-summary', 'lambda or and not in is BOOLEAN '
                        'COMPARISON BITWISE SHIFTING BINARY FORMATTING POWER '
                        'UNARY ATTRIBUTES SUBSCRIPTS SLICINGS CALLS TUPLES '
                        'LISTS DICTIONARIES'),
1680 1681
        'OPERATORS': 'EXPRESSIONS',
        'PRECEDENCE': 'EXPRESSIONS',
1682 1683
        'OBJECTS': ('objects', 'TYPES'),
        'SPECIALMETHODS': ('specialnames', 'BASICMETHODS ATTRIBUTEMETHODS '
1684 1685
                           'CALLABLEMETHODS SEQUENCEMETHODS MAPPINGMETHODS '
                           'NUMBERMETHODS CLASSES'),
1686
        'BASICMETHODS': ('customization', 'hash repr str SPECIALMETHODS'),
1687 1688
        'ATTRIBUTEMETHODS': ('attribute-access', 'ATTRIBUTES SPECIALMETHODS'),
        'CALLABLEMETHODS': ('callable-types', 'CALLS SPECIALMETHODS'),
1689
        'SEQUENCEMETHODS': ('sequence-types', 'SEQUENCES SEQUENCEMETHODS '
1690 1691 1692 1693 1694
                             'SPECIALMETHODS'),
        'MAPPINGMETHODS': ('sequence-types', 'MAPPINGS SPECIALMETHODS'),
        'NUMBERMETHODS': ('numeric-types', 'NUMBERS AUGMENTEDASSIGNMENT '
                          'SPECIALMETHODS'),
        'EXECUTION': ('execmodel', 'NAMESPACES DYNAMICFEATURES EXCEPTIONS'),
1695
        'NAMESPACES': ('naming', 'global nonlocal ASSIGNMENT DELETION DYNAMICFEATURES'),
1696
        'DYNAMICFEATURES': ('dynamic-features', ''),
1697 1698
        'SCOPING': 'NAMESPACES',
        'FRAMES': 'NAMESPACES',
1699 1700 1701 1702 1703 1704 1705
        'EXCEPTIONS': ('exceptions', 'try except finally raise'),
        'CONVERSIONS': ('conversions', ''),
        'IDENTIFIERS': ('identifiers', 'keywords SPECIALIDENTIFIERS'),
        'SPECIALIDENTIFIERS': ('id-classes', ''),
        'PRIVATENAMES': ('atom-identifiers', ''),
        'LITERALS': ('atom-literals', 'STRINGS NUMBERS TUPLELITERALS '
                     'LISTLITERALS DICTIONARYLITERALS'),
1706
        'TUPLES': 'SEQUENCES',
1707 1708 1709 1710 1711 1712
        'TUPLELITERALS': ('exprlists', 'TUPLES LITERALS'),
        'LISTS': ('typesseq-mutable', 'LISTLITERALS'),
        'LISTLITERALS': ('lists', 'LISTS LITERALS'),
        'DICTIONARIES': ('typesmapping', 'DICTIONARYLITERALS'),
        'DICTIONARYLITERALS': ('dict', 'DICTIONARIES LITERALS'),
        'ATTRIBUTES': ('attribute-references', 'getattr hasattr setattr ATTRIBUTEMETHODS'),
1713 1714
        'SUBSCRIPTS': ('subscriptions', 'SEQUENCEMETHODS'),
        'SLICINGS': ('slicings', 'SEQUENCEMETHODS'),
1715 1716 1717 1718 1719 1720 1721 1722
        'CALLS': ('calls', 'EXPRESSIONS'),
        'POWER': ('power', 'EXPRESSIONS'),
        'UNARY': ('unary', 'EXPRESSIONS'),
        'BINARY': ('binary', 'EXPRESSIONS'),
        'SHIFTING': ('shifting', 'EXPRESSIONS'),
        'BITWISE': ('bitwise', 'EXPRESSIONS'),
        'COMPARISON': ('comparisons', 'EXPRESSIONS BASICMETHODS'),
        'BOOLEAN': ('booleans', 'EXPRESSIONS TRUTHVALUE'),
1723
        'ASSERTION': 'assert',
1724 1725
        'ASSIGNMENT': ('assignment', 'AUGMENTEDASSIGNMENT'),
        'AUGMENTEDASSIGNMENT': ('augassign', 'NUMBERMETHODS'),
1726 1727 1728 1729
        'DELETION': 'del',
        'RETURNING': 'return',
        'IMPORTING': 'import',
        'CONDITIONAL': 'if',
1730 1731 1732 1733
        'LOOPING': ('compound', 'for while break continue'),
        'TRUTHVALUE': ('truth', 'if while and or not BASICMETHODS'),
        'DEBUGGING': ('debugger', 'pdb'),
        'CONTEXTMANAGERS': ('context-managers', 'with'),
1734 1735
    }

1736 1737 1738 1739
    def __init__(self, input=None, output=None):
        self._input = input
        self._output = output

1740 1741
    input  = property(lambda self: self._input or sys.stdin)
    output = property(lambda self: self._output or sys.stdout)
1742

1743
    def __repr__(self):
1744
        if inspect.stack()[1][3] == '?':
1745 1746
            self()
            return ''
1747
        return '<pydoc.Helper instance>'
1748

1749 1750 1751
    _GoInteractive = object()
    def __call__(self, request=_GoInteractive):
        if request is not self._GoInteractive:
1752
            self.help(request)
1753
        else:
1754
            self.intro()
1755
            self.interact()
1756
            self.output.write('''
1757
You are now leaving help and returning to the Python interpreter.
1758 1759 1760 1761 1762
If you want to ask for help on a particular object directly from the
interpreter, you can type "help(object)".  Executing "help('string')"
has the same effect as typing a particular string at the help> prompt.
''')

1763 1764
    def interact(self):
        self.output.write('\n')
1765
        while True:
1766
            try:
1767
                request = self.getline('help> ')
1768
                if not request: break
1769 1770
            except (KeyboardInterrupt, EOFError):
                break
1771 1772
            request = replace(request, '"', '', "'", '').strip()
            if request.lower() in ('q', 'quit'): break
1773 1774
            self.help(request)

1775
    def getline(self, prompt):
1776
        """Read one line, using input() when appropriate."""
1777
        if self.input is sys.stdin:
1778
            return input(prompt)
1779 1780 1781 1782 1783
        else:
            self.output.write(prompt)
            self.output.flush()
            return self.input.readline()

1784 1785
    def help(self, request):
        if type(request) is type(''):
1786
            request = request.strip()
1787 1788
            if request == 'help': self.intro()
            elif request == 'keywords': self.listkeywords()
1789
            elif request == 'symbols': self.listsymbols()
1790 1791 1792
            elif request == 'topics': self.listtopics()
            elif request == 'modules': self.listmodules()
            elif request[:8] == 'modules ':
1793
                self.listmodules(request.split()[1])
1794
            elif request in self.symbols: self.showsymbol(request)
1795 1796 1797
            elif request in ['True', 'False', 'None']:
                # special case these keywords since they are objects too
                doc(eval(request), 'Help on %s:')
1798 1799
            elif request in self.keywords: self.showtopic(request)
            elif request in self.topics: self.showtopic(request)
1800
            elif request: doc(request, 'Help on %s:', output=self._output)
1801
        elif isinstance(request, Helper): self()
1802
        else: doc(request, 'Help on %s:', output=self._output)
1803 1804 1805 1806 1807 1808 1809
        self.output.write('\n')

    def intro(self):
        self.output.write('''
Welcome to Python %s!  This is the online help utility.

If this is your first time using Python, you should definitely check out
1810
the tutorial on the Internet at http://docs.python.org/tutorial/.
1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".

To get a list of available modules, keywords, or topics, type "modules",
"keywords", or "topics".  Each module also comes with a one-line summary
of what it does; to list the modules whose summaries contain a given word
such as "spam", type "modules spam".
''' % sys.version[:3])

    def list(self, items, columns=4, width=80):
1823 1824 1825
        items = list(sorted(items))
        colw = width // columns
        rows = (len(items) + columns - 1) // columns
1826 1827 1828 1829 1830 1831
        for row in range(rows):
            for col in range(columns):
                i = col * rows + row
                if i < len(items):
                    self.output.write(items[i])
                    if col < columns - 1:
1832
                        self.output.write(' ' + ' ' * (colw - 1 - len(items[i])))
1833 1834 1835 1836 1837 1838 1839 1840 1841
            self.output.write('\n')

    def listkeywords(self):
        self.output.write('''
Here is a list of the Python keywords.  Enter any keyword to get more help.

''')
        self.list(self.keywords.keys())

1842 1843 1844 1845 1846 1847 1848 1849
    def listsymbols(self):
        self.output.write('''
Here is a list of the punctuation symbols which Python assigns special meaning
to. Enter any symbol to get more help.

''')
        self.list(self.symbols.keys())

1850 1851 1852 1853 1854 1855 1856
    def listtopics(self):
        self.output.write('''
Here is a list of available topics.  Enter any topic name to get more help.

''')
        self.list(self.topics.keys())

1857
    def showtopic(self, topic, more_xrefs=''):
1858
        try:
1859
            import pydoc_data.topics
1860
        except ImportError:
1861
            self.output.write('''
1862
Sorry, topic and keyword documentation is not available because the
1863
module "pydoc_data.topics" could not be found.
1864 1865 1866 1867 1868 1869 1870
''')
            return
        target = self.topics.get(topic, self.keywords.get(topic))
        if not target:
            self.output.write('no documentation found for %s\n' % repr(topic))
            return
        if type(target) is type(''):
1871
            return self.showtopic(target, more_xrefs)
1872

1873
        label, xrefs = target
1874
        try:
1875
            doc = pydoc_data.topics.topics[label]
1876 1877
        except KeyError:
            self.output.write('no documentation found for %s\n' % repr(topic))
1878
            return
1879
        pager(doc.strip() + '\n')
1880 1881
        if more_xrefs:
            xrefs = (xrefs or '') + ' ' + more_xrefs
1882
        if xrefs:
1883
            import formatter
1884
            buffer = io.StringIO()
1885
            formatter.DumbWriter(buffer).send_flowing_data(
1886
                'Related help topics: ' + ', '.join(xrefs.split()) + '\n')
1887
            self.output.write('\n%s\n' % buffer.getvalue())
1888

1889 1890 1891
    def _gettopic(self, topic, more_xrefs=''):
        """Return unbuffered tuple of (topic, xrefs).

1892 1893 1894
        If an error occurs here, the exception is caught and displayed by
        the url handler.

1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906
        This function duplicates the showtopic method but returns its
        result directly so it can be formatted for display in an html page.
        """
        try:
            import pydoc_data.topics
        except ImportError:
            return('''
Sorry, topic and keyword documentation is not available because the
module "pydoc_data.topics" could not be found.
''' , '')
        target = self.topics.get(topic, self.keywords.get(topic))
        if not target:
1907
            raise ValueError('could not find topic')
1908 1909 1910
        if isinstance(target, str):
            return self._gettopic(target, more_xrefs)
        label, xrefs = target
1911
        doc = pydoc_data.topics.topics[label]
1912 1913 1914 1915
        if more_xrefs:
            xrefs = (xrefs or '') + ' ' + more_xrefs
        return doc, xrefs

1916 1917 1918 1919 1920
    def showsymbol(self, symbol):
        target = self.symbols[symbol]
        topic, _, xrefs = target.partition(' ')
        self.showtopic(topic, xrefs)

1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936
    def listmodules(self, key=''):
        if key:
            self.output.write('''
Here is a list of matching modules.  Enter any module name to get more help.

''')
            apropos(key)
        else:
            self.output.write('''
Please wait a moment while I gather a list of all available modules...

''')
            modules = {}
            def callback(path, modname, desc, modules=modules):
                if modname and modname[-9:] == '.__init__':
                    modname = modname[:-9] + ' (package)'
1937
                if modname.find('.') < 0:
1938
                    modules[modname] = 1
1939 1940 1941
            def onerror(modname):
                callback(None, modname, None)
            ModuleScanner().run(callback, onerror=onerror)
1942 1943 1944 1945 1946 1947
            self.list(modules.keys())
            self.output.write('''
Enter any module name to get more help.  Or, type "modules spam" to search
for modules whose descriptions contain the word "spam".
''')

1948
help = Helper()
1949

Ka-Ping Yee's avatar
Ka-Ping Yee committed
1950 1951
class Scanner:
    """A generic tree iterator."""
1952
    def __init__(self, roots, children, descendp):
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1953 1954 1955
        self.roots = roots[:]
        self.state = []
        self.children = children
1956
        self.descendp = descendp
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968

    def next(self):
        if not self.state:
            if not self.roots:
                return None
            root = self.roots.pop(0)
            self.state = [(root, self.children(root))]
        node, children = self.state[-1]
        if not children:
            self.state.pop()
            return self.next()
        child = children.pop(0)
1969
        if self.descendp(child):
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1970 1971 1972 1973
            self.state.append((child, self.children(child)))
        return child


1974 1975
class ModuleScanner:
    """An interruptible scanner that searches module synopses."""
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1976

1977
    def run(self, callback, key=None, completer=None, onerror=None):
1978
        if key: key = key.lower()
1979
        self.quit = False
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1980 1981 1982
        seen = {}

        for modname in sys.builtin_module_names:
1983 1984
            if modname != '__main__':
                seen[modname] = 1
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1985 1986 1987
                if key is None:
                    callback(None, modname, '')
                else:
1988 1989 1990 1991
                    name = __import__(modname).__doc__ or ''
                    desc = name.split('\n')[0]
                    name = modname + ' - ' + desc
                    if name.lower().find(key) >= 0:
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1992
                        callback(None, modname, desc)
Ka-Ping Yee's avatar
Ka-Ping Yee committed
1993

1994
        for importer, modname, ispkg in pkgutil.walk_packages(onerror=onerror):
1995 1996
            if self.quit:
                break
1997 1998 1999 2000 2001 2002 2003 2004 2005

            # XXX Skipping this file is a workaround for a bug
            # that causes python to crash with a segfault.
            # http://bugs.python.org/issue9319
            #
            # TODO Remove this once the bug is fixed.
            if modname in {'test.badsyntax_pep3120', 'badsyntax_pep3120'}:
                continue

2006 2007 2008
            if key is None:
                callback(None, modname, '')
            else:
2009 2010 2011 2012 2013 2014
                try:
                    loader = importer.find_module(modname)
                except SyntaxError:
                    # raised by tests for bad coding cookies or BOM
                    continue
                if hasattr(loader, 'get_source'):
2015 2016 2017 2018 2019 2020 2021
                    try:
                        source = loader.get_source(modname)
                    except UnicodeDecodeError:
                        if onerror:
                            onerror(modname)
                        continue
                    desc = source_synopsis(io.StringIO(source)) or ''
2022
                    if hasattr(loader, 'get_filename'):
2023
                        path = loader.get_filename(modname)
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2024
                    else:
2025 2026
                        path = None
                else:
2027 2028 2029 2030 2031 2032
                    try:
                        module = loader.load_module(modname)
                    except ImportError:
                        if onerror:
                            onerror(modname)
                        continue
2033 2034
                    desc = (module.__doc__ or '').splitlines()[0]
                    path = getattr(module,'__file__',None)
2035 2036
                name = modname + ' - ' + desc
                if name.lower().find(key) >= 0:
2037 2038 2039 2040
                    callback(path, modname, desc)

        if completer:
            completer()
2041 2042 2043

def apropos(key):
    """Print all the one-line module summaries that contain a substring."""
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2044 2045 2046
    def callback(path, modname, desc):
        if modname[-9:] == '.__init__':
            modname = modname[:-9] + ' (package)'
2047
        print(modname, desc and '- ' + desc)
2048 2049
    def onerror(modname):
        pass
2050 2051 2052
    with warnings.catch_warnings():
        warnings.filterwarnings('ignore') # ignore problems during import
        ModuleScanner().run(callback, key, onerror=onerror)
2053

2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129
# --------------------------------------- enhanced Web browser interface

def _start_server(urlhandler, port):
    """Start an HTTP server thread on a specific port.

    Start an HTML/text server thread, so HTML or text documents can be
    browsed dynamically and interactively with a Web browser.  Example use:

        >>> import time
        >>> import pydoc

        Define a URL handler.  To determine what the client is asking
        for, check the URL and content_type.

        Then get or generate some text or HTML code and return it.

        >>> def my_url_handler(url, content_type):
        ...     text = 'the URL sent was: (%s, %s)' % (url, content_type)
        ...     return text

        Start server thread on port 0.
        If you use port 0, the server will pick a random port number.
        You can then use serverthread.port to get the port number.

        >>> port = 0
        >>> serverthread = pydoc._start_server(my_url_handler, port)

        Check that the server is really started.  If it is, open browser
        and get first page.  Use serverthread.url as the starting page.

        >>> if serverthread.serving:
        ...    import webbrowser

        The next two lines are commented out so a browser doesn't open if
        doctest is run on this module.

        #...    webbrowser.open(serverthread.url)
        #True

        Let the server do its thing. We just need to monitor its status.
        Use time.sleep so the loop doesn't hog the CPU.

        >>> starttime = time.time()
        >>> timeout = 1                    #seconds

        This is a short timeout for testing purposes.

        >>> while serverthread.serving:
        ...     time.sleep(.01)
        ...     if serverthread.serving and time.time() - starttime > timeout:
        ...          serverthread.stop()
        ...          break

        Print any errors that may have occurred.

        >>> print(serverthread.error)
        None
   """
    import http.server
    import email.message
    import select
    import threading

    class DocHandler(http.server.BaseHTTPRequestHandler):

        def do_GET(self):
            """Process a request from an HTML browser.

            The URL received is in self.path.
            Get an HTML page from self.urlhandler and send it.
            """
            if self.path.endswith('.css'):
                content_type = 'text/css'
            else:
                content_type = 'text/html'
            self.send_response(200)
2130
            self.send_header('Content-Type', '%s; charset=UTF-8' % content_type)
2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152
            self.end_headers()
            self.wfile.write(self.urlhandler(
                self.path, content_type).encode('utf-8'))

        def log_message(self, *args):
            # Don't log messages.
            pass

    class DocServer(http.server.HTTPServer):

        def __init__(self, port, callback):
            self.host = (sys.platform == 'mac') and '127.0.0.1' or 'localhost'
            self.address = ('', port)
            self.callback = callback
            self.base.__init__(self, self.address, self.handler)
            self.quit = False

        def serve_until_quit(self):
            while not self.quit:
                rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
                if rd:
                    self.handle_request()
2153
            self.server_close()
2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221

        def server_activate(self):
            self.base.server_activate(self)
            if self.callback:
                self.callback(self)

    class ServerThread(threading.Thread):

        def __init__(self, urlhandler, port):
            self.urlhandler = urlhandler
            self.port = int(port)
            threading.Thread.__init__(self)
            self.serving = False
            self.error = None

        def run(self):
            """Start the server."""
            try:
                DocServer.base = http.server.HTTPServer
                DocServer.handler = DocHandler
                DocHandler.MessageClass = email.message.Message
                DocHandler.urlhandler = staticmethod(self.urlhandler)
                docsvr = DocServer(self.port, self.ready)
                self.docserver = docsvr
                docsvr.serve_until_quit()
            except Exception as e:
                self.error = e

        def ready(self, server):
            self.serving = True
            self.host = server.host
            self.port = server.server_port
            self.url = 'http://%s:%d/' % (self.host, self.port)

        def stop(self):
            """Stop the server and this thread nicely"""
            self.docserver.quit = True
            self.serving = False
            self.url = None

    thread = ServerThread(urlhandler, port)
    thread.start()
    # Wait until thread.serving is True to make sure we are
    # really up before returning.
    while not thread.error and not thread.serving:
        time.sleep(.01)
    return thread


def _url_handler(url, content_type="text/html"):
    """The pydoc url handler for use with the pydoc server.

    If the content_type is 'text/css', the _pydoc.css style
    sheet is read and returned if it exits.

    If the content_type is 'text/html', then the result of
    get_html_page(url) is returned.
    """
    class _HTMLDoc(HTMLDoc):

        def page(self, title, contents):
            """Format an HTML page."""
            css_path = "pydoc_data/_pydoc.css"
            css_link = (
                '<link rel="stylesheet" type="text/css" href="%s">' %
                css_path)
            return '''\
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
2222
<html><head><title>Pydoc: %s</title>
2223
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
2224 2225
%s</head><body bgcolor="#f0f0f8">%s<div style="clear:both;padding-top:.5em;">%s</div>
</body></html>''' % (title, css_link, html_navbar(), contents)
2226 2227 2228 2229 2230 2231 2232 2233

        def filelink(self, url, path):
            return '<a href="getfile?key=%s">%s</a>' % (url, path)


    html = _HTMLDoc()

    def html_navbar():
2234 2235 2236
        version = html.escape("%s [%s, %s]" % (platform.python_version(),
                                               platform.python_build()[0],
                                               platform.python_compiler()))
2237 2238
        return """
            <div style='float:left'>
2239
                Python %s<br>%s
2240 2241 2242 2243 2244 2245 2246 2247
            </div>
            <div style='float:right'>
                <div style='text-align:center'>
                  <a href="index.html">Module Index</a>
                  : <a href="topics.html">Topics</a>
                  : <a href="keywords.html">Keywords</a>
                </div>
                <div>
2248
                    <form action="get" style='display:inline;'>
2249 2250
                      <input type=text name=key size=15>
                      <input type=submit value="Get">
2251 2252
                    </form>&nbsp;
                    <form action="search" style='display:inline;'>
2253 2254 2255 2256 2257
                      <input type=text name=key size=15>
                      <input type=submit value="Search">
                    </form>
                </div>
            </div>
2258
            """ % (version, html.escape(platform.platform(terse=True)))
2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282

    def html_index():
        """Module Index page."""

        def bltinlink(name):
            return '<a href="%s.html">%s</a>' % (name, name)

        heading = html.heading(
            '<big><big><strong>Index of Modules</strong></big></big>',
            '#ffffff', '#7799ee')
        names = [name for name in sys.builtin_module_names
                 if name != '__main__']
        contents = html.multicolumn(names, bltinlink)
        contents = [heading, '<p>' + html.bigsection(
            'Built-in Modules', '#ffffff', '#ee77aa', contents)]

        seen = {}
        for dir in sys.path:
            contents.append(html.index(dir, seen))

        contents.append(
            '<p align=right><font color="#909090" face="helvetica,'
            'arial"><strong>pydoc</strong> by Ka-Ping Yee'
            '&lt;ping@lfw.org&gt;</font>')
2283
        return 'Index of Modules', ''.join(contents)
2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310

    def html_search(key):
        """Search results page."""
        # scan for modules
        search_result = []

        def callback(path, modname, desc):
            if modname[-9:] == '.__init__':
                modname = modname[:-9] + ' (package)'
            search_result.append((modname, desc and '- ' + desc))

        with warnings.catch_warnings():
            warnings.filterwarnings('ignore') # ignore problems during import
            ModuleScanner().run(callback, key)

        # format page
        def bltinlink(name):
            return '<a href="%s.html">%s</a>' % (name, name)

        results = []
        heading = html.heading(
            '<big><big><strong>Search Results</strong></big></big>',
            '#ffffff', '#7799ee')
        for name, desc in search_result:
            results.append(bltinlink(name) + desc)
        contents = heading + html.bigsection(
            'key = %s' % key, '#ffffff', '#ee77aa', '<br>'.join(results))
2311
        return 'Search Results', contents
2312 2313 2314

    def html_getfile(path):
        """Get and display a source file listing safely."""
2315
        path = path.replace('%20', ' ')
2316
        with tokenize.open(path) as fp:
2317 2318 2319 2320 2321 2322 2323
            lines = html.escape(fp.read())
        body = '<pre>%s</pre>' % lines
        heading = html.heading(
            '<big><big><strong>File Listing</strong></big></big>',
            '#ffffff', '#7799ee')
        contents = heading + html.bigsection(
            'File: %s' % path, '#ffffff', '#ee77aa', body)
2324
        return 'getfile %s' % path, contents
2325 2326 2327 2328 2329

    def html_topics():
        """Index of topic texts available."""

        def bltinlink(name):
2330
            return '<a href="topic?key=%s">%s</a>' % (name, name)
2331 2332 2333 2334 2335 2336 2337 2338 2339

        heading = html.heading(
            '<big><big><strong>INDEX</strong></big></big>',
            '#ffffff', '#7799ee')
        names = sorted(Helper.topics.keys())

        contents = html.multicolumn(names, bltinlink)
        contents = heading + html.bigsection(
            'Topics', '#ffffff', '#ee77aa', contents)
2340
        return 'Topics', contents
2341 2342 2343 2344 2345 2346 2347 2348 2349

    def html_keywords():
        """Index of keywords."""
        heading = html.heading(
            '<big><big><strong>INDEX</strong></big></big>',
            '#ffffff', '#7799ee')
        names = sorted(Helper.keywords.keys())

        def bltinlink(name):
2350
            return '<a href="topic?key=%s">%s</a>' % (name, name)
2351 2352 2353 2354

        contents = html.multicolumn(names, bltinlink)
        contents = heading + html.bigsection(
            'Keywords', '#ffffff', '#ee77aa', contents)
2355
        return 'Keywords', contents
2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368

    def html_topicpage(topic):
        """Topic or keyword help page."""
        buf = io.StringIO()
        htmlhelp = Helper(buf, buf)
        contents, xrefs = htmlhelp._gettopic(topic)
        if topic in htmlhelp.keywords:
            title = 'KEYWORD'
        else:
            title = 'TOPIC'
        heading = html.heading(
            '<big><big><strong>%s</strong></big></big>' % title,
            '#ffffff', '#7799ee')
2369
        contents = '<pre>%s</pre>' % html.markup(contents)
2370
        contents = html.bigsection(topic , '#ffffff','#ee77aa', contents)
2371 2372
        if xrefs:
            xrefs = sorted(xrefs.split())
2373

2374 2375
            def bltinlink(name):
                return '<a href="topic?key=%s">%s</a>' % (name, name)
2376

2377 2378 2379
            xrefs = html.multicolumn(xrefs, bltinlink)
            xrefs = html.section('Related help topics: ',
                                 '#ffffff', '#ee77aa', xrefs)
2380 2381
        return ('%s %s' % (title, topic),
                ''.join((heading, contents, xrefs)))
2382

2383 2384 2385 2386 2387 2388 2389 2390 2391
    def html_getobj(url):
        obj = locate(url, forceload=1)
        if obj is None and url != 'None':
            raise ValueError('could not find object')
        title = describe(obj)
        content = html.document(obj, url)
        return title, content

    def html_error(url, exc):
2392 2393
        heading = html.heading(
            '<big><big><strong>Error</strong></big></big>',
2394 2395 2396 2397 2398 2399
            '#ffffff', '#7799ee')
        contents = '<br>'.join(html.escape(line) for line in
                               format_exception_only(type(exc), exc))
        contents = heading + html.bigsection(url, '#ffffff', '#bb0000',
                                             contents)
        return "Error - %s" % url, contents
2400 2401 2402

    def get_html_page(url):
        """Generate an HTML page for url."""
2403
        complete_url = url
2404 2405
        if url.endswith('.html'):
            url = url[:-5]
2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435
        try:
            if url in ("", "index"):
                title, content = html_index()
            elif url == "topics":
                title, content = html_topics()
            elif url == "keywords":
                title, content = html_keywords()
            elif '=' in url:
                op, _, url = url.partition('=')
                if op == "search?key":
                    title, content = html_search(url)
                elif op == "getfile?key":
                    title, content = html_getfile(url)
                elif op == "topic?key":
                    # try topics first, then objects.
                    try:
                        title, content = html_topicpage(url)
                    except ValueError:
                        title, content = html_getobj(url)
                elif op == "get?key":
                    # try objects first, then topics.
                    if url in ("", "index"):
                        title, content = html_index()
                    else:
                        try:
                            title, content = html_getobj(url)
                        except ValueError:
                            title, content = html_topicpage(url)
                else:
                    raise ValueError('bad pydoc url')
2436
            else:
2437 2438 2439 2440 2441
                title, content = html_getobj(url)
        except Exception as exc:
            # Catch any errors and display them in an error page.
            title, content = html_error(complete_url, exc)
        return html.page(title, content)
2442 2443 2444 2445 2446

    if url.startswith('/'):
        url = url[1:]
    if content_type == 'text/css':
        path_here = os.path.dirname(os.path.realpath(__file__))
2447 2448 2449
        css_path = os.path.join(path_here, url)
        with open(css_path) as fp:
            return ''.join(fp.readlines())
2450 2451
    elif content_type == 'text/html':
        return get_html_page(url)
2452 2453
    # Errors outside the url handler are caught by the server.
    raise TypeError('unknown content type %r for url %s' % (content_type, url))
2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490


def browse(port=0, *, open_browser=True):
    """Start the enhanced pydoc Web server and open a Web browser.

    Use port '0' to start the server on an arbitrary port.
    Set open_browser to False to suppress opening a browser.
    """
    import webbrowser
    serverthread = _start_server(_url_handler, port)
    if serverthread.error:
        print(serverthread.error)
        return
    if serverthread.serving:
        server_help_msg = 'Server commands: [b]rowser, [q]uit'
        if open_browser:
            webbrowser.open(serverthread.url)
        try:
            print('Server ready at', serverthread.url)
            print(server_help_msg)
            while serverthread.serving:
                cmd = input('server> ')
                cmd = cmd.lower()
                if cmd == 'q':
                    break
                elif cmd == 'b':
                    webbrowser.open(serverthread.url)
                else:
                    print(server_help_msg)
        except (KeyboardInterrupt, EOFError):
            print()
        finally:
            if serverthread.serving:
                serverthread.stop()
                print('Server stopped')


2491 2492
# -------------------------------------------------- command-line interface

2493
def ispath(x):
2494
    return isinstance(x, str) and x.find(os.sep) >= 0
2495

2496
def cli():
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2497
    """Command-line interface (looks at sys.argv to decide what to do)."""
2498
    import getopt
2499
    class BadUsage(Exception): pass
2500

2501 2502 2503 2504 2505 2506 2507
    # Scripts don't get the current directory in their path by default
    # unless they are run with the '-m' switch
    if '' not in sys.path:
        scriptdir = os.path.dirname(sys.argv[0])
        if scriptdir in sys.path:
            sys.path.remove(scriptdir)
        sys.path.insert(0, '.')
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2508

2509
    try:
2510
        opts, args = getopt.getopt(sys.argv[1:], 'bk:p:w')
2511 2512 2513 2514
        writing = False
        start_server = False
        open_browser = False
        port = None
2515
        for opt, val in opts:
2516 2517 2518
            if opt == '-b':
                start_server = True
                open_browser = True
2519
            if opt == '-k':
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2520 2521
                apropos(val)
                return
2522
            if opt == '-p':
2523 2524
                start_server = True
                port = val
2525
            if opt == '-w':
2526 2527 2528 2529 2530 2531 2532
                writing = True

        if start_server == True:
            if port == None:
                port = 0
            browse(port, open_browser=open_browser)
            return
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2533 2534 2535

        if not args: raise BadUsage
        for arg in args:
2536
            if ispath(arg) and not os.path.exists(arg):
2537
                print('file %r does not exist' % arg)
2538
                break
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2539
            try:
2540
                if ispath(arg) and os.path.isfile(arg):
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2541
                    arg = importfile(arg)
2542 2543 2544 2545 2546 2547
                if writing:
                    if ispath(arg) and os.path.isdir(arg):
                        writedocs(arg)
                    else:
                        writedoc(arg)
                else:
2548
                    help.help(arg)
2549
            except ErrorDuringImport as value:
2550
                print(value)
2551 2552

    except (getopt.error, BadUsage):
2553
        cmd = os.path.splitext(os.path.basename(sys.argv[0]))[0]
2554
        print("""pydoc - the Python documentation tool
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2555

2556
{cmd} <name> ...
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2557
    Show text documentation on something.  <name> may be the name of a
2558 2559
    Python keyword, topic, function, module, or package, or a dotted
    reference to a class or function within a module or module in a
2560
    package.  If <name> contains a '{sep}', it is used as the path to a
2561 2562
    Python source file to document. If name is 'keywords', 'topics',
    or 'modules', a listing of these things is displayed.
2563

2564
{cmd} -k <keyword>
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2565
    Search for a keyword in the synopsis lines of all available modules.
2566

2567 2568 2569 2570 2571 2572 2573 2574
{cmd} -p <port>
    Start an HTTP server on the given port on the local machine.  Port
    number 0 can be used to get an arbitrary unused port.

{cmd} -b
    Start an HTTP server on an arbitrary unused port and open a Web browser
    to interactively browse documentation.  The -p option can be used with
    the -b option to explicitly specify the server port.
2575

2576
{cmd} -w <name> ...
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2577
    Write out the HTML documentation for a module to a file in the current
2578
    directory.  If <name> contains a '{sep}', it is treated as a filename; if
2579
    it names a directory, documentation is written for all the contents.
2580
""".format(cmd=cmd, sep=os.sep))
Ka-Ping Yee's avatar
Ka-Ping Yee committed
2581

2582 2583
if __name__ == '__main__':
    cli()