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