"""
By using the class Instant a Python extension module can
be created at runtime. For the user, it behaves somewhat like
an inline module, except you have to import the module manually.

The code can be either C or C++, but like when programming C or C++,
it has to be inside a function or a similar C/C++ construct.

A simple example: (see test1.py)

>>> from Instant import inline
>>> add_func = inline(\"double add(double a, double b){ return a+b; }\")
>>> print "The sum of 3 and 4.5 is ", add_func(3, 4.5)

"""


import os, sys,re
import commands 
import string
import md5




VERBOSE = 0



class Instant:
    # Default values:

    def __init__(self):
        """ Instant constructor """
        self.code         = """
void f()
{
  printf("No code supplied!\\n");
}"""
        self.gen_setup  = 1 
        self.module  = 'instant_swig_module'
        self.swigopts     = '-I.'
        self.init_code    = '  //Code for initialisation here'
        self.system_headers = []
        self.local_headers  = []
        self.wrap_headers   = []
        self.sources        = []
        self.include_dirs   = ['-I.']
        self.libraries      = []
        self.library_dirs   = []
        self.cppargs        = ''
        self.object_files   = []
        self.arrays         = []
        self.additional_definitions = ""
        self.additional_declarations = ""


    def parse_args(self, dict):
        """ A method for parsing arguments. """
        for key in dict.keys():
            if key == 'code':
                self.code = dict[key]
            elif key == 'module':
                self.module = dict[key]
            elif key == 'swigopts':
                self.swigopts = dict[key]
            elif key == 'init_code':
                self.init_code = dict[key]
            elif key == 'sources':
                self.sources = dict[key]
            elif key == 'system_headers':
                self.system_headers = dict[key]
            elif key == 'local_headers':
                self.local_headers = dict[key]
            elif key == 'wrap_headers':
                self.wrap_headers = dict[key]
            elif key == 'include_dirs':
                self.include_dirs = dict[key]
            elif key == 'libraries':
                self.libraries = dict[key]
            elif key == 'library_dirs':
                self.library_dirs = dict[key]
            elif key == 'cppargs':
                self.cppargs = dict[key]
            elif key == 'object_files':
                self.object_files = dict[key]
	    elif key == 'arrays':
                self.arrays = dict[key]
	    elif key == 'additional_definitions':
                self.additional_definitions = dict[key]
	    elif key == 'additional_declarations':
                self.additional_declarations = dict[key]


        self.makefile_name = self.module+".mak"
        self.logfile_name  = self.module+".log"
        self.ifile_name    = self.module+".i"
        # sources has to be put in different variables,
        # depending on the file-suffix (.c or .cpp).
        self.srcs = []
        self.cppsrcs = []
        for file in self.sources:
            if file.endswith('.cpp'):
                self.cppsrcs.append(file)
            elif file.endswith('.c'):
                self.srcs.append(file)
            else:
                print 'Source files must have \'.c\' or \'.cpp\' suffix!'
                print 'This is not the case for',file
                return 1 # Parsing of argurments detected errors
        return 0 # all ok


    def create_extension(self, **args):
        """
        Call this function to instantly create an extension module.
        SWIG is used to generate code that can be compiled and used as
        an ordinary Python module.

        Arguments:
        ==========
           - B{code}:
              - A Python string containing C or C++ function, class, ....
           - B{module}:
              - The name you want for the module (Default is 'instant_swig_module'.). String.
           - B{swigopts}:
              - Options to swig, for instance C{-lpointers.i} to include the
                SWIG pointers.i library. String.
           - B{init_code}:
              - Code that should be executed when the Instant extension is imported. String.
           - B{system_headers}:
              - A list of system header files required by the Instant code. 
           - B{local_headers}:
              - A list of local header files required by the Instant code. 
           - B{wrap_headers}:
              - A list of local header files that should be wrapped by SWIG.
           - B{include_dirs}:
              - A list of directories to search for header files.
           - B{sources}:
              - A list of source files to compile and link with the extension.
           - B{cppargs}:
              - Flags like C{-D}, C{-U}, etc. String.
           - B{libraries}:
              - A list of libraries needed by the Instant extension.
           - B{library_dirs}:
              - A list of directories to search for libraries (C{-l}).
           - B{object_files}:
              - If you want to compile the files yourself. NOT YET SUPPORTED.
           - B{arrays}:
              - A list of the C arrays to be made from NumPy arrays.

           
        """
        if self.parse_args(args):
            print 'Nothing done!'
            return
