#!/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
    

    
