#! /usr/bin/env python
# -*- python -*-

"""
AUTODOC - A python script for automating some documentation tasks.

usage: autodoc [options] filename1 ...

options: -h | --help      Print this message
         -H | --html      Output in HTML
         -p | --python    Output in python
         -r | --restruct  Output in reStructuredText
         -t | --text      Output in standard text (default)
         -v | --version   Print the version number

filename1 ...             A list of SWIG interface files
"""

__version__ = "1.3"
__author__  = "Bill Spotz"
__date__    = "Sep 22 2006"

# System imports
from   getopt   import *
import os
import os.path
import re
import string
import sys

######################################################

class SwigFile:
    """
    Upon initialization, a SwigFile object will open and read a SWIG interface
    file, storing various pieces of information about the file: the name of the
    (package and) module it will create, header files that it %includes and
    %imports, and other SWIG interface files that it %includes and %imports.
    SWIG interface files that are %included are processed recursively to update
    %included header files.
    """

    # Define the regular expressions
    moduleRE  = re.compile(r"%module(\(.+\))?\s+(.+)")
    packageRE = re.compile(r"package\s*=\s*([^,^)]+)")
    includeRE = re.compile(r"%include\s+['\"<](.+)['\">]")
    importRE  = re.compile(r"%import\s+['\"<](.+)['\">]")

    # Define list of headers to ignore
    ignoreFiles = ["Callback.h", "Epetra_NumPyVector.h", "Epetra_VectorHelper.h",
                   "NumPyArray.h", "NumPyWrapper.h", "PyInterface.h",
                   "PyTrilinos.h", "numeric_include.h", "numpy.i", "stl.i",
                   "exceptions.i"]

    def __init__(self, filename):
        """
        Open the swig interface file, and store data about its contents
        """
        self.__dirName, self.__baseName = os.path.split(filename)
        self.__text = open(filename,"r").readlines()
        self.__packageName    = None
        self.__moduleName     = None
        self.__headerIncludes = [ ]
        self.__headerImports  = [ ]
        self.__swigIncludes   = [ ]
        self.__swigImports    = [ ]
        self.__processText()

    def __processText(self):
        # Loop over the lines of text
        for line in self.__text:

            # Look for %module(package="...") ...
            match = SwigFile.moduleRE.search(line)
            if match:
                moduleOptions     = match.group(1)
                self.__moduleName = match.group(2)
                if moduleOptions:
                    match = SwigFile.packageRE.search(moduleOptions)
                    if match:
                        self.__packageName = match.group(1)[1:-1]
                continue

            # Look for %include ...
            match = SwigFile.includeRE.search(line)
            if match:
                includeFile = match.group(1)
                if includeFile not in SwigFile.ignoreFiles:
                    root,ext = os.path.splitext(includeFile)
                    if ext in [".h",".H",".hpp"]:
                        self.__headerIncludes.append(includeFile)
                    elif ext in [".i"]:
                        self.__swigIncludes.append(includeFile)
                continue

            # Look for %import ...
            match = SwigFile.importRE.search(line)
            if match:
                importFile = match.group(1)
                if importFile not in SwigFile.ignoreFiles:
                    root,ext = os.path.splitext(importFile)
                    if ext in [".h",".H",".hpp"]:
                        self.__headerImports.append(importFile)
                    elif ext in [".i"]:
                        self.__swigImports.append(importFile)
                continue

        # Process the SWIG include files
        self.__processSwigIncludes()

        # Alphabetize the lists
        self.__headerIncludes.sort()
        self.__headerImports.sort()
        self.__swigIncludes.sort()
        self.__swigImports.sort()

    def __processSwigIncludes(self):
        # Assumes self.__dirName and self.__swigIncludes have
        # been properly set
        for includeFile in self.__swigIncludes:
            try:
                iFile = SwigFile(os.path.join(self.__dirName,includeFile))
            except IOError:
                try:
                    iFile = SwigFile(includeFile)
                except IOError:
                    continue    # Can't find the include file
            self.__headerIncludes.extend(iFile.headerIncludes())

    def packageName(self):
        """
        Return the name of the package this SWIG interface file is for
        """
        return self.__packageName

    def moduleName(self):
        """
        Return the name of the module this SWIG interface file is for
        """
        return self.__moduleName

    def fullModuleName(self):
        """
        Return the full package and module name this SWIG interface file is for
        """
        if self.__moduleName:
            if self.__packageName:
                return self.__packageName + "." + self.__moduleName
            return self.__moduleName
        return ""

    def headerIncludes(self):
        """
        Return a list of header filenames that the SWIG file %include-s
        """
        return self.__headerIncludes

    def headerImports(self):
        """
        Return a list of header filenames that the SWIG file %import-s
        """
        return self.__headerImports

    def swigIncludes(self):
        """
        Return a list of swig interface filenames that the SWIG file %include-s
        """
        return self.__swigIncludes

    def swigImports(self):
        """
        Return a list of swig interface filenames that the SWIG file %import-s
        """
        return self.__swigImports

######################################################

def main():

    # Initialization
    (progDir,progName) = os.path.split(sys.argv[0])
    options      = "hHprtv"
    long_options = ["help", "html", "python", "restruct", "text", "version"]
    format       = "text"

    # Get the options and arguments from the command line
    (opts,args) = getopt(sys.argv[1:], options, long_options)

    # Loop over options and implement
    for flag in opts:
        if flag[0] in ("-h","--help"):
            print __doc__
            sys.exit()
        elif flag[0] in ("-H", "--html"):
            format = "html"
        elif flag[0] in ("-p", "--python"):
            format = "python"
        elif flag[0] in ("-r", "--restruct"):
            format = "restruct"
        elif flag[0] in ("-t", "--text"):
            format = "text"
        elif flag[0] in ("-v", "--version"):
            print progName, __version__, __date__
            sys.exit()
        else:
            print "Unrecognized flag:", flag[0]
            print __doc__
            sys.exit()

    # From the args list, form the list of filenames.  This will be all elements
    # of args except those that contain wildcards.
    filenames = [ ]
    for filename in args:
        if "*" not in filename and "?" not in filename:
            filenames.append(filename)

    # We are going to create a dictionary whose keys are the module names of the
    # interface files, and whose values are a list of the %include files
    wrapperDict = { }

    # Loop over the arguments
    for interfaceFile in filenames:
        iFile = SwigFile(interfaceFile)
        moduleName = iFile.fullModuleName()
        if moduleName: wrapperDict[moduleName] = iFile.headerIncludes()

    # Output the results
    keys = wrapperDict.keys()
    keys.sort()

    # Standard text output
    if format == "text":
        for key in keys:
            print key + ":"
            for header in wrapperDict[key]:
                print "    " + header

    # HTML output
    elif format == "html":
        print "  <ul>"
        for key in keys:
            print "    <li> %s:\n    <ul>" % key
            for header in wrapperDict[key]:
                print "      <li> %s" % header
            print "    </ul>"
        print "  </ul>"

    # python output
    elif format == "python":
        print wrapperDict

    # reStructuredText output
    elif format == "restruct":
        for key in keys:
            print "*", key
            print
            for header in wrapperDict[key]:
                print "  -", header
            print

######################################################

if __name__ == "__main__":
    main()
