[Python-checkins] CVS: python/dist/src/Lib pydoc.py,1.8,1.9

Ka-Ping Yee ping@users.sourceforge.net
Thu, 01 Mar 2001 05:55:22 -0800


Update of /cvsroot/python/python/dist/src/Lib
In directory usw-pr-cvs1:/tmp/cvs-serv13478

Modified Files:
	pydoc.py 
Log Message:
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.)


Index: pydoc.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/pydoc.py,v
retrieving revision 1.8
retrieving revision 1.9
diff -C2 -r1.8 -r1.9
*** pydoc.py	2001/03/01 00:24:32	1.8
--- pydoc.py	2001/03/01 13:55:20	1.9
***************
*** 2,22 ****
  """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
! help.  Calling help(thing) on a Python object documents the object."""
  
  __author__ = "Ka-Ping Yee <ping@lfw.org>"
--- 2,27 ----
  """Generate Python documentation in HTML or text for interactive use.
  
  In the Python interpreter, do "from pydoc import help" to provide online
! 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>"
***************
*** 30,33 ****
--- 35,42 ----
  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
  from repr import Repr
***************
*** 60,75 ****
      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():
      """Convert sys.path into a list of absolute, existing, unique paths."""
--- 69,72 ----
***************
*** 133,137 ****
      if lower(filename[-3:]) == '.py':
          return filename[:-3]
!     elif lower(filename[-4:]) == '.pyc':
          return filename[:-4]
      elif lower(filename[-11:]) == 'module.so':
--- 130,134 ----
      if lower(filename[-3:]) == '.py':
          return filename[:-3]
!     elif lower(filename[-4:]) in ['.pyc', '.pyd', '.pyo']:
          return filename[:-4]
      elif lower(filename[-11:]) == 'module.so':
***************
*** 185,191 ****
          if inspect.ismodule(object): return apply(self.docmodule, args)
          if inspect.isclass(object): return apply(self.docclass, args)
!         if inspect.ismethod(object): return apply(self.docmethod, 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 " + \
              type(object).__name__
--- 182,186 ----
          if inspect.ismodule(object): return apply(self.docmodule, args)
          if inspect.isclass(object): return apply(self.docclass, args)
!         if inspect.isroutine(object): return apply(self.docroutine, args)
          raise TypeError, "don't know how to document objects of type " + \
              type(object).__name__
***************
*** 259,267 ****
          return """
  <p><table width="100%%" cellspacing=0 cellpadding=0 border=0>
! <tr bgcolor="%s"><td colspan=3 valign=bottom><small><small><br></small></small
! ><font color="%s" face="helvetica, arial">&nbsp;%s</font></td
  ><td align=right valign=bottom
! ><font color="%s" face="helvetica, arial">&nbsp;%s</font></td></tr></table>
!     """ % (bgcol, fgcol, title, fgcol, extras)
  
      def section(self, title, fgcol, bgcol, contents, width=20,
--- 254,263 ----
          return """
  <p><table width="100%%" cellspacing=0 cellpadding=0 border=0>
! <tr bgcolor="%s"><td>&nbsp;</td>
! <td valign=bottom><small><small><br></small></small
! ><font color="%s" face="helvetica"><br>&nbsp;%s</font></td
  ><td align=right valign=bottom
! ><font color="%s" face="helvetica">%s</font></td><td>&nbsp;</td></tr></table>
!     """ % (bgcol, fgcol, title, fgcol, extras or '&nbsp;')
  
      def section(self, title, fgcol, bgcol, contents, width=20,
***************
*** 272,276 ****
          result = """
  <p><table width="100%%" cellspacing=0 cellpadding=0 border=0>
! <tr bgcolor="%s"><td colspan=3 valign=bottom><small><small><br></small></small
  ><font color="%s" face="helvetica, arial">&nbsp;%s</font></td></tr>
      """ % (bgcol, fgcol, title)
--- 268,273 ----
          result = """
  <p><table width="100%%" cellspacing=0 cellpadding=0 border=0>
