• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env @PYTHON@
2
3# If the code below looks horrible and unpythonic, do not panic.
4#
5# It is.
6#
7# This is a manual conversion from the original Perl script to
8# Python. Improvements are welcome.
9#
10from __future__ import print_function, unicode_literals
11
12import argparse
13import os
14import re
15import sys
16import tempfile
17import io
18import errno
19import codecs
20import locale
21
22VERSION_STR = '''glib-mkenums version @VERSION@
23glib-mkenums comes with ABSOLUTELY NO WARRANTY.
24You may redistribute copies of glib-mkenums under the terms of
25the GNU General Public License which can be found in the
26GLib source package. Sources, examples and contact
27information are available at http://www.gtk.org'''
28
29# pylint: disable=too-few-public-methods
30class 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'
37
38
39def 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)
46
47
48def print_error(msg):
49    '''Print an error, and terminate'''
50    print_color(msg, color=Color.RED, prefix='ERROR')
51    sys.exit(1)
52
53
54def 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)
65
66
67def print_info(msg):
68    '''Print a message'''
69    print_color(msg, color=Color.GREEN, prefix='INFO')
70
71
72def get_rspfile_args(rspfile):
73    '''
74    Response files are useful on Windows where there is a command-line character
75    limit of 8191 because when passing sources as arguments to glib-mkenums this
76    limit can be exceeded in large codebases.
77
78    There is no specification for response files and each tool that supports it
79    generally writes them out in slightly different ways, but some sources are:
80    https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-response-files
81    https://docs.microsoft.com/en-us/windows/desktop/midl/the-response-file-command
82    '''
83    import shlex
84    if not os.path.isfile(rspfile):
85        sys.exit('Response file {!r} does not exist'.format(rspfile))
86    try:
87        with open(rspfile, 'r') as f:
88            cmdline = f.read()
89    except OSError as e:
90        sys.exit('Response file {!r} could not be read: {}'
91                 .format(rspfile, e.strerror))
92    return shlex.split(cmdline)
93
94
95def write_output(output):
96    global output_stream
97    print(output, file=output_stream)
98
99
100# Python 2 defaults to ASCII in case stdout is redirected.
101# This should make it match Python 3, which uses the locale encoding.
102if sys.stdout.encoding is None:
103    output_stream = codecs.getwriter(
104        locale.getpreferredencoding())(sys.stdout)
105else:
106    output_stream = sys.stdout
107
108
109# Some source files aren't UTF-8 and the old perl version didn't care.
110# Replace invalid data with a replacement character to keep things working.
111# https://bugzilla.gnome.org/show_bug.cgi?id=785113#c20
112def replace_and_warn(err):
113    # 7 characters of context either side of the offending character
114    print_warning('UnicodeWarning: {} at {} ({})'.format(
115        err.reason, err.start,
116        err.object[err.start - 7:err.end + 7]))
117    return ('?', err.end)
118
119codecs.register_error('replace_and_warn', replace_and_warn)
120
121
122# glib-mkenums.py
123# Information about the current enumeration
124flags = None # Is enumeration a bitmask?
125option_underscore_name = '' # Overridden underscore variant of the enum name
126                            # for example to fix the cases we don't get the
127                            # mixed-case -> underscorized transform right.
128option_lowercase_name = ''  # DEPRECATED.  A lower case name to use as part
129                            # of the *_get_type() function, instead of the
130                            # one that we guess. For instance, when an enum
131                            # uses abnormal capitalization and we can not
132                            # guess where to put the underscores.
133option_since = ''           # User provided version info for the enum.
134seenbitshift = 0            # Have we seen bitshift operators?
135seenprivate = False         # Have we seen a private option?
136enum_prefix = None          # Prefix for this enumeration
137enumname = ''               # Name for this enumeration
138enumshort = ''              # $enumname without prefix
139enumname_prefix = ''        # prefix of $enumname
140enumindex = 0               # Global enum counter
141firstenum = 1               # Is this the first enumeration per file?
142entries = []                # [ name, val ] for each entry
143sandbox = None              # sandbox for safe evaluation of expressions
144
145output = ''                 # Filename to write result into
146
147def parse_trigraph(opts):
148    result = {}
149
150    for opt in re.split(r'\s*,\s*', opts):
151        opt = re.sub(r'^\s*', '', opt)
152        opt = re.sub(r'\s*$', '', opt)
153        m = re.search(r'(\w+)(?:=(.+))?', opt)
154        assert m is not None
155        groups = m.groups()
156        key = groups[0]
157        if len(groups) > 1:
158            val = groups[1]
159        else:
160            val = 1
161        result[key] = val
162    return result
163
164def parse_entries(file, file_name):
165    global entries, enumindex, enumname, seenbitshift, seenprivate, flags
166    looking_for_name = False
167
168    while True:
169        line = file.readline()
170        if not line:
171            break
172
173        line = line.strip()
174
175        # read lines until we have no open comments
176        while re.search(r'/\*([^*]|\*(?!/))*$', line):
177            line += file.readline()
178
179        # strip comments w/o options
180        line = re.sub(r'''/\*(?!<)
181            ([^*]+|\*(?!/))*
182           \*/''', '', line, flags=re.X)
183
184        line = line.rstrip()
185
186        # skip empty lines
187        if len(line.strip()) == 0:
188            continue
189
190        if looking_for_name:
191            m = re.match(r'\s*(\w+)', line)
192            if m:
193                enumname = m.group(1)
194                return True
195
196        # Handle include files
197        m = re.match(r'\#include\s*<([^>]*)>', line)
198        if m:
199            newfilename = os.path.join("..", m.group(1))
200            newfile = io.open(newfilename, encoding="utf-8",
201                              errors="replace_and_warn")
202
203            if not parse_entries(newfile, newfilename):
204                return False
205            else:
206                continue
207
208        m = re.match(r'\s*\}\s*(\w+)', line)
209        if m:
210            enumname = m.group(1)
211            enumindex += 1
212            return 1
213
214        m = re.match(r'\s*\}', line)
215        if m:
216            enumindex += 1
217            looking_for_name = True
218            continue
219
220        m = re.match(r'''\s*
221              (\w+)\s*                   # name
222              (\s+[A-Z]+_(?:AVAILABLE|DEPRECATED)_ENUMERATOR_IN_[0-9_]+(?:_FOR\s*\(\s*\w+\s*\))?\s*)?    # availability
223              (?:=(                      # value
224                   \s*\w+\s*\(.*\)\s*       # macro with multiple args
225                   |                        # OR
226                   (?:[^,/]|/(?!\*))*       # anything but a comma or comment
227                  ))?,?\s*
228              (?:/\*<                    # options
229                (([^*]|\*(?!/))*)
230               >\s*\*/)?,?
231              \s*$''', line, flags=re.X)
232        if m:
233            groups = m.groups()
234            name = groups[0]
235            availability = None
236            value = None
237            options = None
238            if len(groups) > 1:
239                availability = groups[1]
240            if len(groups) > 2:
241                value = groups[2]
242            if len(groups) > 3:
243                options = groups[3]
244            if flags is None and value is not None and '<<' in value:
245                seenbitshift = 1
246
247            if seenprivate:
248                continue
249
250            if options is not None:
251                options = parse_trigraph(options)
252                if 'skip' not in options:
253                    entries.append((name, value, options.get('nick')))
254            else:
255                entries.append((name, value))
256        else:
257            m = re.match(r'''\s*
258                         /\*< (([^*]|\*(?!/))*) >\s*\*/
259                         \s*$''', line, flags=re.X)
260            if m:
261                options = m.groups()[0]
262                if options is not None:
263                    options = parse_trigraph(options)
264                    if 'private' in options:
265                        seenprivate = True
266                        continue
267                    if 'public' in options:
268                        seenprivate = False
269                        continue
270            if re.match(r's*\#', line):
271                pass
272            else:
273                print_warning('Failed to parse "{}" in {}'.format(line, file_name))
274    return False
275
276help_epilog = '''Production text substitutions:
277  \u0040EnumName\u0040            PrefixTheXEnum
278  \u0040enum_name\u0040           prefix_the_xenum
279  \u0040ENUMNAME\u0040            PREFIX_THE_XENUM
280  \u0040ENUMSHORT\u0040           THE_XENUM
281  \u0040ENUMPREFIX\u0040          PREFIX
282  \u0040enumsince\u0040           the user-provided since value given
283  \u0040VALUENAME\u0040           PREFIX_THE_XVALUE
284  \u0040valuenick\u0040           the-xvalue
285  \u0040valuenum\u0040            the integer value (limited support, Since: 2.26)
286  \u0040type\u0040                either enum or flags
287  \u0040Type\u0040                either Enum or Flags
288  \u0040TYPE\u0040                either ENUM or FLAGS
289  \u0040filename\u0040            name of current input file
290  \u0040basename\u0040            base name of the current input file (Since: 2.22)
291'''
292
293
294# production variables:
295idprefix = ""    # "G", "Gtk", etc
296symprefix = ""   # "g", "gtk", etc, if not just lc($idprefix)
297fhead = ""   # output file header
298fprod = ""   # per input file production
299ftail = ""   # output file trailer
300eprod = ""   # per enum text (produced prior to value itarations)
301vhead = ""   # value header, produced before iterating over enum values
302vprod = ""   # value text, produced for each enum value
303vtail = ""   # value tail, produced after iterating over enum values
304comment_tmpl = ""   # comment template
305
306def read_template_file(file):
307    global idprefix, symprefix, fhead, fprod, ftail, eprod, vhead, vprod, vtail, comment_tmpl
308    tmpl = {'file-header': fhead,
309            'file-production': fprod,
310            'file-tail': ftail,
311            'enumeration-production': eprod,
312            'value-header': vhead,
313            'value-production': vprod,
314            'value-tail': vtail,
315            'comment': comment_tmpl,
316           }
317    in_ = 'junk'
318
319    ifile = io.open(file, encoding="utf-8", errors="replace_and_warn")
320    for line in ifile:
321        m = re.match(r'\/\*\*\*\s+(BEGIN|END)\s+([\w-]+)\s+\*\*\*\/', line)
322        if m:
323            if in_ == 'junk' and m.group(1) == 'BEGIN' and m.group(2) in tmpl:
324                in_ = m.group(2)
325                continue
326            elif in_ == m.group(2) and m.group(1) == 'END' and m.group(2) in tmpl:
327                in_ = 'junk'
328                continue
329            else:
330                sys.exit("Malformed template file " + file)
331
332        if in_ != 'junk':
333            tmpl[in_] += line
334
335    if in_ != 'junk':
336        sys.exit("Malformed template file " + file)
337
338    fhead = tmpl['file-header']
339    fprod = tmpl['file-production']
340    ftail = tmpl['file-tail']
341    eprod = tmpl['enumeration-production']
342    vhead = tmpl['value-header']
343    vprod = tmpl['value-production']
344    vtail = tmpl['value-tail']
345    comment_tmpl = tmpl['comment']
346
347parser = argparse.ArgumentParser(epilog=help_epilog,
348                                 formatter_class=argparse.RawDescriptionHelpFormatter)
349
350parser.add_argument('--identifier-prefix', default='', dest='idprefix',
351                    help='Identifier prefix')
352parser.add_argument('--symbol-prefix', default='', dest='symprefix',
353                    help='Symbol prefix')
354parser.add_argument('--fhead', default=[], dest='fhead', action='append',
355                    help='Output file header')
356parser.add_argument('--ftail', default=[], dest='ftail', action='append',
357                    help='Output file footer')
358parser.add_argument('--fprod', default=[], dest='fprod', action='append',
359                    help='Put out TEXT every time a new input file is being processed.')
360parser.add_argument('--eprod', default=[], dest='eprod', action='append',
361                    help='Per enum text, produced prior to value iterations')
362parser.add_argument('--vhead', default=[], dest='vhead', action='append',
363                    help='Value header, produced before iterating over enum values')
364parser.add_argument('--vprod', default=[], dest='vprod', action='append',
365                    help='Value text, produced for each enum value.')
366parser.add_argument('--vtail', default=[], dest='vtail', action='append',
367                    help='Value tail, produced after iterating over enum values')
368parser.add_argument('--comments', default='', dest='comment_tmpl',
369                    help='Comment structure')
370parser.add_argument('--template', default='', dest='template',
371                    help='Template file')
372parser.add_argument('--output', default=None, dest='output')
373parser.add_argument('--version', '-v', default=False, action='store_true', dest='version',
374                    help='Print version information')
375parser.add_argument('args', nargs='*',
376                    help='One or more input files, or a single argument @rspfile_path '
377                    'pointing to a file that contains the actual arguments')
378
379# Support reading an rspfile of the form @filename which contains the args
380# to be parsed
381if len(sys.argv) == 2 and sys.argv[1].startswith('@'):
382    args = get_rspfile_args(sys.argv[1][1:])
383else:
384    args = sys.argv[1:]
385
386options = parser.parse_args(args)
387
388if options.version:
389    print(VERSION_STR)
390    sys.exit(0)
391
392def unescape_cmdline_args(arg):
393    arg = arg.replace('\\n', '\n')
394    arg = arg.replace('\\r', '\r')
395    return arg.replace('\\t', '\t')
396
397if options.template != '':
398    read_template_file(options.template)
399
400idprefix += options.idprefix
401symprefix += options.symprefix
402
403# This is a hack to maintain some semblance of backward compatibility with
404# the old, Perl-based glib-mkenums. The old tool had an implicit ordering
405# on the arguments and templates; each argument was parsed in order, and
406# all the strings appended. This allowed developers to write:
407#
408#   glib-mkenums \
409#     --fhead ... \
410#     --template a-template-file.c.in \
411#     --ftail ...
412#
413# And have the fhead be prepended to the file-head stanza in the template,
414# as well as the ftail be appended to the file-tail stanza in the template.
415# Short of throwing away ArgumentParser and going over sys.argv[] element
416# by element, we can simulate that behaviour by ensuring some ordering in
417# how we build the template strings:
418#
419#   - the head stanzas are always prepended to the template
420#   - the prod stanzas are always appended to the template
421#   - the tail stanzas are always appended to the template
422#
423# Within each instance of the command line argument, we append each value
424# to the array in the order in which it appears on the command line.
425fhead = ''.join([unescape_cmdline_args(x) for x in options.fhead]) + fhead
426vhead = ''.join([unescape_cmdline_args(x) for x in options.vhead]) + vhead
427
428fprod += ''.join([unescape_cmdline_args(x) for x in options.fprod])
429eprod += ''.join([unescape_cmdline_args(x) for x in options.eprod])
430vprod += ''.join([unescape_cmdline_args(x) for x in options.vprod])
431
432ftail = ftail + ''.join([unescape_cmdline_args(x) for x in options.ftail])
433vtail = vtail + ''.join([unescape_cmdline_args(x) for x in options.vtail])
434
435if options.comment_tmpl != '':
436    comment_tmpl = unescape_cmdline_args(options.comment_tmpl)
437elif comment_tmpl == "":
438    # default to C-style comments
439    comment_tmpl = "/* \u0040comment\u0040 */"
440
441output = options.output
442
443if output is not None:
444    (out_dir, out_fn) = os.path.split(options.output)
445    out_suffix = '_' + os.path.splitext(out_fn)[1]
446    if out_dir == '':
447        out_dir = '.'
448    fd, filename = tempfile.mkstemp(dir=out_dir)
449    os.close(fd)
450    tmpfile = io.open(filename, "w", encoding="utf-8")
451    output_stream = tmpfile
452else:
453    tmpfile = None
454
455# put auto-generation comment
456comment = comment_tmpl.replace('\u0040comment\u0040',
457                               'This file is generated by glib-mkenums, do '
458                               'not modify it. This code is licensed under '
459                               'the same license as the containing project. '
460                               'Note that it links to GLib, so must comply '
461                               'with the LGPL linking clauses.')
462write_output("\n" + comment + '\n')
463
464def replace_specials(prod):
465    prod = prod.replace(r'\\a', r'\a')
466    prod = prod.replace(r'\\b', r'\b')
467    prod = prod.replace(r'\\t', r'\t')
468    prod = prod.replace(r'\\n', r'\n')
469    prod = prod.replace(r'\\f', r'\f')
470    prod = prod.replace(r'\\r', r'\r')
471    prod = prod.rstrip()
472    return prod
473
474
475def warn_if_filename_basename_used(section, prod):
476    for substitution in ('\u0040filename\u0040',
477                         '\u0040basename\u0040'):
478        if substitution in prod:
479            print_warning('{} used in {} section.'.format(substitution,
480                                                          section))
481
482if len(fhead) > 0:
483    prod = fhead
484    warn_if_filename_basename_used('file-header', prod)
485    prod = replace_specials(prod)
486    write_output(prod)
487
488def process_file(curfilename):
489    global entries, flags, seenbitshift, seenprivate, enum_prefix
490    firstenum = True
491
492    try:
493        curfile = io.open(curfilename, encoding="utf-8",
494                          errors="replace_and_warn")
495    except IOError as e:
496        if e.errno == errno.ENOENT:
497            print_warning('No file "{}" found.'.format(curfilename))
498            return
499        raise
500
501    while True:
502        line = curfile.readline()
503        if not line:
504            break
505
506        line = line.strip()
507
508        # read lines until we have no open comments
509        while re.search(r'/\*([^*]|\*(?!/))*$', line):
510            line += curfile.readline()
511
512        # strip comments w/o options
513        line = re.sub(r'''/\*(?!<)
514           ([^*]+|\*(?!/))*
515           \*/''', '', line)
516
517        # ignore forward declarations
518        if re.match(r'\s*typedef\s+enum.*;', line):
519            continue
520
521        m = re.match(r'''\s*typedef\s+enum\s*[_A-Za-z]*[_A-Za-z0-9]*\s*
522               ({)?\s*
523               (?:/\*<
524                 (([^*]|\*(?!/))*)
525                >\s*\*/)?
526               \s*({)?''', line, flags=re.X)
527        if m:
528            groups = m.groups()
529            if len(groups) >= 2 and groups[1] is not None:
530                options = parse_trigraph(groups[1])
531                if 'skip' in options:
532                    continue
533                enum_prefix = options.get('prefix', None)
534                flags = options.get('flags', None)
535                if 'flags' in options:
536                    if flags is None:
537                        flags = 1
538                    else:
539                        flags = int(flags)
540                option_lowercase_name = options.get('lowercase_name', None)
541                option_underscore_name = options.get('underscore_name', None)
542                option_since = options.get('since', None)
543            else:
544                enum_prefix = None
545                flags = None
546                option_lowercase_name = None
547                option_underscore_name = None
548                option_since = None
549
550            if option_lowercase_name is not None:
551                if option_underscore_name is not None:
552                    print_warning("lowercase_name overridden with underscore_name")
553                    option_lowercase_name = None
554                else:
555                    print_warning("lowercase_name is deprecated, use underscore_name")
556
557            # Didn't have trailing '{' look on next lines
558            if groups[0] is None and (len(groups) < 4 or groups[3] is None):
559                while True:
560                    line = curfile.readline()
561                    if not line:
562                        print_error("Syntax error when looking for opening { in enum")
563                    if re.match(r'\s*\{', line):
564                        break
565
566            seenbitshift = 0
567            seenprivate = False
568            entries = []
569
570            # Now parse the entries
571            parse_entries(curfile, curfilename)
572
573            # figure out if this was a flags or enums enumeration
574            if flags is None:
575                flags = seenbitshift
576
577            # Autogenerate a prefix
578            if enum_prefix is None:
579                for entry in entries:
580                    if len(entry) < 3 or entry[2] is None:
581                        name = entry[0]
582                        if enum_prefix is not None:
583                            enum_prefix = os.path.commonprefix([name, enum_prefix])
584                        else:
585                            enum_prefix = name
586                if enum_prefix is None:
587                    enum_prefix = ""
588                else:
589                    # Trim so that it ends in an underscore
590                    enum_prefix = re.sub(r'_[^_]*$', '_', enum_prefix)
591            else:
592                # canonicalize user defined prefixes
593                enum_prefix = enum_prefix.upper()
594                enum_prefix = enum_prefix.replace('-', '_')
595                enum_prefix = re.sub(r'(.*)([^_])$', r'\1\2_', enum_prefix)
596
597            fixed_entries = []
598            for e in entries:
599                name = e[0]
600                num = e[1]
601                if len(e) < 3 or e[2] is None:
602                    nick = re.sub(r'^' + enum_prefix, '', name)
603                    nick = nick.replace('_', '-').lower()
604                    e = (name, num, nick)
605                fixed_entries.append(e)
606            entries = fixed_entries
607
608            # Spit out the output
609            if option_underscore_name is not None:
610                enumlong = option_underscore_name.upper()
611                enumsym = option_underscore_name.lower()
612                enumshort = re.sub(r'^[A-Z][A-Z0-9]*_', '', enumlong)
613
614                enumname_prefix = re.sub('_' + enumshort + '$', '', enumlong)
615            elif symprefix == '' and idprefix == '':
616                # enumname is e.g. GMatchType
617                enspace = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname)
618
619                enumshort = re.sub(r'^[A-Z][a-z]*', '', enumname)
620                enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
621                enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
622                enumshort = enumshort.upper()
623
624                enumname_prefix = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname).upper()
625
626                enumlong = enspace.upper() + "_" + enumshort
627                enumsym = enspace.lower() + "_" + enumshort.lower()
628
629                if option_lowercase_name is not None:
630                    enumsym = option_lowercase_name
631            else:
632                enumshort = enumname
633                if idprefix:
634                    enumshort = re.sub(r'^' + idprefix, '', enumshort)
635                else:
636                    enumshort = re.sub(r'/^[A-Z][a-z]*', '', enumshort)
637
638                enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
639                enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
640                enumshort = enumshort.upper()
641
642                if symprefix:
643                    enumname_prefix = symprefix.upper()
644                else:
645                    enumname_prefix = idprefix.upper()
646
647                enumlong = enumname_prefix + "_" + enumshort
648                enumsym = enumlong.lower()
649
650            if option_since is not None:
651                enumsince = option_since
652            else:
653                enumsince = ""
654
655            if firstenum:
656                firstenum = False
657
658                if len(fprod) > 0:
659                    prod = fprod
660                    base = os.path.basename(curfilename)
661
662                    prod = prod.replace('\u0040filename\u0040', curfilename)
663                    prod = prod.replace('\u0040basename\u0040', base)
664                    prod = replace_specials(prod)
665
666                    write_output(prod)
667
668            if len(eprod) > 0:
669                prod = eprod
670
671                prod = prod.replace('\u0040enum_name\u0040', enumsym)
672                prod = prod.replace('\u0040EnumName\u0040', enumname)
673                prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
674                prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
675                prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
676                prod = prod.replace('\u0040enumsince\u0040', enumsince)
677                if flags:
678                    prod = prod.replace('\u0040type\u0040', 'flags')
679                else:
680                    prod = prod.replace('\u0040type\u0040', 'enum')
681                if flags:
682                    prod = prod.replace('\u0040Type\u0040', 'Flags')
683                else:
684                    prod = prod.replace('\u0040Type\u0040', 'Enum')
685                if flags:
686                    prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
687                else:
688                    prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
689                prod = replace_specials(prod)
690                write_output(prod)
691
692            if len(vhead) > 0:
693                prod = vhead
694                prod = prod.replace('\u0040enum_name\u0040', enumsym)
695                prod = prod.replace('\u0040EnumName\u0040', enumname)
696                prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
697                prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
698                prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
699                prod = prod.replace('\u0040enumsince\u0040', enumsince)
700                if flags:
701                    prod = prod.replace('\u0040type\u0040', 'flags')
702                else:
703                    prod = prod.replace('\u0040type\u0040', 'enum')
704                if flags:
705                    prod = prod.replace('\u0040Type\u0040', 'Flags')
706                else:
707                    prod = prod.replace('\u0040Type\u0040', 'Enum')
708                if flags:
709                    prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
710                else:
711                    prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
712                prod = replace_specials(prod)
713                write_output(prod)
714
715            if len(vprod) > 0:
716                prod = vprod
717                next_num = 0
718
719                prod = replace_specials(prod)
720                for name, num, nick in entries:
721                    tmp_prod = prod
722
723                    if '\u0040valuenum\u0040' in prod:
724                        # only attempt to eval the value if it is requested
725                        # this prevents us from throwing errors otherwise
726                        if num is not None:
727                            # use sandboxed evaluation as a reasonable
728                            # approximation to C constant folding
729                            inum = eval(num, {}, {})
730
731                            # make sure it parsed to an integer
732                            if not isinstance(inum, int):
733                                sys.exit("Unable to parse enum value '%s'" % num)
734                            num = inum
735                        else:
736                            num = next_num
737
738                        tmp_prod = tmp_prod.replace('\u0040valuenum\u0040', str(num))
739                        next_num = int(num) + 1
740
741                    tmp_prod = tmp_prod.replace('\u0040VALUENAME\u0040', name)
742                    tmp_prod = tmp_prod.replace('\u0040valuenick\u0040', nick)
743                    if flags:
744                        tmp_prod = tmp_prod.replace('\u0040type\u0040', 'flags')
745                    else:
746                        tmp_prod = tmp_prod.replace('\u0040type\u0040', 'enum')
747                    if flags:
748                        tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Flags')
749                    else:
750                        tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Enum')
751                    if flags:
752                        tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'FLAGS')
753                    else:
754                        tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'ENUM')
755                    tmp_prod = tmp_prod.rstrip()
756
757                    write_output(tmp_prod)
758
759            if len(vtail) > 0:
760                prod = vtail
761                prod = prod.replace('\u0040enum_name\u0040', enumsym)
762                prod = prod.replace('\u0040EnumName\u0040', enumname)
763                prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
764                prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
765                prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
766                prod = prod.replace('\u0040enumsince\u0040', enumsince)
767                if flags:
768                    prod = prod.replace('\u0040type\u0040', 'flags')
769                else:
770                    prod = prod.replace('\u0040type\u0040', 'enum')
771                if flags:
772                    prod = prod.replace('\u0040Type\u0040', 'Flags')
773                else:
774                    prod = prod.replace('\u0040Type\u0040', 'Enum')
775                if flags:
776                    prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
777                else:
778                    prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
779                prod = replace_specials(prod)
780                write_output(prod)
781
782for fname in sorted(options.args):
783    process_file(fname)
784
785if len(ftail) > 0:
786    prod = ftail
787    warn_if_filename_basename_used('file-tail', prod)
788    prod = replace_specials(prod)
789    write_output(prod)
790
791# put auto-generation comment
792comment = comment_tmpl
793comment = comment.replace('\u0040comment\u0040', 'Generated data ends here')
794write_output("\n" + comment + "\n")
795
796if tmpfile is not None:
797    tmpfilename = tmpfile.name
798    tmpfile.close()
799
800    try:
801        os.unlink(options.output)
802    except OSError as error:
803        if error.errno != errno.ENOENT:
804            raise error
805
806    os.rename(tmpfilename, options.output)
807