import sys,types,os,os.path
from pdb import set_trace as trace

def doIndent(indent):
    sys.stdout.write(' ' * indent)

def my_import(name):
    mod = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

def analyseClass(aClass):
    contents = {'methods':[]}
    for childName in dir(aClass):
        if childName[0:2] == '__':
            continue
        childObj = getattr(aClass, childName)
        childDict = {'name':childName,
                     'object':childObj,
                     'doc':childObj.__doc__,
                     }
        if type(childObj) is types.BuiltinMethodType \
               or type(childObj) is types.MethodType \
               or repr(type(childObj)) in ["<type 'builtin_function_or_method'>",
                                           "<type 'method_descriptor'>",
                                           ]:
            contents['methods'].append(childDict)
    return contents

def analyseModule(module):
    contents = {'functions':[], 'classes':[]}
    for childName in dir(module):
        if childName[0:2] == '__' and childName not in ['__init__',
                                                        '__new__',
                                                        '__dealloc__']:
            continue
        childObj = getattr(module, childName)
        childDict = {'name':childName,
                     'object':childObj,
                     'doc':childObj.__doc__,
                     }
        if type(childObj) is types.ClassType or type(childObj) is types.TypeType:
            childDict['contents'] = analyseClass(childObj)
            contents['classes'].append(childDict)
        elif type(childObj) is types.FunctionType or type(childObj) is types.BuiltinFunctionType:
            contents['functions'].append(childDict)
    return contents

def makeDir(dirName):
    if os.path.exists(dirName):
        if not os.path.isdir(dirName):
            print "Cannot create directory '%s': file exists" % dirName
            sys.exit(1)
    else:
        try:
            os.makedirs(dirName)
        except:
            print "Cannot create directory '%s'" % dirName
            sys.exit(1)
    
def makeHtmlIndex():
    fd = file("index.html", "w")
    fd.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
""")
    fd.write("<title>Doco for Pyrex Modules</title>\n")
    fd.write("""<frameset cols="70,*" rows="*" border="5" frameborder="YES" framespacing="4"> 
<frame src="contents/modulelist.html" name="modulelist" frameborder="YES">
<frame src="contents/modulepane.html" name="modulepane" frameborder="YES">
</frameset>
<noframes>
Sorry - you need a frames-enabled browser to view this documentation
</noframes>""")
    fd.write("</html>\n")
    fd.close()

def makeHtmlModuleList(tree):
    fd = file("contents/modulelist.html", "w")
    fd.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
""")
    fd.write("</head>\n<body>\n")
    fd.write("<h4>Modules</h4>\n")
    for module in tree:
        fd.write("""<a href="module-%s.html"
                       style="text-decoration:none"
                       target="modulepane">%s</a><br>\n""" % (
            module['name'], module['name']))
    fd.write("</body>\n</html>\n")
    fd.close()

def makeHtmlModulePaneTop(tree):
    fd = file("contents/modulepane.html", "w")
    fd.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
