]> git.sesse.net Git - vlc/blobdiff - bindings/python-ctypes/generate.py
python-ctypes: add support for media stats in java generator
[vlc] / bindings / python-ctypes / generate.py
index 56a758b120d2fab2b598f0f3605510d4afcadafb..15397021e62cafcb3cdc9826ef6aa6bbe96635ac 100755 (executable)
@@ -29,6 +29,7 @@ wrappers for most methods.
 """
 
 import sys
+import os
 import re
 import time
 import operator
@@ -88,6 +89,7 @@ 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*\};')
+event_def_re=re.compile('^DEF\(\s*(\w+)\s*\)')
 
 # Definition of parameter passing mode for types.  This should not be
 # hardcoded this way, but works alright ATM.
@@ -139,6 +141,8 @@ class Parser(object):
         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.
         """
+        event_names=[]
+
         f=open(name, 'r')
         accumulator=''
         for l in f:
@@ -185,21 +189,35 @@ class Parser(object):
                 comment=''
                 continue
 
+            # Special case, used only for libvlc_events.h
+            # (version after 96a96f60bb0d1f2506e68b356897ceca6f6b586d)
+            m=event_def_re.match(l)
+            if m:
+                # Event definition.
+                event_names.append('libvlc_'+m.group(1))
+                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)) )
+                if event_names:
+                    # event_names were defined through DEF macro
+                    # (see 96a96f60bb0d1f2506e68b356897ceca6f6b586d)
+                    values=list( (n, str(i)) for i, n in enumerate(event_names))
+                else:
+                    # Before 96a96f60bb0d1f2506e68b356897ceca6f6b586d
+                    values=[]
+                    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=''
@@ -312,7 +330,7 @@ class PythonGenerator(object):
         'libvlc_media_t*': 'Media',
         'libvlc_log_t*': 'Log',
         'libvlc_log_iterator_t*': 'LogIterator',
-        'libvlc_log_message_t*': 'LogMessage',
+        'libvlc_log_message_t*': 'ctypes.POINTER(LogMessage)',
         'libvlc_event_type_t': 'ctypes.c_uint',
         'libvlc_event_manager_t*': 'EventManager',
         'libvlc_media_discoverer_t*': 'MediaDiscoverer',
@@ -322,6 +340,7 @@ class PythonGenerator(object):
         'libvlc_media_list_view_t*': 'MediaListView',
         'libvlc_track_description_t*': 'TrackDescription',
         'libvlc_audio_output_t*': 'AudioOutput',
+        'libvlc_media_stats_t*': 'ctypes.POINTER(MediaStats)',
 
         'mediacontrol_Instance*': 'MediaControl',
         'mediacontrol_Exception*': 'MediaControlException',
@@ -385,7 +404,6 @@ class PythonGenerator(object):
             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)
@@ -396,7 +414,7 @@ class PythonGenerator(object):
         self.output("# Not wrapped methods:")
         for m in not_wrapped:
             self.output("#   ", m)
-        
+
         if self.fd != sys.stdout:
             self.fd.close()
 
@@ -420,10 +438,13 @@ class PythonGenerator(object):
         """
         f=open(filename, 'r')
         for l in f:
-            if 'build_date' in l:
+            if l.startswith('build_date'):
                 self.output('build_date="%s"' % time.ctime())
+            elif l.startswith('# GENERATED_ENUMS'):
+                self.generate_enums(self.parser.enums)
             else:
                 self.output(l.rstrip())
+
         f.close()
 
     def convert_enum_names(self, enums):
@@ -445,7 +466,7 @@ class PythonGenerator(object):
                 raise Exception('This method only handles enums')
             pyname=self.type2class[name]
 
-            self.output("class %s(ctypes.c_uint):" % pyname)
+            self.output("class %s(ctypes.c_ulong):" % pyname)
             self.output('    """%s\n    """' % comment)
 
             conv={}
@@ -460,9 +481,6 @@ class PythonGenerator(object):
                     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]))
@@ -471,7 +489,21 @@ class PythonGenerator(object):
             self.output("""
     def __repr__(self):
         return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value]))
+
+    def __eq__(self, other):
+        return ( (isinstance(other, ctypes.c_ulong) and self.value == other.value)
+                 or (isinstance(other, (int, long)) and self.value == other ) )
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
     """)
+            for k, v in values:
+                self.output("%(class)s.%(attribute)s=%(class)s(%(value)s)" % {
+                        'class': pyname,
+                        'attribute': conv[k],
+                        'value': v
+                        })
+            self.output("")
 
     def output_ctypes(self, rtype, method, params, comment):
         """Output ctypes decorator for the given method.
