""" Script to create ctypes based python bindings to ImageMagick. (c) 2007 - Achim Domma - domma@procoders.net """ import os from lxml import etree # Use gcc to create xml definition of the MagickWand API INCLUDE_PATH='/opt/local/include/' os.system('gccxml -I %s %swand/MagickWand.h -fxml=magick.xml'%(INCLUDE_PATH,INCLUDE_PATH)) class TypeNotHandled(Exception): """ User defined exception which is used to signal, that a certain type cannot be handled. Usually the current type, method, ... is simply skiped, if an exception of type TypeNotHandled is thrown. """ def isMagickFkt(element): """ Helper method to check if a function is part of the MagickWand API. Quite simple at the moment, but it works. Might have to be improved in the future. """ name = element.get('name') return 'Magick' in name or 'Draw' in name or 'Wand' in name and not 'Throw' in name # mapping from C data types to ctypes data types FUNDAMENTAL_TYPE_MAPPING = { 'void':None, 'char':'ctypes.c_char', 'long int':'ctypes.c_long', #? 'long unsigned int':'ctypes.c_ulong', #? 'unsigned char' : 'ctypes.c_ubyte', 'double' : 'ctypes.c_double', 'long long unsigned int':'ctypes.c_ulonglong', 'int' : 'ctypes.c_int', 'unsigned int' : 'ctypes.c_uint', 'long long int' : 'ctypes.c_longlong' } # some functions which should be ignored for various reasons IGNORE_FUNCTIONS = ['MagickGetOrientationType'] class Type(object): """ Represents a certain type in the MagickWand API. It gets the informations from the XML definition of the API and creates the matching ctypes representation. At the moment there are some assumptions about types, so the code is no way reusable in a general way: There are Enumerations, FundamentalTypes and Typedefs. If there are CvQualifiedType-Tags, these are ignored because they don't matter for ctypes. The first info element has to be one of these types. The remaining items are assumd to be pointer declarations. If we have a Typedef it must have at least one pointer declaration. Pure Typedefs cannot be handled. """ def __init__(self,infos): infos.reverse() self.name = infos[0][0] self.key = infos[0][1] self.infos = [i[2] for i in infos if not i[2]=='CvQualifiedType'] self.type = self.infos[0] @property def c_type(self): infos = self.infos[1:] result = "" # fundamental types require mapping to # ctypes types. if self.infos[0]=='FundamentalType': # void* pointer are a special case because there # is a special ctypes data type for void* if self.name=='void' and len(self.infos)==2: return "ctypes.c_void_p" else: result = FUNDAMENTAL_TYPE_MAPPING[self.name] else: result = self.name if infos: # Typedefs are already pointers, so we # have to ignore the following pointer declaration. if self.infos[0]=='Typedef': infos.pop() for info in infos: # all remaining info elements have to be PointerType. if not # an exception is thrown. if info!='PointerType': raise RuntimeError("PointerType expected but %s found." % info) result = "ctypes.POINTER(%s)" % result return result class Function(object): def __init__(self,name,result,params): self.name = name self.result = result self.params = params @property def restype(self): return self.result.c_type @property def argtypes(self): """ Creates a tuple of ctypes data types based on the passed params list. """ if len(self.params)==1: type = self.params[0].c_type if not type: return "()" else: return "(" + type + ",)" else: return "(" + ','.join([p.c_type for p in self.params]) + ")" class Parser(object): def __init__(self,path): self.tree = etree.parse(file(path)) self.types = {} self._parseFunctions() def _getTypeInfo(self,key): if not key: raise TypeNotHandled() info = self.tree.xpath('//*[(contains(name(),"Type") or name()="Enumeration") and @id="' + key + '"]') if len(info)==0: raise RuntimeError(key,"not found.") info=info[0] return info.get('name'), info.get('type'), info.tag def getType(self,key): if not key in self.types: infos = [self._getTypeInfo(key)] while not infos[-1][0]: infos.append(self._getTypeInfo(infos[-1][1])) self.types[key] = Type(infos) return self.types[key] def _parseFunctions(self): self.functions = [] for elem in self.tree.xpath('/GCC_XML/Function'): try: if isMagickFkt(elem): name = elem.get('name') if name in IGNORE_FUNCTIONS: print "Ignoring function",name continue result = self.getType(elem.get('returns')) params = [] for p in elem.xpath('Argument'): params.append(self.getType(p.get('type'))) self.functions.append(Function(name,result,params)) except TypeNotHandled: print "TypeNotHandled:",elem.get('name') @property def enumerations(self): return [t for t in self.types.values() if t.type=='Enumeration'] @property def typedefs(self): return [t for t in self.types.values() if t.type=='Typedef'] def _writeEnums(self,out): for enum in self.enumerations: print >>out, "class %s(ctypes.c_int): pass" % enum.name for val in self.tree.xpath('//Enumeration[@name="%s"]/EnumValue' % enum.name): name = val.get('name') value = val.get('init') print >>out, "%s = %s(%s)" % (name,enum.name,value) print >>out, "" def _writeTypedefs(self,out): names = {} for typedef in self.typedefs: name = typedef.name if not name in names: print >>out, "class %s(ctypes.c_void_p): pass" % typedef.name names[name] = None print >>out,"" def _writeFunctions(self,out): for fkt in self.functions: print >>out, "# %s" % fkt.name print >>out, "try:" print >>out, " _magick.%s.restype = %s" % (fkt.name,fkt.restype) print >>out, " _magick.%s.argtypes = %s" % (fkt.name,fkt.argtypes) print >>out, "except AttributeError,e:" print >>out, " print e" print >>out, "else:" print >>out, " %s = _magick.%s" % (fkt.name,fkt.name) def generateModule(self): out = file('PythonMagickWand.py','w') print >>out, '"""' print >>out, file('PythonMagickWand.txt').read() print >>out, '"""' print >>out, """import ctypes _magick = ctypes.CDLL('/opt/local/lib/libWand.dylib') _magick.MagickWandGenesis() """ self._writeEnums(out) self._writeTypedefs(out) self._writeFunctions(out) print >>out,"""if __name__=='__main__': import doctest doctest.testmod()""" out.close() if __name__=='__main__': parser = Parser('magick.xml') parser.generateModule()