]> git.sesse.net Git - vlc/blob - bindings/python-ctypes/generate.py
355536f4599973fda361648f0d92fd7842624ec3
[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 re
33 import time
34 import operator
35 import itertools
36 from optparse import OptionParser
37
38 # DefaultDict from ASPN python cookbook
39 import copy
40 class DefaultDict(dict):
41     """Dictionary with a default value for unknown keys."""
42     def __init__(self, default=None, **items):
43         dict.__init__(self, **items)
44         self.default = default
45
46     def __getitem__(self, key):
47         if key in self:
48             return self.get(key)
49         else:
50             ## Need copy in case self.default is something like []
51             return self.setdefault(key, copy.deepcopy(self.default))
52
53     def __copy__(self):
54         return DefaultDict(self.default, **self)
55
56 # Methods not decorated/not referenced
57 blacklist=[
58     "libvlc_exception_raise",
59     "libvlc_exception_raised",
60     "libvlc_exception_get_message",
61     "libvlc_get_vlc_instance",
62
63     "libvlc_media_list_view_index_of_item",
64     "libvlc_media_list_view_insert_at_index",
65     "libvlc_media_list_view_remove_at_index",
66     "libvlc_media_list_view_add_item",
67
68     # In svn but not in current 1.0.0.
69     #"libvlc_media_add_option_flag",
70     #'libvlc_video_set_deinterlace',
71     #'libvlc_video_get_marquee_option_as_int',
72     #'libvlc_video_get_marquee_option_as_string',
73     #'libvlc_video_set_marquee_option_as_int',
74     #'libvlc_video_set_marquee_option_as_string',
75     #'libvlc_vlm_get_event_manager',
76     #"libvlc_media_list_player_event_manager",
77     #'libvlc_media_player_next_frame',
78
79     'mediacontrol_PlaylistSeq__free',
80     ]
81
82 # Precompiled regexps
83 api_re=re.compile('VLC_PUBLIC_API\s+(\S+\s+.+?)\s*\(\s*(.+?)\s*\)')
84 param_re=re.compile('\s*(const\s*|unsigned\s*|struct\s*)?(\S+\s*\**)\s+(.+)')
85 paramlist_re=re.compile('\s*,\s*')
86 comment_re=re.compile('\\param\s+(\S+)')
87 python_param_re=re.compile('(@param\s+\S+)(.+)')
88 forward_re=re.compile('.+\(\s*(.+?)\s*\)(\s*\S+)')
89 enum_re=re.compile('typedef\s+(enum)\s*(\S+\s*)?\{\s*(.+)\s*\}\s*(\S+);')
90 special_enum_re=re.compile('^(enum)\s*(\S+\s*)?\{\s*(.+)\s*\};')
91 event_def_re=re.compile('^DEF\(\s*(\w+)\s*\)')
92
93 # Definition of parameter passing mode for types.  This should not be
94 # hardcoded this way, but works alright ATM.
95 parameter_passing=DefaultDict(default=1)
96 parameter_passing['libvlc_exception_t*']=3
97
98 class Parser(object):
99     def __init__(self, list_of_files):
100         self.methods=[]
101         self.enums=[]
102
103         for name in list_of_files:
104             self.enums.extend(self.parse_typedef(name))
105             self.methods.extend(self.parse_include(name))
106
107     def parse_param(self, s):
108         """Parse a C parameter expression.
109
110         It is used to parse both the type/name for methods, and type/name
111         for their parameters.
112
113         It returns a tuple (type, name).
114         """
115         s=s.strip()
116         s=s.replace('const', '')
117         if 'VLC_FORWARD' in s:
118             m=forward_re.match(s)
119             s=m.group(1)+m.group(2)
120         m=param_re.search(s)
121         if m:
122             const, typ, name=m.groups()
123             while name.startswith('*'):
124                 typ += '*'
125                 name=name[1:]
126             if name == 'const*':
127                 # K&R definition: const char * const*
128                 name=''
129             typ=typ.replace(' ', '')
130             return typ, name
131         else:
132             # K&R definition: only type
133             return s.replace(' ', ''), ''
134
135     def parse_typedef(self, name):
136         """Parse include file for typedef expressions.
137
138         This generates a tuple for each typedef:
139         (type, name, value_list, comment)
140         with type == 'enum' (for the moment) and value_list being a list of (name, value)
141         Note that values are string, since this is intended for code generation.
142         """
143         event_names=[]
144
145         f=open(name, 'r')
146         accumulator=''
147         for l in f:
148             # Note: lstrip() should not be necessary, but there is 1 badly
149             # formatted comment in vlc1.0.0 includes
150             if l.lstrip().startswith('/**'):
151                 comment=''
152                 continue
153             elif l.startswith(' * '):
154                 comment = comment + l[3:]
155                 continue
156
157             l=l.strip()
158             if l.startswith('/*') or l.endswith('*/'):
159                 continue
160
161             if (l.startswith('typedef enum') or l.startswith('enum')) and not l.endswith(';'):
162                 # Multiline definition. Accumulate until end of definition
163                 accumulator=l
164                 continue
165             elif accumulator:
166                 accumulator=" ".join( (accumulator, l) )
167                 if l.endswith(';'):
168                     # End of definition
169                     l=accumulator
170                     accumulator=''
171
172             m=enum_re.match(l)
173             if m:
174                 values=[]
175                 (typ, dummy, data, name)=m.groups()
176                 for i, l in enumerate(paramlist_re.split(data)):
177                     l=l.strip()
178                     if l.startswith('/*'):
179                         continue
180                     if '=' in l:
181                         # A value was specified. Use it.
182                         values.append(re.split('\s*=\s*', l))
183                     else:
184                         if l:
185                             values.append( (l, str(i)) )
186                 comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '')
187                 yield (typ, name.strip(), values, comment)
188                 comment=''
189                 continue
190
191             # Special case, used only for libvlc_events.h
192             # (version after 96a96f60bb0d1f2506e68b356897ceca6f6b586d)
193             m=event_def_re.match(l)
194             if m:
195                 # Event definition.
196                 event_names.append('libvlc_'+m.group(1))
197                 continue
198
199             # Special case, used only for libvlc_events.h
200             m=special_enum_re.match(l)
201             if m:
202                 (typ, name, data)=m.groups()
203                 if event_names:
204                     # event_names were defined through DEF macro
205                     # (see 96a96f60bb0d1f2506e68b356897ceca6f6b586d)
206                     values=list( (n, str(i)) for i, n in enumerate(event_names))
207                 else:
208                     # Before 96a96f60bb0d1f2506e68b356897ceca6f6b586d
209                     values=[]
210                     for i, l in enumerate(paramlist_re.split(data)):
211                         l=l.strip()
212                         if l.startswith('/*') or l.startswith('#'):
213                             continue
214                         if '=' in l:
215                             # A value was specified. Use it.
216                             values.append(re.split('\s*=\s*', l))
217                         else:
218                             if l:
219                                 values.append( (l, str(i)) )
220                 comment=comment.replace('@{', '').replace('@see', 'See').replace('\ingroup', '')
221                 yield (typ, name.strip(), values, comment)
222                 comment=''
223                 continue
224
225     def parse_include(self, name):
226         """Parse include file.
227
228         This generates a tuple for each function:
229         (return_type, method_name, parameter_list, comment)
230         with parameter_list being a list of tuples (parameter_type, parameter_name).
231         """
232         f=open(name, 'r')
233         accumulator=''
234         comment=''
235         for l in f:
236             # Note: lstrip() should not be necessary, but there is 1 badly
237             # formatted comment in vlc1.0.0 includes
238             if l.lstrip().startswith('/**'):
239                 comment=''
240                 continue
241             elif l.startswith(' * '):
242                 comment = comment + l[3:]
243                 continue
244
245             l=l.strip()
246
247             if accumulator:
248                 accumulator=" ".join( (accumulator, l) )
249                 if l.endswith(');'):
250                     # End of definition
251                     l=accumulator
252                     accumulator=''
253             elif l.startswith('VLC_PUBLIC_API') and not l.endswith(');'):
254                 # Multiline definition. Accumulate until end of definition
255                 accumulator=l
256                 continue
257
258             m=api_re.match(l)
259             if m:
260                 (ret, param)=m.groups()
261
262                 rtype, method=self.parse_param(ret)
263
264                 params=[]
265                 for p in paramlist_re.split(param):
266                     params.append( self.parse_param(p) )
267
268                 if len(params) == 1 and params[0][0] == 'void':
269                     # Empty parameter list
270                     params=[]
271
272                 if list(p for p in params if not p[1]):
273                     # Empty parameter names. Have to poke into comment.
274                     names=comment_re.findall(comment)
275                     if len(names) < len(params):
276                         # Bad description: all parameters are not specified.
277                         # Generate default parameter names
278                         badnames=[ "param%d" % i for i in xrange(len(params)) ]
279                         # Put in the existing ones
280                         for (i, p) in enumerate(names):
281                             badnames[i]=names[i]
282                         names=badnames
283                         print "### Error ###"
284                         print "### Cannot get parameter names from comment for %s: %s" % (method, comment.replace("\n", ' '))
285                         # Note: this was previously
286                         # raise Exception("Cannot get parameter names from comment for %s: %s" % (method, comment))
287                         # but it prevented code generation for a minor detail (some bad descriptions).
288                     params=[ (p[0], names[i]) for (i, p) in enumerate(params) ]
289
290                 # Transform Doxygen syntax into epydoc syntax
291                 comment=comment.replace('\\param', '@param').replace('\\return', '@return')
292
293                 if debug:
294                     print '********************'
295                     print l
296                     print '-------->'
297                     print "%s (%s)" % (method, rtype)
298                     for typ, name in params:
299                         print "        %s (%s)" % (name, typ)
300                     print '********************'
301                 yield (rtype,
302                        method,
303                        params,
304                        comment)
305                 comment=''
306
307     def dump_methods(self):
308         print "** Defined functions **"
309         for (rtype, name, params, comment) in self.methods:
310             print "%(name)s (%(rtype)s):" % locals()
311             for t, n in params:
312                 print "    %(n)s (%(t)s)" % locals()
313
314     def dump_enums(self):
315         print "** Defined enums **"
316         for (typ, name, values, comment) in self.enums:
317             print "%(name)s (%(typ)s):" % locals()
318             for k, v in values:
319                 print "    %(k)s=%(v)s" % locals()
320
321 class PythonGenerator(object):
322     # C-type to ctypes/python type conversion.
323     # Note that enum types conversions are generated (cf convert_enum_names)
324     type2class={
325         'libvlc_exception_t*': 'ctypes.POINTER(VLCException)',
326
327         'libvlc_media_player_t*': 'MediaPlayer',
328         'libvlc_instance_t*': 'Instance',
329         'libvlc_media_t*': 'Media',
330         'libvlc_log_t*': 'Log',
331         'libvlc_log_iterator_t*': 'LogIterator',
332         'libvlc_log_message_t*': 'LogMessage',
333         'libvlc_event_type_t': 'ctypes.c_uint',
334         'libvlc_event_manager_t*': 'EventManager',
335         'libvlc_media_discoverer_t*': 'MediaDiscoverer',
336         'libvlc_media_library_t*': 'MediaLibrary',
337         'libvlc_media_list_t*': 'MediaList',
338         'libvlc_media_list_player_t*': 'MediaListPlayer',
339         'libvlc_media_list_view_t*': 'MediaListView',
340         'libvlc_track_description_t*': 'TrackDescription',
341         'libvlc_audio_output_t*': 'AudioOutput',
342
343         'mediacontrol_Instance*': 'MediaControl',
344         'mediacontrol_Exception*': 'MediaControlException',
345         'mediacontrol_RGBPicture*': 'ctypes.POINTER(RGBPicture)',
346         'mediacontrol_PlaylistSeq*': 'MediaControlPlaylistSeq',
347         'mediacontrol_Position*': 'ctypes.POINTER(MediaControlPosition)',
348         'mediacontrol_StreamInformation*': 'ctypes.POINTER(MediaControlStreamInformation)',
349         'WINDOWHANDLE': 'ctypes.c_ulong',
350
351         'void': 'None',
352         'void*': 'ctypes.c_void_p',
353         'short': 'ctypes.c_short',
354         'char*': 'ctypes.c_char_p',
355         'char**': 'ListPOINTER(ctypes.c_char_p)',
356         'uint32_t': 'ctypes.c_uint',
357         'float': 'ctypes.c_float',
358         'unsigned': 'ctypes.c_uint',
359         'int': 'ctypes.c_int',
360         '...': 'FIXMEva_list',
361         'libvlc_callback_t': 'ctypes.c_void_p',
362         'libvlc_time_t': 'ctypes.c_longlong',
363         }
364
365     # Defined python classes, i.e. classes for which we want to generate
366     # class wrappers around libvlc functions
367     defined_classes=(
368         'MediaPlayer',
369         'Instance',
370         'Media',
371         'Log',
372         'LogIterator',
373         'EventManager',
374         'MediaDiscoverer',
375         'MediaLibrary',
376         'MediaList',
377         'MediaListPlayer',
378         'MediaListView',
379         'TrackDescription',
380         'AudioOutput',
381         'MediaControl',
382         )
383
384     def __init__(self, parser=None):
385         self.parser=parser
386
387         # Generate python names for enums
388         self.type2class.update(self.convert_enum_names(parser.enums))
389         self.check_types()
390
391         # Definition of prefixes that we can strip from method names when
392         # wrapping them into class methods
393         self.prefixes=dict( (v, k[:-2])
394                             for (k, v) in self.type2class.iteritems()
395                             if  v in self.defined_classes )
396         self.prefixes['MediaControl']='mediacontrol_'
397
398     def save(self, filename=None):
399         if filename is None or filename == '-':
400             self.fd=sys.stdout
401         else:
402             self.fd=open(filename, 'w')
403
404         self.insert_code('header.py')
405         self.generate_enums(self.parser.enums)
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 'build_date' in l:
441                 self.output('build_date="%s"' % time.ctime())
442             else:
443                 self.output(l.rstrip())
444         f.close()
445
446     def convert_enum_names(self, enums):
447         res={}
448         for (typ, name, values, comment) in enums:
449             if typ != 'enum':
450                 raise Exception('This method only handles enums')
451             pyname=re.findall('(libvlc|mediacontrol)_(.+?)(_t)?$', name)[0][1]
452             if '_' in pyname:
453                 pyname=pyname.title().replace('_', '')
454             elif not pyname[0].isupper():
455                 pyname=pyname.capitalize()
456             res[name]=pyname
457         return res
458
459     def generate_enums(self, enums):
460         for (typ, name, values, comment) in enums:
461             if typ != 'enum':
462                 raise Exception('This method only handles enums')
463             pyname=self.type2class[name]
464
465             self.output("class %s(ctypes.c_uint):" % pyname)
466             self.output('    """%s\n    """' % comment)
467
468             conv={}
469             # Convert symbol names
470             for k, v in values:
471                 n=k.split('_')[-1]
472                 if len(n) == 1:
473                     # Single character. Some symbols use 1_1, 5_1, etc.
474                     n="_".join( k.split('_')[-2:] )
475                 if re.match('^[0-9]', n):
476                     # Cannot start an identifier with a number
477                     n='_'+n
478                 conv[k]=n
479
480             for k, v in values:
481                 self.output("    %s=%s" % (conv[k], v))
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
493     def output_ctypes(self, rtype, method, params, comment):
494         """Output ctypes decorator for the given method.
495         """
496         if method in blacklist:
497             # FIXME
498             return
499
500         self.output("""if hasattr(dll, '%s'):""" % method)
501         if params:
502             self.output("    prototype=ctypes.CFUNCTYPE(%s, %s)" % (self.type2class.get(rtype, 'FIXME_%s' % rtype),
503                                                                 ",".join( self.type2class[p[0]] for p in params )))
504         else:
505             self.output("    prototype=ctypes.CFUNCTYPE(%s)" % self.type2class.get(rtype, 'FIXME_%s' % rtype))
506
507
508         if not params:
509             flags='    paramflags= tuple()'
510         elif len(params) == 1:
511             flags="    paramflags=( (%d, ), )" % parameter_passing[params[0][0]]
512         else:
513             flags="    paramflags=%s" % ",".join( '(%d,)' % parameter_passing[p[0]] for p in params )
514         self.output(flags)
515         self.output('    %s = prototype( ("%s", dll), paramflags )' % (method, method))
516         if '3' in flags:
517             # A VLCException is present. Process it.
518             self.output("    %s.errcheck = check_vlc_exception" % method)
519         self.output('    %s.__doc__ = """%s"""' % (method, comment))
520         self.output()
521
522     def parse_override(self, name):
523         """Parse override definitions file.
524
525         It is possible to override methods definitions in classes.
526
527         It returns a tuple
528         (code, overriden_methods, docstring)
529         """
530         code={}
531
532         data=[]
533         current=None
534         f=open(name, 'r')
535         for l in f:
536             m=re.match('class (\S+):', l)
537             if m:
538                 # Dump old data
539                 if current is not None:
540                     code[current]="".join(data)
541                 current=m.group(1)
542                 data=[]
543                 continue
544             data.append(l)
545         code[current]="".join(data)
546         f.close()
547
548         docstring={}
549         for k, v in code.iteritems():
550             if v.lstrip().startswith('"""'):
551                 # Starting comment. Use it as docstring.
552                 dummy, docstring[k], code[k]=v.split('"""', 2)
553
554         # Not robust wrt. internal methods, but this works for the moment.
555         overridden_methods=dict( (k, re.findall('^\s+def\s+(\w+)', v, re.MULTILINE)) for (k, v) in code.iteritems() )
556
557         return code, overridden_methods, docstring
558
559     def fix_python_comment(self, c):
560         """Fix comment by removing first and last parameters (self and exception)
561         """
562         data=c.replace('@{', '').replace('@see', 'See').splitlines()
563         body=itertools.takewhile(lambda l: not '@param' in l and not '@return' in l, data)
564         param=[ python_param_re.sub('\\1:\\2', l) for l in  itertools.ifilter(lambda l: '@param' in l, data) ]
565         ret=[ l.replace('@return', '@return:') for l in itertools.ifilter(lambda l: '@return' in l, data) ]
566
567         if len(param) >= 2:
568             param=param[1:-1]
569         elif len(param) == 1:
570             param=[]
571
572         return "\n".join(itertools.chain(body, param, ret))
573
574     def generate_wrappers(self, methods):
575         """Generate class wrappers for all appropriate methods.
576
577         @return: the set of wrapped method names
578         """
579         ret=set()
580         # Sort methods against the element they apply to.
581         elements=sorted( ( (self.type2class.get(params[0][0]), rt, met, params, c)
582                            for (rt, met, params, c) in methods
583                            if params and self.type2class.get(params[0][0], '_') in self.defined_classes
584                            ),
585                          key=operator.itemgetter(0))
586
587         overrides, overriden_methods, docstring=self.parse_override('override.py')
588
589         for classname, el in itertools.groupby(elements, key=operator.itemgetter(0)):
590             self.output("""class %(name)s(object):""" % {'name': classname})
591             if classname in docstring:
592                 self.output('    """%s\n    """' % docstring[classname])
593
594             self.output("""
595     def __new__(cls, pointer=None):
596         '''Internal method used for instanciating wrappers from ctypes.
597         '''
598         if pointer is None:
599             raise Exception("Internal method. Surely this class cannot be instanciated by itself.")
600         if pointer == 0:
601             return None
602         else:
603             o=object.__new__(cls)
604             o._as_parameter_=ctypes.c_void_p(pointer)
605             return o
606
607     @staticmethod
608     def from_param(arg):
609         '''(INTERNAL) ctypes parameter conversion method.
610         '''
611         return arg._as_parameter_
612     """ % {'name': classname})
613
614             if classname in overrides:
615                 self.output(overrides[classname])
616
617             prefix=self.prefixes.get(classname, '')
618
619             for cl, rtype, method, params, comment in el:
620                 if method in blacklist:
621                     continue
622                 # Strip prefix
623                 name=method.replace(prefix, '').replace('libvlc_', '')
624                 ret.add(method)
625                 if name in overriden_methods.get(cl, []):
626                     # Method already defined in override.py
627                     continue
628
629                 if params:
630                     params[0]=(params[0][0], 'self')
631                 if params and params[-1][0] in ('libvlc_exception_t*', 'mediacontrol_Exception*'):
632                     args=", ".join( p[1] for p in params[:-1] )
633                 else:
634                     args=", ".join( p[1] for p in params )
635
636                 self.output("    if hasattr(dll, '%s'):" % method)
637                 self.output("        def %s(%s):" % (name, args))
638                 self.output('            """%s\n        """' % self.fix_python_comment(comment))
639                 if params and params[-1][0] == 'libvlc_exception_t*':
640                     # Exception handling
641                     self.output("            e=VLCException()")
642                     self.output("            return %s(%s, e)" % (method, args))
643                 elif params and params[-1][0] == 'mediacontrol_Exception*':
644                     # Exception handling
645                     self.output("            e=MediaControlException()")
646                     self.output("            return %s(%s, e)" % (method, args))
647                 else:
648                     self.output("            return %s(%s)" % (method, args))
649                 self.output()
650
651                 # Check for standard methods
652                 if name == 'count':
653                     # There is a count method. Generate a __len__ one.
654                     self.output("""    def __len__(self):
655         e=VLCException()
656         return %s(self, e)
657 """ % method)
658                 elif name.endswith('item_at_index'):
659                     # Indexable (and thus iterable)"
660                     self.output("""    def __getitem__(self, i):
661         e=VLCException()
662         return %s(self, i, e)
663
664     def __iter__(self):
665         e=VLCException()
666         for i in xrange(len(self)):
667             yield self[i]
668 """ % method)
669         return ret
670
671 def process(output, list_of_includes):
672     p=Parser(list_of_includes)
673     g=PythonGenerator(p)
674     g.save(output)
675
676 if __name__ == '__main__':
677     opt=OptionParser(usage="""Parse VLC include files and generate bindings code.
678 %prog [options] include_file.h [...]""")
679
680     opt.add_option("-d", "--debug", dest="debug", action="store_true",
681                       default=False,
682                       help="Debug mode")
683
684     opt.add_option("-o", "--output", dest="output", action="store",
685                       type="str", default="-",
686                       help="Output filename")
687
688     (options, args) = opt.parse_args()
689
690     if not args:
691         opt.print_help()
692         sys.exit(1)
693
694     p=Parser(args)
695     if options.debug:
696         p.dump_methods()
697         p.dump_enums()
698         sys.exit(0)
699
700     g=PythonGenerator(p)
701
702     g.save(options.output)