]> git.sesse.net Git - vlc/blob - bindings/python-ctypes/generate.py
Add some locking.
[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_int',
73     #'libvlc_video_get_marquee_string',
74     #'libvlc_video_set_marquee_int',
75     #'libvlc_video_set_marquee_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         'libvlc_media_stats_t*': 'ctypes.POINTER(MediaStats)',
344         'libvlc_media_es_t**': 'ctypes.POINTER(ctypes.POINTER(MediaES))',
345
346         'mediacontrol_Instance*': 'MediaControl',
347         'mediacontrol_Exception*': 'MediaControlException',
348         'mediacontrol_RGBPicture*': 'ctypes.POINTER(RGBPicture)',
349         'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
350         'mediacontrol_Position*': 'ctypes.POINTER(MediaControlPosition)',
351         'mediacontrol_StreamInformation*': 'ctypes.POINTER(MediaControlStreamInformation)',
352         'WINDOWHANDLE': 'ctypes.c_ulong',
353
354         'void': 'None',
355         'void*': 'ctypes.c_void_p',
356         'short': 'ctypes.c_short',
357         'char*': 'ctypes.c_char_p',
358         'char**': 'ListPOINTER(ctypes.c_char_p)',
359         'uint32_t': 'ctypes.c_uint',
360         'float': 'ctypes.c_float',
361         'unsigned': 'ctypes.c_uint',
362         'int': 'ctypes.c_int',
363         '...': 'FIXMEva_list',
364         'libvlc_callback_t': 'ctypes.c_void_p',
365         'libvlc_time_t': 'ctypes.c_longlong',
366         }
367
368     # Defined python classes, i.e. classes for which we want to generate
369     # class wrappers around libvlc functions
370     defined_classes=(
371         'MediaPlayer',
372         'Instance',
373         'Media',
374         'Log',
375         'LogIterator',
376         'EventManager',
377         'MediaDiscoverer',
378         'MediaLibrary',
379         'MediaList',
380         'MediaListPlayer',
381         'MediaListView',
382         'TrackDescription',
383         'AudioOutput',
384         'MediaControl',
385         )
386
387     def __init__(self, parser=None):
388         self.parser=parser
389
390         # Generate python names for enums
391         self.type2class.update(self.convert_enum_names(parser.enums))
392         self.check_types()
393
394         # Definition of prefixes that we can strip from method names when
395         # wrapping them into class methods
396         self.prefixes=dict( (v, k[:-2])
397                             for (k, v) in self.type2class.iteritems()
398                             if  v in self.defined_classes )
399         self.prefixes['MediaControl']='mediacontrol_'
400
401     def save(self, filename=None):
402         if filename is None or filename == '-':
403             self.fd=sys.stdout
404         else:
405             self.fd=open(filename, 'w')
406
407         self.insert_code('header.py')
408         wrapped_methods=self.generate_wrappers(self.parser.methods)
409         for l in self.parser.methods:
410             self.output_ctypes(*l)
411         self.insert_code('footer.py')
412
413         all_methods=set( t[1] for t in self.parser.methods )
414         not_wrapped=all_methods.difference(wrapped_methods)
415         self.output("# Not wrapped methods:")
416         for m in not_wrapped:
417             self.output("#   ", m)
418
419         if self.fd != sys.stdout:
420             self.fd.close()
421
422     def output(self, *p):
423         self.fd.write(" ".join(p))
424         self.fd.write("\n")
425
426     def check_types(self):
427         """Make sure that all types are properly translated.
428
429         This method must be called *after* convert_enum_names, since
430         the latter populates type2class with converted enum names.
431         """
432         for (rt, met, params, c) in self.parser.methods:
433             for typ, name in params:
434                 if not typ in self.type2class:
435                     raise Exception("No conversion for %s (from %s:%s)" % (typ, met, name))
436
437     def insert_code(self, filename):
438         """Generate header/footer code.
439         """
440         f=open(filename, 'r')
441         for l in f:
442             if l.startswith('build_date'):
443                 self.output('build_date="%s"' % time.ctime())
444             elif l.startswith('# GENERATED_ENUMS'):
445                 self.generate_enums(self.parser.enums)
446             else:
447                 self.output(l.rstrip())
448
449         f.close()
450
451     def convert_enum_names(self, enums):
452         res={}
453         for (typ, name, values, comment) in enums:
454             if typ != 'enum':
455                 raise Exception('This method only handles enums')
456             pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
457             if '_' in pyname:
458                 pyname=pyname.title().replace('_', '')
459             elif not pyname[0].isupper():
460                 pyname=pyname.capitalize()
461             res[name]=pyname
462         return res
463
464     def generate_enums(self, enums):
465         for (typ, name, values, comment) in enums:
466             if typ != 'enum':
467                 raise Exception('This method only handles enums')
468             pyname=self.type2class[name]
469
470             self.output("class %s(ctypes.c_ulong):" % pyname)
471             self.output('    """%s\n    """' % comment)
472
473             conv={}
474             # Convert symbol names
475             for k, v in values:
476                 n=k.split('_')[-1]
477                 if len(n) == 1:
478                     # Single character. Some symbols use 1_1, 5_1, etc.
479                     n="_".join( k.split('_')[-2:] )
480                 if re.match('^[0-9]', n):
481                     # Cannot start an identifier with a number
482                     n='_'+n
483                 conv[k]=n
484
485             self.output("    _names={")
486             for k, v in values:
487                 self.output("        %s: '%s'," % (v, conv[k]))
488             self.output("    }")
489
490             self.output("""
491     def __repr__(self):
492         return ".".join((self.__class__.__module__, self.__class__.__name__, self._names[self.value]))
493
494     def __eq__(self, other):
495         return ( (isinstance(other, ctypes.c_ulong) and self.value == other.value)
496                  or (isinstance(other, (int, long)) and self.value == other ) )
497
498     def __ne__(self, other):
499         return not self.__eq__(other)
500     """)
501             for k, v in values:
502                 self.output("%(class)s.%(attribute)s=%(class)s(%(value)s)" % {
503                         'class': pyname,
504                         'attribute': conv[k],
505                         'value': v
506                         })
507             self.output("")
508
509     def output_ctypes(self, rtype, method, params, comment):
510         """Output ctypes decorator for the given method.
511         """
512         if method in blacklist:
513             # FIXME
514             return
515
516         self.output("""if hasattr(dll, '%s'):""" % method)
517         if params:
518             self.output("    prototype=ctypes.CFUNCTYPE(%s, %s)" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
519                                                                 ", ".join( self.type2class[p[0]] for p in params )))
520         else:
521             self.output("    prototype=ctypes.CFUNCTYPE(%s)" % self.type2class.get(rtype, 'FIXME_%s' % rtype))
522
523
524         if not params:
525             flags='    paramflags= tuple()'
526         elif len(params) == 1:
527             flags="    paramflags=( (%d, ), )" % parameter_passing[params[0][0]]
528         else:
529             flags="    paramflags=%s" % ", ".join( '(%d,)' % parameter_passing[p[0]] for p in params )
530         self.output(flags)
531         self.output('    %s = prototype( ("%s", dll), paramflags )' % (method, method))
532         if '3' in flags:
533             # A VLCException is present. Process it.
534             self.output("    %s.errcheck = check_vlc_exception" % method)
535         self.output('    %s.__doc__ = """%s"""' % (method, comment))
536         self.output()
537
538     def parse_override(self, name):
539         """Parse override definitions file.
540
541         It is possible to override methods definitions in classes.
542
543         It returns a tuple
544         (code, overriden_methods, docstring)
545         """
546         code={}
547
548         data=[]
549         current=None
550         f=open(name, 'r')
551         for l in f:
552             m=re.match('class (\S+):', l)
553             if m:
554                 # Dump old data
555                 if current is not None:
556                     code[current]="".join(data)
557                 current=m.group(1)
558                 data=[]
559                 continue
560             data.append(l)
561         code[current]="".join(data)
562         f.close()
563
564         docstring={}
565         for k, v in code.iteritems():
566             if v.lstrip().startswith('"""'):
567                 # Starting comment. Use it as docstring.
568                 dummy, docstring[k], code[k]=v.split('"""', 2)
569
570         # Not robust wrt. internal methods, but this works for the moment.
571         overridden_methods=dict( (k, re.findall('^\s+def\s+(\w+)', v, re.MULTILINE)) for (k, v) in code.iteritems() )
572
573         return code, overridden_methods, docstring
574
575     def fix_python_comment(self, c):
576         """Fix comment by removing first and last parameters (self and exception)
577         """
578         data=c.replace('@{', '').replace('@see', 'See').splitlines()
579         body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data)
580         param=[ python_param_re.sub('\\1:\\2', l) for l in  itertools.ifilter(lambda l: '@param' in l, data) ]
581         ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ]
582
583         if len(param) >= 2:
584             param=param[1:-1]
585         elif len(param) == 1:
586             param=[]
587
588         return "\n".join(itertools.chain(body, param, ret))
589
590     def generate_wrappers(self, methods):
591         """Generate class wrappers for all appropriate methods.
592
593         @return: the set of wrapped method names
594         """
595         ret=set()
596         # Sort methods against the element they apply to.
597         elements=sorted( ( (self.type2class.get(params[0][0]), rt, met, params, c)
598                            for (rt, met, params, c) in methods
599                            if params and self.type2class.get(params[0][0], '_') in self.defined_classes
600                            ),
601                          key=operator.itemgetter(0))
602
603         overrides, overriden_methods, docstring=self.parse_override('override.py')
604
605         for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)):
606             self.output("""class %(name)s(object):""" % {'name': classname})
607             if classname in docstring:
608                 self.output('    """%s\n    """' % docstring[classname])
609
610             if not 'def __new__' in overrides.get(classname, ''):
611                 self.output("""
612     def __new__(cls, pointer=None):
613         '''Internal method used for instanciating wrappers from ctypes.
614         '''
615         if pointer is None:
616             raise Exception("Internal method. Surely this class cannot be instanciated by itself.")
617         if pointer == 0:
618             return None
619         else:
620             o=object.__new__(cls)
621             o._as_parameter_=ctypes.c_void_p(pointer)
622             return o
623 """)
624
625             self.output("""
626     @staticmethod
627     def from_param(arg):
628         '''(INTERNAL) ctypes parameter conversion method.
629         '''
630         return arg._as_parameter_
631 """)
632
633             if classname in overrides:
634                 self.output(overrides[classname])
635
636             prefix=self.prefixes.get(classname, '')
637
638             for cl, rtype, method, params, comment in el:
639                 if method in blacklist:
640                     continue
641                 # Strip prefix
642                 name=method.replace(prefix, '').replace('libvlc_', '')
643                 ret.add(method)
644                 if name in overriden_methods.get(cl, []):
645                     # Method already defined in override.py
646                     continue
647
648                 if params:
649                     params[0]=(params[0][0], 'self')
650                 if params and params[-1][0] in ('libvlc_exception_t*', 'mediacontrol_Exception*'):
651                     args=", ".join( p[1] for p in params[:-1] )
652                 else:
653                     args=", ".join( p[1] for p in params )
654
655                 self.output("    if hasattr(dll, '%s'):" % method)
656                 self.output("        def %s(%s):" % (name, args))
657                 self.output('            """%s\n        """' % self.fix_python_comment(comment))
658                 if params and params[-1][0] == 'libvlc_exception_t*':
659                     # Exception handling
660                     self.output("            e=VLCException()")
661                     self.output("            return %s(%s, e)" % (method, args))
662                 elif params and params[-1][0] == 'mediacontrol_Exception*':
663                     # Exception handling
664                     self.output("            e=MediaControlException()")
665                     self.output("            return %s(%s, e)" % (method, args))
666                 else:
667                     self.output("            return %s(%s)" % (method, args))
668                 self.output()
669
670                 # Check for standard methods
671                 if name == 'count':
672                     # There is a count method. Generate a __len__ one.
673                     if params and params[-1][0] == 'libvlc_exception_t*':
674                         self.output("""    def __len__(self):
675         e=VLCException()
676         return %s(self, e)
677 """ % method)
678                     else:
679                         # No exception
680                         self.output("""    def __len__(self):
681         return %s(self)
682 """ % method)
683                 elif name.endswith('item_at_index'):
684                     # Indexable (and thus iterable)"
685                     self.output("""    def __getitem__(self, i):
686         e=VLCException()
687         return %s(self, i, e)
688
689     def __iter__(self):
690         e=VLCException()
691         for i in xrange(len(self)):
692             yield self[i]
693 """ % method)
694         return ret
695
696 class JavaGenerator(object):
697     # C-type to java/jna type conversion.
698     # Note that enum types conversions are generated (cf convert_enum_names)
699     type2class={
700         'libvlc_exception_t*': 'libvlc_exception_t',
701         'libvlc_media_player_t*': 'LibVlcMediaPlayer',
702         'libvlc_instance_t*': 'LibVlcInstance',
703         'libvlc_media_t*': 'LibVlcMedia',
704         'libvlc_log_t*': 'LibVlcLog',
705         'libvlc_log_iterator_t*': 'LibVlcLogIterator',
706         'libvlc_log_message_t*': 'libvlc_log_message_t',
707         'libvlc_event_type_t': 'int',
708         'libvlc_event_manager_t*': 'LibVlcEventManager',
709         'libvlc_media_discoverer_t*': 'LibVlcMediaDiscoverer',
710         'libvlc_media_library_t*': 'LibVlcMediaLibrary',
711         'libvlc_media_list_t*': 'LibVlcMediaList',
712         'libvlc_media_list_player_t*': 'LibVlcMediaListPlayer',
713         'libvlc_media_list_view_t*': 'LibVlcMediaListView',
714         'libvlc_media_stats_t*': 'LibVlcMediaStats',
715
716         'libvlc_track_description_t*': 'LibVlcTrackDescription',
717         'libvlc_audio_output_t*': 'LibVlcAudioOutput',
718
719         'void': 'void',
720         'void*': 'Pointer',
721         'short': 'short',
722         'char*': 'String',
723         'char**': 'String[]',
724         'uint32_t': 'uint32',
725         'float': 'float',
726         'unsigned': 'int',
727         'int': 'int',
728         '...': 'FIXMEva_list',
729         'libvlc_callback_t': 'LibVlcCallback',
730         'libvlc_time_t': 'long',
731
732         'mediacontrol_RGBPicture*': 'Pointer',
733         'mediacontrol_PlaylistSeq*': 'Pointer',
734         'mediacontrol_StreamInformation*': 'Pointer',
735         }
736
737     def __init__(self, parser=None):
738         self.parser=parser
739
740         # Blacklist all mediacontrol methods
741         for (rt, met, params, c) in self.parser.methods:
742             if met.startswith('mediacontrol'):
743                 blacklist.append(met)
744         # Generate Java names for enums
745         self.type2class.update(self.convert_enum_names(parser.enums))
746         self.check_types()
747
748     def save(self, dirname=None):
749         if dirname is None or dirname == '-':
750             dirname='internal'
751             if not os.path.isdir(dirname):
752                 os.mkdir(dirname)
753
754         print "Generating java code in %s/" % dirname
755
756         # Generate enum files
757         self.generate_enums(dirname, self.parser.enums)
758
759         # Generate LibVlc.java code
760         self.generate_libvlc(dirname)
761
762     def output(self, fd, *p):
763         fd.write(" ".join(p))
764         fd.write("\n")
765
766     def check_types(self):
767         """Make sure that all types are properly translated.
768
769         This method must be called *after* convert_enum_names, since
770         the latter populates type2class with converted enum names.
771         """
772         for (rt, met, params, c) in self.parser.methods:
773             if met in blacklist:
774                 continue
775             for typ, name in params:
776                 if not typ in self.type2class:
777                     raise Exception("No conversion for %s (from %s:%s)" % (typ, met, name))
778
779     def convert_enum_names(self, enums):
780         """Convert enum names into Java names.
781         """
782         res={}
783         for (typ, name, values, comment) in enums:
784             if typ != 'enum':
785                 raise Exception('This method only handles enums')
786             pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
787             if '_' in pyname:
788                 pyname=pyname.title().replace('_', '')
789             elif not pyname[0].isupper():
790                 pyname=pyname.capitalize()
791             res[name]=pyname
792         return res
793
794     def insert_code(self, fd, filename):
795         """Generate header/footer code.
796         """
797         f=open(filename, 'r')
798         for l in f:
799             if l.startswith('build_date'):
800                 self.output(fd, 'build_date="%s";' % time.ctime())
801             else:
802                 self.output(fd, l.rstrip())
803         f.close()
804
805     def generate_header(self, fd):
806         """Generate LibVlc header.
807         """
808         for (c_type, jna_type) in self.type2class.iteritems():
809             if c_type.endswith('*') and jna_type.startswith('LibVlc'):
810                 self.output(fd, '''    public class %s extends PointerType
811     {
812     }
813 ''' % jna_type)
814
815     def generate_libvlc(self, dirname):
816         """Generate LibVlc.java JNA glue code.
817         """
818         filename=os.path.join(dirname, 'LibVlc.java')
819         fd=open(filename, 'w')
820
821         self.insert_code(fd, 'boilerplate.java')
822         self.insert_code(fd, 'LibVlc-header.java')
823         #wrapped_methods=self.generate_wrappers(self.parser.methods)
824         self.generate_header(fd)
825         for (rtype, method, params, comment) in self.parser.methods:
826             if method in blacklist:
827                 # FIXME
828                 continue
829             self.output(fd, "%s %s(%s);\n" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
830                                           method,
831                                           ", ".join( ("%s %s" % (self.type2class[p[0]],
832                                                                  p[1])) for p in params )))
833         self.insert_code(fd, 'LibVlc-footer.java')
834         fd.close()
835
836     def generate_enums(self, dirname, enums):
837         """Generate JNA glue code for enums
838         """
839         for (typ, name, values, comment) in enums:
840             if typ != 'enum':
841                 raise Exception('This method only handles enums')
842             javaname=self.type2class[name]
843
844             filename=javaname+".java"
845
846             fd=open(os.path.join(dirname, filename), 'w')
847
848             self.insert_code(fd, 'boilerplate.java')
849             self.output(fd, """package org.videolan.jvlc.internal;
850
851
852 public enum %s
853 {
854 """ % javaname)
855             # FIXME: write comment
856
857             for k, v in values:
858                 self.output(fd, "        %s (%s)," % (k, v))
859             self.output(fd, "");
860             self.output(fd, "        private final int _value;");
861             self.output(fd, "        %s(int value) { this._value = value; }" % javaname);
862             self.output(fd, "        public int value() { return this._value; }");
863             self.output(fd, "}")
864             fd.close()
865
866     def fix_python_comment(self, c):
867         """Fix comment by removing first and last parameters (self and exception)
868         """
869         data=c.replace('@{', '').replace('@see', 'See').splitlines()
870         body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data)
871         param=[ python_param_re.sub('\\1:\\2', l) for l in  itertools.ifilter(lambda l: '@param' in l, data) ]
872         ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ]
873
874         if len(param) >= 2:
875             param=param[1:-1]
876         elif len(param) == 1:
877             param=[]
878
879         return "\n".join(itertools.chain(body, param, ret))
880
881 def process(output, list_of_includes):
882     p=Parser(list_of_includes)
883     g=PythonGenerator(p)
884     g.save(output)
885
886 if __name__ == '__main__':
887     opt=OptionParser(usage="""Parse VLC include files and generate bindings code.
888 %prog [options] include_file.h [...]""")
889
890     opt.add_option("-d", "--debug", dest="debug", action="store_true",
891                       default=False,
892                       help="Debug mode")
893
894     opt.add_option("-c", "--check", dest="check", action="store_true",
895                       default=False,
896                       help="Check mode")
897
898     opt.add_option("-j", "--java", dest="java", action="store_true",
899                       default=False,
900                       help="Generate java bindings (default is python)")
901
902     opt.add_option("-o", "--output", dest="output", action="store",
903                       type="str", default="-",
904                       help="Output filename(python)/dirname(java)")
905
906     (options, args) = opt.parse_args()
907
908     if not args:
909         opt.print_help()
910         sys.exit(1)
911
912     p=Parser(args)
913     if options.check:
914         # Various consistency checks.
915         for (rt, name, params, comment) in p.methods:
916             if not comment.strip():
917                 print "No comment for %s" % name
918                 continue
919             names=comment_re.findall(comment)
920             if len(names) != len(params):
921                 print "Docstring comment parameters mismatch for %s" % name
922
923     if options.debug:
924         p.dump_methods()
925         p.dump_enums()
926
927     if options.check or options.debug:
928         sys.exit(0)
929
930     if options.java:
931         g=JavaGenerator(p)
932     else:
933         g=PythonGenerator(p)
934
935     g.save(options.output)