Increase the timeout for some GLib tests
[glib.git] / gobject / glib-mkenums.in
blobd551cdcf4e4baa1f8005ade2d00adbbbb48f8b3c
1 #!/usr/bin/env @PYTHON@
3 # If the code below looks horrible and unpythonic, do not panic.
5 # It is.
7 # This is a manual conversion from the original Perl script to
8 # Python. Improvements are welcome.
10 from __future__ import print_function, unicode_literals
12 import argparse
13 import os
14 import re
15 import sys
16 import tempfile
17 import io
18 import errno
19 import codecs
20 import locale
22 VERSION_STR = '''glib-mkenums version @VERSION@
23 glib-mkenums comes with ABSOLUTELY NO WARRANTY.
24 You may redistribute copies of glib-mkenums under the terms of
25 the GNU General Public License which can be found in the
26 GLib source package. Sources, examples and contact
27 information are available at http://www.gtk.org'''
29 # pylint: disable=too-few-public-methods
30 class Color:
31     '''ANSI Terminal colors'''
32     GREEN = '\033[1;32m'
33     BLUE = '\033[1;34m'
34     YELLOW = '\033[1;33m'
35     RED = '\033[1;31m'
36     END = '\033[0m'
39 def print_color(msg, color=Color.END, prefix='MESSAGE'):
40     '''Print a string with a color prefix'''
41     if os.isatty(sys.stderr.fileno()):
42         real_prefix = '{start}{prefix}{end}'.format(start=color, prefix=prefix, end=Color.END)
43     else:
44         real_prefix = prefix
45     print('{prefix}: {msg}'.format(prefix=real_prefix, msg=msg), file=sys.stderr)
48 def print_error(msg):
49     '''Print an error, and terminate'''
50     print_color(msg, color=Color.RED, prefix='ERROR')
51     sys.exit(1)
54 def print_warning(msg, fatal=False):
55     '''Print a warning, and optionally terminate'''
56     if fatal:
57         color = Color.RED
58         prefix = 'ERROR'
59     else:
60         color = Color.YELLOW
61         prefix = 'WARNING'
62     print_color(msg, color, prefix)
63     if fatal:
64         sys.exit(1)
67 def print_info(msg):
68     '''Print a message'''
69     print_color(msg, color=Color.GREEN, prefix='INFO')
72 def write_output(output):
73     global output_stream
74     print(output, file=output_stream)
77 # Python 2 defaults to ASCII in case stdout is redirected.
78 # This should make it match Python 3, which uses the locale encoding.
79 if sys.stdout.encoding is None:
80     output_stream = codecs.getwriter(
81         locale.getpreferredencoding())(sys.stdout)
82 else:
83     output_stream = sys.stdout
86 # Some source files aren't UTF-8 and the old perl version didn't care.
87 # Replace invalid data with a replacement character to keep things working.
88 # https://bugzilla.gnome.org/show_bug.cgi?id=785113#c20
89 def replace_and_warn(err):
90     # 7 characters of context either side of the offending character
91     print_warning('UnicodeWarning: {} at {} ({})'.format(
92         err.reason, err.start,
93         err.object[err.start - 7:err.end + 7]))
94     return ('?', err.end)
96 codecs.register_error('replace_and_warn', replace_and_warn)
99 # glib-mkenums.py
100 # Information about the current enumeration
101 flags = None # Is enumeration a bitmask?
102 option_underscore_name = '' # Overriden underscore variant of the enum name
103                             # for example to fix the cases we don't get the
104                             # mixed-case -> underscorized transform right.
105 option_lowercase_name = ''  # DEPRECATED.  A lower case name to use as part
106                             # of the *_get_type() function, instead of the
107                             # one that we guess. For instance, when an enum
108                             # uses abnormal capitalization and we can not
109                             # guess where to put the underscores.
110 seenbitshift = 0        # Have we seen bitshift operators?
111 enum_prefix = None        # Prefix for this enumeration
112 enumname = ''            # Name for this enumeration
113 enumshort = ''           # $enumname without prefix
114 enumname_prefix = ''       # prefix of $enumname
115 enumindex = 0        # Global enum counter
116 firstenum = 1        # Is this the first enumeration per file?
117 entries = []            # [ name, val ] for each entry
118 sandbox = None      # sandbox for safe evaluation of expressions
120 output = ''            # Filename to write result into
122 def parse_trigraph(opts):
123     result = {}
125     for opt in re.split(r'\s*,\s*', opts):
126         opt = re.sub(r'^\s*', '', opt)
127         opt = re.sub(r'\s*$', '', opt)
128         m = re.search(r'(\w+)(?:=(.+))?', opt)
129         assert m is not None
130         groups = m.groups()
131         key = groups[0]
132         if len(groups) > 1:
133             val = groups[1]
134         else:
135             val = 1
136         result[key] = val
137     return result
139 def parse_entries(file, file_name):
140     global entries, enumindex, enumname, seenbitshift, flags
141     looking_for_name = False
143     while True:
144         line = file.readline()
145         if not line:
146             break
148         line = line.strip()
150         # read lines until we have no open comments
151         while re.search(r'/\*([^*]|\*(?!/))*$', line):
152             line += file.readline()
154         # strip comments w/o options
155         line = re.sub(r'''/\*(?!<)
156             ([^*]+|\*(?!/))*
157            \*/''', '', line, flags=re.X)
159         line = line.rstrip()
161         # skip empty lines
162         if len(line.strip()) == 0:
163             continue
165         if looking_for_name:
166             m = re.match(r'\s*(\w+)', line)
167             if m:
168                 enumname = m.group(1)
169                 return True
171         # Handle include files
172         m = re.match(r'\#include\s*<([^>]*)>', line)
173         if m:
174             newfilename = os.path.join("..", m.group(1))
175             newfile = io.open(newfilename, encoding="utf-8",
176                               errors="replace_and_warn")
178             if not parse_entries(newfile, newfilename):
179                 return False
180             else:
181                 continue
183         m = re.match(r'\s*\}\s*(\w+)', line)
184         if m:
185             enumname = m.group(1)
186             enumindex += 1
187             return 1
189         m = re.match(r'\s*\}', line)
190         if m:
191             enumindex += 1
192             looking_for_name = True
193             continue
195         m = re.match(r'''\s*
196               (\w+)\s*                   # name
197               (?:=(                      # value
198                    \s*\w+\s*\(.*\)\s*       # macro with multiple args
199                    |                        # OR
200                    (?:[^,/]|/(?!\*))*       # anything but a comma or comment
201                   ))?,?\s*
202               (?:/\*<                    # options
203                 (([^*]|\*(?!/))*)
204                >\s*\*/)?,?
205               \s*$''', line, flags=re.X)
206         if m:
207             groups = m.groups()
208             name = groups[0]
209             value = None
210             options = None
211             if len(groups) > 1:
212                 value = groups[1]
213             if len(groups) > 2:
214                 options = groups[2]
215             if flags is None and value is not None and '<<' in value:
216                 seenbitshift = 1
218             if options is not None:
219                 options = parse_trigraph(options)
220                 if 'skip' not in options:
221                     entries.append((name, value, options['nick']))
222             else:
223                 entries.append((name, value))
224         elif re.match(r's*\#', line):
225             pass
226         else:
227             print_warning('Failed to parse "{}" in {}'.format(line, file_name))
228     return False
230 help_epilog = '''Production text substitutions:
231   \u0040EnumName\u0040            PrefixTheXEnum
232   \u0040enum_name\u0040           prefix_the_xenum
233   \u0040ENUMNAME\u0040            PREFIX_THE_XENUM
234   \u0040ENUMSHORT\u0040           THE_XENUM
235   \u0040ENUMPREFIX\u0040          PREFIX
236   \u0040VALUENAME\u0040           PREFIX_THE_XVALUE
237   \u0040valuenick\u0040           the-xvalue
238   \u0040valuenum\u0040            the integer value (limited support, Since: 2.26)
239   \u0040type\u0040                either enum or flags
240   \u0040Type\u0040                either Enum or Flags
241   \u0040TYPE\u0040                either ENUM or FLAGS
242   \u0040filename\u0040            name of current input file
243   \u0040basename\u0040            base name of the current input file (Since: 2.22)
247 # production variables:
248 idprefix = ""    # "G", "Gtk", etc
249 symprefix = ""   # "g", "gtk", etc, if not just lc($idprefix)
250 fhead = ""   # output file header
251 fprod = ""   # per input file production
252 ftail = ""   # output file trailer
253 eprod = ""   # per enum text (produced prior to value itarations)
254 vhead = ""   # value header, produced before iterating over enum values
255 vprod = ""   # value text, produced for each enum value
256 vtail = ""   # value tail, produced after iterating over enum values
257 comment_tmpl = ""   # comment template
259 def read_template_file(file):
260     global idprefix, symprefix, fhead, fprod, ftail, eprod, vhead, vprod, vtail, comment_tmpl
261     tmpl = {'file-header': fhead,
262             'file-production': fprod,
263             'file-tail': ftail,
264             'enumeration-production': eprod,
265             'value-header': vhead,
266             'value-production': vprod,
267             'value-tail': vtail,
268             'comment': comment_tmpl,
269            }
270     in_ = 'junk'
272     ifile = io.open(file, encoding="utf-8", errors="replace_and_warn")
273     for line in ifile:
274         m = re.match(r'\/\*\*\*\s+(BEGIN|END)\s+([\w-]+)\s+\*\*\*\/', line)
275         if m:
276             if in_ == 'junk' and m.group(1) == 'BEGIN' and m.group(2) in tmpl:
277                 in_ = m.group(2)
278                 continue
279             elif in_ == m.group(2) and m.group(1) == 'END' and m.group(2) in tmpl:
280                 in_ = 'junk'
281                 continue
282             else:
283                 sys.exit("Malformed template file " + file)
285         if in_ != 'junk':
286             tmpl[in_] += line
288     if in_ != 'junk':
289         sys.exit("Malformed template file " + file)
291     fhead = tmpl['file-header']
292     fprod = tmpl['file-production']
293     ftail = tmpl['file-tail']
294     eprod = tmpl['enumeration-production']
295     vhead = tmpl['value-header']
296     vprod = tmpl['value-production']
297     vtail = tmpl['value-tail']
298     comment_tmpl = tmpl['comment']
300 parser = argparse.ArgumentParser(epilog=help_epilog,
301                                  formatter_class=argparse.RawDescriptionHelpFormatter)
303 parser.add_argument('--identifier-prefix', default='', dest='idprefix',
304                     help='Identifier prefix')
305 parser.add_argument('--symbol-prefix', default='', dest='symprefix',
306                     help='symbol-prefix')
307 parser.add_argument('--fhead', default=[], dest='fhead', action='append',
308                     help='Output file header')
309 parser.add_argument('--ftail', default=[], dest='ftail', action='append',
310                     help='Per input file production')
311 parser.add_argument('--fprod', default=[], dest='fprod', action='append',
312                     help='Put out TEXT everytime a new input file is being processed.')
313 parser.add_argument('--eprod', default=[], dest='eprod', action='append',
314                     help='Per enum text (produced prior to value iterations)')
315 parser.add_argument('--vhead', default=[], dest='vhead', action='append',
316                     help='Value header, produced before iterating over enum values')
317 parser.add_argument('--vprod', default=[], dest='vprod', action='append',
318                     help='Value text, produced for each enum value.')
319 parser.add_argument('--vtail', default=[], dest='vtail', action='append',
320                     help='Value tail, produced after iterating over enum values')
321 parser.add_argument('--comments', default='', dest='comment_tmpl',
322                     help='Comment structure')
323 parser.add_argument('--template', default='', dest='template',
324                     help='Template file')
325 parser.add_argument('--output', default=None, dest='output')
326 parser.add_argument('--version', '-v', default=False, action='store_true', dest='version',
327                     help='Print version informations')
328 parser.add_argument('args', nargs='*')
330 options = parser.parse_args()
332 if options.version:
333     print(VERSION_STR)
334     sys.exit(0)
336 def unescape_cmdline_args(arg):
337     arg = arg.replace('\\n', '\n')
338     arg = arg.replace('\\r', '\r')
339     return arg.replace('\\t', '\t')
341 if options.template != '':
342     read_template_file(options.template)
344 idprefix += options.idprefix
345 symprefix += options.symprefix
347 # This is a hack to maintain some semblance of backward compatibility with
348 # the old, Perl-based glib-mkenums. The old tool had an implicit ordering
349 # on the arguments and templates; each argument was parsed in order, and
350 # all the strings appended. This allowed developers to write:
352 #   glib-mkenums \
353 #     --fhead ... \
354 #     --template a-template-file.c.in \
355 #     --ftail ...
357 # And have the fhead be prepended to the file-head stanza in the template,
358 # as well as the ftail be appended to the file-tail stanza in the template.
359 # Short of throwing away ArgumentParser and going over sys.argv[] element
360 # by element, we can simulate that behaviour by ensuring some ordering in
361 # how we build the template strings:
363 #   - the head stanzas are always prepended to the template
364 #   - the prod stanzas are always appended to the template
365 #   - the tail stanzas are always appended to the template
367 # Within each instance of the command line argument, we append each value
368 # to the array in the order in which it appears on the command line.
369 fhead = ''.join([unescape_cmdline_args(x) for x in options.fhead]) + fhead
370 vhead = ''.join([unescape_cmdline_args(x) for x in options.vhead]) + vhead
372 fprod += ''.join([unescape_cmdline_args(x) for x in options.fprod])
373 eprod += ''.join([unescape_cmdline_args(x) for x in options.eprod])
374 vprod += ''.join([unescape_cmdline_args(x) for x in options.vprod])
376 ftail = ftail + ''.join([unescape_cmdline_args(x) for x in options.ftail])
377 vtail = vtail + ''.join([unescape_cmdline_args(x) for x in options.vtail])
379 if options.comment_tmpl != '':
380     comment_tmpl = unescape_cmdline_args(options.comment_tmpl)
381 elif comment_tmpl == "":
382     # default to C-style comments
383     comment_tmpl = "/* \u0040comment\u0040 */"
385 output = options.output
387 if output is not None:
388     (out_dir, out_fn) = os.path.split(options.output)
389     out_suffix = '_' + os.path.splitext(out_fn)[1]
390     if out_dir == '':
391         out_dir = '.'
392     fd, filename = tempfile.mkstemp(dir=out_dir)
393     os.close(fd)
394     tmpfile = io.open(filename, "w", encoding="utf-8")
395     output_stream = tmpfile
396 else:
397     tmpfile = None
399 # put auto-generation comment
400 comment = comment_tmpl.replace('\u0040comment\u0040',
401                                'This file is generated by glib-mkenums, do '
402                                'not modify it. This code is licensed under '
403                                'the same license as the containing project. '
404                                'Note that it links to GLib, so must comply '
405                                'with the LGPL linking clauses.')
406 write_output("\n" + comment + '\n')
408 def replace_specials(prod):
409     prod = prod.replace(r'\\a', r'\a')
410     prod = prod.replace(r'\\b', r'\b')
411     prod = prod.replace(r'\\t', r'\t')
412     prod = prod.replace(r'\\n', r'\n')
413     prod = prod.replace(r'\\f', r'\f')
414     prod = prod.replace(r'\\r', r'\r')
415     prod = prod.rstrip()
416     return prod
418 if len(fhead) > 0:
419     prod = fhead
420     base = os.path.basename(options.args[0])
422     prod = prod.replace('\u0040filename\u0040', options.args[0])
423     prod = prod.replace('\u0040basename\u0040', base)
424     prod = replace_specials(prod)
425     write_output(prod)
427 def process_file(curfilename):
428     global entries, flags, seenbitshift, enum_prefix
429     firstenum = True
431     try:
432         curfile = io.open(curfilename, encoding="utf-8",
433                           errors="replace_and_warn")
434     except IOError as e:
435         if e.errno == errno.ENOENT:
436             print_warning('No file "{}" found.'.format(curfilename))
437             return
438         raise
440     while True:
441         line = curfile.readline()
442         if not line:
443             break
445         line = line.strip()
447         # read lines until we have no open comments
448         while re.search(r'/\*([^*]|\*(?!/))*$', line):
449             line += curfile.readline()
451         # strip comments w/o options
452         line = re.sub(r'''/\*(?!<)
453            ([^*]+|\*(?!/))*
454            \*/''', '', line)
456         # ignore forward declarations
457         if re.match(r'\s*typedef\s+enum.*;', line):
458             continue
460         m = re.match(r'''\s*typedef\s+enum\s*
461                ({)?\s*
462                (?:/\*<
463                  (([^*]|\*(?!/))*)
464                 >\s*\*/)?
465                \s*({)?''', line, flags=re.X)
466         if m:
467             groups = m.groups()
468             if len(groups) >= 2 and groups[1] is not None:
469                 options = parse_trigraph(groups[1])
470                 if 'skip' in options:
471                     continue
472                 enum_prefix = options.get('prefix', None)
473                 flags = options.get('flags', None)
474                 if 'flags' in options:
475                     if flags is None:
476                         flags = 1
477                     else:
478                         flags = int(flags)
479                 option_lowercase_name = options.get('lowercase_name', None)
480                 option_underscore_name = options.get('underscore_name', None)
481             else:
482                 enum_prefix = None
483                 flags = None
484                 option_lowercase_name = None
485                 option_underscore_name = None
487             if option_lowercase_name is not None:
488                 if option_underscore_name is not None:
489                     print_warning("lowercase_name overriden with underscore_name")
490                     option_lowercase_name = None
491                 else:
492                     print_warning("lowercase_name is deprecated, use underscore_name")
494             # Didn't have trailing '{' look on next lines
495             if groups[0] is None and (len(groups) < 4 or groups[3] is None):
496                 while True:
497                     line = curfile.readline()
498                     if re.match(r'\s*\{', line):
499                         break
501             seenbitshift = 0
502             entries = []
504             # Now parse the entries
505             parse_entries(curfile, curfilename)
507             # figure out if this was a flags or enums enumeration
508             if flags is None:
509                 flags = seenbitshift
511             # Autogenerate a prefix
512             if enum_prefix is None:
513                 for entry in entries:
514                     if len(entry) < 3 or entry[2] is None:
515                         name = entry[0]
516                         if enum_prefix is not None:
517                             enum_prefix = os.path.commonprefix([name, enum_prefix])
518                         else:
519                             enum_prefix = name
520                 if enum_prefix is None:
521                     enum_prefix = ""
522                 else:
523                     # Trim so that it ends in an underscore
524                     enum_prefix = re.sub(r'_[^_]*$', '_', enum_prefix)
525             else:
526                 # canonicalize user defined prefixes
527                 enum_prefix = enum_prefix.upper()
528                 enum_prefix = enum_prefix.replace('-', '_')
529                 enum_prefix = re.sub(r'(.*)([^_])$', r'\1\2_', enum_prefix)
531             fixed_entries = []
532             for e in entries:
533                 name = e[0]
534                 num = e[1]
535                 if len(e) < 3 or e[2] is None:
536                     nick = re.sub(r'^' + enum_prefix, '', name)
537                     nick = nick.replace('_', '-').lower()
538                     e = (name, num, nick)
539                 fixed_entries.append(e)
540             entries = fixed_entries
542             # Spit out the output
543             if option_underscore_name is not None:
544                 enumlong = option_underscore_name.upper()
545                 enumsym = option_underscore_name.lower()
546                 enumshort = re.sub(r'^[A-Z][A-Z0-9]*_', '', enumlong)
548                 enumname_prefix = re.sub('_' + enumshort + '$', '', enumlong)
549             elif symprefix == '' and idprefix == '':
550                 # enumname is e.g. GMatchType
551                 enspace = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname)
553                 enumshort = re.sub(r'^[A-Z][a-z]*', '', enumname)
554                 enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
555                 enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
556                 enumshort = enumshort.upper()
558                 enumname_prefix = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname).upper()
560                 enumlong = enspace.upper() + "_" + enumshort
561                 enumsym = enspace.lower() + "_" + enumshort.lower()
563                 if option_lowercase_name is not None:
564                     enumsym = option_lowercase_name
565             else:
566                 enumshort = enumname
567                 if idprefix:
568                     enumshort = re.sub(r'^' + idprefix, '', enumshort)
569                 else:
570                     enumshort = re.sub(r'/^[A-Z][a-z]*', '', enumshort)
572                 enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
573                 enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
574                 enumshort = enumshort.upper()
576                 if symprefix:
577                     enumname_prefix = symprefix.upper()
578                 else:
579                     enumname_prefix = idprefix.upper()
581                 enumlong = enumname_prefix + "_" + enumshort
582                 enumsym = enumlong.lower()
584             if firstenum:
585                 firstenum = False
587                 if len(fprod) > 0:
588                     prod = fprod
589                     base = os.path.basename(curfilename)
591                     prod = prod.replace('\u0040filename\u0040', curfilename)
592                     prod = prod.replace('\u0040basename\u0040', base)
593                     prod = replace_specials(prod)
595                     write_output(prod)
597             if len(eprod) > 0:
598                 prod = eprod
600                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
601                 prod = prod.replace('\u0040EnumName\u0040', enumname)
602                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
603                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
604                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
605                 if flags:
606                     prod = prod.replace('\u0040type\u0040', 'flags')
607                 else:
608                     prod = prod.replace('\u0040type\u0040', 'enum')
609                 if flags:
610                     prod = prod.replace('\u0040Type\u0040', 'Flags')
611                 else:
612                     prod = prod.replace('\u0040Type\u0040', 'Enum')
613                 if flags:
614                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
615                 else:
616                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
617                 prod = replace_specials(prod)
618                 write_output(prod)
620             if len(vhead) > 0:
621                 prod = vhead
622                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
623                 prod = prod.replace('\u0040EnumName\u0040', enumname)
624                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
625                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
626                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
627                 if flags:
628                     prod = prod.replace('\u0040type\u0040', 'flags')
629                 else:
630                     prod = prod.replace('\u0040type\u0040', 'enum')
631                 if flags:
632                     prod = prod.replace('\u0040Type\u0040', 'Flags')
633                 else:
634                     prod = prod.replace('\u0040Type\u0040', 'Enum')
635                 if flags:
636                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
637                 else:
638                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
639                 prod = replace_specials(prod)
640                 write_output(prod)
642             if len(vprod) > 0:
643                 prod = vprod
644                 next_num = 0
646                 prod = replace_specials(prod)
647                 for name, num, nick in entries:
648                     tmp_prod = prod
650                     if '\u0040valuenum\u0040' in prod:
651                         # only attempt to eval the value if it is requested
652                         # this prevents us from throwing errors otherwise
653                         if num is not None:
654                             # use sandboxed evaluation as a reasonable
655                             # approximation to C constant folding
656                             inum = eval(num, {}, {})
658                             # make sure it parsed to an integer
659                             if not isinstance(inum, int):
660                                 sys.exit("Unable to parse enum value '%s'" % num)
661                             num = inum
662                         else:
663                             num = next_num
665                         tmp_prod = tmp_prod.replace('\u0040valuenum\u0040', str(num))
666                         next_num = int(num) + 1
668                     tmp_prod = tmp_prod.replace('\u0040VALUENAME\u0040', name)
669                     tmp_prod = tmp_prod.replace('\u0040valuenick\u0040', nick)
670                     if flags:
671                         tmp_prod = tmp_prod.replace('\u0040type\u0040', 'flags')
672                     else:
673                         tmp_prod = tmp_prod.replace('\u0040type\u0040', 'enum')
674                     if flags:
675                         tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Flags')
676                     else:
677                         tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Enum')
678                     if flags:
679                         tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'FLAGS')
680                     else:
681                         tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'ENUM')
682                     tmp_prod = tmp_prod.rstrip()
684                     write_output(tmp_prod)
686             if len(vtail) > 0:
687                 prod = vtail
688                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
689                 prod = prod.replace('\u0040EnumName\u0040', enumname)
690                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
691                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
692                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
693                 if flags:
694                     prod = prod.replace('\u0040type\u0040', 'flags')
695                 else:
696                     prod = prod.replace('\u0040type\u0040', 'enum')
697                 if flags:
698                     prod = prod.replace('\u0040Type\u0040', 'Flags')
699                 else:
700                     prod = prod.replace('\u0040Type\u0040', 'Enum')
701                 if flags:
702                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
703                 else:
704                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
705                 prod = replace_specials(prod)
706                 write_output(prod)
708 for fname in sorted(options.args):
709     process_file(fname)
711 if len(ftail) > 0:
712     prod = ftail
713     base = os.path.basename(options.args[-1]) # FIXME, wrong
715     prod = prod.replace('\u0040filename\u0040', 'ARGV') # wrong too
716     prod = prod.replace('\u0040basename\u0040', base)
717     prod = replace_specials(prod)
718     write_output(prod)
720 # put auto-generation comment
721 comment = comment_tmpl
722 comment = comment.replace('\u0040comment\u0040', 'Generated data ends here')
723 write_output("\n" + comment + "\n")
725 if tmpfile is not None:
726     tmpfilename = tmpfile.name
727     tmpfile.close()
729     try:
730         os.unlink(options.output)
731     except OSError as error:
732         if error.errno != errno.ENOENT:
733             raise error
735     os.rename(tmpfilename, options.output)