""")
    fd.write("</head>\n<body>\n")
    fd.write("<h1>Documentation for Pyrex-generated modules</h1>\n")
    fd.write("Please click on a link in the left pane")
    fd.write("</body>\n</html>\n")
    fd.close()

def makeHtmlModulePane(module):
    oldCwd = os.getcwd()
    os.chdir("contents")

    moduleName = module['name']
    contents = module['contents']

    # Generate top-level module frameset
    fd = file("module-%s.html" % moduleName, "w")
    fd.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
""")
    fd.write("<title>%s: Pyrex Module Documentation</title>\n" % moduleName)
    fd.write("""<frameset cols="150,359*" rows="*" border="5" frameborder="YES" framespacing="4"> 
<frame src="module-%s-contents.html" name="modulecontents" frameborder="YES">
<frame src="module-%s-detail.html" name="moduledetail" frameborder="YES">
</frameset>
<noframes>
Sorry - you need a frames-enabled browser to view this documentation
</noframes>""" % (moduleName, moduleName))
    fd.write("</html>\n")
    fd.close()

    # Generate module list pane
    fd = file("module-%s-contents.html" % moduleName, "w")
    fd.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">\n<html>\n<head>\n""")
    fd.write("<title>Module %s Contents</title>\n" % moduleName)
    fd.write("</head>\n<body>\n")

    fd.write("<h4>Module<br>%s</h4>\n" % moduleName)
    if len(contents['classes']) != 0:
        fd.write("<h4>Classes</h4>\n")
        for item in contents['classes']:
            className = item['name']
            fd.write("""<a href="module-%s-detail.html#class_%s" target="moduledetail">%s</a>\n""" % (
                     moduleName, className, className))
            fd.write("<br>\n")
            classContents = item['contents']
            if len(classContents['methods']) > 0:
                for m in classContents['methods']:
                    methodName = m['name']
                    # here - need to put the url's '#' component as an arg, otherwise poor doddery
                    # old emacs loses the plot vis a vis strings
                    fd.write("""&nbsp;&nbsp;&nbsp;<font size=-1><a href="module-%s-detail.html%smethod_%s_%s"
                                                     target="moduledetail">%s</a></font><br>\n""" % (
                       moduleName, "#", className, methodName, methodName))
                fd.write("</blockquote>\n")
                fd.write("</blockquote>\n")
    else:
        fd.write("<i><h4>No Classes</h4></i>\n")
    fd.write("<hr>\n")
    if len(contents['functions']) != 0:
        fd.write("<h4>Functions</h4>\n")
        for item in contents['functions']:
            funcName = item['name']
            fd.write("""<a href="module-%s-detail.html#function_%s" target="moduledetail">%s</a>\n""" % (
                     moduleName, funcName, funcName))
            fd.write("<br>\n")
    else:
        fd.write("<i><h4>No Functions</h4></i>\n")
    fd.write("<hr>\n")
    fd.write("</body>\n</html>\n")
    fd.close()

    # Generate module detail pane
    fd = file("module-%s-detail.html" % moduleName, "w")
    fd.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">\n<html>\n<head>\n""")
    fd.write("<title>Module %s Details</title>\n" % moduleName)
    fd.write("</head>\n<body>\n")

    fd.write("<h1>Module: %s</h1>\n" % moduleName)
    fd.write("<blockquote>%s</blockquote>\n" % module['doc'])
    fd.write("<hr>\n")
    if len(contents['classes']) != 0:
        for item in contents['classes']:
            className = item['name']
            fd.write("""<a name="class_%s"></a>""" % className)
            fd.write("<h2>Class: %s</h2>\n" % className)
            fd.write("<blockquote>%s</blockquote>\n" % item['doc'])
            #trace()
            classContents = item['contents']
            if len(classContents['methods']) > 0:
                fd.write("<blockquote><hr>\n")
                fd.write("<h3>Methods for class %s</h3>\n" % className)
                fd.write("<blockquote>\n")
                for m in classContents['methods']:
                    methodName = m['name']
                    fd.write("""<a name="method_%s_%s"></a>""" % (className, methodName))
                    fd.write("<h3>%s.%s</h3>\n" % (className, methodName))
                    fd.write("<blockquote>%s</blockquote>\n" % m['doc'])
                fd.write("</blockquote>\n")
                fd.write("</blockquote>\n")
            fd.write("<hr>\n")
    if len(contents['functions']) != 0:
        for item in contents['functions']:
            funcName = item['name']
            fd.write("""<a name="function_%s"></a>""" % funcName)
            fd.write("<h2>Function: %s</h2>\n" % item['name'])
            fd.write("<blockquote>%s</blockquote>\n" % item['doc'])
            fd.write("<hr>\n")

    fd.write("</body>\n</html>\n")
    fd.close()

    # Done - back to old directory
    os.chdir(oldCwd)

def makeHtml(tree):
    """
    Generates html doco from analysed tree
    """

    # Create and enter Doco directory
    makeDir("Doco")
    oldCwd = os.getcwd()
    os.chdir("Doco")
    makeDir("contents")

    # generate frameset in index.html
    makeHtmlIndex()

    # generate module list frame
    makeHtmlModuleList(tree)

    # generate empty contents pane
    makeHtmlModulePaneTop(tree)

    # generate each module's right panes
    for module in tree:
        makeHtmlModulePane(module)

    # Done
    os.chdir(oldCwd)
    print "Documentation successfully generated"
    
def docgen(modlist):
    modules = []
    if modlist == []:
        return # nothing to do
    for modName in modlist:
        try:
            modObj = my_import(modName)
            modProps = {'name':modName, 'object':modObj, 'doc':modObj.__doc__}
            modProps['contents'] = analyseModule(modObj)
            modules.append(modProps)
        except:
            print "ignoring unimportable module '%s'" % modName
    #print "docgen: modules"
    #print modules
    makeHtml(modules)

def usage():
    progname = sys.argv[0]
    print
    print "Usage: %s module1 [module2 ...]" % progname
    print
    print "%s generates no-frills but usable documentation for modules" % progname
    print "created with Pyrex."
    print
    print "This utility is needed because the regular Python doco generators"
    print "like epydoc, pydoc, happydoc etc lose the plot when analysing"
    print "modules created with Pyrex."
    print
    print "For instance, the other doc generators may ignore Pyrex-generated classes"
    print "and/or functions, or regard class methods as instance variables, or other"
    print "equally annoying behaviour."
    print
    print "Note - the arguments above should be module *names*, not files."
    print
    print "%s was written by David McNab <david@rebirthing.co.nz>" % progname
    print
    
def main():
    if '.' not in sys.path:
        sys.path.insert(0, '.')
        pushed = True
    else:
        pushed = False
    if len(sys.argv) <= 1 or sys.argv[1] in ['-h', '-?', '-help', '--help']:
        usage()
    docgen(sys.argv[1:])
    if pushed and sys.path[0] == '.':
        sys.path.pop(0)

if __name__ == '__main__':
    main()