#        self.debug()
        if not os.path.isdir(self.module): 
            os.mkdir(self.module)
        os.chdir(self.module)
        f = open("__init__.py", 'w')
        f.write("from %s import *"% self.module)
        
        self.generate_Interfacefile()
	if self.check_md5sum(): return 1 
	if sys.platform=='win32':
		null='nul'
	else:
		null='/dev/null'
	if (os.system("swig -version 2 > %s" % null ) == 0 ):   
   	    if ( not self.gen_setup ):   
                self.generate_Makefile()
                if os.path.isfile(self.makefile_name):
                    os.system("make -f "+self.makefile_name+" clean")
                os.system("make -f "+self.makefile_name+" &> "+self.logfile_name)
                if VERBOSE == 9:
                    os.remove(self.logfile_name)
	    else: 
                self.generate_setup()
	        os.system("python " + self.module + "_setup.py build_ext")
	        os.system("python " + self.module + "_setup.py install --install-platlib=.")
#            print "Module name is \'"+self.module+"\'"
	else: 
	    raise RuntimeError, "Could not find swig!\nYou can download swig from http://www.swig.org" 


    def debug(self):
        """
	print out all instance variable
	"""
        print 'DEBUG CODE:'
        print 'code',self.code
        print 'module',self.module
        print 'swigopts',self.swigopts
        print 'init_code',self.init_code
        print 'system_headers',self.system_headers
        print 'local_headers',self.local_headers
        print 'wrap_headers',self.wrap_headers
        print 'include_dirs',self.include_dirs
        print 'sources',self.sources
        print 'srcs',self.srcs
        print 'cppsrcs',self.cppsrcs
        print 'cppargs',self.cppargs


    def clean(self):
        """ Clean up files the current session. """
	if ( not gen_setup ) :  
            for file in [self.module+".log",
                         self.module+".log",
                         self.module+".i",
                         self.module+".mak",
                         self.module+".py",
                         self.module+".pyc",
                         "_"+self.module+".so"]:
                if os.path.isfile(file):
                    os.remove(file)

    def generate_Interfacefile(self):
        """
        Use this function to generate a SWIG interface file.
        
        To generate an interface file it uses the following class-variables:

         - code
         - ifile_name (The SWIG input file)
         - init_code (Code to put in the init section of the interface file)
         - system_headers (A list of system headers with declarations needed)
         - local_headers (A list of local headers with declarations needed)
         - wrap_headers (A list of local headers that will be wrapped by SWIG)

        """
        if VERBOSE > 0:
            print "\nGenerating interface file \'"+ self.ifile_name +"\':"
    

        func_name = self.code[:self.code.index(')')+1]


        # create typemaps 
        typemaps = "" 
	if (len(self.arrays) > 0): 
          for a in self.arrays:  
            # 1 dimentional arrays, ie. vectors
            if (len(a) == 2):  
  	      typemap = """
%%typemap(in) (int %(n)s,double* %(array)s){
  if (!PyArray_Check($input)) { 
    PyErr_SetString(PyExc_TypeError, "Not a NumPy array");
    return NULL; ;
  }
  PyArrayObject* pyarray;
  pyarray = (PyArrayObject*)$input; 
  $1 = pyarray->dimensions[0];
  $2 = (double*)pyarray->data;
}
""" % { 'n' : a[0] , 'array' : a[1] }
              typemaps += typemap
            # n dimentional arrays, ie. matrices and tensors  
            elif (len(a) == 3):  
  	      typemap = """
%%typemap(in) (int %(n)s,int* %(ptv)s,double* %(array)s){
  if (!PyArray_Check($input)) { 
    PyErr_SetString(PyExc_TypeError, "Not a NumPy array");
    return NULL; ;
  }
  PyArrayObject* pyarray;
  pyarray = (PyArrayObject*)$input; 
  $1 = pyarray->nd;
  $2 = pyarray->dimensions;
  $3 = (double*)pyarray->data;
}
""" % { 'n' : a[0] , 'ptv' : a[1], 'array' : a[2] }
              typemaps += typemap

        self.system_headers_code = "\n".join(['#include <%s>' % header for header in self.system_headers])
        self.local_headers_code = "\n".join(['#include "%s"' % header for header in self.local_headers])
        self.wrap_headers_code1 = "\n".join(['#include "%s"' % header for header in self.wrap_headers])
        self.wrap_headers_code2 = "\n".join(['%%include "%s"' % header for header in self.wrap_headers])

        self.typemaps = typemaps 

        interface_string = """
%%module (directors="1") %(module)s

%%feature("director");

%%{
#include <iostream>
%(additional_definitions)s 
%(system_headers_code)s 
%(local_headers_code)s 
%(wrap_headers_code1)s 
%(code)s
%%}

%%feature("autodoc", "1");
%%init%%{
%(init_code)s
%%}

%(additional_definitions)s
%(additional_declarations)s
%(wrap_headers_code2)s
%(typemaps)s
%(code)s;

""" % vars(self)
     

        f = open(self.ifile_name, 'w')
        f.write(interface_string)
        f.close()
        if VERBOSE > 0:
            print '... Done'
        return func_name[func_name.rindex(' ')+1:func_name.index('(')]

    def getmd5sumfiles(self, filenames):
        '''
        get the md5 value of filename
        modified based on Python24\Tools\Scripts\md5sum.py
        '''

        print filenames
        for filename in filenames: 
         
