[Python-checkins] CVS: python/nondist/sandbox/doctools textdoc.py,NONE,1.1 README,1.1,1.2 htmldoc.py,1.1,1.2 onlinehelp.py,1.1,1.2

Paul Prescod python-dev@python.org
Sat, 22 Jul 2000 11:51:38 -0700


Update of /cvsroot/python/python/nondist/sandbox/doctools
In directory slayer.i.sourceforge.net:/tmp/cvs-serv15410

Modified Files:
	README htmldoc.py onlinehelp.py 
Added Files:
	textdoc.py 
Log Message:
Better support for module documentation. Extracts classes, funcs, etc.
automatically.


--- NEW FILE ---
#!/usr/bin/env python
"""Generate text documentation from Python objects.

This module uses Ka-Ping Yee's inspect module to generate text documentation
given a python object. It borrows heavily from Ka-Ping Yee's htmldoc. Further
details of those modules can be found at http://www.lfw.org/python/.

Use:

import textdoc,cgi

print textdoc.document(cgi) # document an entire module
print textdoc.document(cgi.FieldStorage) # document a class
print textdoc.document(cgi.initlog) # document a function
print textdoc.document(len) # document a builtin

contact: richard_chamberlain@ntlworld.com
"""

# I, Richard Chamberlain, the author of this contribution, hereby grant to anyone
# and everyone a nonexclusive, irrevocable, royalty-free, worldwide license to
# reproduce, distribute, perform and/or display publicly, prepare derivative
# versions, and otherwise use this contribution in any fashion, or any
# derivative versions thereof, at no cost to anyone, and to authorize others
# to do so.  This software is provided "as is", with NO WARRANTY WHATSOEVER,
# not even a warranty of merchantability or fitness for any particular purpose.

__version__ = "22 July 2000"

import inspect,string

def _getdoc(object):
    """Returns doc string for a given object."""
    result=''
    doc = inspect.getdoc(object)
    if not doc:
        try: doc = inspect.getcomments(object)
        except: pass
    if doc:
        for line in string.split(doc,'\n'):            
            result=result+'\t'+line+'\n'
    if result=='': result='\tno doc string'
    return result and string.rstrip(result) + "\n" or ""

def _tab(str,_tab=2):
    """Increase the indent on all but the first line"""
    result=[]
    lines=string.split(str,'\t')
    result.append(lines[0])
    for line in lines[1:]:
        result.append(('\t')*_tab+line)
    result=string.join(result)
    return result
        

def _document_module(object):
    """Produce text documentation for a given module."""
    results=[]
    name=object.__name__
    if hasattr(object,"__version__"):
        name=name+" (version: %s)\n" % object.__version__
    else: name=name+" \n"
    results.append(name)
    doc=_getdoc(object)
    results.append(doc)

    cadr = lambda list: list[0]
    # Get the modules
    modules = map(cadr,inspect.getmembers(object, inspect.ismodule))
    if modules:
        results.append('\nModules:\n\n')
        results.append(string.join(modules,', '))
    # Get the classes
    classes=inspect.getmembers(object,inspect.isclass)
    if classes:
        results.append('\n\nClasses:\n\n')
        for aclass in classes:
            results.append(_document_class(aclass[1]))
        results.append('\n')
        
    functions=inspect.getmembers(object,inspect.isroutine)
    if functions:
        results.append('Module Functions:\n\n')
        for function in functions:
            results.append(_document_function(function[1]))
    return results

def _document_class(object):
    """Produce text documentation for a given class object."""
    name = object.__name__
    bases = object.__bases__
    results = []
    title = "class %s" % name
    if bases:
        parents = []
        for base in bases:
            parents.append(base.__name__)
        title = title + "(%s)" % string.join(parents, ", ")
    results.append(title+":\n\n")
    doc=_getdoc(object)
    results.append(doc)
        
    functions=inspect.getmembers(object,inspect.isroutine)
    if functions:
        results.append('\n\tMethods:\n\n')
        for function in functions:
            results.append("\t"+_tab(document(function[1])))
       
    return results

def _document_method(object):
    """Produce text documentation for a given method."""
    return _document_function(object.im_func)

def defaultFormat(object):
    rep=repr( obj )
    match=re.match( r"<(.+?) at ......>", rep )
    if match:
        return "<"+match.group(1)+">"
    else:
        return rep


def _document_function(object):
    """Produce text documentation for a given function."""
    try:
        args, varargs, varkw, defaults = inspect.getargspec(object)
        argspec = inspect.formatargspec(
            args, varargs, varkw, defaults)
    except TypeError:
        argspec = "(no arg info)"
    if object.__name__ == "<lambda>":
        decl = ["lambda ", argspec[1:-1]]
    else:
        decl = [object.__name__, argspec, "\n"]
    doc = _getdoc(object)
    return [decl, doc+"\n"]

