From 109ee984f6dabf1e114cff49da73e8f0c1b49390 Mon Sep 17 00:00:00 2001 From: Olivier Aubert Date: Mon, 10 Aug 2009 00:34:24 +0200 Subject: [PATCH] python-ctypes: refactor generate.py to be more modular --- bindings/python-ctypes/TODO | 11 - bindings/python-ctypes/generate.py | 1067 +++++++++++++++------------- 2 files changed, 569 insertions(+), 509 deletions(-) diff --git a/bindings/python-ctypes/TODO b/bindings/python-ctypes/TODO index 088fd6aebd..087d87bed0 100644 --- a/bindings/python-ctypes/TODO +++ b/bindings/python-ctypes/TODO @@ -4,14 +4,3 @@ * Support multiple VLC versions: define a front-end module which will load the appropriate versionned module from a subdirectory. - -* Support options: --debug/-c, --output/-o, --check-symbols/-s, ... - -* Use setup.py - -* Refactor code: - class Parser(list_of_includes) -> properties enums, methods..., - dump() - check()... - - class PythonGenerator(parser) -> p.save( filename ) diff --git a/bindings/python-ctypes/generate.py b/bindings/python-ctypes/generate.py index 3fbb64455d..032af7d4a9 100755 --- a/bindings/python-ctypes/generate.py +++ b/bindings/python-ctypes/generate.py @@ -33,6 +33,7 @@ import re import time import operator import itertools +from optparse import OptionParser # DefaultDict from ASPN python cookbook import copy @@ -91,520 +92,590 @@ special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};') parameter_passing=DefaultDict(default=1) parameter_passing['libvlc_exception_t*']=3 -# C-type to ctypes/python type conversion. -# Note that enum types conversions are generated (cf convert_enum_names) -typ2class={ - 'libvlc_exception_t*': 'ctypes.POINTER(VLCException)', - - 'libvlc_media_player_t*': 'MediaPlayer', - 'libvlc_instance_t*': 'Instance', - 'libvlc_media_t*': 'Media', - 'libvlc_log_t*': 'Log', - 'libvlc_log_iterator_t*': 'LogIterator', - 'libvlc_log_message_t*': 'LogMessage', - 'libvlc_event_type_t': 'ctypes.c_uint', - 'libvlc_event_manager_t*': 'EventManager', - 'libvlc_media_discoverer_t*': 'MediaDiscoverer', - 'libvlc_media_library_t*': 'MediaLibrary', - 'libvlc_media_list_t*': 'MediaList', - 'libvlc_media_list_player_t*': 'MediaListPlayer', - 'libvlc_media_list_view_t*': 'MediaListView', - 'libvlc_track_description_t*': 'TrackDescription', - 'libvlc_audio_output_t*': 'AudioOutput', - - 'mediacontrol_Instance*': 'MediaControl', - 'mediacontrol_Exception*': 'MediaControlException', - 'mediacontrol_RGBPicture*': 'ctypes.POINTER(RGBPicture)', - 'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq', - 'mediacontrol_Position*': 'ctypes.POINTER(MediaControlPosition)', - 'mediacontrol_StreamInformation*': 'ctypes.POINTER(MediaControlStreamInformation)', - 'WINDOWHANDLE': 'ctypes.c_ulong', - - 'void': 'None', - 'void*': 'ctypes.c_void_p', - 'short': 'ctypes.c_short', - 'char*': 'ctypes.c_char_p', - 'char**': 'ListPOINTER(ctypes.c_char_p)', - 'uint32_t': 'ctypes.c_uint', - 'float': 'ctypes.c_float', - 'unsigned': 'ctypes.c_uint', - 'int': 'ctypes.c_int', - '...': 'FIXMEva_list', - 'libvlc_callback_t': 'ctypes.c_void_p', - 'libvlc_time_t': 'ctypes.c_longlong', - } - -# Defined python classes, i.e. classes for which we want to generate -# class wrappers around libvlc functions -defined_classes=( - 'MediaPlayer', - 'Instance', - 'Media', - 'Log', - 'LogIterator', - #'LogMessage', - 'EventManager', - 'MediaDiscoverer', - 'MediaLibrary', - 'MediaList', - 'MediaListPlayer', - 'MediaListView', - 'TrackDescription', - 'AudioOutput', - 'MediaControl', - ) - -# Definition of prefixes that we can strip from method names when -# wrapping them into class methods -prefixes=dict( (v, k[:-2]) for (k, v) in typ2class.iteritems() if v in defined_classes ) -prefixes['MediaControl']='mediacontrol_' - -def parse_param(s): - """Parse a C parameter expression. - - It is used to parse both the type/name for methods, and type/name - for their parameters. - - It returns a tuple (type, name). - """ - s=s.strip() - s=s.replace('const', '') - if 'VLC_FORWARD' in s: - m=forward_re.match(s) - s=m.group(1)+m.group(2) - m=param_re.search(s) - if m: - const, typ, name=m.groups() - while name.startswith('*'): - typ += '*' - name=name[1:] - if name == 'const*': - # K&R definition: const char * const* - name='' - typ=typ.replace(' ', '') - return typ, name - else: - # K&R definition: only type - return s.replace(' ', ''), '' - -def insert_code(filename): - """Generate header/footer code. - """ - f=open(filename, 'r') - for l in f: - if 'build_date' in l: - print 'build_date="%s"' % time.ctime() +class Parser(object): + def __init__(self, list_of_files): + self.methods=[] + self.enums=[] + + for name in list_of_files: + self.enums.extend(self.parse_typedef(name)) + self.methods.extend(self.parse_include(name)) + + def parse_param(self, s): + """Parse a C parameter expression. + + It is used to parse both the type/name for methods, and type/name + for their parameters. + + It returns a tuple (type, name). + """ + s=s.strip() + s=s.replace('const', '') + if 'VLC_FORWARD' in s: + m=forward_re.match(s) + s=m.group(1)+m.group(2) + m=param_re.search(s) + if m: + const, typ, name=m.groups() + while name.startswith('*'): + typ += '*' + name=name[1:] + if name == 'const*': + # K&R definition: const char * const* + name='' + typ=typ.replace(' ', '') + return typ, name else: - print l, - f.close() - -def convert_enum_names(enums): - res={} - for (typ, name, values, comment) in enums: - if typ != 'enum': - raise Exception('This method only handles enums') - pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1] - if '_' in pyname: - pyname=pyname.title().replace('_', '') - elif not pyname[0].isupper(): - pyname=pyname.capitalize() - res[name]=pyname - return res - -def generate_enums(enums): - for (typ, name, values, comment) in enums: - if typ != 'enum': - raise Exception('This method only handles enums') - pyname=typ2class[name] - - print "class %s(ctypes.c_uint):" % pyname - print ' """%s\n """' % comment - - conv={} - # Convert symbol names - for k, v in values: - n=k.split('_')[-1] - if len(n) == 1: - # Single character. Some symbols use 1_1, 5_1, etc. - n="_".join( k.split('_')[-2:] ) - if re.match('^[0-9]', n): - # Cannot start an identifier with a number - n='_'+n - conv[k]=n - - for k, v in values: - print " %s=%s" % (conv[k], v) - - print " _names={" - for k, v in values: - print " %s: '%s'," % (v, conv[k]) - print " }" - - print """ - def __repr__(self): - return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value])) -""" + # K&R definition: only type + return s.replace(' ', ''), '' + + def parse_typedef(self, name): + """Parse include file for typedef expressions. + + This generates a tuple for each typedef: + (type, name, value_list, comment) + with type == 'enum' (for the moment) and value_list being a list of (name, value) + Note that values are string, since this is intended for code generation. + """ + f=open(name, 'r') + accumulator='' + for l in f: + # Note: lstrip() should not be necessary, but there is 1 badly + # formatted comment in vlc1.0.0 includes + if l.lstrip().startswith('/**'): + comment='' + continue + elif l.startswith(' * '): + comment = comment + l[3:] + continue -def parse_typedef(name): - """Parse include file for typedef expressions. - - This generates a tuple for each typedef: - (type, name, value_list, comment) - with type == 'enum' (for the moment) and value_list being a list of (name, value) - Note that values are string, since this is intended for code generation. - """ - f=open(name, 'r') - accumulator='' - for l in f: - # Note: lstrip() should not be necessary, but there is 1 badly - # formatted comment in vlc1.0.0 includes - if l.lstrip().startswith('/**'): - comment='' - continue - elif l.startswith(' * '): - comment = comment + l[3:] - continue - - l=l.strip() - if l.startswith('/*') or l.endswith('*/'): - continue - - if (l.startswith('typedef enum') or l.startswith('enum')) and not l.endswith(';'): - # Multiline definition. Accumulate until end of definition - accumulator=l - continue - elif accumulator: - accumulator=" ".join( (accumulator, l) ) - if l.endswith(';'): - # End of definition - l=accumulator - accumulator='' - - m=enum_re.match(l) - if m: - values=[] - (typ, dummy, data, name)=m.groups() - for i, l in enumerate(paramlist_re.split(data)): - l=l.strip() - if l.startswith('/*'): - continue - if '=' in l: - # A value was specified. Use it. - values.append(re.split('\s*=\s*', l)) - else: - if l: - values.append( (l, str(i)) ) - comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '') - yield (typ, name.strip(), values, comment) - comment='' - continue - - # Special case, used only for libvlc_events.h - m=special_enum_re.match(l) - if m: - values=[] - (typ, name, data)=m.groups() - for i, l in enumerate(paramlist_re.split(data)): - l=l.strip() - if l.startswith('/*') or l.startswith('#'): - continue - if '=' in l: - # A value was specified. Use it. - values.append(re.split('\s*=\s*', l)) - else: - if l: - values.append( (l, str(i)) ) - comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '') - yield (typ, name.strip(), values, comment) - comment='' - continue - -def parse_include(name): - """Parse include file. - - This generates a tuple for each function: - (return_type, method_name, parameter_list, comment) - with parameter_list being a list of tuples (parameter_type, parameter_name). - """ - f=open(name, 'r') - accumulator='' - comment='' - for l in f: - # Note: lstrip() should not be necessary, but there is 1 badly - # formatted comment in vlc1.0.0 includes - if l.lstrip().startswith('/**'): - comment='' - continue - elif l.startswith(' * '): - comment = comment + l[3:] - continue - - l=l.strip() - - if accumulator: - accumulator=" ".join( (accumulator, l) ) - if l.endswith(');'): - # End of definition - l=accumulator - accumulator='' - elif l.startswith('VLC_PUBLIC_API') and not l.endswith(');'): - # Multiline definition. Accumulate until end of definition - accumulator=l - continue - - m=api_re.match(l) - if m: - (ret, param)=m.groups() + l=l.strip() + if l.startswith('/*') or l.endswith('*/'): + continue - rtype, method=parse_param(ret) + if (l.startswith('typedef enum') or l.startswith('enum')) and not l.endswith(';'): + # Multiline definition. Accumulate until end of definition + accumulator=l + continue + elif accumulator: + accumulator=" ".join( (accumulator, l) ) + if l.endswith(';'): + # End of definition + l=accumulator + accumulator='' + + m=enum_re.match(l) + if m: + values=[] + (typ, dummy, data, name)=m.groups() + for i, l in enumerate(paramlist_re.split(data)): + l=l.strip() + if l.startswith('/*'): + continue + if '=' in l: + # A value was specified. Use it. + values.append(re.split('\s*=\s*', l)) + else: + if l: + values.append( (l, str(i)) ) + comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '') + yield (typ, name.strip(), values, comment) + comment='' + continue - params=[] - for p in paramlist_re.split(param): - params.append( parse_param(p) ) + # Special case, used only for libvlc_events.h + m=special_enum_re.match(l) + if m: + values=[] + (typ, name, data)=m.groups() + for i, l in enumerate(paramlist_re.split(data)): + l=l.strip() + if l.startswith('/*') or l.startswith('#'): + continue + if '=' in l: + # A value was specified. Use it. + values.append(re.split('\s*=\s*', l)) + else: + if l: + values.append( (l, str(i)) ) + comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '') + yield (typ, name.strip(), values, comment) + comment='' + continue - if len(params) == 1 and params[0][0] == 'void': - # Empty parameter list - params=[] + def parse_include(self, name): + """Parse include file. + + This generates a tuple for each function: + (return_type, method_name, parameter_list, comment) + with parameter_list being a list of tuples (parameter_type, parameter_name). + """ + f=open(name, 'r') + accumulator='' + comment='' + for l in f: + # Note: lstrip() should not be necessary, but there is 1 badly + # formatted comment in vlc1.0.0 includes + if l.lstrip().startswith('/**'): + comment='' + continue + elif l.startswith(' * '): + comment = comment + l[3:] + continue + + l=l.strip() + + if accumulator: + accumulator=" ".join( (accumulator, l) ) + if l.endswith(');'): + # End of definition + l=accumulator + accumulator='' + elif l.startswith('VLC_PUBLIC_API') and not l.endswith(');'): + # Multiline definition. Accumulate until end of definition + accumulator=l + continue - if list(p for p in params if not p[1]): - # Empty parameter names. Have to poke into comment. - names=comment_re.findall(comment) - if len(names) < len(params): - # Bad description: all parameters are not specified. - # Generate default parameter names - badnames=[ "param%d" % i for i in xrange(len(params)) ] - # Put in the existing ones - for (i, p) in enumerate(names): - badnames[i]=names[i] - names=badnames - print "### Error ###" - print "### Cannot get parameter names from comment for %s: %s" % (method, comment.replace("\n", ' ')) - # Note: this was previously - # raise Exception("Cannot get parameter names from comment for %s: %s" % (method, comment)) - # but it prevented code generation for a minor detail (some bad descriptions). - params=[ (p[0], names[i]) for (i, p) in enumerate(params) ] + m=api_re.match(l) + if m: + (ret, param)=m.groups() + rtype, method=self.parse_param(ret) + + params=[] + for p in paramlist_re.split(param): + params.append( self.parse_param(p) ) + + if len(params) == 1 and params[0][0] == 'void': + # Empty parameter list + params=[] + + if list(p for p in params if not p[1]): + # Empty parameter names. Have to poke into comment. + names=comment_re.findall(comment) + if len(names) < len(params): + # Bad description: all parameters are not specified. + # Generate default parameter names + badnames=[ "param%d" % i for i in xrange(len(params)) ] + # Put in the existing ones + for (i, p) in enumerate(names): + badnames[i]=names[i] + names=badnames + print "### Error ###" + print "### Cannot get parameter names from comment for %s: %s" % (method, comment.replace("\n", ' ')) + # Note: this was previously + # raise Exception("Cannot get parameter names from comment for %s: %s" % (method, comment)) + # but it prevented code generation for a minor detail (some bad descriptions). + params=[ (p[0], names[i]) for (i, p) in enumerate(params) ] + + # Transform Doxygen syntax into epydoc syntax + comment=comment.replace('\\param', '@param').replace('\\return', '@return') + + if debug: + print '********************' + print l + print '-------->' + print "%s (%s)" % (method, rtype) + for typ, name in params: + print " %s (%s)" % (name, typ) + print '********************' + yield (rtype, + method, + params, + comment) + comment='' + + def dump_methods(self): + print "** Defined functions **" + for (rtype, name, params, comment) in self.methods: + print "%(name)s (%(rtype)s):" % locals() + for t, n in params: + print " %(n)s (%(t)s)" % locals() + + def dump_enums(self): + print "** Defined enums **" + for (typ, name, values, comment) in self.enums: + print "%(name)s (%(typ)s):" % locals() + for k, v in values: + print " %(k)s=%(v)s" % locals() + +class PythonGenerator(object): + # C-type to ctypes/python type conversion. + # Note that enum types conversions are generated (cf convert_enum_names) + type2class={ + 'libvlc_exception_t*': 'ctypes.POINTER(VLCException)', + + 'libvlc_media_player_t*': 'MediaPlayer', + 'libvlc_instance_t*': 'Instance', + 'libvlc_media_t*': 'Media', + 'libvlc_log_t*': 'Log', + 'libvlc_log_iterator_t*': 'LogIterator', + 'libvlc_log_message_t*': 'LogMessage', + 'libvlc_event_type_t': 'ctypes.c_uint', + 'libvlc_event_manager_t*': 'EventManager', + 'libvlc_media_discoverer_t*': 'MediaDiscoverer', + 'libvlc_media_library_t*': 'MediaLibrary', + 'libvlc_media_list_t*': 'MediaList', + 'libvlc_media_list_player_t*': 'MediaListPlayer', + 'libvlc_media_list_view_t*': 'MediaListView', + 'libvlc_track_description_t*': 'TrackDescription', + 'libvlc_audio_output_t*': 'AudioOutput', + + 'mediacontrol_Instance*': 'MediaControl', + 'mediacontrol_Exception*': 'MediaControlException', + 'mediacontrol_RGBPicture*': 'ctypes.POINTER(RGBPicture)', + 'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq', + 'mediacontrol_Position*': 'ctypes.POINTER(MediaControlPosition)', + 'mediacontrol_StreamInformation*': 'ctypes.POINTER(MediaControlStreamInformation)', + 'WINDOWHANDLE': 'ctypes.c_ulong', + + 'void': 'None', + 'void*': 'ctypes.c_void_p', + 'short': 'ctypes.c_short', + 'char*': 'ctypes.c_char_p', + 'char**': 'ListPOINTER(ctypes.c_char_p)', + 'uint32_t': 'ctypes.c_uint', + 'float': 'ctypes.c_float', + 'unsigned': 'ctypes.c_uint', + 'int': 'ctypes.c_int', + '...': 'FIXMEva_list', + 'libvlc_callback_t': 'ctypes.c_void_p', + 'libvlc_time_t': 'ctypes.c_longlong', + } + + # Defined python classes, i.e. classes for which we want to generate + # class wrappers around libvlc functions + defined_classes=( + 'MediaPlayer', + 'Instance', + 'Media', + 'Log', + 'LogIterator', + 'EventManager', + 'MediaDiscoverer', + 'MediaLibrary', + 'MediaList', + 'MediaListPlayer', + 'MediaListView', + 'TrackDescription', + 'AudioOutput', + 'MediaControl', + ) + + def __init__(self, parser=None): + self.parser=parser + + # Generate python names for enums + self.type2class.update(self.convert_enum_names(parser.enums)) + self.check_types() + + # Definition of prefixes that we can strip from method names when + # wrapping them into class methods + self.prefixes=dict( (v, k[:-2]) + for (k, v) in self.type2class.iteritems() + if v in self.defined_classes ) + self.prefixes['MediaControl']='mediacontrol_' + + def save(self, filename=None): + if filename is None or filename == '-': + self.fd=sys.stdout + else: + self.fd=open(filename, 'w') + + self.insert_code('header.py') + self.generate_enums(self.parser.enums) + wrapped_methods=self.generate_wrappers(self.parser.methods) + for l in self.parser.methods: + self.output_ctypes(*l) + self.insert_code('footer.py') + + all_methods=set( t[1] for t in self.parser.methods ) + not_wrapped=all_methods.difference(wrapped_methods) + self.output("# Not wrapped methods:") + for m in not_wrapped: + self.output("# ", m) + + if self.fd != sys.stdout: + self.fd.close() + + def output(self, *p): + self.fd.write(" ".join(p)) + self.fd.write("\n") + + def check_types(self): + """Make sure that all types are properly translated. + + This method must be called *after* convert_enum_names, since + the latter populates type2class with converted enum names. + """ + for (rt, met, params, c) in self.parser.methods: for typ, name in params: - if not typ in typ2class: - raise Exception("No conversion for %s (from %s:%s)" % (typ, method, name)) - - # Transform Doxygen syntax into epydoc syntax - comment=comment.replace('\\param', '@param').replace('\\return', '@return') - - if debug: - print '********************' - print l - print '-------->' - print "%s (%s)" % (method, rtype) - for typ, name in params: - print " %s (%s)" % (name, typ) - print '********************' - yield (rtype, - method, - params, - comment) - comment='' - -def output_ctypes(rtype, method, params, comment): - """Output ctypes decorator for the given method. - """ - if method in blacklist: - # FIXME - return - - if params: - print "prototype=ctypes.CFUNCTYPE(%s, %s)" % (typ2class.get(rtype, 'FIXME_%s' % rtype), - ",".join( typ2class[p[0]] for p in params )) - else: - print "prototype=ctypes.CFUNCTYPE(%s)" % typ2class.get(rtype, 'FIXME_%s' % rtype) - - - if not params: - flags='paramflags= tuple()' - elif len(params) == 1: - flags="paramflags=( (%d, ), )" % parameter_passing[params[0][0]] - else: - flags="paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params ) - print flags - print '%s = prototype( ("%s", dll), paramflags )' % (method, method) - if '3' in flags: - # A VLCException is present. Process it. - print "%s.errcheck = check_vlc_exception" % method - print '%s.__doc__ = """%s"""' % (method, comment) - print - -def parse_override(name): - """Parse override definitions file. - - It is possible to override methods definitions in classes. - - It returns a tuple - (code, overriden_methods, docstring) - """ - code={} - - data=[] - current=None - f=open(name, 'r') - for l in f: - m=re.match('class (\S+):', l) - if m: - # Dump old data - if current is not None: - code[current]="".join(data) - current=m.group(1) - data=[] - continue - data.append(l) - code[current]="".join(data) - f.close() - - docstring={} - for k, v in code.iteritems(): - if v.lstrip().startswith('"""'): - # Starting comment. Use it as docstring. - dummy, docstring[k], code[k]=v.split('"""', 2) - - # Not robust wrt. internal methods, but this works for the moment. - overridden_methods=dict( (k, re.findall('^\s+def\s+(\w+)', v, re.MULTILINE)) for (k, v) in code.iteritems() ) - - return code, overridden_methods, docstring - -def fix_python_comment(c): - """Fix comment by removing first and last parameters (self and exception) - """ - data=c.replace('@{', '').replace('@see', 'See').splitlines() - body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data) - param=[ python_param_re.sub('\\1:\\2', l) for l in itertools.ifilter(lambda l: '@param' in l, data) ] - ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ] - - if len(param) >= 2: - param=param[1:-1] - elif len(param) == 1: - param=[] - - return "\n".join(itertools.chain(body, param, ret)) - -def generate_wrappers(methods): - """Generate class wrappers for all appropriate methods. - - @return: the set of wrapped method names - """ - ret=set() - # Sort methods against the element they apply to. - elements=sorted( ( (typ2class.get(params[0][0]), rt, met, params, c) - for (rt, met, params, c) in methods - if params and typ2class.get(params[0][0], '_') in defined_classes - ), - key=operator.itemgetter(0)) - - overrides, overriden_methods, docstring=parse_override('override.py') - - for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)): - print """class %(name)s(object):""" % {'name': classname} - if classname in docstring: - print ' """%s\n """' % docstring[classname] - - print """ - def __new__(cls, pointer=None): - '''Internal method used for instanciating wrappers from ctypes. - ''' - if pointer is None: - raise Exception("Internal method. You should instanciate objects through other class methods (probably named 'new' or ending with 'new')") - if pointer == 0: - return None + if not typ in self.type2class: + raise Exception("No conversion for %s (from %s:%s)" % (typ, met, name)) + + def insert_code(self, filename): + """Generate header/footer code. + """ + f=open(filename, 'r') + for l in f: + if 'build_date' in l: + self.output('build_date="%s"' % time.ctime()) + else: + self.output(l.rstrip()) + f.close() + + def convert_enum_names(self, enums): + res={} + for (typ, name, values, comment) in enums: + if typ != 'enum': + raise Exception('This method only handles enums') + pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1] + if '_' in pyname: + pyname=pyname.title().replace('_', '') + elif not pyname[0].isupper(): + pyname=pyname.capitalize() + res[name]=pyname + return res + + def generate_enums(self, enums): + for (typ, name, values, comment) in enums: + if typ != 'enum': + raise Exception('This method only handles enums') + pyname=self.type2class[name] + + self.output("class %s(ctypes.c_uint):" % pyname) + self.output(' """%s\n """' % comment) + + conv={} + # Convert symbol names + for k, v in values: + n=k.split('_')[-1] + if len(n) == 1: + # Single character. Some symbols use 1_1, 5_1, etc. + n="_".join( k.split('_')[-2:] ) + if re.match('^[0-9]', n): + # Cannot start an identifier with a number + n='_'+n + conv[k]=n + + for k, v in values: + self.output(" %s=%s" % (conv[k], v)) + + self.output(" _names={") + for k, v in values: + self.output(" %s: '%s'," % (v, conv[k])) + self.output(" }") + + self.output(""" + def __repr__(self): + return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value])) + """) + + def output_ctypes(self, rtype, method, params, comment): + """Output ctypes decorator for the given method. + """ + if method in blacklist: + # FIXME + return + + if params: + self.output("prototype=ctypes.CFUNCTYPE(%s, %s)" % (self.type2class.get(rtype, 'FIXME_%s' % rtype), + ",".join( self.type2class[p[0]] for p in params ))) else: - o=object.__new__(cls) - o._as_parameter_=ctypes.c_void_p(pointer) - return o + self.output("prototype=ctypes.CFUNCTYPE(%s)" % self.type2class.get(rtype, 'FIXME_%s' % rtype)) - @staticmethod - def from_param(arg): - '''(INTERNAL) ctypes parameter conversion method. - ''' - return arg._as_parameter_ -""" % {'name': classname} - if classname in overrides: - print overrides[classname] + if not params: + flags='paramflags= tuple()' + elif len(params) == 1: + flags="paramflags=( (%d, ), )" % parameter_passing[params[0][0]] + else: + flags="paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params ) + self.output(flags) + self.output('%s = prototype( ("%s", dll), paramflags )' % (method, method)) + if '3' in flags: + # A VLCException is present. Process it. + self.output("%s.errcheck = check_vlc_exception" % method) + self.output('%s.__doc__ = """%s"""' % (method, comment)) + self.output() + + def parse_override(self, name): + """Parse override definitions file. + + It is possible to override methods definitions in classes. + + It returns a tuple + (code, overriden_methods, docstring) + """ + code={} + + data=[] + current=None + f=open(name, 'r') + for l in f: + m=re.match('class (\S+):', l) + if m: + # Dump old data + if current is not None: + code[current]="".join(data) + current=m.group(1) + data=[] + continue + data.append(l) + code[current]="".join(data) + f.close() + + docstring={} + for k, v in code.iteritems(): + if v.lstrip().startswith('"""'): + # Starting comment. Use it as docstring. + dummy, docstring[k], code[k]=v.split('"""', 2) + + # Not robust wrt. internal methods, but this works for the moment. + overridden_methods=dict( (k, re.findall('^\s+def\s+(\w+)', v, re.MULTILINE)) for (k, v) in code.iteritems() ) + + return code, overridden_methods, docstring + + def fix_python_comment(self, c): + """Fix comment by removing first and last parameters (self and exception) + """ + data=c.replace('@{', '').replace('@see', 'See').splitlines() + body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data) + param=[ python_param_re.sub('\\1:\\2', l) for l in itertools.ifilter(lambda l: '@param' in l, data) ] + ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ] + + if len(param) >= 2: + param=param[1:-1] + elif len(param) == 1: + param=[] + + return "\n".join(itertools.chain(body, param, ret)) + + def generate_wrappers(self, methods): + """Generate class wrappers for all appropriate methods. + + @return: the set of wrapped method names + """ + ret=set() + # Sort methods against the element they apply to. + elements=sorted( ( (self.type2class.get(params[0][0]), rt, met, params, c) + for (rt, met, params, c) in methods + if params and self.type2class.get(params[0][0], '_') in self.defined_classes + ), + key=operator.itemgetter(0)) + + overrides, overriden_methods, docstring=self.parse_override('override.py') + + for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)): + self.output("""class %(name)s(object):""" % {'name': classname}) + if classname in docstring: + self.output(' """%s\n """' % docstring[classname]) + + self.output(""" + def __new__(cls, pointer=None): + '''Internal method used for instanciating wrappers from ctypes. + ''' + if pointer is None: + raise Exception("Internal method. Surely this class cannot be instanciated by itself.") + if pointer == 0: + return None + else: + o=object.__new__(cls) + o._as_parameter_=ctypes.c_void_p(pointer) + return o - prefix=prefixes.get(classname, '') + @staticmethod + def from_param(arg): + '''(INTERNAL) ctypes parameter conversion method. + ''' + return arg._as_parameter_ + """ % {'name': classname}) - for cl, rtype, method, params, comment in el: - if method in blacklist: - continue - # Strip prefix - name=method.replace(prefix, '').replace('libvlc_', '') - ret.add(method) - if name in overriden_methods.get(cl, []): - # Method already defined in override.py - continue + if classname in overrides: + self.output(overrides[classname]) - if params: - params[0]=(params[0][0], 'self') - if params and params[-1][0] in ('libvlc_exception_t*', 'mediacontrol_Exception*'): - args=", ".join( p[1] for p in params[:-1] ) - else: - args=", ".join( p[1] for p in params ) - - print " def %s(%s):" % (name, args) - print ' """%s\n """' % fix_python_comment(comment) - if params and params[-1][0] == 'libvlc_exception_t*': - # Exception handling - print " e=VLCException()" - print " return %s(%s, e)" % (method, args) - elif params and params[-1][0] == 'mediacontrol_Exception*': - # Exception handling - print " e=MediaControlException()" - print " return %s(%s, e)" % (method, args) - else: - print " return %s(%s)" % (method, args) - print - - # Check for standard methods - if name == 'count': - # There is a count method. Generate a __len__ one. - print " def __len__(self):" - print " e=VLCException()" - print " return %s(self, e)" % method - print - elif name.endswith('item_at_index'): - # Indexable (and thus iterable)" - print " def __getitem__(self, i):" - print " e=VLCException()" - print " return %s(self, i, e)" % method - print - print " def __iter__(self):" - print " e=VLCException()" - print " for i in xrange(len(self)):" - print " yield self[i]" - print - - return ret + prefix=self.prefixes.get(classname, '') + + for cl, rtype, method, params, comment in el: + if method in blacklist: + continue + # Strip prefix + name=method.replace(prefix, '').replace('libvlc_', '') + ret.add(method) + if name in overriden_methods.get(cl, []): + # Method already defined in override.py + continue + + if params: + params[0]=(params[0][0], 'self') + if params and params[-1][0] in ('libvlc_exception_t*', 'mediacontrol_Exception*'): + args=", ".join( p[1] for p in params[:-1] ) + else: + args=", ".join( p[1] for p in params ) + + self.output(" def %s(%s):" % (name, args)) + self.output(' """%s\n """' % self.fix_python_comment(comment)) + if params and params[-1][0] == 'libvlc_exception_t*': + # Exception handling + self.output(" e=VLCException()") + self.output(" return %s(%s, e)" % (method, args)) + elif params and params[-1][0] == 'mediacontrol_Exception*': + # Exception handling + self.output(" e=MediaControlException()") + self.output(" return %s(%s, e)" % (method, args)) + else: + self.output(" return %s(%s)" % (method, args)) + self.output() + + # Check for standard methods + if name == 'count': + # There is a count method. Generate a __len__ one. + self.output(""" def __len__(self): + e=VLCException() + return %s(self, e) +""" % method) + elif name.endswith('item_at_index'): + # Indexable (and thus iterable)" + self.output(""" def __getitem__(self, i): + e=VLCException() + return %s(self, i, e) + + def __iter__(self): + e=VLCException() + for i in xrange(len(self)): + yield self[i] +""" % method) + return ret + +def process(output, list_of_includes): + p=Parser(list_of_includes) + g=PythonGenerator(p) + g.save(output) if __name__ == '__main__': - enums=[] - for name in sys.argv[1:]: - enums.extend(list(parse_typedef(name))) - # Generate python names for enums - typ2class.update(convert_enum_names(enums)) - - methods=[] - for name in sys.argv[1:]: - methods.extend(list(parse_include(name))) - if debug: - sys.exit(0) + opt=OptionParser(usage="""Parse VLC include files and generate bindings code. +%prog [options] include_file.h [...]""") - insert_code('header.py') - generate_enums(enums) - wrapped=generate_wrappers(methods) - for l in methods: - output_ctypes(*l) - insert_code('footer.py') + opt.add_option("-d", "--debug", dest="debug", action="store_true", + default=False, + help="Debug mode") + + opt.add_option("-o", "--output", dest="output", action="store", + type="str", default="-", + help="Output filename") + + (options, args) = opt.parse_args() + + if not args: + opt.print_help() + sys.exit(1) + + p=Parser(args) + if options.debug: + p.dump_methods() + p.dump_enums() + sys.exit(0) - all=set( t[1] for t in methods ) - not_wrapped=all.difference(wrapped) - print "# Not wrapped methods:" - for m in not_wrapped: - print "# ", m + g=PythonGenerator(p) + g.save(options.output) -- 2.39.2