python-ctypes: refactor generate.py to be more modular
authorOlivier Aubert <olivier.aubert@liris.cnrs.fr>
Sun, 9 Aug 2009 22:34:24 +0000 (00:34 +0200)
committerOlivier Aubert <olivier.aubert@liris.cnrs.fr>
Sun, 9 Aug 2009 22:34:24 +0000 (00:34 +0200)
TODO
generate.py

diff --git a/TODO b/TODO
index 088fd6a..087d87b 100644 (file)
--- a/TODO
+++ b/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 )
index 3fbb644..032af7d 100755 (executable)
@@ -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)