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