def _document_builtin(object):
    """Produce text documentation for a given builtin."""
    results=[]
    results.append(object.__name__+'\n')
    doc=_getdoc(object)
    results.append('\n'+doc)
    return results

def document(object):
    """Generate documentation for a given object."""
    if inspect.ismodule(object): results = _document_module(object)
    elif inspect.isclass(object): results = _document_class(object)
    elif inspect.ismethod(object): results = _document_method(object)
    elif inspect.isfunction(object): results = _document_function(object)
    elif inspect.isbuiltin(object): results = _document_builtin(object)
    else: raise TypeError, "don't know how to document this kind of object"
    return _serialise(results)

def _serialise(list):
    """Combine a list containing strings and nested lists into a single
    string.  This lets us manipulate lists until the last moment, since
    rearranging lists is faster than rearranging strings."""
    results = []
    if list==None: return ""
    for item in list:
        if type(item) is type(""): results.append(item)
        else: results.append(_serialise(item))
    return string.join(results, "")

if __name__=='__main__':
    # Test Code
    import Tkinter,cgi,calendar
    print document(Tkinter) # Try a module with classes
    print document(calendar) # Module without classes
    print document(cgi.FieldStorage) # Just a class
    print document(inspect.getdoc) # a method
    print document(len) # a builtin
    

    

Index: README
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/doctools/README,v
retrieving revision 1.1
retrieving revision 1.2
diff -C2 -r1.1 -r1.2
*** README	2000/07/21 21:31:27	1.1
--- README	2000/07/22 18:51:36	1.2
***************
*** 4,7 ****
  
    onlinehelp.py -- onlinehelp for Python. Read the docstring.
-   htmldoc.py -- Builds HTML documentation from docstrings
    inspect.py -- introspection API used by both of the above
--- 4,8 ----
  
    onlinehelp.py -- onlinehelp for Python. Read the docstring.
    inspect.py -- introspection API used by both of the above
+   htmldoc.py -- Builds HTML documentation from docstrings
+   textdoc.py -- Builds raw text documentation from docstrings

Index: htmldoc.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/doctools/htmldoc.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -C2 -r1.1 -r1.2
*** htmldoc.py	2000/07/21 21:31:27	1.1
--- htmldoc.py	2000/07/22 18:51:36	1.2
***************
*** 10,15 ****
      return string.replace(string.replace(str, "&", "&amp;"), "<", "&lt;")
  
  def htmlrepr(object):
!     return htmlescape(repr(object))
  
  def preformat(str):
--- 10,27 ----
      return string.replace(string.replace(str, "&", "&amp;"), "<", "&lt;")
  
+ 
+ # added by prescod
+ #   physical addresses produced by repr were defeating diff and they are 
+ #   ugly anyhow
+ def smartRepr( obj ):
+     rep=repr( obj )
+     match=re.match( r"<(.+?) at ......>", rep )
+     if match:
+         return "<"+match.group(1)+">"
+     else:
+         return rep
+ 
  def htmlrepr(object):
!     return htmlescape(smartRepr(object))
  
  def preformat(str):

Index: onlinehelp.py
===================================================================
RCS file: /cvsroot/python/python/nondist/sandbox/doctools/onlinehelp.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -C2 -r1.1 -r1.2
*** onlinehelp.py	2000/07/21 21:31:27	1.1
--- onlinehelp.py	2000/07/22 18:51:36	1.2
***************
*** 41,44 ****
--- 41,49 ----
  
  python onlinehelp.py if
