meson: Simplify the use of built tools
[glib.git] / gobject / glib-mkenums.in
blob9ae036bf691bd9866e7de02b85736483afcfb2b6
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
18 VERSION_STR = '''glib-mkenums version @VERSION@
19 glib-genmarshal comes with ABSOLUTELY NO WARRANTY.
20 You may redistribute copies of glib-genmarshal under the terms of
21 the GNU General Public License which can be found in the
22 GLib source package. Sources, examples and contact
23 information are available at http://www.gtk.org'''
25 output_stream = sys.stdout
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'
37 def print_color(msg, color=Color.END, prefix='MESSAGE'):
38     '''Print a string with a color prefix'''
39     if os.isatty(sys.stderr.fileno()):
40         real_prefix = '{start}{prefix}{end}'.format(start=color, prefix=prefix, end=Color.END)
41     else:
42         real_prefix = prefix
43     print('{prefix}: {msg}'.format(prefix=real_prefix, msg=msg), file=sys.stderr)
46 def print_error(msg):
47     '''Print an error, and terminate'''
48     print_color(msg, color=Color.RED, prefix='ERROR')
49     sys.exit(1)
52 def print_warning(msg, fatal=False):
53     '''Print a warning, and optionally terminate'''
54     if fatal:
55         color = Color.RED
56         prefix = 'ERROR'
57     else:
58         color = Color.YELLOW
59         prefix = 'WARNING'
60     print_color(msg, color, prefix)
61     if fatal:
62         sys.exit(1)
65 def print_info(msg):
66     '''Print a message'''
67     print_color(msg, color=Color.GREEN, prefix='INFO')
70 def write_output(output):
71     global output_stream
72     print(output, file=output_stream)
74 # glib-mkenums.py
75 # Information about the current enumeration
76 flags = False               # Is enumeration a bitmask?
77 option_underscore_name = '' # Overriden underscore variant of the enum name
78                             # for example to fix the cases we don't get the
79                             # mixed-case -> underscorized transform right.
80 option_lowercase_name = ''  # DEPRECATED.  A lower case name to use as part
81                             # of the *_get_type() function, instead of the
82                             # one that we guess. For instance, when an enum
83                             # uses abnormal capitalization and we can not
84                             # guess where to put the underscores.
85 seenbitshift = 0        # Have we seen bitshift operators?
86 enum_prefix = None        # Prefix for this enumeration
87 enumname = ''            # Name for this enumeration
88 enumshort = ''           # $enumname without prefix
89 enumname_prefix = ''       # prefix of $enumname
90 enumindex = 0        # Global enum counter
91 firstenum = 1        # Is this the first enumeration per file?
92 entries = []            # [ name, val ] for each entry
93 sandbox = None      # sandbox for safe evaluation of expressions
95 output = ''            # Filename to write result into
97 def parse_trigraph(opts):
98     result = {}
100     for opt in re.split(r'\s*,\s*', opts):
101         opt = re.sub(r'^\s*', '', opt)
102         opt = re.sub(r'\s*$', '', opt)
103         m = re.search(r'(\w+)(?:=(.+))?', opt)
104         assert m is not None
105         groups = m.groups()
106         key = groups[0]
107         if len(groups) > 1:
108             val = groups[1]
109         else:
110             val = 1
111         result[key] = val
112     return result
114 def parse_entries(file, file_name):
115     global entries, enumindex, enumname, seenbitshift, flags
116     looking_for_name = False
118     for line in file:
119         # read lines until we have no open comments
120         while re.search(r'/\*([^*]|\*(?!/))*$', line):
121             line += file.readline()
123         # strip comments w/o options
124         line = re.sub(r'''/\*(?!<)
125             ([^*]+|\*(?!/))*
126            \*/''', '', line, flags=re.X)
128         line = line.rstrip()
130         # skip empty lines
131         if len(line.strip()) == 0:
132             continue
134         if looking_for_name:
135             m = re.match(r'\s*(\w+)', line)
136             if m:
137                 enumname = m.group(1)
138                 return True
140         # Handle include files
141         m = re.match(r'\#include\s*<([^>]*)>', line)
142         if m:
143             newfilename = os.path.join("..", m.group(1))
144             newfile = open(newfilename)
146             if not parse_entries(newfile, newfilename):
147                 return False
148             else:
149                 continue
151         m = re.match(r'\s*\}\s*(\w+)', line)
152         if m:
153             enumname = m.group(1)
154             enumindex += 1
155             return 1
157         m = re.match(r'\s*\}', line)
158         if m:
159             enumindex += 1
160             looking_for_name = True
161             continue
163         m = re.match(r'''\s*
164               (\w+)\s*                   # name
165               (?:=(                      # value
166                    \s*\w+\s*\(.*\)\s*       # macro with multiple args
167                    |                        # OR
168                    (?:[^,/]|/(?!\*))*       # anything but a comma or comment
169                   ))?,?\s*
170               (?:/\*<                    # options
171                 (([^*]|\*(?!/))*)
172                >\s*\*/)?,?
173               \s*$''', line, flags=re.X)
174         if m:
175             groups = m.groups()
176             name = groups[0]
177             value = None
178             options = None
179             if len(groups) > 1:
180                 value = groups[1]
181             if len(groups) > 2:
182                 options = groups[2]
183             if not flags and value is not None and '<<' in value:
184                 seenbitshift = 1
186             if options is not None:
187                 options = parse_trigraph(options)
188                 if 'skip' not in options:
189                     entries.append((name, value, options['nick']))
190             else:
191                 entries.append((name, value))
192         elif re.match(r's*\#', line):
193             pass
194         else:
195             sys.exit("Failed to parse %s." % file_name)
196     return False
198 help_epilog = '''Production text substitutions:
199   \u0040EnumName\u0040            PrefixTheXEnum
200   \u0040enum_name\u0040           prefix_the_xenum
201   \u0040ENUMNAME\u0040            PREFIX_THE_XENUM
202   \u0040ENUMSHORT\u0040           THE_XENUM
203   \u0040ENUMPREFIX\u0040          PREFIX
204   \u0040VALUENAME\u0040           PREFIX_THE_XVALUE
205   \u0040valuenick\u0040           the-xvalue
206   \u0040valuenum\u0040            the integer value (limited support, Since: 2.26)
207   \u0040type\u0040                either enum or flags
208   \u0040Type\u0040                either Enum or Flags
209   \u0040TYPE\u0040                either ENUM or FLAGS
210   \u0040filename\u0040            name of current input file
211   \u0040basename\u0040            base name of the current input file (Since: 2.22)
215 # production variables:
216 idprefix = ""    # "G", "Gtk", etc
217 symprefix = ""   # "g", "gtk", etc, if not just lc($idprefix)
218 fhead = ""   # output file header
219 fprod = ""   # per input file production
220 ftail = ""   # output file trailer
221 eprod = ""   # per enum text (produced prior to value itarations)
222 vhead = ""   # value header, produced before iterating over enum values
223 vprod = ""   # value text, produced for each enum value
224 vtail = ""   # value tail, produced after iterating over enum values
225 comment_tmpl = ""   # comment template
227 def read_template_file(file):
228     global idprefix, symprefix, fhead, fprod, ftail, eprod, vhead, vprod, vtail, comment_tmpl
229     tmpl = {'file-header': fhead,
230             'file-production': fprod,
231             'file-tail': ftail,
232             'enumeration-production': eprod,
233             'value-header': vhead,
234             'value-production': vprod,
235             'value-tail': vtail,
236             'comment': comment_tmpl,
237            }
238     in_ = 'junk'
240     ifile = open(file)
241     for line in ifile:
242         m = re.match(r'\/\*\*\*\s+(BEGIN|END)\s+([\w-]+)\s+\*\*\*\/', line)
243         if m:
244             if in_ == 'junk' and m.group(1) == 'BEGIN' and m.group(2) in tmpl:
245                 in_ = m.group(2)
246                 continue
247             elif in_ == m.group(2) and m.group(1) == 'END' and m.group(2) in tmpl:
248                 in_ = 'junk'
249                 continue
250             else:
251                 sys.exit("Malformed template file " + file)
253         if in_ != 'junk':
254             tmpl[in_] += line
256     if in_ != 'junk':
257         sys.exit("Malformed template file " + file)
259     fhead = tmpl['file-header']
260     fprod = tmpl['file-production']
261     ftail = tmpl['file-tail']
262     eprod = tmpl['enumeration-production']
263     vhead = tmpl['value-header']
264     vprod = tmpl['value-production']
265     vtail = tmpl['value-tail']
266     comment_tmpl = tmpl['comment']
268     # default to C-style comments
269     if comment_tmpl == "":
270         comment_tmpl = "/* \u0040comment\u0040 */"
272 parser = argparse.ArgumentParser(epilog=help_epilog,
273                                  formatter_class=argparse.RawDescriptionHelpFormatter)
275 parser.add_argument('--identifier-prefix', default='', dest='idprefix',
276                     help='Identifier prefix')
277 parser.add_argument('--symbol-prefix', default='', dest='symprefix',
278                     help='symbol-prefix')
279 parser.add_argument('--fhead', default=[], dest='fhead', action='append',
280                     help='Output file header')
281 parser.add_argument('--ftail', default=[], dest='ftail', action='append',
282                     help='Per input file production')
283 parser.add_argument('--fprod', default=[], dest='fprod', action='append',
284                     help='Put out TEXT everytime a new input file is being processed.')
285 parser.add_argument('--eprod', default=[], dest='eprod', action='append',
286                     help='Per enum text (produced prior to value iterations)')
287 parser.add_argument('--vhead', default=[], dest='vhead', action='append',
288                     help='Value header, produced before iterating over enum values')
289 parser.add_argument('--vprod', default=[], dest='vprod', action='append',
290                     help='Value text, produced for each enum value.')
291 parser.add_argument('--vtail', default=[], dest='vtail', action='append',
292                     help='Value tail, produced after iterating over enum values')
293 parser.add_argument('--comments', default='', dest='comment_tmpl',
294                     help='Comment structure')
295 parser.add_argument('--template', default='', dest='template',
296                     help='Template file')
297 parser.add_argument('--output', default=None, dest='output')
298 parser.add_argument('--version', '-v', default=False, action='store_true', dest='version',
299                     help='Print version informations')
300 parser.add_argument('args', nargs='*')
302 options = parser.parse_args()
304 if options.version:
305     print(VERSION_STR)
306     sys.exit(0)
308 def unescape_cmdline_args(arg):
309     arg = arg.replace('\\n', '\n')
310     arg = arg.replace('\\r', '\r')
311     return arg.replace('\\t', '\t')
313 if options.template != '':
314     read_template_file(options.template)
316 idprefix += options.idprefix
317 symprefix += options.symprefix
319 # This is a hack to maintain some semblance of backward compatibility with
320 # the old, Perl-based glib-mkenums. The old tool had an implicit ordering
321 # on the arguments and templates; each argument was parsed in order, and
322 # all the strings appended. This allowed developers to write:
324 #   glib-mkenums \
325 #     --fhead ... \
326 #     --template a-template-file.c.in \
327 #     --ftail ...
329 # And have the fhead be prepended to the file-head stanza in the template,
330 # as well as the ftail be appended to the file-tail stanza in the template.
331 # Short of throwing away ArgumentParser and going over sys.argv[] element
332 # by element, we can simulate that behaviour by ensuring some ordering in
333 # how we build the template strings:
335 #   - the head stanzas are always prepended to the template
336 #   - the prod stanzas are always appended to the template
337 #   - the tail stanzas are always appended to the template
339 # Within each instance of the command line argument, we append each value
340 # to the array in the order in which it appears on the command line.
341 fhead = ''.join([unescape_cmdline_args(x) for x in options.fhead]) + fhead
342 vhead = ''.join([unescape_cmdline_args(x) for x in options.vhead]) + vhead
344 eprod += ''.join([unescape_cmdline_args(x) for x in options.eprod])
345 vprod += ''.join([unescape_cmdline_args(x) for x in options.vprod])
347 ftail = ftail + ''.join([unescape_cmdline_args(x) for x in options.ftail])
348 vtail = vtail + ''.join([unescape_cmdline_args(x) for x in options.vtail])
350 if options.comment_tmpl != '':
351     comment_tmpl = unescape_cmdline_args(options.comment_tmpl)
353 output = options.output
355 if output is not None:
356     (out_dir, out_fn) = os.path.split(options.output)
357     out_suffix = '_' + os.path.splitext(out_fn)[1]
358     if out_dir == '':
359         out_dir = '.'
360     tmpfile = tempfile.NamedTemporaryFile(dir=out_dir, delete=False)
361     output_stream = tmpfile
362 else:
363     tmpfile = None
365 # put auto-generation comment
366 comment = comment_tmpl.replace('\u0040comment\u0040', 'Generated data (by glib-mkenums)')
367 write_output("\n" + comment + '\n')
369 def replace_specials(prod):
370     prod = prod.replace(r'\\a', r'\a')
371     prod = prod.replace(r'\\b', r'\b')
372     prod = prod.replace(r'\\t', r'\t')
373     prod = prod.replace(r'\\n', r'\n')
374     prod = prod.replace(r'\\f', r'\f')
375     prod = prod.replace(r'\\r', r'\r')
376     prod = prod.rstrip()
377     return prod
379 if len(fhead) > 0:
380     prod = fhead
381     base = os.path.basename(options.args[0])
383     prod = prod.replace('\u0040filename\u0040', options.args[0])
384     prod = prod.replace('\u0040basename\u0040', base)
385     prod = replace_specials(prod)
386     write_output(prod)
388 def process_file(curfilename):
389     global entries, flags, seenbitshift, enum_prefix
390     firstenum = True
392     try:
393         curfile = open(curfilename)
394     except FileNotFoundError:
395         print_warning('No file "{}" found.'.format(curfilename))
396         return
398     for line in curfile:
399         # read lines until we have no open comments
400         while re.search(r'/\*([^*]|\*(?!/))*$', line):
401             line += curfile.readline()
403         # strip comments w/o options
404         line = re.sub(r'''/\*(?!<)
405            ([^*]+|\*(?!/))*
406            \*/''', '', line)
408         # ignore forward declarations
409         if re.match(r'\s*typedef\s+enum.*;', line):
410             continue
412         m = re.match(r'''\s*typedef\s+enum\s*
413                ({)?\s*
414                (?:/\*<
415                  (([^*]|\*(?!/))*)
416                 >\s*\*/)?
417                \s*({)?''', line, flags=re.X)
418         if m:
419             groups = m.groups()
420             if len(groups) >= 2 and groups[1] is not None:
421                 options = parse_trigraph(groups[1])
422                 if 'skip' in options:
423                     continue
424                 enum_prefix = options.get('prefix', None)
425                 flags = 'flags' in options
426                 option_lowercase_name = options.get('lowercase_name', None)
427                 option_underscore_name = options.get('underscore_name', None)
428             else:
429                 enum_prefix = None
430                 flags = False
431                 option_lowercase_name = None
432                 option_underscore_name = None
434             if option_lowercase_name is not None:
435                 if option_underscore_name is not None:
436                     print_warning("lowercase_name overriden with underscore_name")
437                     option_lowercase_name = None
438                 else:
439                     print_warning("lowercase_name is deprecated, use underscore_name")
441             # Didn't have trailing '{' look on next lines
442             if groups[0] is None and (len(groups) < 4 or groups[3] is None):
443                 while True:
444                     line = curfile.readline()
445                     if re.match(r'\s*\{', line):
446                         break
448             seenbitshift = 0
449             entries = []
451             # Now parse the entries
452             parse_entries(curfile, curfilename)
454             # figure out if this was a flags or enums enumeration
455             if not flags:
456                 flags = seenbitshift
458             # Autogenerate a prefix
459             if enum_prefix is None:
460                 for entry in entries:
461                     if len(entry) < 3 or entry[2] is None:
462                         name = entry[0]
463                         if enum_prefix is not None:
464                             enum_prefix = os.path.commonprefix([name, enum_prefix])
465                         else:
466                             enum_prefix = name
467                 if enum_prefix is None:
468                     enum_prefix = ""
469                 else:
470                     # Trim so that it ends in an underscore
471                     enum_prefix = re.sub(r'_[^_]*$', '_', enum_prefix)
472             else:
473                 # canonicalize user defined prefixes
474                 enum_prefix = enum_prefix.upper()
475                 enum_prefix = enum_prefix.replace('-', '_')
476                 enum_prefix = re.sub(r'(.*)([^_])$', r'\1\2_', enum_prefix)
478             fixed_entries = []
479             for e in entries:
480                 name = e[0]
481                 num = e[1]
482                 if len(e) < 3 or e[2] is None:
483                     nick = re.sub(r'^' + enum_prefix, '', name)
484                     nick = nick.replace('_', '-').lower()
485                     e = (name, num, nick)
486                 fixed_entries.append(e)
487             entries = fixed_entries
489             # Spit out the output
490             if option_underscore_name is not None:
491                 enumlong = option_underscore_name.upper()
492                 enumsym = option_underscore_name.lower()
493                 enumshort = re.sub(r'^[A-Z][A-Z0-9]*_', '', enumlong)
495                 enumname_prefix = re.sub('_' + enumshort + '$', '', enumlong)
496             elif symprefix == '' and idprefix == '':
497                 # enumname is e.g. GMatchType
498                 enspace = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname)
500                 enumshort = re.sub(r'^[A-Z][a-z]*', '', enumname)
501                 enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
502                 enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
503                 enumshort = enumshort.upper()
505                 enumname_prefix = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname).upper()
507                 enumlong = enspace.upper() + "_" + enumshort
508                 enumsym = enspace.lower() + "_" + enumshort.lower()
510                 if option_lowercase_name is not None:
511                     enumsym = option_lowercase_name
512             else:
513                 enumshort = enumname
514                 if idprefix:
515                     enumshort = re.sub(r'^' + idprefix, '', enumshort)
516                 else:
517                     enumshort = re.sub(r'/^[A-Z][a-z]*', '', enumshort)
519                 enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
520                 enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
521                 enumshort = enumshort.upper()
523                 if symprefix:
524                     enumname_prefix = symprefix.upper()
525                 else:
526                     enumname_prefix = idprefix.upper()
528                 enumlong = enumname_prefix + "_" + enumshort
529                 enumsym = enumlong.lower()
531             if firstenum:
532                 firstenum = False
534                 if len(fprod) > 0:
535                     prod = fprod
536                     base = os.path.basename(curfilename)
538                     prod = prod.replace('\u0040filename\u0040', curfilename)
539                     prod = prod.replace('\u0040basename\u0040', base)
540                     prod = replace_specials(prod)
542                     write_output(prod)
544             if len(eprod) > 0:
545                 prod = eprod
547                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
548                 prod = prod.replace('\u0040EnumName\u0040', enumname)
549                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
550                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
551                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
552                 if flags:
553                     prod = prod.replace('\u0040type\u0040', 'flags')
554                 else:
555                     prod = prod.replace('\u0040type\u0040', 'enum')
556                 if flags:
557                     prod = prod.replace('\u0040Type\u0040', 'Flags')
558                 else:
559                     prod = prod.replace('\u0040Type\u0040', 'Enum')
560                 if flags:
561                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
562                 else:
563                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
564                 prod = replace_specials(prod)
565                 write_output(prod)
567             if len(vhead) > 0:
568                 prod = vhead
569                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
570                 prod = prod.replace('\u0040EnumName\u0040', enumname)
571                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
572                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
573                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
574                 if flags:
575                     prod = prod.replace('\u0040type\u0040', 'flags')
576                 else:
577                     prod = prod.replace('\u0040type\u0040', 'enum')
578                 if flags:
579                     prod = prod.replace('\u0040Type\u0040', 'Flags')
580                 else:
581                     prod = prod.replace('\u0040Type\u0040', 'Enum')
582                 if flags:
583                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
584                 else:
585                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
586                 prod = replace_specials(prod)
587                 write_output(prod)
589             if len(vprod) > 0:
590                 prod = vprod
591                 next_num = 0
593                 prod = replace_specials(prod)
594                 for name, num, nick in entries:
595                     tmp_prod = prod
597                     if '\u0040valuenum\u0040' in prod:
598                         # only attempt to eval the value if it is requested
599                         # this prevents us from throwing errors otherwise
600                         if num is not None:
601                             # use sandboxed evaluation as a reasonable
602                             # approximation to C constant folding
603                             inum = eval(num, {}, {})
605                             # make sure it parsed to an integer
606                             if not isinstance(inum, int):
607                                 sys.exit("Unable to parse enum value '%s'" % num)
608                             num = inum
609                         else:
610                             num = next_num
612                         tmp_prod = tmp_prod.replace('\u0040valuenum\u0040', str(num))
613                         next_num = int(num) + 1
615                     tmp_prod = tmp_prod.replace('\u0040VALUENAME\u0040', name)
616                     tmp_prod = tmp_prod.replace('\u0040valuenick\u0040', nick)
617                     if flags:
618                         tmp_prod = tmp_prod.replace('\u0040type\u0040', 'flags')
619                     else:
620                         tmp_prod = tmp_prod.replace('\u0040type\u0040', 'enum')
621                     if flags:
622                         tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Flags')
623                     else:
624                         tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Enum')
625                     if flags:
626                         tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'FLAGS')
627                     else:
628                         tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'ENUM')
629                     tmp_prod = tmp_prod.rstrip()
631                     write_output(tmp_prod)
633             if len(vtail) > 0:
634                 prod = vtail
635                 prod = prod.replace('\u0040enum_name\u0040', enumsym)
636                 prod = prod.replace('\u0040EnumName\u0040', enumname)
637                 prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
638                 prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
639                 prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
640                 if flags:
641                     prod = prod.replace('\u0040type\u0040', 'flags')
642                 else:
643                     prod = prod.replace('\u0040type\u0040', 'enum')
644                 if flags:
645                     prod = prod.replace('\u0040Type\u0040', 'Flags')
646                 else:
647                     prod = prod.replace('\u0040Type\u0040', 'Enum')
648                 if flags:
649                     prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
650                 else:
651                     prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
652                 prod = replace_specials(prod)
653                 write_output(prod)
655 for fname in options.args:
656     process_file(fname)
658 if len(ftail) > 0:
659     prod = ftail
660     base = os.path.basename(options.args[-1]) # FIXME, wrong
662     prod = prod.replace('\u0040filename\u0040', 'ARGV') # wrong too
663     prod = prod.replace('\u0040basename\u0040', base)
664     prod = replace_specials(prod)
665     write_output(prod)
667 # put auto-generation comment
668 comment = comment_tmpl
669 comment = comment.replace('\u0040comment\u0040', 'Generated data ends here')
670 write_output("\n" + comment + "\n")
672 if tmpfile is not None:
673     tmpfilename = tmpfile.name
674     tmpfile.close()
675     os.unlink(options.output)
676     os.rename(tmpfilename, options.output)