]> git.sesse.net Git - vlc/blobdiff - bindings/python-ctypes/generate.py
python-ctypes: capitalize enum names only if they do not already start with an upperc...
[vlc] / bindings / python-ctypes / generate.py
index 41f69b3fece2066dbc4f91169d7e54bfb1d08ad2..87a5b37e3ed76db4bc5547d3656c8cf2585dde7f 100755 (executable)
@@ -59,13 +59,13 @@ blacklist=[
     "libvlc_exception_get_message",
     "libvlc_get_vlc_instance",
 
-    "libvlc_media_add_option_flag",
     "libvlc_media_list_view_index_of_item",
     "libvlc_media_list_view_insert_at_index",
     "libvlc_media_list_view_remove_at_index",
     "libvlc_media_list_view_add_item",
 
     # In svn but not in current 1.0.0
+    "libvlc_media_add_option_flag",
     'libvlc_video_set_deinterlace',
     'libvlc_video_get_marquee_option_as_int',
     'libvlc_video_get_marquee_option_as_string',
@@ -74,10 +74,6 @@ blacklist=[
     'libvlc_vlm_get_event_manager',
 
     'mediacontrol_PlaylistSeq__free',
-
-    # TODO
-    "libvlc_event_detach",
-    "libvlc_event_attach",
     ]
 
 # Precompiled regexps
@@ -87,13 +83,16 @@ paramlist_re=re.compile('\s*,\s*')
 comment_re=re.compile('\\param\s+(\S+)')
 python_param_re=re.compile('(@param\s+\S+)(.+)')
 forward_re=re.compile('.+\(\s*(.+?)\s*\)(\s*\S+)')
+enum_re=re.compile('typedef\s+(enum)\s*(\S+\s*)?\{\s*(.+)\s*\}\s*(\S+);')
+special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};')
 
 # Definition of parameter passing mode for types.  This should not be
 # hardcoded this way, but works alright ATM.
 parameter_passing=DefaultDict(default=1)
 parameter_passing['libvlc_exception_t*']=3
 
-# C-type to ctypes/python type conversion
+# 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)',
 
@@ -103,7 +102,7 @@ typ2class={
     'libvlc_log_t*': 'Log',
     'libvlc_log_iterator_t*': 'LogIterator',
     'libvlc_log_message_t*': 'LogMessage',
-    'libvlc_event_type_t': 'EventType',
+    'libvlc_event_type_t': 'ctypes.c_uint',
     'libvlc_event_manager_t*': 'EventManager',
     'libvlc_media_discoverer_t*': 'MediaDiscoverer',
     'libvlc_media_library_t*': 'MediaLibrary',
@@ -119,28 +118,20 @@ typ2class={
     'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
     'mediacontrol_Position*': 'MediaControlPosition',
     'mediacontrol_StreamInformation*': 'MediaControlStreamInformation',
-    'mediacontrol_PositionOrigin': 'ctypes.c_uint',
-    'mediacontrol_PositionKey': 'ctypes.c_uint',
     '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',
-    'void': 'None',
-    'void*': 'ctypes.c_void_p',
     'int': 'ctypes.c_int',
     '...': 'FIXMEva_list',
-    'libvlc_callback_t': 'FIXMEcallback',
+    'libvlc_callback_t': 'ctypes.c_void_p',
     'libvlc_time_t': 'ctypes.c_longlong',
-    'libvlc_video_marquee_int_option_t': 'ctypes.c_int',
-    'libvlc_video_marquee_string_option_t': 'ctypes.c_char_p',
-    # FIXME: enums -> to be processed
-    'libvlc_media_option_t': 'ctypes.c_uint',
-    'libvlc_meta_t': 'ctypes.c_uint',
-    'libvlc_state_t': 'State',
     }
 
 # Defined python classes, i.e. classes for which we want to generate
@@ -152,7 +143,6 @@ defined_classes=(
     'Log',
     'LogIterator',
     #'LogMessage',
-    'EventType',
     'EventManager',
     'MediaDiscoverer',
     'MediaLibrary',
@@ -162,9 +152,6 @@ defined_classes=(
     'TrackDescription',
     'AudioOutput',
     'MediaControl',
-    #'RGBPicture',
-    #'MediaControlPosition',
-    #'MediaControlStreamInformation',
     )
 
 # Definition of prefixes that we can strip from method names when
@@ -200,10 +187,10 @@ def parse_param(s):
         # K&R definition: only type
         return s.replace(' ', ''), ''
 
-def generate_header(classes=None):
-    """Generate header code.
+def insert_code(filename):
+    """Generate header/footer code.
     """
-    f=open('header.py', 'r')
+    f=open(filename, 'r')
     for l in f:
         if 'build_date' in l:
             print 'build_date="%s"' % time.ctime()
@@ -211,6 +198,127 @@ def generate_header(classes=None):
             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]))
+"""
+
+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.
 
@@ -295,6 +403,7 @@ def parse_include(name):
                    method,
                    params,
                    comment)
+            comment=''
 
 def output_ctypes(rtype, method, params, comment):
     """Output ctypes decorator for the given method.
@@ -328,8 +437,11 @@ 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)
     """
-    res={}
+    code={}
 
     data=[]
     current=None
@@ -339,20 +451,30 @@ def parse_override(name):
         if m:
             # Dump old data
             if current is not None:
-                res[current]="\n".join(data)
+                code[current]="".join(data)
             current=m.group(1)
             data=[]
             continue
         data.append(l)
-    res[current]="\n".join(data)
+    code[current]="".join(data)
     f.close()
-    return res
+
+    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.splitlines()
-    body=itertools.takewhile(lambda l: not '@param' in l, data)
+    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) ]
 
@@ -376,17 +498,25 @@ def generate_wrappers(methods):
                        ),
                      key=operator.itemgetter(0))
 
-    overrides=parse_override('override.py')
+    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 """
-class %(name)s(object):
-    def __init__(self, pointer=None):
+    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')")
-        self._as_parameter_=ctypes.c_void_p(pointer)
+        if pointer == 0:
+            return None
+        else:
+            o=object.__new__(cls)
+            o._as_parameter_=ctypes.c_void_p(pointer)
+            return o
 
     @staticmethod
     def from_param(arg):
@@ -406,6 +536,10 @@ class %(name)s(object):
             # 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*'):
@@ -414,7 +548,7 @@ class %(name)s(object):
                 args=", ".join( p[1] for p in params )
 
             print "    def %s(%s):" % (name, args)
-            print '        """%s\n"""' % fix_python_comment(comment)
+            print '        """%s\n        """' % fix_python_comment(comment)
             if params and params[-1][0] == 'libvlc_exception_t*':
                 # Exception handling
                 print "        e=VLCException()"
@@ -426,19 +560,47 @@ class %(name)s(object):
             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
 
 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)
 
-    generate_header()
+    insert_code('header.py')
+    generate_enums(enums)
     wrapped=generate_wrappers(methods)
     for l in methods:
         output_ctypes(*l)
+    insert_code('footer.py')
 
     all=set( t[1] for t in methods )
     not_wrapped=all.difference(wrapped)