#            print "Adding file ", filename, "to md5 sum "

            try:
                fp = open(filename, 'rb')
            except IOError, msg:
                sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg))
                return None

            m = md5.new()
            try:
                while 1:
                    data = fp.read()
                    if not data:
                        break
                    m.update(data)
            except IOError, msg:
                sys.stderr.write('%s: I/O error: %s\n' % (filename, msg))
                return None
            fp.close() 
        return '%s %s\n' % (m.hexdigest().upper(), filename)

    def writemd5sumfile(self, filenames, md5out=sys.stdout):
        result=self.getmd5sumfiles(filenames)
        try:
            fp = open(md5out, 'w')
        except IOError, msg:
            sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg))
        fp.write(result)
        fp.close()

    def check_md5sum(self): 
        """ 
        Check if the md5sum of the generated interface file has changed since the last
        time the module was compiled. If it has changed then recompilation is necessary.  
        """ 
        md5sum_files = []
        md5sum_files.append(self.ifile_name)
        for i in self.sources : md5sum_files.append(i)
        for i in self.wrap_headers : md5sum_files.append(i)
        for i in self.local_headers: md5sum_files.append(i)
        print md5sum_files 


        if (os.path.isfile(self.module+".md5")):
            current_md5sum = self.getmd5sumfiles(md5sum_files )
            file = open(self.module + ".md5") 
            last_md5sum = file.readline()
            if ( current_md5sum == last_md5sum) : return 1  
            else: 
                self.writemd5sumfile(md5sum_files, self.module + ".md5")
                return 0 
                
            
        else:
            self.writemd5sumfile(md5sum_files, self.module + ".md5")
            return 0
        
        return 0; 


    
#    def check_md5sum(self): 
#        """ 
#        Check if the md5sum of the generated interface file has changed since the last
#        time the module was compiled. If it has changed then recompilation is necessary.  
#        """ 
#        if (os.path.isfile(self.module+".md5")):
#            pipe = os.popen("md5sum " + self.ifile_name)  
#            current_md5sum = pipe.readline() 
#            file = open(self.module + ".md5") 
#            last_md5sum = file.readline()
#            if ( current_md5sum == last_md5sum) : return 1  
#            else: 
#                os.system("md5sum " + self.ifile_name +  " > " + self.module + ".md5")  
#                return 0 
#                
#            
#        else:
#            os.system("md5sum " + self.ifile_name +  " > " + self.module + ".md5")  
#            return 0
#        
#        return 0; 

    def generate_setup(self): 
        """
	Generates a setup.py file
	"""
        self.cppsrcs.append( "%s_wrap.cxx" %self.module )
	f = open(self.module+'_setup.py', 'w')
	f.write(""" 
import os
from distutils.core import setup, Extension
name = '%s' 
swig_cmd ='swig -python -c++ %s %s'
os.system(swig_cmd)
sources = %s 
setup(name = '%s', 
      ext_modules = [Extension('_' + '%s', sources, 
                     include_dirs=%s, 
                     library_dirs=%s, libraries=%s)])  
	""" % (self.module, self.swigopts, self.ifile_name, 
	       self.cppsrcs, 
	       self.module, self.module, self.include_dirs, self.library_dirs, self.libraries ))   
	f.close()


    def generate_Makefile(self):
        """
        Generates a project dependent Makefile, which includes and
        uses SWIG's own Makefile to create an extension module of
        the supplied C/C++ code.
        """
        f = open(self.makefile_name, 'w')
        f.write("""
LIBS = %s
LDPATH = 

FLAGS = %s

SWIG       = swig 
SWIGOPT    = %s
INTERFACE  = %s
TARGET     = %s
INCLUDES   = 

SWIGMAKEFILE = $(SWIGSRC)/Examples/Makefile

python::
	$(MAKE) -f '$(SWIGMAKEFILE)' INTERFACE='$(INTERFACE)' \\
	SWIG='$(SWIG)' SWIGOPT='$(SWIGOPT)'  \\
        SRCS='%s' \\
        CPPSRCS='%s' \\
	INCLUDES='$(INCLUDES) %s' \\
        LIBS='$(LIBS) %s' \\
        CFLAGS='$(CFLAGS) $(FLAGS)' \\
        TARGET='$(TARGET)' \\
	python_cpp

clean::
	rm -f *_wrap* _%s.so *.o $(OBJ_FILES)  *~
    """ % (list2str(self.libraries),
           self.cppargs,
           self.swigopts,
           self.ifile_name,
           self.module,
           list2str(self.srcs),
           list2str(self.cppsrcs),
           list2str(self.include_dirs),
           list2str(self.library_dirs),
           self.module
           ))
        f.close()
        
        if VERBOSE > 0:
            print 'Makefile', self.makefile_name, 'generated'
            

