Docstring improvements.

Add checks for .pyo and .pyd.
Collapse docfunction, docmethod, docbuiltin into the one method docroutine.
Small formatting fixes.
Link the segments of a package path in the title.
Link to the source file only if it exists.
Allow modules (e.g. repr.py) to take precedence over built-ins (e.g. repr()).
Add interruptible synopsis scanner (so we can do searches in the background).
Make HTTP server quit.
Add small GUI for controlling the server and launching searches (like -k).
    (Tested on Win2k, Win98, and Linux.)
This commit is contained in:
Ka-Ping Yee 2001-03-01 13:55:20 +00:00
parent dbe6ebbeff
commit 66efbc7481

View File

@ -1,23 +1,28 @@
#!/usr/bin/env python #!/usr/bin/env python
"""Generate Python documentation in HTML or text for interactive use. """Generate Python documentation in HTML or text for interactive use.
At the shell command line outside of Python, run "pydoc <name>" to show
documentation on something. <name> may be the name of a Python function,
module, package, or a dotted reference to a class or function within a
module or module in a package. Alternatively, the argument can be the
path to a Python source file.
Or, at the shell prompt, run "pydoc -k <keyword>" to search for a keyword
in the one-line descriptions of modules.
Or, at the shell prompt, run "pydoc -p <port>" to start an HTTP server
on a given port on the local machine to generate documentation web pages.
Or, at the shell prompt, run "pydoc -w <name>" to write out the HTML
documentation for a module to a file named "<name>.html".
In the Python interpreter, do "from pydoc import help" to provide online In the Python interpreter, do "from pydoc import help" to provide online
help. Calling help(thing) on a Python object documents the object.""" help. Calling help(thing) on a Python object documents the object.
At the shell command line outside of Python:
Run "pydoc <name>" to show documentation on something. <name> may be
the name of a function, module, package, or a dotted reference to a
class or function within a module or module in a package. If the
argument contains a path segment delimiter (e.g. slash on Unix,
backslash on Windows) it is treated as the path to a Python source file.
Run "pydoc -k <keyword>" to search for a keyword in the synopsis lines
of all available modules.
Run "pydoc -p <port>" to start an HTTP server on a given port on the
local machine to generate documentation web pages.
For platforms without a command line, "pydoc -g" starts the HTTP server
and also pops up a little window for controlling it.
Run "pydoc -w <name>" to write out the HTML documentation for a module
to a file named "<name>.html".
"""
__author__ = "Ka-Ping Yee <ping@lfw.org>" __author__ = "Ka-Ping Yee <ping@lfw.org>"
__date__ = "26 February 2001" __date__ = "26 February 2001"
@ -29,6 +34,10 @@ Richard Chamberlain, for the first implementation of textdoc.
Mynd you, møøse bites Kan be pretty nasti...""" Mynd you, møøse bites Kan be pretty nasti..."""
# Note: this module is designed to deploy instantly and run under any
# version of Python from 1.5 and up. That's why it's a single file and
# some 2.0 features (like string methods) are conspicuously avoided.
import sys, imp, os, stat, re, types, inspect import sys, imp, os, stat, re, types, inspect
from repr import Repr from repr import Repr
from string import expandtabs, find, join, lower, split, strip, rstrip from string import expandtabs, find, join, lower, split, strip, rstrip
@ -59,18 +68,6 @@ def synopsis(filename, cache={}):
cache[filename] = (mtime, result) cache[filename] = (mtime, result)
return result return result
def index(dir):
"""Return a list of (module-name, synopsis) pairs for a directory tree."""
results = []
for entry in os.listdir(dir):
path = os.path.join(dir, entry)
if ispackage(path):
results.extend(map(
lambda (m, s), pkg=entry: (pkg + '.' + m, s), index(path)))
elif os.path.isfile(path) and entry[-3:] == '.py':
results.append((entry[:-3], synopsis(path)))
return results
def pathdirs(): def pathdirs():
"""Convert sys.path into a list of absolute, existing, unique paths.""" """Convert sys.path into a list of absolute, existing, unique paths."""
dirs = [] dirs = []
@ -132,7 +129,7 @@ def modulename(path):
filename = os.path.basename(path) filename = os.path.basename(path)
if lower(filename[-3:]) == '.py': if lower(filename[-3:]) == '.py':
return filename[:-3] return filename[:-3]
elif lower(filename[-4:]) == '.pyc': elif lower(filename[-4:]) in ['.pyc', '.pyd', '.pyo']:
return filename[:-4] return filename[:-4]
elif lower(filename[-11:]) == 'module.so': elif lower(filename[-11:]) == 'module.so':
return filename[:-11] return filename[:-11]
@ -184,9 +181,7 @@ class Doc:
args = (object,) + args args = (object,) + args
if inspect.ismodule(object): return apply(self.docmodule, args) if inspect.ismodule(object): return apply(self.docmodule, args)
if inspect.isclass(object): return apply(self.docclass, args) if inspect.isclass(object): return apply(self.docclass, args)
if inspect.ismethod(object): return apply(self.docmethod, args) if inspect.isroutine(object): return apply(self.docroutine, args)
if inspect.isbuiltin(object): return apply(self.docbuiltin, args)
if inspect.isfunction(object): return apply(self.docfunction, args)
raise TypeError, "don't know how to document objects of type " + \ raise TypeError, "don't know how to document objects of type " + \
type(object).__name__ type(object).__name__
@ -258,11 +253,12 @@ class HTMLDoc(Doc):
"""Format a page heading.""" """Format a page heading."""
return """ return """
<p><table width="100%%" cellspacing=0 cellpadding=0 border=0> <p><table width="100%%" cellspacing=0 cellpadding=0 border=0>
<tr bgcolor="%s"><td colspan=3 valign=bottom><small><small><br></small></small <tr bgcolor="%s"><td>&nbsp;</td>
><font color="%s" face="helvetica, arial">&nbsp;%s</font></td <td valign=bottom><small><small><br></small></small
><font color="%s" face="helvetica"><br>&nbsp;%s</font></td
><td align=right valign=bottom ><td align=right valign=bottom
><font color="%s" face="helvetica, arial">&nbsp;%s</font></td></tr></table> ><font color="%s" face="helvetica">%s</font></td><td>&nbsp;</td></tr></table>
""" % (bgcol, fgcol, title, fgcol, extras) """ % (bgcol, fgcol, title, fgcol, extras or '&nbsp;')
def section(self, title, fgcol, bgcol, contents, width=20, def section(self, title, fgcol, bgcol, contents, width=20,
prelude='', marginalia=None, gap='&nbsp;&nbsp;&nbsp;'): prelude='', marginalia=None, gap='&nbsp;&nbsp;&nbsp;'):
@ -271,7 +267,8 @@ class HTMLDoc(Doc):
marginalia = '&nbsp;' * width marginalia = '&nbsp;' * width
result = """ result = """
<p><table width="100%%" cellspacing=0 cellpadding=0 border=0> <p><table width="100%%" cellspacing=0 cellpadding=0 border=0>
<tr bgcolor="%s"><td colspan=3 valign=bottom><small><small><br></small></small <tr bgcolor="%s"><td rowspan=2>&nbsp;</td>
<td colspan=3 valign=bottom><small><small><br></small></small
><font color="%s" face="helvetica, arial">&nbsp;%s</font></td></tr> ><font color="%s" face="helvetica, arial">&nbsp;%s</font></td></tr>
""" % (bgcol, fgcol, title) """ % (bgcol, fgcol, title)
if prelude: if prelude:
@ -291,14 +288,6 @@ class HTMLDoc(Doc):
title = '<big><strong>%s</strong></big>' % title title = '<big><strong>%s</strong></big>' % title
return apply(self.section, (title,) + args) return apply(self.section, (title,) + args)
def footer(self):
return """
<table width="100%"><tr><td align=right>
<font face="helvetica, arial"><small><small>generated with
<strong>htmldoc</strong> by Ka-Ping Yee</a></small></small></font>
</td></tr></table>
"""
def namelink(self, name, *dicts): def namelink(self, name, *dicts):
"""Make a link for an identifier, given name-to-URL mappings.""" """Make a link for an identifier, given name-to-URL mappings."""
for dict in dicts: for dict in dicts:
@ -390,10 +379,18 @@ class HTMLDoc(Doc):
def docmodule(self, object): def docmodule(self, object):
"""Produce HTML documentation for a module object.""" """Produce HTML documentation for a module object."""
name = object.__name__ name = object.__name__
result = '' parts = split(name, '.')
head = '<br><big><big><strong>&nbsp;%s</strong></big></big>' % name links = []
for i in range(len(parts)-1):
links.append(
'<a href="%s.html"><font color="#ffffff">%s</font></a>' %
(join(parts[:i+1], '.'), parts[i]))
linkedname = join(links + parts[-1:], '.')
head = '<big><big><strong>%s</strong></big></big>' % linkedname
try: try:
path = os.path.abspath(inspect.getfile(object)) path = os.path.abspath(inspect.getfile(object))
sourcepath = os.path.abspath(inspect.getsourcefile(object))
if os.path.isfile(sourcepath): path = sourcepath
filelink = '<a href="file:%s">%s</a>' % (path, path) filelink = '<a href="file:%s">%s</a>' % (path, path)
except TypeError: except TypeError:
filelink = '(built-in)' filelink = '(built-in)'
@ -407,7 +404,7 @@ class HTMLDoc(Doc):
info.append(self.escape(str(object.__date__))) info.append(self.escape(str(object.__date__)))
if info: if info:
head = head + ' (%s)' % join(info, ', ') head = head + ' (%s)' % join(info, ', ')
result = result + self.heading( result = self.heading(
head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink) head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)
second = lambda list: list[1] second = lambda list: list[1]
@ -519,21 +516,21 @@ class HTMLDoc(Doc):
title = title + '(%s)' % join(parents, ', ') title = title + '(%s)' % join(parents, ', ')
doc = self.markup(getdoc(object), self.preformat, doc = self.markup(getdoc(object), self.preformat,
funcs, classes, mdict) funcs, classes, mdict)
if doc: doc = '<small><tt>' + doc + '<br>&nbsp;</tt></small>' if doc: doc = '<small><tt>' + doc + '</tt></small>'
return self.section(title, '#000000', '#ffc8d8', contents, 10, doc) return self.section(title, '#000000', '#ffc8d8', contents, 10, doc)
def docmethod(self, object, funcs={}, classes={}, methods={}, clname=''):
"""Produce HTML documentation for a method object."""
return self.document(
object.im_func, funcs, classes, methods, clname)
def formatvalue(self, object): def formatvalue(self, object):
"""Format an argument default value as text.""" """Format an argument default value as text."""
return ('<small><font color="#909090">=%s</font></small>' % return ('<small><font color="#909090">=%s</font></small>' %
self.repr(object)) self.repr(object))
def docfunction(self, object, funcs={}, classes={}, methods={}, clname=''): def docroutine(self, object, funcs={}, classes={}, methods={}, clname=''):
"""Produce HTML documentation for a function object.""" """Produce HTML documentation for a function or method object."""
if inspect.ismethod(object): object = object.im_func
if inspect.isbuiltin(object):
decl = '<a name="%s"><strong>%s</strong>(...)</a>\n' % (
clname + '-' + object.__name__, object.__name__)
else:
args, varargs, varkw, defaults = inspect.getargspec(object) args, varargs, varkw, defaults = inspect.getargspec(object)
argspec = inspect.formatargspec( argspec = inspect.formatargspec(
args, varargs, varkw, defaults, formatvalue=self.formatvalue) args, varargs, varkw, defaults, formatvalue=self.formatvalue)
@ -550,15 +547,11 @@ class HTMLDoc(Doc):
doc = doc and '<tt>%s</tt>' % doc doc = doc and '<tt>%s</tt>' % doc
return '<dl><dt>%s<dd><small>%s</small></dl>' % (decl, doc) return '<dl><dt>%s<dd><small>%s</small></dl>' % (decl, doc)
def docbuiltin(self, object, *extras):
"""Produce HTML documentation for a built-in function."""
return '<dl><dt><strong>%s</strong>(...)</dl>' % object.__name__
def page(self, object): def page(self, object):
"""Produce a complete HTML page of documentation for an object.""" """Produce a complete HTML page of documentation for an object."""
return '''<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"> return '''
<html><title>Python: %s</title> <!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
<body bgcolor="#ffffff"> <html><title>Python: %s</title><body bgcolor="#ffffff">
%s %s
</body></html> </body></html>
''' % (describe(object), self.document(object)) ''' % (describe(object), self.document(object))
@ -757,23 +750,19 @@ class TextDoc(Doc):
if not contents: return title + '\n' if not contents: return title + '\n'
return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n' return title + '\n' + self.indent(rstrip(contents), ' | ') + '\n'
def docmethod(self, object):
"""Produce text documentation for a method object."""
return self.document(object.im_func)
def formatvalue(self, object): def formatvalue(self, object):
"""Format an argument default value as text.""" """Format an argument default value as text."""
return '=' + self.repr(object) return '=' + self.repr(object)
def docfunction(self, object): def docroutine(self, object):
"""Produce text documentation for a function object.""" """Produce text documentation for a function or method object."""
try: if inspect.ismethod(object): object = object.im_func
if inspect.isbuiltin(object):
decl = self.bold(object.__name__) + '(...)'
else:
args, varargs, varkw, defaults = inspect.getargspec(object) args, varargs, varkw, defaults = inspect.getargspec(object)
argspec = inspect.formatargspec( argspec = inspect.formatargspec(
args, varargs, varkw, defaults, formatvalue=self.formatvalue) args, varargs, varkw, defaults, formatvalue=self.formatvalue)
except TypeError:
argspec = '(...)'
if object.__name__ == '<lambda>': if object.__name__ == '<lambda>':
decl = '<lambda> ' + argspec[1:-1] decl = '<lambda> ' + argspec[1:-1]
else: else:
@ -784,11 +773,6 @@ class TextDoc(Doc):
else: else:
return decl + '\n' return decl + '\n'
def docbuiltin(self, object):
"""Produce text documentation for a built-in function object."""
return (self.bold(object.__name__) + '(...)\n' +
rstrip(self.indent(object.__doc__)) + '\n')
# --------------------------------------------------------- user interfaces # --------------------------------------------------------- user interfaces
def pager(text): def pager(text):
@ -914,8 +898,6 @@ def locate(path):
return None, None return None, None
if type(path) is not types.StringType: if type(path) is not types.StringType:
return None, path return None, path
if hasattr(__builtins__, path):
return None, getattr(__builtins__, path)
parts = split(path, '.') parts = split(path, '.')
n = 1 n = 1
while n <= len(parts): while n <= len(parts):
@ -924,7 +906,7 @@ def locate(path):
module = __import__(path) module = __import__(path)
module = reload(module) module = reload(module)
except: except:
# Did the error occur before or after we found the module? # determine if error occurred before or after module was found
if sys.modules.has_key(path): if sys.modules.has_key(path):
filename = sys.modules[path].__file__ filename = sys.modules[path].__file__
elif sys.exc_type is SyntaxError: elif sys.exc_type is SyntaxError:
@ -942,6 +924,8 @@ def locate(path):
except AttributeError: except AttributeError:
n = n + 1 n = n + 1
continue continue
if hasattr(__builtins__, path):
return None, getattr(__builtins__, path)
return None, None return None, None
# --------------------------------------- interactive interpreter interface # --------------------------------------- interactive interpreter interface
@ -955,12 +939,12 @@ def doc(thing):
try: try:
path, x = locate(thing) path, x = locate(thing)
except DocImportError, value: except DocImportError, value:
print 'problem in %s - %s' % (value.filename, value.args) print 'Problem in %s - %s' % (value.filename, value.args)
return return
if x: if x:
thing = x thing = x
else: else:
print 'could not find or import %s' % repr(thing) print 'No Python documentation found for %s.' % repr(thing)
return return
desc = describe(thing) desc = describe(thing)
@ -969,20 +953,6 @@ def doc(thing):
desc = desc + ' in module ' + module.__name__ desc = desc + ' in module ' + module.__name__
pager('Help on %s:\n\n' % desc + text.document(thing)) pager('Help on %s:\n\n' % desc + text.document(thing))
def writedocs(path, pkgpath=''):
if os.path.isdir(path):
dir = path
for file in os.listdir(dir):
path = os.path.join(dir, file)
if os.path.isdir(path):
writedocs(path, file + '.' + pkgpath)
if os.path.isfile(path):
writedocs(path, pkgpath)
if os.path.isfile(path):
modname = modulename(path)
if modname:
writedoc(pkgpath + modname)
def writedoc(key): def writedoc(key):
"""Write HTML documentation to a file in the current directory.""" """Write HTML documentation to a file in the current directory."""
path, object = locate(key) path, object = locate(key)
@ -1015,33 +985,87 @@ def man(key):
pager('\n' + title + '\n\n' + text.document(object)) pager('\n' + title + '\n\n' + text.document(object))
found = 1 found = 1
else: else:
print 'could not find or import %s' % repr(key) print 'No Python documentation found for %s.' % repr(key)
class Scanner:
"""A generic tree iterator."""
def __init__(self, roots, children, recurse):
self.roots = roots[:]
self.state = []
self.children = children
self.recurse = recurse
def next(self):
if not self.state:
if not self.roots:
return None
root = self.roots.pop(0)
self.state = [(root, self.children(root))]
node, children = self.state[-1]
if not children:
self.state.pop()
return self.next()
child = children.pop(0)
if self.recurse(child):
self.state.append((child, self.children(child)))
return child
class ModuleScanner(Scanner):
"""An interruptible scanner that searches module synopses."""
def __init__(self):
roots = map(lambda dir: (dir, ''), pathdirs())
Scanner.__init__(self, roots, self.submodules, self.ispackage)
def submodules(self, (dir, package)):
children = []
for file in os.listdir(dir):
path = os.path.join(dir, file)
if ispackage(path):
children.append((path, package + (package and '.') + file))
else:
children.append((path, package))
children.sort()
return children
def ispackage(self, (dir, package)):
return ispackage(dir)
def run(self, key, callback, completer=None):
self.quit = 0
seen = {}
for modname in sys.builtin_module_names:
seen[modname] = 1
desc = split(__import__(modname).__doc__ or '', '\n')[0]
if find(lower(modname + ' - ' + desc), lower(key)) >= 0:
callback(None, modname, desc)
while not self.quit:
node = self.next()
if not node: break
path, package = node
modname = modulename(path)
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)
if completer: completer()
def apropos(key): def apropos(key):
"""Print all the one-line module summaries that contain a substring.""" """Print all the one-line module summaries that contain a substring."""
key = lower(key) def callback(path, modname, desc):
for module in sys.builtin_module_names: if modname[-9:] == '.__init__':
desc = __import__(module).__doc__ or '' modname = modname[:-9] + ' (package)'
desc = split(desc, '\n')[0] print modname, '-', desc or '(no description)'
if find(lower(module + ' ' + desc), key) >= 0: ModuleScanner().run(key, callback)
print module, '-', desc or '(no description)'
modules = []
for dir in pathdirs():
for module, desc in index(dir):
desc = desc or ''
if module not in modules:
modules.append(module)
if find(lower(module + ' ' + desc), key) >= 0:
desc = desc or '(no description)'
if module[-9:] == '.__init__':
print module[:-9], '(package) -', desc
else:
print module, '-', desc
# --------------------------------------------------- web browser interface # --------------------------------------------------- web browser interface
def serve(address, callback=None): def serve(port, callback=None):
import BaseHTTPServer, mimetools import BaseHTTPServer, mimetools, select
# Patch up mimetools.Message so it doesn't break if rfc822 is reloaded. # Patch up mimetools.Message so it doesn't break if rfc822 is reloaded.
class Message(mimetools.Message): class Message(mimetools.Message):
@ -1055,14 +1079,16 @@ def serve(address, callback=None):
class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler): class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def send_document(self, title, contents): def send_document(self, title, contents):
try:
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'text/html') self.send_header('Content-Type', 'text/html')
self.end_headers() self.end_headers()
self.wfile.write( self.wfile.write('''
'''<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN"> <!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><title>Python: %s</title><body bgcolor="#ffffff">''' % title) <html><title>Python: %s</title><body bgcolor="#ffffff">
self.wfile.write(contents) %s
self.wfile.write('</body></html>') </body></html>''' % (title, contents))
except IOError: pass
def do_GET(self): def do_GET(self):
path = self.path path = self.path
@ -1073,19 +1099,17 @@ def serve(address, callback=None):
p, x = locate(path) p, x = locate(path)
except DocImportError, value: except DocImportError, value:
self.send_document(path, html.escape( self.send_document(path, html.escape(
'problem with %s - %s' % (value.filename, value.args))) 'Problem in %s - %s' % (value.filename, value.args)))
return return
if x: if x:
self.send_document(describe(x), html.document(x)) self.send_document(describe(x), html.document(x))
else: else:
self.send_document(path, self.send_document(path,
'There is no Python module or object named "%s".' % path) 'No Python documentation found for %s.' % repr(path))
else: else:
heading = html.heading( heading = html.heading(
'<br><big><big><strong>&nbsp;' '<big><big><strong>Python: Index of Modules</strong></big></big>',
'Python: Index of Modules' '#ffffff', '#7799ee')
'</strong></big></big>',
'#ffffff', '#7799ee')
builtins = [] builtins = []
for name in sys.builtin_module_names: for name in sys.builtin_module_names:
builtins.append('<a href="%s.html">%s</a>' % (name, name)) builtins.append('<a href="%s.html">%s</a>' % (name, name))
@ -1093,100 +1117,275 @@ def serve(address, callback=None):
seen = {} seen = {}
for dir in pathdirs(): for dir in pathdirs():
indices.append(html.index(dir, seen)) indices.append(html.index(dir, seen))
self.send_document('Index of Modules', heading + join(indices)) contents = heading + join(indices) + """<p align=right>
<small><small><font color="#909090" face="helvetica, arial"><strong>
pydoc</strong> by Ka-Ping Yee &lt;ping@lfw.org&gt;</font></small></small>"""
self.send_document('Index of Modules', contents)
def log_message(self, *args): pass def log_message(self, *args): pass
class DocServer(BaseHTTPServer.HTTPServer): class DocServer(BaseHTTPServer.HTTPServer):
def __init__(self, address, callback): def __init__(self, port, callback):
self.address = ('127.0.0.1', port)
self.url = 'http://127.0.0.1:%d/' % port
self.callback = callback self.callback = callback
self.base.__init__(self, address, self.handler) self.base.__init__(self, self.address, self.handler)
def serve_until_quit(self):
import select
self.quit = 0
while not self.quit:
rd, wr, ex = select.select([self.socket.fileno()], [], [], 1)
if rd: self.handle_request()
def server_activate(self): def server_activate(self):
self.base.server_activate(self) self.base.server_activate(self)
if self.callback: self.callback() if self.callback: self.callback(self)
DocServer.base = BaseHTTPServer.HTTPServer DocServer.base = BaseHTTPServer.HTTPServer
DocServer.handler = DocHandler DocServer.handler = DocHandler
DocHandler.MessageClass = Message DocHandler.MessageClass = Message
try: try:
DocServer(address, callback).serve_forever() DocServer(port, callback).serve_until_quit()
except KeyboardInterrupt: except (KeyboardInterrupt, select.error):
pass
print 'server stopped' print 'server stopped'
# ----------------------------------------------------- graphical interface
def gui():
"""Graphical interface (starts web server and pops up a control window)."""
class GUI:
def __init__(self, window, port=7464):
self.window = window
self.server = None
self.scanner = None
import Tkinter
self.server_frm = Tkinter.Frame(window)
self.title_lbl = Tkinter.Label(self.server_frm,
text='Starting server...\n ')
self.open_btn = Tkinter.Button(self.server_frm,
text='open browser', command=self.open, state='disabled')
self.quit_btn = Tkinter.Button(self.server_frm,
text='quit serving', command=self.quit, state='disabled')
self.search_frm = Tkinter.Frame(window)
self.search_lbl = Tkinter.Label(self.search_frm, text='Search for')
self.search_ent = Tkinter.Entry(self.search_frm)
self.search_ent.bind('<Return>', self.search)
self.stop_btn = Tkinter.Button(self.search_frm,
text='stop', pady=0, command=self.stop, state='disabled')
if sys.platform == 'win32':
# Attempting to hide and show this button crashes under Windows.
self.stop_btn.pack(side='right')
self.window.title('pydoc')
self.window.protocol('WM_DELETE_WINDOW', self.quit)
self.title_lbl.pack(side='top', fill='x')
self.open_btn.pack(side='left', fill='x', expand=1)
self.quit_btn.pack(side='right', fill='x', expand=1)
self.server_frm.pack(side='top', fill='x')
self.search_lbl.pack(side='left')
self.search_ent.pack(side='right', fill='x', expand=1)
self.search_frm.pack(side='top', fill='x')
self.search_ent.focus_set()
self.result_lst = Tkinter.Listbox(window,
font=('helvetica', 8), height=6)
self.result_lst.bind('<Button-1>', self.select)
self.result_lst.bind('<Double-Button-1>', self.goto)
self.result_scr = Tkinter.Scrollbar(window,
orient='vertical', command=self.result_lst.yview)
self.result_lst.config(yscrollcommand=self.result_scr.set)
self.result_frm = Tkinter.Frame(window)
self.goto_btn = Tkinter.Button(self.result_frm,
text='go to selected', command=self.goto)
self.hide_btn = Tkinter.Button(self.result_frm,
text='hide results', command=self.hide)
self.goto_btn.pack(side='left', fill='x', expand=1)
self.hide_btn.pack(side='right', fill='x', expand=1)
self.window.update()
self.minwidth = self.window.winfo_width()
self.minheight = self.window.winfo_height()
self.bigminheight = (self.server_frm.winfo_reqheight() +
self.search_frm.winfo_reqheight() +
self.result_lst.winfo_reqheight() +
self.result_frm.winfo_reqheight())
self.bigwidth, self.bigheight = self.minwidth, self.bigminheight
self.expanded = 0
self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
self.window.wm_minsize(self.minwidth, self.minheight)
import threading
threading.Thread(target=serve, args=(port, self.ready)).start()
def ready(self, server):
self.server = server
self.title_lbl.config(
text='Python documentation server at\n' + server.url)
self.open_btn.config(state='normal')
self.quit_btn.config(state='normal')
def open(self, event=None):
import webbrowser
webbrowser.open(self.server.url)
def quit(self, event=None):
if self.server:
self.server.quit = 1
self.window.quit()
def search(self, event=None):
key = self.search_ent.get()
self.stop_btn.pack(side='right')
self.stop_btn.config(state='normal')
self.search_lbl.config(text='Searching for "%s"...' % key)
self.search_ent.forget()
self.search_lbl.pack(side='left')
self.result_lst.delete(0, 'end')
self.goto_btn.config(state='disabled')
self.expand()
import threading
if self.scanner:
self.scanner.quit = 1
self.scanner = ModuleScanner()
threading.Thread(target=self.scanner.run,
args=(key, self.update, self.done)).start()
def update(self, path, modname, desc):
if modname[-9:] == '.__init__':
modname = modname[:-9] + ' (package)'
self.result_lst.insert('end',
modname + ' - ' + (desc or '(no description)'))
def stop(self, event=None):
if self.scanner:
self.scanner.quit = 1
self.scanner = None
def done(self):
self.scanner = None
self.search_lbl.config(text='Search for')
self.search_lbl.pack(side='left')
self.search_ent.pack(side='right', fill='x', expand=1)
if sys.platform != 'win32': self.stop_btn.forget()
self.stop_btn.config(state='disabled')
def select(self, event=None):
self.goto_btn.config(state='normal')
def goto(self, event=None):
selection = self.result_lst.curselection()
if selection:
import webbrowser
modname = split(self.result_lst.get(selection[0]))[0]
webbrowser.open(self.server.url + modname + '.html')
def collapse(self):
if not self.expanded: return
self.result_frm.forget()
self.result_scr.forget()
self.result_lst.forget()
self.bigwidth = self.window.winfo_width()
self.bigheight = self.window.winfo_height()
self.window.wm_geometry('%dx%d' % (self.minwidth, self.minheight))
self.window.wm_minsize(self.minwidth, self.minheight)
self.expanded = 0
def expand(self):
if self.expanded: return
self.result_frm.pack(side='bottom', fill='x')
self.result_scr.pack(side='right', fill='y')
self.result_lst.pack(side='top', fill='both', expand=1)
self.window.wm_geometry('%dx%d' % (self.bigwidth, self.bigheight))
self.window.wm_minsize(self.minwidth, self.bigminheight)
self.expanded = 1
def hide(self, event=None):
self.stop()
self.collapse()
import Tkinter
try:
gui = GUI(Tkinter.Tk())
Tkinter.mainloop()
except KeyboardInterrupt:
pass
# -------------------------------------------------- command-line interface # -------------------------------------------------- command-line interface
def cli(): def cli():
"""Command-line interface (looks at sys.argv to decide what to do)."""
import getopt import getopt
class BadUsage: pass class BadUsage: pass
try: try:
opts, args = getopt.getopt(sys.argv[1:], 'k:p:w') if sys.platform in ['mac', 'win', 'win32', 'nt'] and not sys.argv[1:]:
# CLI-less platforms
gui()
return
opts, args = getopt.getopt(sys.argv[1:], 'gk:p:w')
writing = 0 writing = 0
for opt, val in opts: for opt, val in opts:
if opt == '-g':
gui()
return
if opt == '-k': if opt == '-k':
apropos(lower(val)) apropos(val)
break return
if opt == '-p': if opt == '-p':
try: try:
port = int(val) port = int(val)
except ValueError: except ValueError:
raise BadUsage raise BadUsage
def ready(port=port): def ready(server):
print 'server ready at http://127.0.0.1:%d/' % port print 'server ready at %s' % server.url
serve(('127.0.0.1', port), ready) serve(port, ready)
break return
if opt == '-w': if opt == '-w':
if not args: raise BadUsage
writing = 1 writing = 1
else:
if args: if not args: raise BadUsage
for arg in args: for arg in args:
try: try:
if os.path.isfile(arg): if find(arg, os.sep) >= 0 and os.path.isfile(arg):
arg = importfile(arg) arg = importfile(arg)
if writing: if writing: writedoc(arg)
if os.path.isdir(arg): writedocs(arg)
else: writedoc(arg)
else: man(arg) else: man(arg)
except DocImportError, value: except DocImportError, value:
print 'problem in %s - %s' % ( print 'Problem in %s - %s' % (value.filename, value.args)
value.filename, value.args)
else:
if sys.platform in ['mac', 'win', 'win32', 'nt']:
# GUI platforms with threading
import threading
ready = threading.Event()
address = ('127.0.0.1', 12346)
threading.Thread(
target=serve, args=(address, ready.set)).start()
ready.wait()
import webbrowser
webbrowser.open('http://127.0.0.1:12346/')
else:
raise BadUsage
except (getopt.error, BadUsage): except (getopt.error, BadUsage):
print """%s <name> ... cmd = sys.argv[0]
Show documentation on something. print """pydoc - the Python documentation tool
<name> may be the name of a Python function, module, or package,
or a dotted reference to a class or function within a module or %s <name> ...
module in a package, or the filename of a Python module to import. Show text documentation on something. <name> may be the name of a
function, module, or package, or a dotted reference to a class or
function within a module or module in a package. If <name> contains
a '%s', it is used as the path to a Python source file to document.
%s -k <keyword> %s -k <keyword>
Search for a keyword in the synopsis lines of all modules. Search for a keyword in the synopsis lines of all available modules.
%s -p <port> %s -p <port>
Start an HTTP server on the given port on the local machine. Start an HTTP server on the given port on the local machine.
%s -w <module> ... %s -g
Write out the HTML documentation for a module to a file. Pop up a graphical interface for serving and finding documentation.
%s -w <name> ...
Write out the HTML documentation for a module to a file in the current
directory. If <name> contains a '%s', it is treated as a filename.
""" % (cmd, os.sep, cmd, cmd, cmd, cmd, os.sep)
if __name__ == '__main__': cli()
%s -w <moduledir>
Write out the HTML documentation for all modules in the tree
under a given directory to files in the current directory.
""" % ((sys.argv[0],) * 5)
if __name__ == '__main__':
cli()