@@ -483,7 +515,7 @@ class PythonGenerator(object):
         self.output("""if hasattr(dll, '%s'):""" % method)
         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 )))
+                                                                ", ".join( self.type2class[p[0]] for p in params )))
         else:
             self.output("    prototype=ctypes.CFUNCTYPE(%s)" % self.type2class.get(rtype, 'FIXME_%s' % rtype))
 
@@ -493,7 +525,7 @@ class PythonGenerator(object):
         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 )
+            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:
@@ -574,7 +606,8 @@ class PythonGenerator(object):
             if classname in docstring:
                 self.output('    """%s\n    """' % docstring[classname])
 
-            self.output("""
+            if not 'def __new__' in overrides.get(classname, ''):
+                self.output("""
     def __new__(cls, pointer=None):
         '''Internal method used for instanciating wrappers from ctypes.
         '''
@@ -586,13 +619,15 @@ class PythonGenerator(object):
             o=object.__new__(cls)
             o._as_parameter_=ctypes.c_void_p(pointer)
             return o
+""")
 
+            self.output("""
     @staticmethod
     def from_param(arg):
         '''(INTERNAL) ctypes parameter conversion method.
         '''
         return arg._as_parameter_
-    """ % {'name': classname})
+""")
 
             if classname in overrides:
                 self.output(overrides[classname])
@@ -634,9 +669,15 @@ class PythonGenerator(object):
                 # Check for standard methods
                 if name == 'count':
                     # There is a count method. Generate a __len__ one.
-                    self.output("""    def __len__(self):
+                    if params and params[-1][0] == 'libvlc_exception_t*':
+                        self.output("""    def __len__(self):
         e=VLCException()
         return %s(self, e)
+""" % method)
+                    else:
+                        # No exception
+                        self.output("""    def __len__(self):
+        return %s(self)
 """ % method)
                 elif name.endswith('item_at_index'):
                     # Indexable (and thus iterable)"
@@ -651,6 +692,187 @@ class PythonGenerator(object):
 """ % method)
         return ret
 
