tests/strfuncs: mingw-w64 prints 3 digits for the %e exponent
[glib.git] / gobject / glib-mkenums.in
blobfe9a10756c657794519c558665c9bf37f88b6572
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 = '' # Overridden 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*[_A-Za-z]*[_A-Za-z0-9]*\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 overridden 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 not line:
499                         print_error("Syntax error when looking for opening { in enum")
500                     if re.match(r'\s*\{', line):
501                         break
503             seenbitshift = 0
504             entries = []
506             # Now parse the entries
507             parse_entries(curfile, curfilename)
509             # figure out if this was a flags or enums enumeration
510             if flags is None:
511                 flags = seenbitshift
513             # Autogenerate a prefix
514             if enum_prefix is None:
515                 for entry in entries:
516                     if len(entry) < 3 or entry[2] is None:
517                         name = entry[0]
518                         if enum_prefix is not None:
519                             enum_prefix = os.path.commonprefix([name, enum_prefix])
520                         else:
521                             enum_prefix = name
522                 if enum_prefix is None:
523                     enum_prefix = ""
524                 else:
525                     # Trim so that it ends in an underscore
526                     enum_prefix = re.sub(r'_[^_]*$', '_', enum_prefix)
527             else:
528                 # canonicalize user defined prefixes
529                 enum_prefix = enum_prefix.upper()
530                 enum_prefix = enum_prefix.replace('-', '_')
531                 enum_prefix = re.sub(r'(.*)([^_])$', r'\1\2_', enum_prefix)
533             fixed_entries = []
534             for e in entries:
535                 name = e[0]
536                 num = e[1]
537                 if len(e) < 3 or e[2] is None:
538                     nick = re.sub(r'^' + enum_prefix, '', name)
539                     nick = nick.replace('_', '-').lower()
540                     e = (name, num, nick)
541                 fixed_entries.append(e)
542             entries = fixed_entries
544             # Spit out the output
545             if option_underscore_name is not None:
546                 enumlong = option_underscore_name.upper()
547                 enumsym = option_underscore_name.lower()
548                 enumshort = re.sub(r'^[A-Z][A-Z0-9]*_', '', enumlong)
550                 enumname_prefix = re.sub('_' + enumshort + '$', '', enumlong)
551             elif symprefix == '' and idprefix == '':
552                 # enumname is e.g. GMatchType
553                 enspace = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname)
555                 enumshort = re.sub(r'^[A-Z][a-z]*', '', enumname)
556                 enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
557                 enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
558                 enumshort = enumshort.upper()
560                 enumname_prefix = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname).upper()
562                 enumlong = enspace.upper() + "_" + enumshort
563                 enumsym = enspace.lower() + "_" + enumshort.lower()
565                 if option_lowercase_name is not None:
566                     enumsym = option_lowercase_name
567             else:
568                 enumshort = enumname
569                 if idprefix:
570                     enumshort = re.sub(r'^' + idprefix, '', enumshort)
571                 else:
572                     enumshort = re.sub(r'/^[A-Z][a-z]*', '', enumshort)
574                 enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
575                 enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
576                 enumshort = enumshort.upper()
578                 if symprefix:
579                     enumname_prefix = symprefix.upper()
580                 else:
581                     enumname_prefix = idprefix.upper()
583                 enumlong = enumname_prefix + "_" + enumshort
584                 enumsym = enumlong.lower()
586             if firstenum:
587                 firstenum = False
589                 if len(fprod) > 0:
590                     prod = fprod
591                     base = os.path.basename(curfilename)
593                     prod = prod.replace('\u0040filename\u0040', curfilename)
594                     prod = prod.replace('\u0040basename\u0040', base)
595                     prod = replace_specials(prod)
597                     write_output(prod)
599             if len(eprod) > 0:
600                 prod = eprod
602                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
603                 prod = prod.replace('\u0040EnumName\u0040', enumname)
604                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
605                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
606                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
607                 if flags:
608                     prod = prod.replace('\u0040type\u0040', 'flags')
609                 else:
610                     prod = prod.replace('\u0040type\u0040', 'enum')
611                 if flags:
612                     prod = prod.replace('\u0040Type\u0040', 'Flags')
613                 else:
614                     prod = prod.replace('\u0040Type\u0040', 'Enum')
615                 if flags:
616                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
617                 else:
618                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
619                 prod = replace_specials(prod)
620                 write_output(prod)
622             if len(vhead) > 0:
623                 prod = vhead
624                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
625                 prod = prod.replace('\u0040EnumName\u0040', enumname)
626                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
627                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
628                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
629                 if flags:
630                     prod = prod.replace('\u0040type\u0040', 'flags')
631                 else:
632                     prod = prod.replace('\u0040type\u0040', 'enum')
633                 if flags:
634                     prod = prod.replace('\u0040Type\u0040', 'Flags')
635                 else:
636                     prod = prod.replace('\u0040Type\u0040', 'Enum')
637                 if flags:
638                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
639                 else:
640                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
641                 prod = replace_specials(prod)
642                 write_output(prod)
644             if len(vprod) > 0:
645                 prod = vprod
646                 next_num = 0
648                 prod = replace_specials(prod)
649                 for name, num, nick in entries:
650                     tmp_prod = prod
652                     if '\u0040valuenum\u0040' in prod:
653                         # only attempt to eval the value if it is requested
654                         # this prevents us from throwing errors otherwise
655                         if num is not None:
656                             # use sandboxed evaluation as a reasonable
657                             # approximation to C constant folding
658                             inum = eval(num, {}, {})
660                             # make sure it parsed to an integer
661                             if not isinstance(inum, int):
662                                 sys.exit("Unable to parse enum value '%s'" % num)
663                             num = inum
664                         else:
665                             num = next_num
667                         tmp_prod = tmp_prod.replace('\u0040valuenum\u0040', str(num))
668                         next_num = int(num) + 1
670                     tmp_prod = tmp_prod.replace('\u0040VALUENAME\u0040', name)
671                     tmp_prod = tmp_prod.replace('\u0040valuenick\u0040', nick)
672                     if flags:
673                         tmp_prod = tmp_prod.replace('\u0040type\u0040', 'flags')
674                     else:
675                         tmp_prod = tmp_prod.replace('\u0040type\u0040', 'enum')
676                     if flags:
677                         tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Flags')
678                     else:
679                         tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Enum')
680                     if flags:
681                         tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'FLAGS')
682                     else:
683                         tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'ENUM')
684                     tmp_prod = tmp_prod.rstrip()
686                     write_output(tmp_prod)
688             if len(vtail) > 0:
689                 prod = vtail
690                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
691                 prod = prod.replace('\u0040EnumName\u0040', enumname)
692                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
693                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
694                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
695                 if flags:
696                     prod = prod.replace('\u0040type\u0040', 'flags')
697                 else:
698                     prod = prod.replace('\u0040type\u0040', 'enum')
699                 if flags:
700                     prod = prod.replace('\u0040Type\u0040', 'Flags')
701                 else:
702                     prod = prod.replace('\u0040Type\u0040', 'Enum')
703                 if flags:
704                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
705                 else:
706                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
707                 prod = replace_specials(prod)
708                 write_output(prod)
710 for fname in sorted(options.args):
711     process_file(fname)
713 if len(ftail) > 0:
714     prod = ftail
715     base = os.path.basename(options.args[-1]) # FIXME, wrong
717     prod = prod.replace('\u0040filename\u0040', 'ARGV') # wrong too
718     prod = prod.replace('\u0040basename\u0040', base)
719     prod = replace_specials(prod)
720     write_output(prod)
722 # put auto-generation comment
723 comment = comment_tmpl
724 comment = comment.replace('\u0040comment\u0040', 'Generated data ends here')
725 write_output("\n" + comment + "\n")
727 if tmpfile is not None:
728     tmpfilename = tmpfile.name
729     tmpfile.close()
731     try:
732         os.unlink(options.output)
733     except OSError as error:
734         if error.errno != errno.ENOENT:
735             raise error
737     os.rename(tmpfilename, options.output)