! <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>
      """ % (bgcol, fgcol, title)
***************
*** 292,303 ****
          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):
          """Make a link for an identifier, given name-to-URL mappings."""
--- 289,292 ----
***************
*** 391,398 ****
          """Produce HTML documentation for a module object."""
          name = object.__name__
!         result = ''
!         head = '<br><big><big><strong>&nbsp;%s</strong></big></big>' % name
          try:
              path = os.path.abspath(inspect.getfile(object))
              filelink = '<a href="file:%s">%s</a>' % (path, path)
          except TypeError:
--- 380,395 ----
          """Produce HTML documentation for a module object."""
          name = object.__name__
!         parts = split(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:
              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)
          except TypeError:
***************
*** 408,412 ****
          if info:
              head = head + ' (%s)' % join(info, ', ')
!         result = result + self.heading(
              head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)
  
--- 405,409 ----
          if info:
              head = head + ' (%s)' % join(info, ', ')
!         result = self.heading(
              head, '#ffffff', '#7799ee', '<a href=".">index</a><br>' + filelink)
  
***************
*** 520,531 ****
          doc = self.markup(getdoc(object), self.preformat,
                            funcs, classes, mdict)
!         if doc: doc = '<small><tt>' + doc + '<br>&nbsp;</tt></small>'
          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):
          """Format an argument default value as text."""
--- 517,523 ----
          doc = self.markup(getdoc(object), self.preformat,
                            funcs, classes, mdict)
!         if doc: doc = '<small><tt>' + doc + '</tt></small>'
          return self.section(title, '#000000', '#ffc8d8', contents, 10, doc)
  
      def formatvalue(self, object):
          """Format an argument default value as text."""
***************
*** 533,548 ****
                  self.repr(object))
  
!     def docfunction(self, object, funcs={}, classes={}, methods={}, clname=''):
!         """Produce HTML documentation for a function object."""
!         args, varargs, varkw, defaults = inspect.getargspec(object)
!         argspec = inspect.formatargspec(
!             args, varargs, varkw, defaults, formatvalue=self.formatvalue)
! 
!         if object.__name__ == '<lambda>':
!             decl = '<em>lambda</em> ' + argspec[1:-1]
          else:
!             anchor = clname + '-' + object.__name__
!             decl = '<a name="%s"\n><strong>%s</strong>%s</a>\n' % (
!                 anchor, object.__name__, argspec)
          doc = self.markup(getdoc(object), self.preformat,
                            funcs, classes, methods)
--- 525,545 ----
                  self.repr(object))
  
!     def docroutine(self, object, funcs={}, classes={}, methods={}, clname=''):
!         """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)
!             argspec = inspect.formatargspec(
!                 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
! 
!             if object.__name__ == '<lambda>':
!                 decl = '<em>lambda</em> ' + argspec[1:-1]
!             else:
!                 anchor = clname + '-' + object.__name__
!                 decl = '<a name="%s"\n><strong>%s</strong>%s</a>\n' % (
!                     anchor, object.__name__, argspec)
          doc = self.markup(getdoc(object), self.preformat,
                            funcs, classes, methods)
***************
*** 551,563 ****
          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):
          """Produce a complete HTML page of documentation for an object."""
!         return '''<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
! <html><title>Python: %s</title>
! <body bgcolor="#ffffff">
  %s
  </body></html>
--- 548,556 ----
          return '<dl><dt>%s<dd><small>%s</small></dl>' % (decl, doc)
  
      def page(self, object):
          """Produce a complete HTML page of documentation for an object."""
!         return '''
! <!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
! <html><title>Python: %s</title><body bgcolor="#ffffff">
  %s
  </body></html>
***************
*** 758,782 ****
          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):
          """Format an argument default value as text."""
          return '=' + self.repr(object)
  
!     def docfunction(self, object):
!         """Produce text documentation for a function object."""
!         try:
              args, varargs, varkw, defaults = inspect.getargspec(object)
              argspec = inspect.formatargspec(
                  args, varargs, varkw, defaults, formatvalue=self.formatvalue)
!         except TypeError:
!             argspec = '(...)'
! 
!         if object.__name__ == '<lambda>':
!             decl = '<lambda> ' + argspec[1:-1]
!         else:
!             decl = self.bold(object.__name__) + argspec
          doc = getdoc(object)
          if doc:
--- 751,771 ----
          return title + '\n' + self.indent(rstrip(contents), ' |  ') + '\n'
  
      def formatvalue(self, object):
          """Format an argument default value as text."""
          return '=' + self.repr(object)
  
!     def docroutine(self, object):
!         """Produce text documentation for a function or method object."""
!         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)
              argspec = inspect.formatargspec(
                  args, varargs, varkw, defaults, formatvalue=self.formatvalue)
!             if object.__name__ == '<lambda>':
!                 decl = '<lambda> ' + argspec[1:-1]
!             else:
!                 decl = self.bold(object.__name__) + argspec
          doc = getdoc(object)
          if doc:
