]> git.sesse.net Git - vlc/blob - bindings/python-ctypes/generate.py
python-ctypes: first shot at generating java JNA glue code
[vlc] / bindings / python-ctypes / generate.py
1 #! /usr/bin/python
2 debug=False
3
4 #
5 # Code generator for python ctypes bindings for VLC
6 # Copyright (C) 2009 the VideoLAN team
7 # $Id: $
8 #
9 # Authors: Olivier Aubert <olivier.aubert at liris.cnrs.fr>
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 #
25
26 """This module parses VLC public API include files and generates
27 corresponding python/ctypes code. Moreover, it generates class
28 wrappers for most methods.
29 """
30
31 import sys
32 import os
33 import re
34 import time
35 import operator
36 import itertools
37 from optparse import OptionParser
38
39 # DefaultDict from ASPN python cookbook
40 import copy
41 class DefaultDict(dict):
42     """Dictionary with a default value for unknown keys."""
43     def __init__(self, default=None, **items):
44         dict.__init__(self, **items)
45         self.default = default
46
47     def __getitem__(self, key):
48         if key in self:
49             return self.get(key)
50         else:
51             ## Need copy in case self.default is something like []
52             return self.setdefault(key, copy.deepcopy(self.default))
53
54     def __copy__(self):
55         return DefaultDict(self.default, **self)
56
57 # Methods not decorated/not referenced
58 blacklist=[
59     "libvlc_exception_raise",
60     "libvlc_exception_raised",
61     "libvlc_exception_get_message",
62     "libvlc_get_vlc_instance",
63
64     "libvlc_media_list_view_index_of_item",
65     "libvlc_media_list_view_insert_at_index",
66     "libvlc_media_list_view_remove_at_index",
67     "libvlc_media_list_view_add_item",
68
69     # In svn but not in current 1.0.0.
70     #"libvlc_media_add_option_flag",
71     #'libvlc_video_set_deinterlace',
72     #'libvlc_video_get_marquee_option_as_int',
73     #'libvlc_video_get_marquee_option_as_string',
74     #'libvlc_video_set_marquee_option_as_int',
75     #'libvlc_video_set_marquee_option_as_string',
76     #'libvlc_vlm_get_event_manager',
77     #"libvlc_media_list_player_event_manager",
78     #'libvlc_media_player_next_frame',
79
80     'mediacontrol_PlaylistSeq__free',
81     ]
82
83 # Precompiled regexps
84 api_re=re.compile('VLC_PUBLIC_API\s+(\S+\s+.+?)\s*\(\s*(.+?)\s*\)')
85 param_re=re.compile('\s*(const\s*|unsigned\s*|struct\s*)?(\S+\s*\**)\s+(.+)')
86 paramlist_re=re.compile('\s*,\s*')
87 comment_re=re.compile('\\param\s+(\S+)')
88 python_param_re=re.compile('(@param\s+\S+)(.+)')
89 forward_re=re.compile('.+\(\s*(.+?)\s*\)(\s*\S+)')
90 enum_re=re.compile('typedef\s+(enum)\s*(\S+\s*)?\{\s*(.+)\s*\}\s*(\S+);')
91 special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};')
92 event_def_re=re.compile('^DEF\(\s*(\w+)\s*\)')
93
94 # Definition of parameter passing mode for types.  This should not be
95 # hardcoded this way, but works alright ATM.
96 parameter_passing=DefaultDict(default=1)
97 parameter_passing['libvlc_exception_t*']=3
98
99 class Parser(object):
100     def __init__(self, list_of_files):
101         self.methods=[]
102         self.enums=[]
103
104         for name in list_of_files:
105             self.enums.extend(self.parse_typedef(name))
106             self.methods.extend(self.parse_include(name))
107
108     def parse_param(self, s):
109         """Parse a C parameter expression.
110
111         It is used to parse both the type/name for methods, and type/name
112         for their parameters.
113
114         It returns a tuple (type, name).
115         """
116         s=s.strip()
117         s=s.replace('const', '')
118         if 'VLC_FORWARD' in s:
119             m=forward_re.match(s)
120             s=m.group(1)+m.group(2)
121         m=param_re.search(s)
122         if m:
123             const, typ, name=m.groups()
124             while name.startswith('*'):
125                 typ += '*'
126                 name=name[1:]
127             if name == 'const*':
128                 # K&R definition: const char * const*
129                 name=''
130             typ=typ.replace(' ', '')
131             return typ, name
132         else:
133             # K&R definition: only type
134             return s.replace(' ', ''), ''
135
136     def parse_typedef(self, name):
137         """Parse include file for typedef expressions.
138
139         This generates a tuple for each typedef:
140         (type, name, value_list, comment)
141         with type == 'enum' (for the moment) and value_list being a list of (name, value)
142         Note that values are string, since this is intended for code generation.
143         """
144         event_names=[]
145
146         f=open(name, 'r')
147         accumulator=''
148         for l in f:
149             # Note: lstrip() should not be necessary, but there is 1 badly
150             # formatted comment in vlc1.0.0 includes
151             if l.lstrip().startswith('/**'):
152                 comment=''
153                 continue
154             elif l.startswith(' * '):
155                 comment = comment + l[3:]
156                 continue
157
158             l=l.strip()
159             if l.startswith('/*') or l.endswith('*/'):
160                 continue
161
162             if (l.startswith('typedef enum') or l.startswith('enum')) and not l.endswith(';'):
163                 # Multiline definition. Accumulate until end of definition
164                 accumulator=l
165                 continue
166             elif accumulator:
167                 accumulator=" ".join( (accumulator, l) )
168                 if l.endswith(';'):
169                     # End of definition
170                     l=accumulator
171                     accumulator=''
172
173             m=enum_re.match(l)
174             if m:
175                 values=[]
176                 (typ, dummy, data, name)=m.groups()
177                 for i, l in enumerate(paramlist_re.split(data)):
178                     l=l.strip()
179                     if l.startswith('/*'):
180                         continue
181                     if '=' in l:
182                         # A value was specified. Use it.
183                         values.append(re.split('\s*=\s*', l))
184                     else:
185                         if l:
186                             values.append( (l, str(i)) )
187                 comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '')
188                 yield (typ, name.strip(), values, comment)
189                 comment=''
190                 continue
191
192             # Special case, used only for libvlc_events.h
193             # (version after 96a96f60bb0d1f2506e68b356897ceca6f6b586d)
194             m=event_def_re.match(l)
195             if m:
196                 # Event definition.
197                 event_names.append('libvlc_'+m.group(1))
198                 continue
199
200             # Special case, used only for libvlc_events.h
201             m=special_enum_re.match(l)
202             if m:
203                 (typ, name, data)=m.groups()
204                 if event_names:
205                     # event_names were defined through DEF macro
206                     # (see 96a96f60bb0d1f2506e68b356897ceca6f6b586d)
207                     values=list( (n, str(i)) for i, n in enumerate(event_names))
208                 else:
209                     # Before 96a96f60bb0d1f2506e68b356897ceca6f6b586d
210                     values=[]
211                     for i, l in enumerate(paramlist_re.split(data)):
212                         l=l.strip()
213                         if l.startswith('/*') or l.startswith('#'):
214                             continue
215                         if '=' in l:
216                             # A value was specified. Use it.
217                             values.append(re.split('\s*=\s*', l))
218                         else:
219                             if l:
220                                 values.append( (l, str(i)) )
221                 comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '')
222                 yield (typ, name.strip(), values, comment)
223                 comment=''
224                 continue
225
226     def parse_include(self, name):
227         """Parse include file.
228
229         This generates a tuple for each function:
230         (return_type, method_name, parameter_list, comment)
231         with parameter_list being a list of tuples (parameter_type, parameter_name).
232         """
233         f=open(name, 'r')
234         accumulator=''
235         comment=''
236         for l in f:
237             # Note: lstrip() should not be necessary, but there is 1 badly
238             # formatted comment in vlc1.0.0 includes
239             if l.lstrip().startswith('/**'):
240                 comment=''
241                 continue
242             elif l.startswith(' * '):
243                 comment = comment + l[3:]
244                 continue
245
246             l=l.strip()
247
248             if accumulator:
249                 accumulator=" ".join( (accumulator, l) )
250                 if l.endswith(');'):
251                     # End of definition
252                     l=accumulator
253                     accumulator=''
254             elif l.startswith('VLC_PUBLIC_API') and not l.endswith(');'):
255                 # Multiline definition. Accumulate until end of definition
256                 accumulator=l
257                 continue
258
259             m=api_re.match(l)
260             if m:
261                 (ret, param)=m.groups()
262
263                 rtype, method=self.parse_param(ret)
264
265                 params=[]
266                 for p in paramlist_re.split(param):
267                     params.append( self.parse_param(p) )
268
269                 if len(params) == 1 and params[0][0] == 'void':
270                     # Empty parameter list
271                     params=[]
272
273                 if list(p for p in params if not p[1]):
274                     # Empty parameter names. Have to poke into comment.
275                     names=comment_re.findall(comment)
276                     if len(names) < len(params):
277                         # Bad description: all parameters are not specified.
278                         # Generate default parameter names
279                         badnames=[ "param%d" % i for i in xrange(len(params)) ]
280                         # Put in the existing ones
281                         for (i, p) in enumerate(names):
282                             badnames[i]=names[i]
283                         names=badnames
284                         print "### Error ###"
285                         print "### Cannot get parameter names from comment for %s: %s" % (method, comment.replace("\n", ' '))
286                         # Note: this was previously
287                         # raise Exception("Cannot get parameter names from comment for %s: %s" % (method, comment))
288                         # but it prevented code generation for a minor detail (some bad descriptions).
289                     params=[ (p[0], names[i]) for (i, p) in enumerate(params) ]
290
291                 # Transform Doxygen syntax into epydoc syntax
292                 comment=comment.replace('\\param', '@param').replace('\\return', '@return')
293
294                 if debug:
295                     print '********************'
296                     print l
297                     print '-------->'
298                     print "%s (%s)" % (method, rtype)
299                     for typ, name in params:
300                         print "        %s (%s)" % (name, typ)
301                     print '********************'
302                 yield (rtype,
303                        method,
304                        params,
305                        comment)
306                 comment=''
307
308     def dump_methods(self):
309         print "** Defined functions **"
310         for (rtype, name, params, comment) in self.methods:
311             print "%(name)s (%(rtype)s):" % locals()
312             for t, n in params:
313                 print "    %(n)s (%(t)s)" % locals()
314
315     def dump_enums(self):
316         print "** Defined enums **"
317         for (typ, name, values, comment) in self.enums:
318             print "%(name)s (%(typ)s):" % locals()
319             for k, v in values:
320                 print "    %(k)s=%(v)s" % locals()
321
322 class PythonGenerator(object):
323     # C-type to ctypes/python type conversion.
324     # Note that enum types conversions are generated (cf convert_enum_names)
325     type2class={
326         'libvlc_exception_t*': 'ctypes.POINTER(VLCException)',
327
328         'libvlc_media_player_t*': 'MediaPlayer',
329         'libvlc_instance_t*': 'Instance',
330         'libvlc_media_t*': 'Media',
331         'libvlc_log_t*': 'Log',
332         'libvlc_log_iterator_t*': 'LogIterator',
333         'libvlc_log_message_t*': 'ctypes.POINTER(LogMessage)',
334         'libvlc_event_type_t': 'ctypes.c_uint',
335         'libvlc_event_manager_t*': 'EventManager',
336         'libvlc_media_discoverer_t*': 'MediaDiscoverer',
337         'libvlc_media_library_t*': 'MediaLibrary',
338         'libvlc_media_list_t*': 'MediaList',
339         'libvlc_media_list_player_t*': 'MediaListPlayer',
340         'libvlc_media_list_view_t*': 'MediaListView',
341         'libvlc_track_description_t*': 'TrackDescription',
342         'libvlc_audio_output_t*': 'AudioOutput',
343
344         'mediacontrol_Instance*': 'MediaControl',
345         'mediacontrol_Exception*': 'MediaControlException',
346         'mediacontrol_RGBPicture*': 'ctypes.POINTER(RGBPicture)',
347         'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
348         'mediacontrol_Position*': 'ctypes.POINTER(MediaControlPosition)',
349         'mediacontrol_StreamInformation*': 'ctypes.POINTER(MediaControlStreamInformation)',
350         'WINDOWHANDLE': 'ctypes.c_ulong',
351
352         'void': 'None',
353         'void*': 'ctypes.c_void_p',
354         'short': 'ctypes.c_short',
355         'char*': 'ctypes.c_char_p',
356         'char**': 'ListPOINTER(ctypes.c_char_p)',
357         'uint32_t': 'ctypes.c_uint',
358         'float': 'ctypes.c_float',
359         'unsigned': 'ctypes.c_uint',
360         'int': 'ctypes.c_int',
361         '...': 'FIXMEva_list',
362         'libvlc_callback_t': 'ctypes.c_void_p',
363         'libvlc_time_t': 'ctypes.c_longlong',
364         }
365
366     # Defined python classes, i.e. classes for which we want to generate
367     # class wrappers around libvlc functions
368     defined_classes=(
369         'MediaPlayer',
370         'Instance',
371         'Media',
372         'Log',
373         'LogIterator',
374         'EventManager',
375         'MediaDiscoverer',
376         'MediaLibrary',
377         'MediaList',
378         'MediaListPlayer',
379         'MediaListView',
380         'TrackDescription',
381         'AudioOutput',
382         'MediaControl',
383         )
384
385     def __init__(self, parser=None):
386         self.parser=parser
387
388         # Generate python names for enums
389         self.type2class.update(self.convert_enum_names(parser.enums))
390         self.check_types()
391
392         # Definition of prefixes that we can strip from method names when
393         # wrapping them into class methods
394         self.prefixes=dict( (v, k[:-2])
395                             for (k, v) in self.type2class.iteritems()
396                             if  v in self.defined_classes )
397         self.prefixes['MediaControl']='mediacontrol_'
398
399     def save(self, filename=None):
400         if filename is None or filename == '-':
401             self.fd=sys.stdout
402         else:
403             self.fd=open(filename, 'w')
404
405         self.insert_code('header.py')
406         wrapped_methods=self.generate_wrappers(self.parser.methods)
407         for l in self.parser.methods:
408             self.output_ctypes(*l)
409         self.insert_code('footer.py')
410
411         all_methods=set( t[1] for t in self.parser.methods )
412         not_wrapped=all_methods.difference(wrapped_methods)
413         self.output("# Not wrapped methods:")
414         for m in not_wrapped:
415             self.output("#   ", m)
416
417         if self.fd != sys.stdout:
418             self.fd.close()
419
420     def output(self, *p):
421         self.fd.write(" ".join(p))
422         self.fd.write("\n")
423
424     def check_types(self):
425         """Make sure that all types are properly translated.
426
427         This method must be called *after* convert_enum_names, since
428         the latter populates type2class with converted enum names.
429         """
430         for (rt, met, params, c) in self.parser.methods:
431             for typ, name in params:
432                 if not typ in self.type2class:
433                     raise Exception("No conversion for %s (from %s:%s)" % (typ, met, name))
434
435     def insert_code(self, filename):
436         """Generate header/footer code.
437         """
438         f=open(filename, 'r')
439         for l in f:
440             if l.startswith('build_date'):
441                 self.output('build_date="%s"' % time.ctime())
442             elif l.startswith('# GENERATED_ENUMS'):
443                 self.generate_enums(self.parser.enums)
444             else:
445                 self.output(l.rstrip())
446
447         f.close()
448
449     def convert_enum_names(self, enums):
450         res={}
451         for (typ, name, values, comment) in enums:
452             if typ != 'enum':
453                 raise Exception('This method only handles enums')
454             pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
455             if '_' in pyname:
456                 pyname=pyname.title().replace('_', '')
457             elif not pyname[0].isupper():
458                 pyname=pyname.capitalize()
459             res[name]=pyname
460         return res
461
462     def generate_enums(self, enums):
463         for (typ, name, values, comment) in enums:
464             if typ != 'enum':
465                 raise Exception('This method only handles enums')
466             pyname=self.type2class[name]
467
468             self.output("class %s(ctypes.c_ulong):" % pyname)
469             self.output('    """%s\n    """' % comment)
470
471             conv={}
472             # Convert symbol names
473             for k, v in values:
474                 n=k.split('_')[-1]
475                 if len(n) == 1:
476                     # Single character. Some symbols use 1_1, 5_1, etc.
477                     n="_".join( k.split('_')[-2:] )
478                 if re.match('^[0-9]', n):
479                     # Cannot start an identifier with a number
480                     n='_'+n
481                 conv[k]=n
482
483             self.output("    _names={")
484             for k, v in values:
485                 self.output("        %s: '%s'," % (v, conv[k]))
486             self.output("    }")
487
488             self.output("""
489     def __repr__(self):
490         return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value]))
491
492     def __eq__(self, other):
493         return ( (isinstance(other, ctypes.c_ulong) and self.value == other.value)
494                  or (isinstance(other, (int, long)) and self.value == other ) )
495
496     def __ne__(self, other):
497         return not self.__eq__(other)
498     """)
499             for k, v in values:
500                 self.output("%(class)s.%(attribute)s=%(class)s(%(value)s)" % {
501                         'class': pyname,
502                         'attribute': conv[k],
503                         'value': v
504                         })
505             self.output("")
506
507     def output_ctypes(self, rtype, method, params, comment):
508         """Output ctypes decorator for the given method.
509         """
510         if method in blacklist:
511             # FIXME
512             return
513
514         self.output("""if hasattr(dll, '%s'):""" % method)
515         if params:
516             self.output("    prototype=ctypes.CFUNCTYPE(%s, %s)" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
517                                                                 ", ".join( self.type2class[p[0]] for p in params )))
518         else:
519             self.output("    prototype=ctypes.CFUNCTYPE(%s)" % self.type2class.get(rtype, 'FIXME_%s' % rtype))
520
521
522         if not params:
523             flags='    paramflags= tuple()'
524         elif len(params) == 1:
525             flags="    paramflags=( (%d, ), )" % parameter_passing[params[0][0]]
526         else:
527             flags="    paramflags=%s" % ", ".join( '(%d,)' % parameter_passing[p[0]] for p in params )
528         self.output(flags)
529         self.output('    %s = prototype( ("%s", dll), paramflags )' % (method, method))
530         if '3' in flags:
531             # A VLCException is present. Process it.
532             self.output("    %s.errcheck = check_vlc_exception" % method)
533         self.output('    %s.__doc__ = """%s"""' % (method, comment))
534         self.output()
535
536     def parse_override(self, name):
537         """Parse override definitions file.
538
539         It is possible to override methods definitions in classes.
540
541         It returns a tuple
542         (code, overriden_methods, docstring)
543         """
544         code={}
545
546         data=[]
547         current=None
548         f=open(name, 'r')
549         for l in f:
550             m=re.match('class (\S+):', l)
551             if m:
552                 # Dump old data
553                 if current is not None:
554                     code[current]="".join(data)
555                 current=m.group(1)
556                 data=[]
557                 continue
558             data.append(l)
559         code[current]="".join(data)
560         f.close()
561
562         docstring={}
563         for k, v in code.iteritems():
564             if v.lstrip().startswith('"""'):
565                 # Starting comment. Use it as docstring.
566                 dummy, docstring[k], code[k]=v.split('"""', 2)
567
568         # Not robust wrt. internal methods, but this works for the moment.
569         overridden_methods=dict( (k, re.findall('^\s+def\s+(\w+)', v, re.MULTILINE)) for (k, v) in code.iteritems() )
570
571         return code, overridden_methods, docstring
572
573     def fix_python_comment(self, c):
574         """Fix comment by removing first and last parameters (self and exception)
575         """
576         data=c.replace('@{', '').replace('@see', 'See').splitlines()
577         body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data)
578         param=[ python_param_re.sub('\\1:\\2', l) for l in  itertools.ifilter(lambda l: '@param' in l, data) ]
579         ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ]
580
581         if len(param) >= 2:
582             param=param[1:-1]
583         elif len(param) == 1:
584             param=[]
585
586         return "\n".join(itertools.chain(body, param, ret))
587
588     def generate_wrappers(self, methods):
589         """Generate class wrappers for all appropriate methods.
590
591         @return: the set of wrapped method names
592         """
593         ret=set()
594         # Sort methods against the element they apply to.
595         elements=sorted( ( (self.type2class.get(params[0][0]), rt, met, params, c)
596                            for (rt, met, params, c) in methods
597                            if params and self.type2class.get(params[0][0], '_') in self.defined_classes
598                            ),
599                          key=operator.itemgetter(0))
600
601         overrides, overriden_methods, docstring=self.parse_override('override.py')
602
603         for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)):
604             self.output("""class %(name)s(object):""" % {'name': classname})
605             if classname in docstring:
606                 self.output('    """%s\n    """' % docstring[classname])
607
608             if not 'def __new__' in overrides.get(classname, ''):
609                 self.output("""
610     def __new__(cls, pointer=None):
611         '''Internal method used for instanciating wrappers from ctypes.
612         '''
613         if pointer is None:
614             raise Exception("Internal method. Surely this class cannot be instanciated by itself.")
615         if pointer == 0:
616             return None
617         else:
618             o=object.__new__(cls)
619             o._as_parameter_=ctypes.c_void_p(pointer)
620             return o
621 """)
622
623             self.output("""
624     @staticmethod
625     def from_param(arg):
626         '''(INTERNAL) ctypes parameter conversion method.
627         '''
628         return arg._as_parameter_
629 """)
630
631             if classname in overrides:
632                 self.output(overrides[classname])
633
634             prefix=self.prefixes.get(classname, '')
635
636             for cl, rtype, method, params, comment in el:
637                 if method in blacklist:
638                     continue
639                 # Strip prefix
640                 name=method.replace(prefix, '').replace('libvlc_', '')
641                 ret.add(method)
642                 if name in overriden_methods.get(cl, []):
643                     # Method already defined in override.py
644                     continue
645
646                 if params:
647                     params[0]=(params[0][0], 'self')
648                 if params and params[-1][0] in ('libvlc_exception_t*', 'mediacontrol_Exception*'):
649                     args=", ".join( p[1] for p in params[:-1] )
650                 else:
651                     args=", ".join( p[1] for p in params )
652
653                 self.output("    if hasattr(dll, '%s'):" % method)
654                 self.output("        def %s(%s):" % (name, args))
655                 self.output('            """%s\n        """' % self.fix_python_comment(comment))
656                 if params and params[-1][0] == 'libvlc_exception_t*':
657                     # Exception handling
658                     self.output("            e=VLCException()")
659                     self.output("            return %s(%s, e)" % (method, args))
660                 elif params and params[-1][0] == 'mediacontrol_Exception*':
661                     # Exception handling
662                     self.output("            e=MediaControlException()")
663                     self.output("            return %s(%s, e)" % (method, args))
664                 else:
665                     self.output("            return %s(%s)" % (method, args))
666                 self.output()
667
668                 # Check for standard methods
669                 if name == 'count':
670                     # There is a count method. Generate a __len__ one.
671                     if params and params[-1][0] == 'libvlc_exception_t*':
672                         self.output("""    def __len__(self):
673         e=VLCException()
674         return %s(self, e)
675 """ % method)
676                     else:
677                         # No exception
678                         self.output("""    def __len__(self):
679         return %s(self)
680 """ % method)
681                 elif name.endswith('item_at_index'):
682                     # Indexable (and thus iterable)"
683                     self.output("""    def __getitem__(self, i):
684         e=VLCException()
685         return %s(self, i, e)
686
687     def __iter__(self):
688         e=VLCException()
689         for i in xrange(len(self)):
690             yield self[i]
691 """ % method)
692         return ret
693
694 class JavaGenerator(object):
695     # C-type to java/jna type conversion.
696     # Note that enum types conversions are generated (cf convert_enum_names)
697     type2class={
698         'libvlc_exception_t*': 'libvlc_exception_t',
699         'libvlc_media_player_t*': 'LibVlcMediaPlayer',
700         'libvlc_instance_t*': 'LibVlcInstance',
701         'libvlc_media_t*': 'LibVlcMedia',
702         'libvlc_log_t*': 'LibVlcLog',
703         'libvlc_log_iterator_t*': 'LibVlcLogIterator',
704         'libvlc_log_message_t*': 'libvlc_log_message_t',
705         'libvlc_event_type_t': 'int',
706         'libvlc_event_manager_t*': 'LibVlcEventManager',
707         'libvlc_media_discoverer_t*': 'LibVlcMediaDiscoverer',
708         'libvlc_media_library_t*': 'LibVlcMediaLibrary',
709         'libvlc_media_list_t*': 'LibVlcMediaList',
710         'libvlc_media_list_player_t*': 'LibVlcMediaListPlayer',
711         'libvlc_media_list_view_t*': 'LibVlcMediaListView',
712
713         'libvlc_track_description_t*': 'LibVlcTrackDescription',
714         'libvlc_audio_output_t*': 'LibVlcAudioOutput',
715
716         'void': 'void',
717         'void*': 'Pointer',
718         'short': 'short',
719         'char*': 'String',
720         'char**': 'String[]',
721         'uint32_t': 'uint32',
722         'float': 'float',
723         'unsigned': 'int',
724         'int': 'int',
725         '...': 'FIXMEva_list',
726         'libvlc_callback_t': 'LibVlcCallback',
727         'libvlc_time_t': 'long',
728
729         'mediacontrol_RGBPicture*': 'Pointer',
730         'mediacontrol_PlaylistSeq*': 'Pointer',
731         'mediacontrol_StreamInformation*': 'Pointer',
732         }
733
734     def __init__(self, parser=None):
735         self.parser=parser
736
737         # Blacklist all mediacontrol methods
738         for (rt, met, params, c) in self.parser.methods:
739             if met.startswith('mediacontrol'):
740                 blacklist.append(met)
741         # Generate Java names for enums
742         self.type2class.update(self.convert_enum_names(parser.enums))
743         self.check_types()
744
745     def save(self, dirname=None):
746         if dirname is None or dirname == '-':
747             dirname='internal'
748             if not os.path.isdir(dirname):
749                 os.mkdir(dirname)
750
751         print "Generating java code in %s/" % dirname
752
753         # Generate enum files
754         self.generate_enums(dirname, self.parser.enums)
755
756         # Generate LibVlc.java code
757         self.generate_libvlc(dirname)
758
759     def output(self, fd, *p):
760         fd.write(" ".join(p))
761         fd.write("\n")
762
763     def check_types(self):
764         """Make sure that all types are properly translated.
765
766         This method must be called *after* convert_enum_names, since
767         the latter populates type2class with converted enum names.
768         """
769         for (rt, met, params, c) in self.parser.methods:
770             if met in blacklist:
771                 continue
772             for typ, name in params:
773                 if not typ in self.type2class:
774                     raise Exception("No conversion for %s (from %s:%s)" % (typ, met, name))
775
776     def convert_enum_names(self, enums):
777         """Convert enum names into Java names.
778         """
779         res={}
780         for (typ, name, values, comment) in enums:
781             if typ != 'enum':
782                 raise Exception('This method only handles enums')
783             pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
784             if '_' in pyname:
785                 pyname=pyname.title().replace('_', '')
786             elif not pyname[0].isupper():
787                 pyname=pyname.capitalize()
788             res[name]=pyname
789         return res
790
791     def insert_code(self, fd, filename):
792         """Generate header/footer code.
793         """
794         f=open(filename, 'r')
795         for l in f:
796             if l.startswith('build_date'):
797                 self.output(fd, 'build_date="%s";' % time.ctime())
798             else:
799                 self.output(fd, l.rstrip())
800         f.close()
801
802     def generate_header(self, fd):
803         """Generate LibVlc header.
804         """
805         for (c_type, jna_type) in self.type2class.iteritems():
806             if c_type.endswith('*') and jna_type.startswith('LibVlc'):
807                 self.output(fd, '''    public class %s extends PointerType
808     {
809     }
810 ''' % jna_type)
811
812     def generate_libvlc(self, dirname):
813         """Generate LibVlc.java JNA glue code.
814         """
815         filename=os.path.join(dirname, 'LibVlc.java')
816         fd=open(filename, 'w')
817
818         self.insert_code(fd, 'boilerplate.java')
819         self.insert_code(fd, 'LibVlc-header.java')
820         #wrapped_methods=self.generate_wrappers(self.parser.methods)
821         self.generate_header(fd)
822         for (rtype, method, params, comment) in self.parser.methods:
823             if method in blacklist:
824                 # FIXME
825                 continue
826             self.output(fd, "%s %s(%s);\n" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
827                                           method,
828                                           ", ".join( ("%s %s" % (self.type2class[p[0]],
829                                                                  p[1])) for p in params )))
830         self.insert_code(fd, 'LibVlc-footer.java')
831         fd.close()
832
833     def generate_enums(self, dirname, enums):
834         """Generate JNA glue code for enums
835         """
836         for (typ, name, values, comment) in enums:
837             if typ != 'enum':
838                 raise Exception('This method only handles enums')
839             javaname=self.type2class[name]
840
841             filename=javaname+".java"
842
843             fd=open(os.path.join(dirname, filename), 'w')
844
845             self.insert_code(fd, 'boilerplate.java')
846             self.output(fd, """package org.videolan.jvlc.internal;
847
848
849 public enum %s
850 {
851 """ % javaname)
852             # FIXME: write comment
853
854             for k, v in values:
855                 self.output(fd, "        %s, // %s," % (k, v))
856             self.output(fd, "}")
857             fd.close()
858
859     def fix_python_comment(self, c):
860         """Fix comment by removing first and last parameters (self and exception)
861         """
862         data=c.replace('@{', '').replace('@see', 'See').splitlines()
863         body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data)
864         param=[ python_param_re.sub('\\1:\\2', l) for l in  itertools.ifilter(lambda l: '@param' in l, data) ]
865         ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ]
866
867         if len(param) >= 2:
868             param=param[1:-1]
869         elif len(param) == 1:
870             param=[]
871
872         return "\n".join(itertools.chain(body, param, ret))
873
874 def process(output, list_of_includes):
875     p=Parser(list_of_includes)
876     g=PythonGenerator(p)
877     g.save(output)
878
879 if __name__ == '__main__':
880     opt=OptionParser(usage="""Parse VLC include files and generate bindings code.
881 %prog [options] include_file.h [...]""")
882
883     opt.add_option("-d", "--debug", dest="debug", action="store_true",
884                       default=False,
885                       help="Debug mode")
886
887     opt.add_option("-c", "--check", dest="check", action="store_true",
888                       default=False,
889                       help="Check mode")
890
891     opt.add_option("-j", "--java", dest="java", action="store_true",
892                       default=False,
893                       help="Generate java bindings (default is python)")
894
895     opt.add_option("-o", "--output", dest="output", action="store",
896                       type="str", default="-",
897                       help="Output filename(python)/dirname(java)")
898
899     (options, args) = opt.parse_args()
900
901     if not args:
902         opt.print_help()
903         sys.exit(1)
904
905     p=Parser(args)
906     if options.check:
907         # Various consistency checks.
908         for (rt, name, params, comment) in p.methods:
909             if not comment.strip():
910                 print "No comment for %s" % name
911                 continue
912             names=comment_re.findall(comment)
913             if len(names) != len(params):
914                 print "Docstring comment parameters mismatch for %s" % name
915
916     if options.debug:
917         p.dump_methods()
918         p.dump_enums()
919
920     if options.check or options.debug:
921         sys.exit(0)
922
923     if options.java:
924         g=JavaGenerator(p)
925     else:
926         g=PythonGenerator(p)
927
928     g.save(options.output)