+ 
+ Security warning: this module will attempt to import modules with the same
+                   names as requested topics. Don't use the modules if you
+                   are not confident that everything in your pythonpath is
+                   from a trusted source.
  """
  
***************
*** 64,67 ****
--- 69,73 ----
  import os, sys
  import re
+ import textdoc, types
  
  prompt="--more-- (enter for more, q to quit) "
***************
*** 213,216 ****
--- 219,233 ----
  envir_var="PYTHONDOCS"
  
+ def my_import(name):
+     try:
+         mod = __import__(name)
+     except ImportError:
+         return None
+ 
+     components = name.split('.')
+     for comp in components[1:]:
+         mod = getattr(mod, comp)
+     return mod
+ 
  class Help:
      def __init__( self, out, line_length, docdir=None ):
***************
*** 257,301 ****
      def call( self, ob, out ):
          self.pager=out or self.Pager( self.out, self.line_length )
  
!         if type( ob ) in (type(""),type(u"")):
!             if ob.startswith( "<" ):
!                 ob=ob[1:]
!             if ob.endswith( ">" ):
!                 ob=ob[:-1]
! 
!             self.write( 'Topic: help( "%s" )\n' % ob )
! 
!             if ob.startswith("doc:"):
!                 path=ob[4:]
!                 if not self.docdir:
!                     self.initDocDir()
!                 fullpath=os.path.join( self.docdir, path )
!                 data=open( fullpath ).read()
!                 index=ob.rfind( "/" )
!                 self.writeHTML( ob[:index], data )
              else:
!                 try:
!                     info=topics[ob]
!                     docrlmatch=re.search( "(<doc:[^>]+>)", info.split("\n")[0] )
!                     if docrlmatch: # a first-line redirect
!                         self( docrlmatch.group(1) )
!                     else:
!                         self.writeHTML( "", info )
!                 except KeyError:
!                     glo=__builtins__.__dict__.get( ob, 0 )
!                     if glo:
!                         self( glo )
!                     else:
!                         sys.stderr.write( "No such topic "+`ob` )
!                         return None
          else:
!             self.write( 'Topic: help( %s )\n' % ob )
!             self.writeText( self.getdoc( ob ) )
  
!     def getdoc( self, ob ):
!             if hasattr( ob, "__doc__" ):
!                 return ob.__doc__
              else:
!                 type( ob ).__doc__
      
  
--- 274,374 ----
      def call( self, ob, out ):
          self.pager=out or self.Pager( self.out, self.line_length )
+ 
+         if type( ob ) in (types.StringType,types.UnicodeType): #string
  
!             # doc form of URL
!             if ob.startswith("doc:") or ob.startswith( "<doc:" ):
!                 self.handleDocrl( ob )
              else:
!                 self.handleTopic( ob )
          else:
!             self.handleObject( ob )
! 
!     def handleDocrl( self, docrl ):
!         # strip leading/trailing "<" and ">"
!         if docrl.startswith( "<" ): 
!             docrl=docrl[1:]
!         if docrl.endswith( ">" ):
!             docrl=ob[:-1]
! 
!         path=docrl[4:]
!         if not self.docdir:
!             self.initDocDir()
! 
!         fullpath=os.path.join( self.docdir, path )
!         data=open( fullpath ).read()
!         index=docrl.rfind( "/" )
! 
!         self.write( 'Topic: help( "%s" )\n' % docrl )
!         self.writeHTML( ob[:index], data )
! 
!     def matchDocrlPattern( self, info ):
!         firstline=info.split("\n")[0]
!         docrlmatch=re.search( "<(doc:[^>]+)>", firstline )
!         if docrlmatch:
!             return docrlmatch.group( 1 )
!         else:
!             return None
  
!     def handleTopic( self, topic ):
!         # otherwise a topic
!         info=topics.get(topic, 0 )
!         if info:
!             match=self.matchDocrlPattern( info )
!             if match: # a first-line redirect
!                 self.handledocrl( match )
              else:
!                 self.write( 'Topic: help( "%s" )\n' % topic )
!                 self.writeHTML( "", info )
!             return None
! 
!         # try again -- this time in builtins
!         glo=__builtins__.__dict__.get( topic, 0 )
!         if glo:
!             self.handleObject( glo )
!             return None
! 
!         # try again -- this time as a module
!         mod=my_import( topic )
!         if mod:
!             print mod
!             self.handleObject( mod )
!             return None
! 
!         # try again -- this time as an attribute OF a module
!         ### FIXME/XXX/TODO: this code is not finished yet!
!         parts=string.split( topic, "." )
!         for i in range( len( parts ), -1, -1 ):
!             if i:
!                 front=string.join( parts[:i], "." )
!             mod=my_import( front )
!             if mod:
!                 self.handleObject( mod )
!                 return None
! 
!         sys.stderr.write( "No such topic "+`topic` )
!         return None
! 
!     def handleObject( self, ob ):
!         docstr=self.getdocstring( ob )
!         
!         if docstr:
!             match=self.matchDocrlPattern( docstr )
!         else:
!             match=None
! 
!         if match: # a first-line redirect
!             self.handledocrl( match )
!         else:
!             text=textdoc.document( ob )
!             self.write( 'Topic: help( %s )\n' % ob )
!             self.writeText( text )
! 
!     def getdocstring( self, ob ):
!         # todo: use inspect.py instead
!         if hasattr( ob, "__doc__" ):
!             return ob.__doc__
!         else:
!             return type( ob ).__doc__
      
  
***************
*** 374,377 ****
          test()
      else:
!         help( eval( sys.argv[1] ) )
  
--- 447,450 ----
          test()
      else:
!         help( sys.argv[1] )