# convert list values to string
def list2str(list):
    s = str(list)
    for c in ['[', ']', ',', '\'']:
        s = s.replace(c, '')
    return s



def create_extension(**args):
    """
        This is a small wrapper around the create_extension function
        in Instant.

        Call this function to instantly create an extension module.
        SWIG is used to generate code that can be compiled and used as
        an ordinary Python module.

        Arguments:
        ==========
           - B{code}:
              - A Python string containing C or C++ function, class, ....
           - B{module}:
              - The name you want for the module (Default is 'instant_swig_module'.). String.
           - B{swigopts}:
              - Options to swig, for instance C{-lpointers.i} to include the
                SWIG pointers.i library. String.
           - B{init_code}:
              - Code that should be executed when the Instant extension is imported. String.
           - B{system_headers}:
              - A list of system header files required by the Instant code. 
           - B{local_headers}:
              - A list of local header files required by the Instant code. 
           - B{wrap_headers}:
              - A list of local header files that will be wrapped by SWIG.
           - B{include_dirs}:
              - A list of directories to search for header files.
           - B{sources}:
              - A list of source files to compile and link with the extension.
           - B{cppargs}:
              - Flags like C{-D}, C{-U}, etc. String.
           - B{libraries}:
              - A list of libraries needed by the Instant extension.
           - B{library_dirs}:
              - A list of directories to search for libraries (C{-l}).
           - B{object_files}:
              - If you want to compile the files yourself. NOT YET SUPPORTED.
           - B{arrays}:
              - A list of the C arrays to be made from NumPy arrays.
    """ 
    ext = Instant()
    ext.create_extension(**args)


def inline(c_code):
    """
       This is a short wrapper around the create_extention function 
       in Instant. 
       
       It creates an extension module given that
       the input is a valid C function. It is only possible
       to inline one C function each time. 

       Usage: 

       >>> from Instant import inline
       >>> add_func = inline("double add(double a, double b){ return a+b; }")
       >>> print "The sum of 3 and 4.5 is ", add_func(3, 4.5)


    """
    ext = Instant()
    func = c_code[:c_code.index('(')]
    ret, func_name = func.split()
    ext.create_extension(code=c_code, module="inline_ext")
    exec("from inline_ext import %s as func_name"% func_name) 
    return func_name


def inline_with_numpy(c_code, **args_dict):
    """
       This is a short wrapper around the create_extention function 
       in Instant. 
       
       It creates an extension module given that
       the input is a valid C function. It is only possible
       to inline one C function each time. The difference between
       this function and the inline function is that C-arrays can be used. 
       The following example illustrates that. 

       Usage: 

       >>> import numpy
       >>> import time
       >>> from Instant import inline_with_numpy
       >>> c_code = \"\"\"
           double sum (int n1, double* array1){
               double tmp = 0.0; 
               for (int i=0; i<n1; i++) {  
                   tmp += array1[i]; 
               }
               return tmp; 
           }
           \"\"\"
       >>> sum_func = inline_with_numpy(c_code,  arrays = [['n1', 'array1']])
       >>> a = numpy.arange(10000000); a = numpy.sin(a)
       >>> sum_func(a)
    """

    ext = Instant()
    func = c_code[:c_code.index('(')]
    ret, func_name = func.split()
    import numpy	
    ext.create_extension(code=c_code, module="inline_ext_numpy", 
                         system_headers=["arrayobject.h"], cppargs='-O3',
                         include_dirs= ["%s/numpy"% numpy.get_include()],
                         init_code='import_array();', arrays = args_dict["arrays"])
    exec("from inline_ext_numpy import %s as func_name"% func_name) 
    return func_name