+class JavaGenerator(object):
+    # C-type to java/jna type conversion.
+    # Note that enum types conversions are generated (cf convert_enum_names)
+    type2class={
+        'libvlc_exception_t*': 'libvlc_exception_t',
+        'libvlc_media_player_t*': 'LibVlcMediaPlayer',
+        'libvlc_instance_t*': 'LibVlcInstance',
+        'libvlc_media_t*': 'LibVlcMedia',
+        'libvlc_log_t*': 'LibVlcLog',
+        'libvlc_log_iterator_t*': 'LibVlcLogIterator',
+        'libvlc_log_message_t*': 'libvlc_log_message_t',
+        'libvlc_event_type_t': 'int',
+        'libvlc_event_manager_t*': 'LibVlcEventManager',
+        'libvlc_media_discoverer_t*': 'LibVlcMediaDiscoverer',
+        'libvlc_media_library_t*': 'LibVlcMediaLibrary',
+        'libvlc_media_list_t*': 'LibVlcMediaList',
+        'libvlc_media_list_player_t*': 'LibVlcMediaListPlayer',
+        'libvlc_media_list_view_t*': 'LibVlcMediaListView',
+        'libvlc_media_stats_t*': 'LibVlcMediaStats',
+
+        'libvlc_track_description_t*': 'LibVlcTrackDescription',
+        'libvlc_audio_output_t*': 'LibVlcAudioOutput',
+
+        'void': 'void',
+        'void*': 'Pointer',
+        'short': 'short',
+        'char*': 'String',
+        'char**': 'String[]',
+        'uint32_t': 'uint32',
+        'float': 'float',
+        'unsigned': 'int',
+        'int': 'int',
+        '...': 'FIXMEva_list',
+        'libvlc_callback_t': 'LibVlcCallback',
+        'libvlc_time_t': 'long',
+
+        'mediacontrol_RGBPicture*': 'Pointer',
+        'mediacontrol_PlaylistSeq*': 'Pointer',
+        'mediacontrol_StreamInformation*': 'Pointer',
+        }
+
+    def __init__(self, parser=None):
+        self.parser=parser
+
+        # Blacklist all mediacontrol methods
+        for (rt, met, params, c) in self.parser.methods:
+            if met.startswith('mediacontrol'):
+                blacklist.append(met)
+        # Generate Java names for enums
+        self.type2class.update(self.convert_enum_names(parser.enums))
+        self.check_types()
+
+    def save(self, dirname=None):
+        if dirname is None or dirname == '-':
+            dirname='internal'
+            if not os.path.isdir(dirname):
+                os.mkdir(dirname)
+
+        print "Generating java code in %s/" % dirname
+
+        # Generate enum files
+        self.generate_enums(dirname, self.parser.enums)
+
+        # Generate LibVlc.java code
+        self.generate_libvlc(dirname)
+
+    def output(self, fd, *p):
+        fd.write(" ".join(p))
+        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:
+            if met in blacklist:
+                continue
+            for typ, name in params:
+                if not typ in self.type2class:
+                    raise Exception("No conversion for %s (from %s:%s)" % (typ, met, name))
+
+    def convert_enum_names(self, enums):
+        """Convert enum names into Java names.
+        """
+        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 insert_code(self, fd, filename):
+        """Generate header/footer code.
+        """
+        f=open(filename, 'r')
+        for l in f:
+            if l.startswith('build_date'):
+                self.output(fd, 'build_date="%s";' % time.ctime())
+            else:
+                self.output(fd, l.rstrip())
+        f.close()
+
+    def generate_header(self, fd):
+        """Generate LibVlc header.
+        """
+        for (c_type, jna_type) in self.type2class.iteritems():
+            if c_type.endswith('*') and jna_type.startswith('LibVlc'):
+                self.output(fd, '''    public class %s extends PointerType
+    {
+    }
+''' % jna_type)
+
+    def generate_libvlc(self, dirname):
+        """Generate LibVlc.java JNA glue code.
+        """
+        filename=os.path.join(dirname, 'LibVlc.java')
+        fd=open(filename, 'w')
+
+        self.insert_code(fd, 'boilerplate.java')
+        self.insert_code(fd, 'LibVlc-header.java')
+        #wrapped_methods=self.generate_wrappers(self.parser.methods)
+        self.generate_header(fd)
+        for (rtype, method, params, comment) in self.parser.methods:
+            if method in blacklist:
+                # FIXME
+                continue
+            self.output(fd, "%s %s(%s);\n" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
+                                          method,
+                                          ", ".join( ("%s %s" % (self.type2class[p[0]],
+                                                                 p[1])) for p in params )))
+        self.insert_code(fd, 'LibVlc-footer.java')
+        fd.close()
+
+    def generate_enums(self, dirname, enums):
+        """Generate JNA glue code for enums
+        """
+        for (typ, name, values, comment) in enums:
+            if typ != 'enum':
+                raise Exception('This method only handles enums')
+            javaname=self.type2class[name]
+
+            filename=javaname+".java"
+
+            fd=open(os.path.join(dirname, filename), 'w')
+
+            self.insert_code(fd, 'boilerplate.java')
+            self.output(fd, """package org.videolan.jvlc.internal;
+
+
+public enum %s
+{
+""" % javaname)
+            # FIXME: write comment
+
+            for k, v in values:
+                self.output(fd, "        %s, // %s," % (k, v))
+            self.output(fd, "}")
+            fd.close()
+
+    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 process(output, list_of_includes):
     p=Parser(list_of_includes)
     g=PythonGenerator(p)
@@ -664,9 +886,17 @@ if __name__ == '__main__':
                       default=False,
                       help="Debug mode")
 
+    opt.add_option("-c", "--check", dest="check", action="store_true",
+                      default=False,
+                      help="Check mode")
+
+    opt.add_option("-j", "--java", dest="java", action="store_true",
+                      default=False,
+                      help="Generate java bindings (default is python)")
+
     opt.add_option("-o", "--output", dest="output", action="store",
                       type="str", default="-",
-                      help="Output filename")
+                      help="Output filename(python)/dirname(java)")
 
     (options, args) = opt.parse_args()
 
@@ -675,11 +905,26 @@ if __name__ == '__main__':
         sys.exit(1)
 
     p=Parser(args)
+    if options.check:
+        # Various consistency checks.
+        for (rt, name, params, comment) in p.methods:
+            if not comment.strip():
+                print "No comment for %s" % name
+                continue
+            names=comment_re.findall(comment)
+            if len(names) != len(params):
+                print "Docstring comment parameters mismatch for %s" % name
+
     if options.debug:
         p.dump_methods()
         p.dump_enums()
+
+    if options.check or options.debug:
         sys.exit(0)
 
-    g=PythonGenerator(p)
+    if options.java:
+        g=JavaGenerator(p)
+    else:
+        g=PythonGenerator(p)
 
     g.save(options.output)