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