diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 2fab0dd99d5..5b52316fd65 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -66,7 +66,7 @@ def synopsis(filename, cache={}):
result = split(module.__doc__ or '', '\n')[0]
else: # text modules can be directly examined
line = file.readline()
- while line[:1] == '#' or strip(line) == '':
+ while line[:1] == '#' or not strip(line):
line = file.readline()
if not line: break
line = strip(line)
@@ -125,8 +125,9 @@ def isconstant(object):
def replace(text, *pairs):
"""Do a series of global replacements on a string."""
- for old, new in pairs:
- text = join(split(text, old), new)
+ while pairs:
+ text = join(split(text, pairs[0]), pairs[1])
+ pairs = pairs[2:]
return text
def cram(text, maxlen):
@@ -224,7 +225,7 @@ class HTMLRepr(Repr):
self.maxstring = self.maxother = 100
def escape(self, text):
- return replace(text, ('&', '&'), ('<', '<'), ('>', '>'))
+ return replace(text, '&', '&', '<', '<', '>', '>')
def repr(self, object):
return Repr.repr(self, object)
@@ -239,7 +240,7 @@ class HTMLRepr(Repr):
def repr_string(self, x, level):
test = cram(x, self.maxstring)
testrepr = repr(test)
- if '\\' in test and '\\' not in replace(testrepr, (r'\\', '')):
+ if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
# Backslashes are only literal in the string and are never
# needed to make any special characters, so show a raw string.
return 'r' + testrepr[0] + self.escape(test) + testrepr[0]
@@ -316,8 +317,8 @@ TT { font-family: lucida console, lucida typewriter, courier }
def preformat(self, text):
"""Format literal preformatted text."""
text = self.escape(expandtabs(text))
- return replace(text, ('\n\n', '\n \n'), ('\n\n', '\n \n'),
- (' ', ' '), ('\n', '
\n'))
+ return replace(text, '\n\n', '\n \n', '\n\n', '\n \n',
+ ' ', ' ', '\n', '
\n')
def multicolumn(self, list, format, cols=4):
"""Format a list of items into a multi-column list."""
@@ -343,9 +344,7 @@ TT { font-family: lucida console, lucida typewriter, courier }
def classlink(self, object, modname, *dicts):
"""Make a link for a class."""
- name = object.__name__
- if object.__module__ != modname:
- name = object.__module__ + '.' + name
+ name = classname(object, modname)
for dict in dicts:
if dict.has_key(object):
return '%s' % (dict[object], name)
@@ -425,7 +424,7 @@ TT { font-family: lucida console, lucida typewriter, courier }
entry, modname, classes, c)
return '
\n%s
\n' % result
- def docmodule(self, object, name=None):
+ def docmodule(self, object, name=None, mod=None):
"""Produce HTML documentation for a module object."""
name = object.__name__ # ignore the passed-in name
parts = split(name, '.')
@@ -509,13 +508,13 @@ TT { font-family: lucida console, lucida typewriter, courier }
contents = [self.formattree(
inspect.getclasstree(classlist, 1), name, cdict)]
for key, value in classes:
- contents.append(self.document(value, key, fdict, cdict))
+ contents.append(self.document(value, key, name, fdict, cdict))
result = result + self.bigsection(
'Classes', '#ffffff', '#ee77aa', join(contents))
if funcs:
contents = []
for key, value in funcs:
- contents.append(self.document(value, key, fdict, cdict))
+ contents.append(self.document(value, key, name, fdict, cdict))
result = result + self.bigsection(
'Functions', '#ffffff', '#eeaa77', join(contents))
if constants:
@@ -535,21 +534,20 @@ TT { font-family: lucida console, lucida typewriter, courier }
return result
- def docclass(self, object, name=None, funcs={}, classes={}):
+ def docclass(self, object, name=None, mod=None, funcs={}, classes={}):
"""Produce HTML documentation for a class object."""
realname = object.__name__
name = name or realname
bases = object.__bases__
contents = ''
- methods, mdict = [], {}
- for key, value in allmethods(object).items():
- methods.append((key, value))
- mdict[key] = mdict[value] = '#' + name + '-' + key
+ methods, mdict = allmethods(object).items(), {}
methods.sort()
+ for key, value in methods:
+ mdict[key] = mdict[value] = '#' + name + '-' + key
for key, value in methods:
contents = contents + self.document(
- value, key, funcs, classes, mdict, object)
+ value, key, mod, funcs, classes, mdict, object)
if name == realname:
title = 'class %s' % (
@@ -573,7 +571,7 @@ TT { font-family: lucida console, lucida typewriter, courier }
"""Format an argument default value as text."""
return self.small(self.grey('=' + self.repr(object)))
- def docroutine(self, object, name=None,
+ def docroutine(self, object, name=None, mod=None,
funcs={}, classes={}, methods={}, cl=None):
"""Produce HTML documentation for a function or method object."""
realname = object.__name__
@@ -583,18 +581,16 @@ TT { font-family: lucida console, lucida typewriter, courier }
skipdocs = 0
if inspect.ismethod(object):
if cl:
- if object.im_class is not cl:
- base = object.im_class
- url = '#%s-%s' % (base.__name__, name)
- basename = base.__name__
- if base.__module__ != cl.__module__:
- url = base.__module__ + '.html' + url
- basename = base.__module__ + '.' + basename
- note = ' from %s' % (url, basename)
+ imclass = object.im_class
+ if imclass is not cl:
+ url = '%s.html#%s-%s' % (
+ imclass.__module__, imclass.__name__, name)
+ note = ' from %s' % (
+ url, classname(imclass, mod))
skipdocs = 1
else:
note = (object.im_self and
- ' method of ' + self.repr(object.im_self) or
+ ' method of %s instance' + object.im_self.__class__ or
' unbound %s method' % object.im_class.__name__)
object = object.im_func
@@ -631,9 +627,10 @@ TT { font-family: lucida console, lucida typewriter, courier }
doc = doc and '' + self.small('%s' % doc)
return '- %s%s
\n' % (decl, doc)
- def docother(self, object, name=None):
+ def docother(self, object, name=None, mod=None):
"""Produce HTML documentation for a data object."""
- return '%s = %s' % (name, self.repr(object))
+ lhs = name and '%s = ' % name or ''
+ return lhs + self.repr(object)
def index(self, dir, shadowed=None):
"""Generate an HTML index for a directory of modules."""
@@ -682,7 +679,7 @@ class TextRepr(Repr):
def repr_string(self, x, level):
test = cram(x, self.maxstring)
testrepr = repr(test)
- if '\\' in test and '\\' not in replace(testrepr, (r'\\', '')):
+ if '\\' in test and '\\' not in replace(testrepr, r'\\', ''):
# Backslashes are only literal in the string and are never
# needed to make any special characters, so show a raw string.
return 'r' + testrepr[0] + test + testrepr[0]
@@ -736,7 +733,7 @@ class TextDoc(Doc):
entry, modname, c, prefix + ' ')
return result
- def docmodule(self, object, name=None):
+ def docmodule(self, object, name=None, mod=None):
"""Produce text documentation for a given module object."""
name = object.__name__ # ignore the passed-in name
synop, desc = splitdoc(getdoc(object))
@@ -780,19 +777,19 @@ class TextDoc(Doc):
contents = [self.formattree(
inspect.getclasstree(classlist, 1), name)]
for key, value in classes:
- contents.append(self.document(value, key))
+ contents.append(self.document(value, key, name))
result = result + self.section('CLASSES', join(contents, '\n'))
if funcs:
contents = []
for key, value in funcs:
- contents.append(self.document(value, key))
+ contents.append(self.document(value, key, name))
result = result + self.section('FUNCTIONS', join(contents, '\n'))
if constants:
contents = []
for key, value in constants:
- contents.append(self.docother(value, key, 70))
+ contents.append(self.docother(value, key, name, 70))
result = result + self.section('CONSTANTS', join(contents, '\n'))
if hasattr(object, '__version__'):
@@ -808,7 +805,7 @@ class TextDoc(Doc):
result = result + self.section('CREDITS', str(object.__credits__))
return result
- def docclass(self, object, name=None):
+ def docclass(self, object, name=None, mod=None):
"""Produce text documentation for a given class object."""
realname = object.__name__
name = name or realname
@@ -828,7 +825,7 @@ class TextDoc(Doc):
methods = allmethods(object).items()
methods.sort()
for key, value in methods:
- contents = contents + '\n' + self.document(value, key, object)
+ contents = contents + '\n' + self.document(value, key, mod, object)
if not contents: return title + '\n'
return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'
@@ -837,26 +834,22 @@ class TextDoc(Doc):
"""Format an argument default value as text."""
return '=' + self.repr(object)
- def docroutine(self, object, name=None, cl=None):
+ def docroutine(self, object, name=None, mod=None, cl=None):
"""Produce text documentation for a function or method object."""
realname = object.__name__
name = name or realname
note = ''
skipdocs = 0
if inspect.ismethod(object):
+ imclass = object.im_class
if cl:
- if object.im_class is not cl:
- base = object.im_class
- basename = base.__name__
- if base.__module__ != cl.__module__:
- basename = base.__module__ + '.' + basename
- note = ' from %s' % basename
+ if imclass is not cl:
+ note = ' from ' + classname(imclass, mod)
skipdocs = 1
else:
- if object.im_self:
- note = ' method of %s' % self.repr(object.im_self)
- else:
- note = ' unbound %s method' % object.im_class.__name__
+ note = (object.im_self and
+ ' method of %s instance' + object.im_self.__class__ or
+ ' unbound %s method' % classname(imclass, mod))
object = object.im_func
if name == realname:
@@ -883,14 +876,14 @@ class TextDoc(Doc):
doc = getdoc(object) or ''
return decl + '\n' + (doc and rstrip(self.indent(doc)) + '\n')
- def docother(self, object, name=None, maxlen=None):
+ def docother(self, object, name=None, mod=None, maxlen=None):
"""Produce text documentation for a data object."""
repr = self.repr(object)
if maxlen:
- line = name + ' = ' + repr
+ line = (name and name + ' = ' or '') + repr
chop = maxlen - len(line)
if chop < 0: repr = repr[:chop] + '...'
- line = self.bold(name) + ' = ' + repr
+ line = (name and self.bold(name) + ' = ' or '') + repr
return line
# --------------------------------------------------------- user interfaces
@@ -1017,95 +1010,83 @@ def describe(thing):
return 'instance of ' + thing.__class__.__name__
return type(thing).__name__
-def freshimport(name, cache={}):
- """Import a module, reloading it if the source file has changed."""
- topmodule = __import__(name)
- module = None
- for component in split(name, '.'):
- if module == None:
- module = topmodule
- path = split(name, '.')[0]
+def freshimport(path, cache={}):
+ """Import a module freshly from disk, making sure it's up to date."""
+ if sys.modules.has_key(path):
+ # This is the only way to be sure. Checking the mtime of the file
+ # isn't good enough (e.g. what if the module contains a class that
+ # inherits from another module that has changed?).
+ if path not in sys.builtin_module_names:
+ del sys.modules[path]
+ try:
+ module = __import__(path)
+ except:
+ # Did the error occur before or after the module was found?
+ (exc, value, tb) = info = sys.exc_info()
+ if sys.modules.has_key(path):
+ # An error occured while executing the imported module.
+ raise ErrorDuringImport(sys.modules[path].__file__, info)
+ elif exc is SyntaxError:
+ # A SyntaxError occurred before we could execute the module.
+ raise ErrorDuringImport(value.filename, info)
+ elif exc is ImportError and \
+ split(lower(str(value)))[:2] == ['no', 'module']:
+ # The module was not found.
+ return None
else:
- module = getattr(module, component)
- path = path + '.' + component
- if hasattr(module, '__file__'):
- file = module.__file__
- if os.path.exists(file):
- info = (file, os.path.getmtime(file), os.path.getsize(file))
- if cache.get(path) == info:
- continue
- module = reload(module)
- file = module.__file__
- if os.path.exists(file):
- info = (file, os.path.getmtime(file), os.path.getsize(file))
- cache[path] = info
+ # Some other error occurred during the importing process.
+ raise ErrorDuringImport(path, sys.exc_info())
+ for part in split(path, '.')[1:]:
+ try: module = getattr(module, part)
+ except AttributeError: return None
return module
def locate(path):
- """Locate an object by name (or dotted path), importing as necessary."""
- if not path: # special case: imp.find_module('') strangely succeeds
- return None
- if type(path) is not types.StringType:
- return path
+ """Locate an object by name or dotted path, importing as necessary."""
parts = split(path, '.')
- n = len(parts)
- while n > 0:
- path = join(parts[:n], '.')
- try:
- module = freshimport(path)
- except:
- # Did the error occur before or after the module was found?
- (exc, value, tb) = info = sys.exc_info()
- if sys.modules.has_key(path):
- # An error occured while executing the imported module.
- raise ErrorDuringImport(sys.modules[path].__file__, info)
- elif exc is SyntaxError:
- # A SyntaxError occurred before we could execute the module.
- raise ErrorDuringImport(value.filename, info)
- elif exc is ImportError and \
- split(lower(str(value)))[:2] == ['no', 'module']:
- # The module was not found.
- n = n - 1
- continue
- else:
- # Some other error occurred before executing the module.
- raise ErrorDuringImport(path, sys.exc_info())
- try:
- x = module
- for p in parts[n:]:
- x = getattr(x, p)
- return x
- except AttributeError:
- n = n - 1
- continue
- if hasattr(__builtins__, path):
- return getattr(__builtins__, path)
- return None
+ module, n = None, 0
+ while n < len(parts):
+ nextmodule = freshimport(join(parts[:n+1], '.'))
+ if nextmodule: module, n = nextmodule, n + 1
+ else: break
+ if module:
+ object = module
+ for part in parts[n:]:
+ try: object = getattr(object, part)
+ except AttributeError: return None
+ return object
+ else:
+ import __builtin__
+ if hasattr(__builtin__, path):
+ return getattr(__builtin__, path)
# --------------------------------------- interactive interpreter interface
text = TextDoc()
html = HTMLDoc()
-def doc(thing):
- """Display documentation on an object (for interactive use)."""
- if type(thing) is type(""):
+def doc(thing, title='Python Library Documentation: '):
+ """Display text documentation, given an object or a path to an object."""
+ suffix, name = '', None
+ if type(thing) is type(''):
try:
object = locate(thing)
except ErrorDuringImport, value:
print value
return
- if object:
- thing = object
- else:
+ if not object:
print 'no Python documentation found for %s' % repr(thing)
return
+ parts = split(thing, '.')
+ if len(parts) > 1: suffix = ' in ' + join(parts[:-1], '.')
+ name = parts[-1]
+ thing = object
desc = describe(thing)
module = inspect.getmodule(thing)
- if module and module is not thing:
- desc = desc + ' in module ' + module.__name__
- pager('Help on %s:\n\n' % desc + text.document(thing))
+ if not suffix and module and module is not thing:
+ suffix = ' in module ' + module.__name__
+ pager(title + desc + suffix + '\n\n' + text.document(object, name))
def writedoc(key):
"""Write HTML documentation to a file in the current directory."""
@@ -1154,25 +1135,13 @@ help(module) or call help('modulename').''' % sys.version[:3]
help = Helper()
-def man(key):
- """Display documentation on an object in a form similar to man(1)."""
- object = locate(key)
- if object:
- title = 'Python Library Documentation: ' + describe(object)
- lastdot = rfind(key, '.')
- if lastdot > 0: title = title + ' in ' + key[:lastdot]
- pager('\n' + title + '\n\n' + text.document(object, key))
- found = 1
- else:
- print 'no Python documentation found for %s' % repr(key)
-
class Scanner:
"""A generic tree iterator."""
- def __init__(self, roots, children, recurse):
+ def __init__(self, roots, children, descendp):
self.roots = roots[:]
self.state = []
self.children = children
- self.recurse = recurse
+ self.descendp = descendp
def next(self):
if not self.state:
@@ -1185,7 +1154,7 @@ class Scanner:
self.state.pop()
return self.next()
child = children.pop(0)
- if self.recurse(child):
+ if self.descendp(child):
self.state.append((child, self.children(child)))
return child
@@ -1203,13 +1172,14 @@ class ModuleScanner(Scanner):
children.append((path, package + (package and '.') + file))
else:
children.append((path, package))
- children.sort()
+ children.sort() # so that spam.py comes before spam.pyc or spam.pyo
return children
def ispackage(self, (dir, package)):
return ispackage(dir)
def run(self, key, callback, completer=None):
+ key = lower(key)
self.quit = 0
seen = {}
@@ -1217,7 +1187,7 @@ class ModuleScanner(Scanner):
if modname != '__main__':
seen[modname] = 1
desc = split(freshimport(modname).__doc__ or '', '\n')[0]
- if find(lower(modname + ' - ' + desc), lower(key)) >= 0:
+ if find(lower(modname + ' - ' + desc), key) >= 0:
callback(None, modname, desc)
while not self.quit:
@@ -1228,10 +1198,13 @@ class ModuleScanner(Scanner):
if os.path.isfile(path) and modname:
modname = package + (package and '.') + modname
if not seen.has_key(modname):
- seen[modname] = 1
- desc = synopsis(path) or ''
- if find(lower(modname + ' - ' + desc), lower(key)) >= 0:
- callback(path, modname, desc)
+ seen[modname] = 1 # if we see spam.py, skip spam.pyc
+ if key:
+ desc = synopsis(path) or ''
+ if find(lower(modname + ' - ' + desc), key) >= 0:
+ callback(path, modname, desc)
+ else:
+ callback(path, modname, '')
if completer: completer()
def apropos(key):
@@ -1247,8 +1220,8 @@ def apropos(key):
# --------------------------------------------------- web browser interface
-def serve(port, callback=None):
- import BaseHTTPServer, mimetools, select
+def serve(port, callback=None, finalizer=None):
+ import BaseHTTPServer, SocketServer, mimetools, select
# Patch up mimetools.Message so it doesn't break if rfc822 is reloaded.
class Message(mimetools.Message):
@@ -1306,7 +1279,7 @@ pydoc by Ka-Ping Yee <ping@lfw.org>'''
def log_message(self, *args): pass
- class DocServer(BaseHTTPServer.HTTPServer):
+ class DocServer(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
def __init__(self, port, callback):
host = (sys.platform == 'mac') and '127.0.0.1' or 'localhost'
self.address = ('', port)
@@ -1329,10 +1302,12 @@ pydoc by Ka-Ping Yee <ping@lfw.org>'''
DocServer.handler = DocHandler
DocHandler.MessageClass = Message
try:
- DocServer(port, callback).serve_until_quit()
- except (KeyboardInterrupt, select.error):
- pass
- print 'server stopped'
+ try:
+ DocServer(port, callback).serve_until_quit()
+ except (KeyboardInterrupt, select.error):
+ pass
+ finally:
+ if finalizer: finalizer()
# ----------------------------------------------------- graphical interface
@@ -1404,7 +1379,8 @@ def gui():
self.window.wm_minsize(self.minwidth, self.minheight)
import threading
- threading.Thread(target=serve, args=(port, self.ready)).start()
+ threading.Thread(
+ target=serve, args=(port, self.ready, self.quit)).start()
def ready(self, server):
self.server = server
@@ -1544,8 +1520,10 @@ def cli():
except ValueError:
raise BadUsage
def ready(server):
- print 'server ready at %s' % server.url
- serve(port, ready)
+ print 'pydoc server ready at %s' % server.url
+ def stopped():
+ print 'pydoc server stopped'
+ serve(port, ready, stopped)
return
if opt == '-w':
writing = 1
@@ -1561,7 +1539,7 @@ def cli():
else:
writedoc(arg)
else:
- man(arg)
+ doc(arg)
except ErrorDuringImport, value:
print value