glib-mkenums 31 KB

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