def inline_with_numeric(c_code, **args_dict):
    """
       This is a short wrapper around the create_extention function 
       in Instant. 
       
       It creates an extension module given that
       the input is a valid C function. It is only possible
       to inline one C function each time. The difference between
       this function and the inline function is that C-arrays can be used. 
       The following example illustrates that. 

       Usage: 

       >>> import numpy 
       >>> import time
       >>> from Instant import inline_with_numeric
       >>> c_code = \"\"\"
           double sum (int n1, double* array1){
               double tmp = 0.0; 
               for (int i=0; i<n1; i++) {  
                   tmp += array1[i]; 
               }
               return tmp; 
           }
           \"\"\"
       >>> sum_func = inline_with_numeric(c_code,  arrays = [['n1', 'array1']])
       >>> a = numpy.arange(10000000); a = numpy.sin(a)
       >>> sum_func(a)
    """

    ext = Instant()
    func = c_code[:c_code.index('(')]
    ret, func_name = func.split()
    ext.create_extension(code=c_code, module="inline_ext_numeric", 
                         system_headers=["arrayobject.h"], cppargs='-O3',
                         include_dirs= [[sys.prefix + "/include/python" + sys.version[:3] + "/Numeric", 
                         	sys.prefix + "/include" + "/Numeric"][sys.platform=='win32']],
                         init_code='import_array();', arrays = args_dict["arrays"])
    exec("from inline_ext_numeric import %s as func_name"% func_name) 
    return func_name


def inline_with_numarray(c_code, **args_dict):
    """
       This is a short wrapper around the create_extention function 
       in Instant. 
       
       It creates an extension module given that
       the input is a valid C function. It is only possible
       to inline one C function each time. The difference between
       this function and the inline function is that C-arrays can be used. 
       The following example illustrates that. 

       Usage: 

       >>> import numarray 
       >>> import time
       >>> from Instant import inline_with_numarray
       >>> c_code = \"\"\"
           double sum (int n1, double* array1){
               double tmp = 0.0; 
               for (int i=0; i<n1; i++) {  
                   tmp += array1[i]; 
               }
               return tmp; 
           }
           \"\"\"
       >>> sum_func = inline_with_numarray(c_code,  arrays = [['n1', 'array1']])
       >>> a = numarray.arange(10000000); a = numarray.sin(a)
       >>> sum_func(a)
    """

    ext = Instant()
    func = c_code[:c_code.index('(')]
    ret, func_name = func.split()
    ext.create_extension(code=c_code, module="inline_ext_numarray", 
                         system_headers=["arrayobject.h"], cppargs='-O3',
                         include_dirs= [[sys.prefix + "/include/python" + sys.version[:3] + "/numarray",
                         	sys.prefix + "/include" + "/numarray"][sys.platform=='win32']],
                         init_code='import_array();', arrays = args_dict["arrays"])
    exec("from inline_ext_numarray import %s as func_name"% func_name) 
    return func_name






def header_and_libs_from_pkgconfig(*packages):
    """
    This function returns list of include files, flags, libraries and library directories obtain from a pkgconfig file. 
    The usage is: 
    (includes, flags, libraries, libdirs) = header_and_libs_from_pkgconfig(list_of_packages)

    """
    includes = []
    flags = []
    libs = []
    libdirs = []
    for pack in packages:
#        print commands.getstatusoutput("pkg-config --exists %s " % pack)
        if  commands.getstatusoutput("pkg-config --exists %s " % pack)[0] == 0: 
            tmp = string.split(commands.getoutput("pkg-config --cflags-only-I %s " % pack ))  
            for i in tmp: includes.append(i[2:]) 
            tmp = string.split(commands.getoutput("pkg-config --cflags-only-other %s " % pack ))  
            for i in tmp: flags.append(i) 
            tmp = string.split(commands.getoutput("pkg-config --libs-only-l  %s " % pack ))  
            for i in tmp: libs.append(i[2:]) 
            tmp = string.split(commands.getoutput("pkg-config --libs-only-L  %s " % pack ))  
            for i in tmp: libdirs.append(i[2:]) 
        else: 
            raise OSError, "The pkg-config file %s does not exist" % pack  


    return (includes,flags,libs, libdirs) 
        








syntax highlighted by Code2HTML, v. 0.9.1