***************
*** 785,793 ****
              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
  
--- 774,777 ----
***************
*** 915,920 ****
      if type(path) is not types.StringType:
          return None, path
-     if hasattr(__builtins__, path):
-         return None, getattr(__builtins__, path)
      parts = split(path, '.')
      n = 1
--- 899,902 ----
***************
*** 925,929 ****
              module = reload(module)
          except:
!             # Did the error occur before or after we found the module?
              if sys.modules.has_key(path):
                  filename = sys.modules[path].__file__
--- 907,911 ----
              module = reload(module)
          except:
!             # determine if error occurred before or after module was found
              if sys.modules.has_key(path):
                  filename = sys.modules[path].__file__
***************
*** 943,946 ****
--- 925,930 ----
              n = n + 1
              continue
+     if hasattr(__builtins__, path):
+         return None, getattr(__builtins__, path)
      return None, None
  
***************
*** 956,965 ****
              path, x = locate(thing)
          except DocImportError, value:
!             print 'problem in %s - %s' % (value.filename, value.args)
              return
          if x:
              thing = x
          else:
!             print 'could not find or import %s' % repr(thing)
              return
  
--- 940,949 ----
              path, x = locate(thing)
          except DocImportError, value:
!             print 'Problem in %s - %s' % (value.filename, value.args)
              return
          if x:
              thing = x
          else:
!             print 'No Python documentation found for %s.' % repr(thing)
              return
  
***************
*** 970,987 ****
      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):
      """Write HTML documentation to a file in the current directory."""
--- 954,957 ----
***************
*** 1016,1046 ****
          found = 1
      else:
!         print 'could not find or import %s' % repr(key)
  
  def apropos(key):
      """Print all the one-line module summaries that contain a substring."""
!     key = lower(key)
!     for module in sys.builtin_module_names:
!         desc = __import__(module).__doc__ or ''
!         desc = split(desc, '\n')[0]
!         if find(lower(module + ' ' + desc), key) >= 0:
!             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
  
! def serve(address, callback=None):
!     import BaseHTTPServer, mimetools
  
      # Patch up mimetools.Message so it doesn't break if rfc822 is reloaded.
--- 986,1070 ----
          found = 1
      else:
!         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):
      """Print all the one-line module summaries that contain a substring."""
!     def callback(path, modname, desc):
!         if modname[-9:] == '.__init__':
!             modname = modname[:-9] + ' (package)'
!         print modname, '-', desc or '(no description)'
!     ModuleScanner().run(key, callback)
  
  # --------------------------------------------------- web browser interface
  
! def serve(port, callback=None):
!     import BaseHTTPServer, mimetools, select
  
      # Patch up mimetools.Message so it doesn't break if rfc822 is reloaded.
***************
*** 1056,1067 ****
      class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler):
          def send_document(self, title, contents):
!             self.send_response(200)
!             self.send_header('Content-Type', 'text/html')
!             self.end_headers()
!             self.wfile.write(
! '''<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
! <html><title>Python: %s</title><body bgcolor="#ffffff">''' % title)
!             self.wfile.write(contents)
!             self.wfile.write('</body></html>')
  
          def do_GET(self):
--- 1080,1093 ----
      class DocHandler(BaseHTTPServer.BaseHTTPRequestHandler):
          def send_document(self, title, contents):
!             try:
!                 self.send_response(200)
!                 self.send_header('Content-Type', 'text/html')
!                 self.end_headers()
!                 self.wfile.write('''
! <!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
! <html><title>Python: %s</title><body bgcolor="#ffffff">
! %s
! </body></html>''' % (title, contents))
!             except IOError: pass
  
          def do_GET(self):
***************
*** 1074,1078 ****
                  except DocImportError, value:
                      self.send_document(path, html.escape(
!                         'problem with %s - %s' % (value.filename, value.args)))
                      return
                  if x:
--- 1100,1104 ----
                  except DocImportError, value:
                      self.send_document(path, html.escape(
!                         'Problem in %s - %s' % (value.filename, value.args)))
                      return
                  if x:
***************
*** 1080,1090 ****
                  else:
                      self.send_document(path,
! 'There is no Python module or object named "%s".' % path)
              else:
                  heading = html.heading(
!                     '<br><big><big><strong>&nbsp;'
!                     'Python: Index of Modules'
!                     '</strong></big></big>',
!                     '#ffffff', '#7799ee')
                  builtins = []
                  for name in sys.builtin_module_names:
--- 1106,1114 ----
                  else:
                      self.send_document(path,
! 'No Python documentation found for %s.' % repr(path))
              else:
                  heading = html.heading(
! '<big><big><strong>Python: Index of Modules</strong></big></big>',
! '#ffffff', '#7799ee')
                  builtins = []
                  for name in sys.builtin_module_names:
***************
*** 1094,1132 ****
                  for dir in pathdirs():
                      indices.append(html.index(dir, seen))
!                 self.send_document('Index of Modules', heading + join(indices))
  
          def log_message(self, *args): pass
  
      class DocServer(BaseHTTPServer.HTTPServer):
!         def __init__(self, address, callback):
              self.callback = callback
!             self.base.__init__(self, address, self.handler)
  
          def server_activate(self):
              self.base.server_activate(self)
!             if self.callback: self.callback()
  
      DocServer.base = BaseHTTPServer.HTTPServer
      DocServer.handler = DocHandler
      DocHandler.MessageClass = Message
      try:
!         DocServer(address, callback).serve_forever()
      except KeyboardInterrupt:
!         print 'server stopped'
  
  # -------------------------------------------------- command-line interface
  
  def cli():
      import getopt
      class BadUsage: pass
  
      try:
!         opts, args = getopt.getopt(sys.argv[1:], 'k:p:w')
          writing = 0
  
          for opt, val in opts:
              if opt == '-k':
!                 apropos(lower(val))
!                 break
              if opt == '-p':
                  try:
--- 1118,1344 ----
                  for dir in pathdirs():
                      indices.append(html.index(dir, seen))
!                 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
  
      class DocServer(BaseHTTPServer.HTTPServer):
!         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.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):
              self.base.server_activate(self)
!             if self.callback: self.callback(self)
  
      DocServer.base = BaseHTTPServer.HTTPServer
      DocServer.handler = DocHandler
      DocHandler.MessageClass = Message
+     try:
+         DocServer(port, callback).serve_until_quit()
+     except (KeyboardInterrupt, select.error):
+         pass
+     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
  
  def cli():
+     """Command-line interface (looks at sys.argv to decide what to do)."""
      import getopt
      class BadUsage: pass
  
      try:
!         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
  
          for opt, val in opts:
+             if opt == '-g':
+                 gui()
+                 return
              if opt == '-k':
!                 apropos(val)
!                 return
              if opt == '-p':
                  try:
***************
*** 1134,1192 ****
                  except ValueError:
                      raise BadUsage
!                 def ready(port=port):
!                     print 'server ready at http://127.0.0.1:%d/' % port
!                 serve(('127.0.0.1', port), ready)
!                 break
              if opt == '-w':
-                 if not args: raise BadUsage
                  writing = 1
-         else:
-             if args:
-                 for arg in args:
-                     try:
-                         if os.path.isfile(arg):
-                             arg = importfile(arg)
-                         if writing:
-                             if os.path.isdir(arg): writedocs(arg)
-                             else: writedoc(arg)
-                         else: man(arg)
-                     except DocImportError, value:
-                         print 'problem in %s - %s' % (
-                             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):
!         print """%s <name> ...
!     Show documentation on something.
!     <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
!     module in a package, or the filename of a Python module to import.
  
  %s -k <keyword>
!     Search for a keyword in the synopsis lines of all modules.
  
  %s -p <port>
      Start an HTTP server on the given port on the local machine.
  
! %s -w <module> ...
!     Write out the HTML documentation for a module to a file.
  
- %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()
--- 1346,1391 ----
                  except ValueError:
                      raise BadUsage
!                 def ready(server):
!                     print 'server ready at %s' % server.url
!                 serve(port, ready)
!                 return
              if opt == '-w':
                  writing = 1
  
+         if not args: raise BadUsage
+         for arg in args:
+             try:
+                 if find(arg, os.sep) >= 0 and os.path.isfile(arg):
+                     arg = importfile(arg)
+                 if writing: writedoc(arg)
+                 else: man(arg)
+             except DocImportError, value:
+                 print 'Problem in %s - %s' % (value.filename, value.args)
+ 
      except (getopt.error, BadUsage):
!         cmd = sys.argv[0]
!         print """pydoc - the Python documentation tool
! 
! %s <name> ...
!     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>
!     Search for a keyword in the synopsis lines of all available modules.
  
  %s -p <port>
      Start an HTTP server on the given port on the local machine.
+ 
+ %s -g
+     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()