2022-06-29 16:02:05 +08:00
|
|
|
#!/usr/bin/env @PYTHON@
|
|
|
|
|
|
|
|
# If the code below looks horrible and unpythonic, do not panic.
|
|
|
|
#
|
|
|
|
# It is.
|
|
|
|
#
|
|
|
|
# This is a manual conversion from the original Perl script to
|
|
|
|
# Python. Improvements are welcome.
|
|
|
|
#
|
|
|
|
from __future__ import print_function, unicode_literals
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
import tempfile
|
|
|
|
import io
|
|
|
|
import errno
|
|
|
|
import codecs
|
|
|
|
import locale
|
|
|
|
|
2023-02-14 16:00:02 +08:00
|
|
|
# Non-english locale systems might complain to unrecognized character
|
|
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding='utf-8')
|
|
|
|
|
2022-06-29 16:02:05 +08:00
|
|
|
VERSION_STR = '''glib-mkenums version @VERSION@
|
|
|
|
glib-mkenums comes with ABSOLUTELY NO WARRANTY.
|
|
|
|
You may redistribute copies of glib-mkenums under the terms of
|
|
|
|
the GNU General Public License which can be found in the
|
|
|
|
GLib source package. Sources, examples and contact
|
|
|
|
information are available at http://www.gtk.org'''
|
|
|
|
|
|
|
|
# pylint: disable=too-few-public-methods
|
|
|
|
class Color:
|
|
|
|
'''ANSI Terminal colors'''
|
|
|
|
GREEN = '\033[1;32m'
|
|
|
|
BLUE = '\033[1;34m'
|
|
|
|
YELLOW = '\033[1;33m'
|
|
|
|
RED = '\033[1;31m'
|
|
|
|
END = '\033[0m'
|
|
|
|
|
|
|
|
|
|
|
|
def print_color(msg, color=Color.END, prefix='MESSAGE'):
|
|
|
|
'''Print a string with a color prefix'''
|
|
|
|
if os.isatty(sys.stderr.fileno()):
|
|
|
|
real_prefix = '{start}{prefix}{end}'.format(start=color, prefix=prefix, end=Color.END)
|
|
|
|
else:
|
|
|
|
real_prefix = prefix
|
|
|
|
print('{prefix}: {msg}'.format(prefix=real_prefix, msg=msg), file=sys.stderr)
|
|
|
|
|
|
|
|
|
|
|
|
def print_error(msg):
|
|
|
|
'''Print an error, and terminate'''
|
|
|
|
print_color(msg, color=Color.RED, prefix='ERROR')
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
def print_warning(msg, fatal=False):
|
|
|
|
'''Print a warning, and optionally terminate'''
|
|
|
|
if fatal:
|
|
|
|
color = Color.RED
|
|
|
|
prefix = 'ERROR'
|
|
|
|
else:
|
|
|
|
color = Color.YELLOW
|
|
|
|
prefix = 'WARNING'
|
|
|
|
print_color(msg, color, prefix)
|
|
|
|
if fatal:
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
def print_info(msg):
|
|
|
|
'''Print a message'''
|
|
|
|
print_color(msg, color=Color.GREEN, prefix='INFO')
|
|
|
|
|
|
|
|
|
|
|
|
def get_rspfile_args(rspfile):
|
|
|
|
'''
|
|
|
|
Response files are useful on Windows where there is a command-line character
|
|
|
|
limit of 8191 because when passing sources as arguments to glib-mkenums this
|
|
|
|
limit can be exceeded in large codebases.
|
|
|
|
|
|
|
|
There is no specification for response files and each tool that supports it
|
|
|
|
generally writes them out in slightly different ways, but some sources are:
|
|
|
|
https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-response-files
|
|
|
|
https://docs.microsoft.com/en-us/windows/desktop/midl/the-response-file-command
|
|
|
|
'''
|
|
|
|
import shlex
|
|
|
|
if not os.path.isfile(rspfile):
|
|
|
|
sys.exit('Response file {!r} does not exist'.format(rspfile))
|
|
|
|
try:
|
|
|
|
with open(rspfile, 'r') as f:
|
|
|
|
cmdline = f.read()
|
|
|
|
except OSError as e:
|
|
|
|
sys.exit('Response file {!r} could not be read: {}'
|
|
|
|
.format(rspfile, e.strerror))
|
|
|
|
return shlex.split(cmdline)
|
|
|
|
|
|
|
|
|
|
|
|
def write_output(output):
|
|
|
|
global output_stream
|
|
|
|
print(output, file=output_stream)
|
|
|
|
|
|
|
|
|
|
|
|
# Python 2 defaults to ASCII in case stdout is redirected.
|
|
|
|
# This should make it match Python 3, which uses the locale encoding.
|
|
|
|
if sys.stdout.encoding is None:
|
|
|
|
output_stream = codecs.getwriter(
|
|
|
|
locale.getpreferredencoding())(sys.stdout)
|
|
|
|
else:
|
|
|
|
output_stream = sys.stdout
|
|
|
|
|
|
|
|
|
|
|
|
# Some source files aren't UTF-8 and the old perl version didn't care.
|
|
|
|
# Replace invalid data with a replacement character to keep things working.
|
|
|
|
# https://bugzilla.gnome.org/show_bug.cgi?id=785113#c20
|
|
|
|
def replace_and_warn(err):
|
|
|
|
# 7 characters of context either side of the offending character
|
|
|
|
print_warning('UnicodeWarning: {} at {} ({})'.format(
|
|
|
|
err.reason, err.start,
|
|
|
|
err.object[err.start - 7:err.end + 7]))
|
|
|
|
return ('?', err.end)
|
|
|
|
|
|
|
|
codecs.register_error('replace_and_warn', replace_and_warn)
|
|
|
|
|
|
|
|
|
|
|
|
# glib-mkenums.py
|
|
|
|
# Information about the current enumeration
|
|
|
|
flags = None # Is enumeration a bitmask?
|
|
|
|
option_underscore_name = '' # Overridden underscore variant of the enum name
|
|
|
|
# for example to fix the cases we don't get the
|
|
|
|
# mixed-case -> underscorized transform right.
|
|
|
|
option_lowercase_name = '' # DEPRECATED. A lower case name to use as part
|
|
|
|
# of the *_get_type() function, instead of the
|
|
|
|
# one that we guess. For instance, when an enum
|
|
|
|
# uses abnormal capitalization and we can not
|
|
|
|
# guess where to put the underscores.
|
2023-02-14 16:00:02 +08:00
|
|
|
option_since = '' # User provided version info for the enum.
|
|
|
|
seenbitshift = 0 # Have we seen bitshift operators?
|
|
|
|
seenprivate = False # Have we seen a private option?
|
|
|
|
enum_prefix = None # Prefix for this enumeration
|
|
|
|
enumname = '' # Name for this enumeration
|
|
|
|
enumshort = '' # $enumname without prefix
|
|
|
|
enumname_prefix = '' # prefix of $enumname
|
|
|
|
enumindex = 0 # Global enum counter
|
|
|
|
firstenum = 1 # Is this the first enumeration per file?
|
|
|
|
entries = [] # [ name, val ] for each entry
|
2024-02-29 14:32:35 +08:00
|
|
|
c_namespace = {} # C symbols namespace.
|
2023-02-14 16:00:02 +08:00
|
|
|
|
|
|
|
output = '' # Filename to write result into
|
2022-06-29 16:02:05 +08:00
|
|
|
|
|
|
|
def parse_trigraph(opts):
|
|
|
|
result = {}
|
2024-02-29 14:32:35 +08:00
|
|
|
for opt in re.findall(r'(?:[^\s,"]|"(?:\\.|[^"])*")+', opts):
|
2022-06-29 16:02:05 +08:00
|
|
|
opt = re.sub(r'^\s*', '', opt)
|
|
|
|
opt = re.sub(r'\s*$', '', opt)
|
|
|
|
m = re.search(r'(\w+)(?:=(.+))?', opt)
|
|
|
|
assert m is not None
|
|
|
|
groups = m.groups()
|
|
|
|
key = groups[0]
|
|
|
|
if len(groups) > 1:
|
|
|
|
val = groups[1]
|
|
|
|
else:
|
|
|
|
val = 1
|
2024-02-29 14:32:35 +08:00
|
|
|
result[key] = val.strip('"') if val is not None else None
|
2022-06-29 16:02:05 +08:00
|
|
|
return result
|
|
|
|
|
|
|
|
def parse_entries(file, file_name):
|
2023-02-14 16:00:02 +08:00
|
|
|
global entries, enumindex, enumname, seenbitshift, seenprivate, flags
|
2022-06-29 16:02:05 +08:00
|
|
|
looking_for_name = False
|
|
|
|
|
|
|
|
while True:
|
|
|
|
line = file.readline()
|
|
|
|
if not line:
|
|
|
|
break
|
|
|
|
|
|
|
|
line = line.strip()
|
|
|
|
|
|
|
|
# read lines until we have no open comments
|
|
|
|
while re.search(r'/\*([^*]|\*(?!/))*$', line):
|
|
|
|
line += file.readline()
|
|
|
|
|
|
|
|
# strip comments w/o options
|
|
|
|
line = re.sub(r'''/\*(?!<)
|
|
|
|
([^*]+|\*(?!/))*
|
|
|
|
\*/''', '', line, flags=re.X)
|
|
|
|
|
|
|
|
line = line.rstrip()
|
|
|
|
|
|
|
|
# skip empty lines
|
|
|
|
if len(line.strip()) == 0:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if looking_for_name:
|
|
|
|
m = re.match(r'\s*(\w+)', line)
|
|
|
|
if m:
|
|
|
|
enumname = m.group(1)
|
|
|
|
return True
|
|
|
|
|
|
|
|
# Handle include files
|
|
|
|
m = re.match(r'\#include\s*<([^>]*)>', line)
|
|
|
|
if m:
|
|
|
|
newfilename = os.path.join("..", m.group(1))
|
|
|
|
newfile = io.open(newfilename, encoding="utf-8",
|
|
|
|
errors="replace_and_warn")
|
|
|
|
|
|
|
|
if not parse_entries(newfile, newfilename):
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
|
|
|
|
m = re.match(r'\s*\}\s*(\w+)', line)
|
|
|
|
if m:
|
|
|
|
enumname = m.group(1)
|
|
|
|
enumindex += 1
|
|
|
|
return 1
|
|
|
|
|
|
|
|
m = re.match(r'\s*\}', line)
|
|
|
|
if m:
|
|
|
|
enumindex += 1
|
|
|
|
looking_for_name = True
|
|
|
|
continue
|
|
|
|
|
|
|
|
m = re.match(r'''\s*
|
|
|
|
(\w+)\s* # name
|
2023-02-14 16:00:02 +08:00
|
|
|
(\s+[A-Z]+_(?:AVAILABLE|DEPRECATED)_ENUMERATOR_IN_[0-9_]+(?:_FOR\s*\(\s*\w+\s*\))?\s*)? # availability
|
2022-06-29 16:02:05 +08:00
|
|
|
(?:=( # value
|
|
|
|
\s*\w+\s*\(.*\)\s* # macro with multiple args
|
|
|
|
| # OR
|
|
|
|
(?:[^,/]|/(?!\*))* # anything but a comma or comment
|
|
|
|
))?,?\s*
|
|
|
|
(?:/\*< # options
|
|
|
|
(([^*]|\*(?!/))*)
|
|
|
|
>\s*\*/)?,?
|
|
|
|
\s*$''', line, flags=re.X)
|
|
|
|
if m:
|
|
|
|
groups = m.groups()
|
|
|
|
name = groups[0]
|
2023-02-14 16:00:02 +08:00
|
|
|
availability = None
|
2022-06-29 16:02:05 +08:00
|
|
|
value = None
|
|
|
|
options = None
|
|
|
|
if len(groups) > 1:
|
2023-02-14 16:00:02 +08:00
|
|
|
availability = groups[1]
|
2022-06-29 16:02:05 +08:00
|
|
|
if len(groups) > 2:
|
2023-02-14 16:00:02 +08:00
|
|
|
value = groups[2]
|
|
|
|
if len(groups) > 3:
|
|
|
|
options = groups[3]
|
2022-06-29 16:02:05 +08:00
|
|
|
if flags is None and value is not None and '<<' in value:
|
|
|
|
seenbitshift = 1
|
|
|
|
|
|
|
|
if options is not None:
|
|
|
|
options = parse_trigraph(options)
|
|
|
|
if 'skip' not in options:
|
2024-02-29 14:32:35 +08:00
|
|
|
entries.append((name, value, seenprivate, options.get('nick')))
|
2022-06-29 16:02:05 +08:00
|
|
|
else:
|
2024-02-29 14:32:35 +08:00
|
|
|
entries.append((name, value, seenprivate))
|
2022-06-29 16:02:05 +08:00
|
|
|
else:
|
2023-02-14 16:00:02 +08:00
|
|
|
m = re.match(r'''\s*
|
|
|
|
/\*< (([^*]|\*(?!/))*) >\s*\*/
|
|
|
|
\s*$''', line, flags=re.X)
|
|
|
|
if m:
|
|
|
|
options = m.groups()[0]
|
|
|
|
if options is not None:
|
|
|
|
options = parse_trigraph(options)
|
|
|
|
if 'private' in options:
|
|
|
|
seenprivate = True
|
|
|
|
continue
|
|
|
|
if 'public' in options:
|
|
|
|
seenprivate = False
|
|
|
|
continue
|
|
|
|
if re.match(r's*\#', line):
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
print_warning('Failed to parse "{}" in {}'.format(line, file_name))
|
2022-06-29 16:02:05 +08:00
|
|
|
return False
|
|
|
|
|
|
|
|
help_epilog = '''Production text substitutions:
|
|
|
|
\u0040EnumName\u0040 PrefixTheXEnum
|
|
|
|
\u0040enum_name\u0040 prefix_the_xenum
|
|
|
|
\u0040ENUMNAME\u0040 PREFIX_THE_XENUM
|
|
|
|
\u0040ENUMSHORT\u0040 THE_XENUM
|
|
|
|
\u0040ENUMPREFIX\u0040 PREFIX
|
2023-02-14 16:00:02 +08:00
|
|
|
\u0040enumsince\u0040 the user-provided since value given
|
2022-06-29 16:02:05 +08:00
|
|
|
\u0040VALUENAME\u0040 PREFIX_THE_XVALUE
|
|
|
|
\u0040valuenick\u0040 the-xvalue
|
|
|
|
\u0040valuenum\u0040 the integer value (limited support, Since: 2.26)
|
|
|
|
\u0040type\u0040 either enum or flags
|
|
|
|
\u0040Type\u0040 either Enum or Flags
|
|
|
|
\u0040TYPE\u0040 either ENUM or FLAGS
|
|
|
|
\u0040filename\u0040 name of current input file
|
|
|
|
\u0040basename\u0040 base name of the current input file (Since: 2.22)
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
|
|
# production variables:
|
|
|
|
idprefix = "" # "G", "Gtk", etc
|
|
|
|
symprefix = "" # "g", "gtk", etc, if not just lc($idprefix)
|
|
|
|
fhead = "" # output file header
|
|
|
|
fprod = "" # per input file production
|
|
|
|
ftail = "" # output file trailer
|
|
|
|
eprod = "" # per enum text (produced prior to value itarations)
|
|
|
|
vhead = "" # value header, produced before iterating over enum values
|
|
|
|
vprod = "" # value text, produced for each enum value
|
|
|
|
vtail = "" # value tail, produced after iterating over enum values
|
|
|
|
comment_tmpl = "" # comment template
|
|
|
|
|
|
|
|
def read_template_file(file):
|
|
|
|
global idprefix, symprefix, fhead, fprod, ftail, eprod, vhead, vprod, vtail, comment_tmpl
|
|
|
|
tmpl = {'file-header': fhead,
|
|
|
|
'file-production': fprod,
|
|
|
|
'file-tail': ftail,
|
|
|
|
'enumeration-production': eprod,
|
|
|
|
'value-header': vhead,
|
|
|
|
'value-production': vprod,
|
|
|
|
'value-tail': vtail,
|
|
|
|
'comment': comment_tmpl,
|
|
|
|
}
|
|
|
|
in_ = 'junk'
|
|
|
|
|
|
|
|
ifile = io.open(file, encoding="utf-8", errors="replace_and_warn")
|
|
|
|
for line in ifile:
|
|
|
|
m = re.match(r'\/\*\*\*\s+(BEGIN|END)\s+([\w-]+)\s+\*\*\*\/', line)
|
|
|
|
if m:
|
|
|
|
if in_ == 'junk' and m.group(1) == 'BEGIN' and m.group(2) in tmpl:
|
|
|
|
in_ = m.group(2)
|
|
|
|
continue
|
|
|
|
elif in_ == m.group(2) and m.group(1) == 'END' and m.group(2) in tmpl:
|
|
|
|
in_ = 'junk'
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
sys.exit("Malformed template file " + file)
|
|
|
|
|
|
|
|
if in_ != 'junk':
|
|
|
|
tmpl[in_] += line
|
|
|
|
|
|
|
|
if in_ != 'junk':
|
|
|
|
sys.exit("Malformed template file " + file)
|
|
|
|
|
|
|
|
fhead = tmpl['file-header']
|
|
|
|
fprod = tmpl['file-production']
|
|
|
|
ftail = tmpl['file-tail']
|
|
|
|
eprod = tmpl['enumeration-production']
|
|
|
|
vhead = tmpl['value-header']
|
|
|
|
vprod = tmpl['value-production']
|
|
|
|
vtail = tmpl['value-tail']
|
|
|
|
comment_tmpl = tmpl['comment']
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(epilog=help_epilog,
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
|
|
|
|
|
|
parser.add_argument('--identifier-prefix', default='', dest='idprefix',
|
|
|
|
help='Identifier prefix')
|
|
|
|
parser.add_argument('--symbol-prefix', default='', dest='symprefix',
|
|
|
|
help='Symbol prefix')
|
|
|
|
parser.add_argument('--fhead', default=[], dest='fhead', action='append',
|
|
|
|
help='Output file header')
|
|
|
|
parser.add_argument('--ftail', default=[], dest='ftail', action='append',
|
|
|
|
help='Output file footer')
|
|
|
|
parser.add_argument('--fprod', default=[], dest='fprod', action='append',
|
|
|
|
help='Put out TEXT every time a new input file is being processed.')
|
|
|
|
parser.add_argument('--eprod', default=[], dest='eprod', action='append',
|
|
|
|
help='Per enum text, produced prior to value iterations')
|
|
|
|
parser.add_argument('--vhead', default=[], dest='vhead', action='append',
|
|
|
|
help='Value header, produced before iterating over enum values')
|
|
|
|
parser.add_argument('--vprod', default=[], dest='vprod', action='append',
|
|
|
|
help='Value text, produced for each enum value.')
|
|
|
|
parser.add_argument('--vtail', default=[], dest='vtail', action='append',
|
|
|
|
help='Value tail, produced after iterating over enum values')
|
|
|
|
parser.add_argument('--comments', default='', dest='comment_tmpl',
|
|
|
|
help='Comment structure')
|
|
|
|
parser.add_argument('--template', default='', dest='template',
|
|
|
|
help='Template file')
|
|
|
|
parser.add_argument('--output', default=None, dest='output')
|
|
|
|
parser.add_argument('--version', '-v', default=False, action='store_true', dest='version',
|
|
|
|
help='Print version information')
|
|
|
|
parser.add_argument('args', nargs='*',
|
|
|
|
help='One or more input files, or a single argument @rspfile_path '
|
|
|
|
'pointing to a file that contains the actual arguments')
|
|
|
|
|
|
|
|
# Support reading an rspfile of the form @filename which contains the args
|
|
|
|
# to be parsed
|
|
|
|
if len(sys.argv) == 2 and sys.argv[1].startswith('@'):
|
|
|
|
args = get_rspfile_args(sys.argv[1][1:])
|
|
|
|
else:
|
|
|
|
args = sys.argv[1:]
|
|
|
|
|
|
|
|
options = parser.parse_args(args)
|
|
|
|
|
|
|
|
if options.version:
|
|
|
|
print(VERSION_STR)
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
def unescape_cmdline_args(arg):
|
|
|
|
arg = arg.replace('\\n', '\n')
|
|
|
|
arg = arg.replace('\\r', '\r')
|
|
|
|
return arg.replace('\\t', '\t')
|
|
|
|
|
|
|
|
if options.template != '':
|
|
|
|
read_template_file(options.template)
|
|
|
|
|
|
|
|
idprefix += options.idprefix
|
|
|
|
symprefix += options.symprefix
|
|
|
|
|
|
|
|
# This is a hack to maintain some semblance of backward compatibility with
|
|
|
|
# the old, Perl-based glib-mkenums. The old tool had an implicit ordering
|
|
|
|
# on the arguments and templates; each argument was parsed in order, and
|
|
|
|
# all the strings appended. This allowed developers to write:
|
|
|
|
#
|
|
|
|
# glib-mkenums \
|
|
|
|
# --fhead ... \
|
|
|
|
# --template a-template-file.c.in \
|
|
|
|
# --ftail ...
|
|
|
|
#
|
|
|
|
# And have the fhead be prepended to the file-head stanza in the template,
|
|
|
|
# as well as the ftail be appended to the file-tail stanza in the template.
|
|
|
|
# Short of throwing away ArgumentParser and going over sys.argv[] element
|
|
|
|
# by element, we can simulate that behaviour by ensuring some ordering in
|
|
|
|
# how we build the template strings:
|
|
|
|
#
|
|
|
|
# - the head stanzas are always prepended to the template
|
|
|
|
# - the prod stanzas are always appended to the template
|
|
|
|
# - the tail stanzas are always appended to the template
|
|
|
|
#
|
|
|
|
# Within each instance of the command line argument, we append each value
|
|
|
|
# to the array in the order in which it appears on the command line.
|
|
|
|
fhead = ''.join([unescape_cmdline_args(x) for x in options.fhead]) + fhead
|
|
|
|
vhead = ''.join([unescape_cmdline_args(x) for x in options.vhead]) + vhead
|
|
|
|
|
|
|
|
fprod += ''.join([unescape_cmdline_args(x) for x in options.fprod])
|
|
|
|
eprod += ''.join([unescape_cmdline_args(x) for x in options.eprod])
|
|
|
|
vprod += ''.join([unescape_cmdline_args(x) for x in options.vprod])
|
|
|
|
|
|
|
|
ftail = ftail + ''.join([unescape_cmdline_args(x) for x in options.ftail])
|
|
|
|
vtail = vtail + ''.join([unescape_cmdline_args(x) for x in options.vtail])
|
|
|
|
|
|
|
|
if options.comment_tmpl != '':
|
|
|
|
comment_tmpl = unescape_cmdline_args(options.comment_tmpl)
|
|
|
|
elif comment_tmpl == "":
|
|
|
|
# default to C-style comments
|
|
|
|
comment_tmpl = "/* \u0040comment\u0040 */"
|
|
|
|
|
|
|
|
output = options.output
|
|
|
|
|
|
|
|
if output is not None:
|
|
|
|
(out_dir, out_fn) = os.path.split(options.output)
|
|
|
|
out_suffix = '_' + os.path.splitext(out_fn)[1]
|
|
|
|
if out_dir == '':
|
|
|
|
out_dir = '.'
|
|
|
|
fd, filename = tempfile.mkstemp(dir=out_dir)
|
|
|
|
os.close(fd)
|
|
|
|
tmpfile = io.open(filename, "w", encoding="utf-8")
|
|
|
|
output_stream = tmpfile
|
|
|
|
else:
|
|
|
|
tmpfile = None
|
|
|
|
|
|
|
|
# put auto-generation comment
|
|
|
|
comment = comment_tmpl.replace('\u0040comment\u0040',
|
|
|
|
'This file is generated by glib-mkenums, do '
|
|
|
|
'not modify it. This code is licensed under '
|
|
|
|
'the same license as the containing project. '
|
|
|
|
'Note that it links to GLib, so must comply '
|
|
|
|
'with the LGPL linking clauses.')
|
|
|
|
write_output("\n" + comment + '\n')
|
|
|
|
|
|
|
|
def replace_specials(prod):
|
|
|
|
prod = prod.replace(r'\\a', r'\a')
|
|
|
|
prod = prod.replace(r'\\b', r'\b')
|
|
|
|
prod = prod.replace(r'\\t', r'\t')
|
|
|
|
prod = prod.replace(r'\\n', r'\n')
|
|
|
|
prod = prod.replace(r'\\f', r'\f')
|
|
|
|
prod = prod.replace(r'\\r', r'\r')
|
|
|
|
prod = prod.rstrip()
|
|
|
|
return prod
|
|
|
|
|
|
|
|
|
|
|
|
def warn_if_filename_basename_used(section, prod):
|
|
|
|
for substitution in ('\u0040filename\u0040',
|
|
|
|
'\u0040basename\u0040'):
|
|
|
|
if substitution in prod:
|
|
|
|
print_warning('{} used in {} section.'.format(substitution,
|
|
|
|
section))
|
|
|
|
|
|
|
|
if len(fhead) > 0:
|
|
|
|
prod = fhead
|
|
|
|
warn_if_filename_basename_used('file-header', prod)
|
|
|
|
prod = replace_specials(prod)
|
|
|
|
write_output(prod)
|
|
|
|
|
|
|
|
def process_file(curfilename):
|
2024-02-29 14:32:35 +08:00
|
|
|
global entries, flags, seenbitshift, seenprivate, enum_prefix, c_namespace
|
2022-06-29 16:02:05 +08:00
|
|
|
firstenum = True
|
|
|
|
|
|
|
|
try:
|
|
|
|
curfile = io.open(curfilename, encoding="utf-8",
|
|
|
|
errors="replace_and_warn")
|
|
|
|
except IOError as e:
|
|
|
|
if e.errno == errno.ENOENT:
|
|
|
|
print_warning('No file "{}" found.'.format(curfilename))
|
|
|
|
return
|
|
|
|
raise
|
|
|
|
|
|
|
|
while True:
|
|
|
|
line = curfile.readline()
|
|
|
|
if not line:
|
|
|
|
break
|
|
|
|
|
|
|
|
line = line.strip()
|
|
|
|
|
|
|
|
# read lines until we have no open comments
|
|
|
|
while re.search(r'/\*([^*]|\*(?!/))*$', line):
|
|
|
|
line += curfile.readline()
|
|
|
|
|
|
|
|
# strip comments w/o options
|
|
|
|
line = re.sub(r'''/\*(?!<)
|
|
|
|
([^*]+|\*(?!/))*
|
|
|
|
\*/''', '', line)
|
|
|
|
|
|
|
|
# ignore forward declarations
|
|
|
|
if re.match(r'\s*typedef\s+enum.*;', line):
|
|
|
|
continue
|
|
|
|
|
|
|
|
m = re.match(r'''\s*typedef\s+enum\s*[_A-Za-z]*[_A-Za-z0-9]*\s*
|
|
|
|
({)?\s*
|
|
|
|
(?:/\*<
|
|
|
|
(([^*]|\*(?!/))*)
|
|
|
|
>\s*\*/)?
|
|
|
|
\s*({)?''', line, flags=re.X)
|
|
|
|
if m:
|
|
|
|
groups = m.groups()
|
|
|
|
if len(groups) >= 2 and groups[1] is not None:
|
|
|
|
options = parse_trigraph(groups[1])
|
|
|
|
if 'skip' in options:
|
|
|
|
continue
|
|
|
|
enum_prefix = options.get('prefix', None)
|
|
|
|
flags = options.get('flags', None)
|
|
|
|
if 'flags' in options:
|
|
|
|
if flags is None:
|
|
|
|
flags = 1
|
|
|
|
else:
|
|
|
|
flags = int(flags)
|
|
|
|
option_lowercase_name = options.get('lowercase_name', None)
|
|
|
|
option_underscore_name = options.get('underscore_name', None)
|
2023-02-14 16:00:02 +08:00
|
|
|
option_since = options.get('since', None)
|
2022-06-29 16:02:05 +08:00
|
|
|
else:
|
|
|
|
enum_prefix = None
|
|
|
|
flags = None
|
|
|
|
option_lowercase_name = None
|
|
|
|
option_underscore_name = None
|
2023-02-14 16:00:02 +08:00
|
|
|
option_since = None
|
2022-06-29 16:02:05 +08:00
|
|
|
|
|
|
|
if option_lowercase_name is not None:
|
|
|
|
if option_underscore_name is not None:
|
|
|
|
print_warning("lowercase_name overridden with underscore_name")
|
|
|
|
option_lowercase_name = None
|
|
|
|
else:
|
|
|
|
print_warning("lowercase_name is deprecated, use underscore_name")
|
|
|
|
|
|
|
|
# Didn't have trailing '{' look on next lines
|
|
|
|
if groups[0] is None and (len(groups) < 4 or groups[3] is None):
|
|
|
|
while True:
|
|
|
|
line = curfile.readline()
|
|
|
|
if not line:
|
|
|
|
print_error("Syntax error when looking for opening { in enum")
|
|
|
|
if re.match(r'\s*\{', line):
|
|
|
|
break
|
|
|
|
|
|
|
|
seenbitshift = 0
|
2023-02-14 16:00:02 +08:00
|
|
|
seenprivate = False
|
2022-06-29 16:02:05 +08:00
|
|
|
entries = []
|
|
|
|
|
|
|
|
# Now parse the entries
|
|
|
|
parse_entries(curfile, curfilename)
|
|
|
|
|
|
|
|
# figure out if this was a flags or enums enumeration
|
|
|
|
if flags is None:
|
|
|
|
flags = seenbitshift
|
|
|
|
|
|
|
|
# Autogenerate a prefix
|
|
|
|
if enum_prefix is None:
|
|
|
|
for entry in entries:
|
2024-02-29 14:32:35 +08:00
|
|
|
if not entry[2] and (len(entry) < 4 or entry[3] is None):
|
2022-06-29 16:02:05 +08:00
|
|
|
name = entry[0]
|
|
|
|
if enum_prefix is not None:
|
|
|
|
enum_prefix = os.path.commonprefix([name, enum_prefix])
|
|
|
|
else:
|
|
|
|
enum_prefix = name
|
|
|
|
if enum_prefix is None:
|
|
|
|
enum_prefix = ""
|
|
|
|
else:
|
|
|
|
# Trim so that it ends in an underscore
|
|
|
|
enum_prefix = re.sub(r'_[^_]*$', '_', enum_prefix)
|
|
|
|
else:
|
|
|
|
# canonicalize user defined prefixes
|
|
|
|
enum_prefix = enum_prefix.upper()
|
|
|
|
enum_prefix = enum_prefix.replace('-', '_')
|
|
|
|
enum_prefix = re.sub(r'(.*)([^_])$', r'\1\2_', enum_prefix)
|
|
|
|
|
|
|
|
fixed_entries = []
|
|
|
|
for e in entries:
|
|
|
|
name = e[0]
|
|
|
|
num = e[1]
|
2024-02-29 14:32:35 +08:00
|
|
|
private = e[2]
|
|
|
|
if len(e) < 4 or e[3] is None:
|
2022-06-29 16:02:05 +08:00
|
|
|
nick = re.sub(r'^' + enum_prefix, '', name)
|
|
|
|
nick = nick.replace('_', '-').lower()
|
2024-02-29 14:32:35 +08:00
|
|
|
e = (name, num, private, nick)
|
2022-06-29 16:02:05 +08:00
|
|
|
fixed_entries.append(e)
|
|
|
|
entries = fixed_entries
|
|
|
|
|
|
|
|
# Spit out the output
|
|
|
|
if option_underscore_name is not None:
|
|
|
|
enumlong = option_underscore_name.upper()
|
|
|
|
enumsym = option_underscore_name.lower()
|
|
|
|
enumshort = re.sub(r'^[A-Z][A-Z0-9]*_', '', enumlong)
|
|
|
|
|
|
|
|
enumname_prefix = re.sub('_' + enumshort + '$', '', enumlong)
|
|
|
|
elif symprefix == '' and idprefix == '':
|
|
|
|
# enumname is e.g. GMatchType
|
|
|
|
enspace = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname)
|
|
|
|
|
|
|
|
enumshort = re.sub(r'^[A-Z][a-z]*', '', enumname)
|
|
|
|
enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
|
|
|
|
enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
|
|
|
|
enumshort = enumshort.upper()
|
|
|
|
|
|
|
|
enumname_prefix = re.sub(r'^([A-Z][a-z]*).*$', r'\1', enumname).upper()
|
|
|
|
|
|
|
|
enumlong = enspace.upper() + "_" + enumshort
|
|
|
|
enumsym = enspace.lower() + "_" + enumshort.lower()
|
|
|
|
|
|
|
|
if option_lowercase_name is not None:
|
|
|
|
enumsym = option_lowercase_name
|
|
|
|
else:
|
|
|
|
enumshort = enumname
|
|
|
|
if idprefix:
|
|
|
|
enumshort = re.sub(r'^' + idprefix, '', enumshort)
|
|
|
|
else:
|
|
|
|
enumshort = re.sub(r'/^[A-Z][a-z]*', '', enumshort)
|
|
|
|
|
|
|
|
enumshort = re.sub(r'([^A-Z])([A-Z])', r'\1_\2', enumshort)
|
|
|
|
enumshort = re.sub(r'([A-Z][A-Z])([A-Z][0-9a-z])', r'\1_\2', enumshort)
|
|
|
|
enumshort = enumshort.upper()
|
|
|
|
|
|
|
|
if symprefix:
|
|
|
|
enumname_prefix = symprefix.upper()
|
|
|
|
else:
|
|
|
|
enumname_prefix = idprefix.upper()
|
|
|
|
|
|
|
|
enumlong = enumname_prefix + "_" + enumshort
|
|
|
|
enumsym = enumlong.lower()
|
|
|
|
|
2023-02-14 16:00:02 +08:00
|
|
|
if option_since is not None:
|
|
|
|
enumsince = option_since
|
|
|
|
else:
|
|
|
|
enumsince = ""
|
|
|
|
|
2022-06-29 16:02:05 +08:00
|
|
|
if firstenum:
|
|
|
|
firstenum = False
|
|
|
|
|
|
|
|
if len(fprod) > 0:
|
|
|
|
prod = fprod
|
|
|
|
base = os.path.basename(curfilename)
|
|
|
|
|
|
|
|
prod = prod.replace('\u0040filename\u0040', curfilename)
|
|
|
|
prod = prod.replace('\u0040basename\u0040', base)
|
|
|
|
prod = replace_specials(prod)
|
|
|
|
|
|
|
|
write_output(prod)
|
|
|
|
|
|
|
|
if len(eprod) > 0:
|
|
|
|
prod = eprod
|
|
|
|
|
|
|
|
prod = prod.replace('\u0040enum_name\u0040', enumsym)
|
|
|
|
prod = prod.replace('\u0040EnumName\u0040', enumname)
|
|
|
|
prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
|
|
|
|
prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
|
|
|
|
prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
|
2023-02-14 16:00:02 +08:00
|
|
|
prod = prod.replace('\u0040enumsince\u0040', enumsince)
|
2022-06-29 16:02:05 +08:00
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040type\u0040', 'flags')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040type\u0040', 'enum')
|
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040Type\u0040', 'Flags')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040Type\u0040', 'Enum')
|
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
|
|
|
|
prod = replace_specials(prod)
|
|
|
|
write_output(prod)
|
|
|
|
|
|
|
|
if len(vhead) > 0:
|
|
|
|
prod = vhead
|
|
|
|
prod = prod.replace('\u0040enum_name\u0040', enumsym)
|
|
|
|
prod = prod.replace('\u0040EnumName\u0040', enumname)
|
|
|
|
prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
|
|
|
|
prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
|
|
|
|
prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
|
2023-02-14 16:00:02 +08:00
|
|
|
prod = prod.replace('\u0040enumsince\u0040', enumsince)
|
2022-06-29 16:02:05 +08:00
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040type\u0040', 'flags')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040type\u0040', 'enum')
|
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040Type\u0040', 'Flags')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040Type\u0040', 'Enum')
|
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
|
|
|
|
prod = replace_specials(prod)
|
|
|
|
write_output(prod)
|
|
|
|
|
|
|
|
if len(vprod) > 0:
|
|
|
|
prod = vprod
|
|
|
|
next_num = 0
|
|
|
|
|
|
|
|
prod = replace_specials(prod)
|
2024-02-29 14:32:35 +08:00
|
|
|
for name, num, private, nick in entries:
|
2022-06-29 16:02:05 +08:00
|
|
|
tmp_prod = prod
|
|
|
|
|
|
|
|
if '\u0040valuenum\u0040' in prod:
|
|
|
|
# only attempt to eval the value if it is requested
|
|
|
|
# this prevents us from throwing errors otherwise
|
|
|
|
if num is not None:
|
|
|
|
# use sandboxed evaluation as a reasonable
|
|
|
|
# approximation to C constant folding
|
2024-02-29 14:32:35 +08:00
|
|
|
inum = eval(num, {}, c_namespace)
|
2022-06-29 16:02:05 +08:00
|
|
|
|
|
|
|
# make sure it parsed to an integer
|
|
|
|
if not isinstance(inum, int):
|
|
|
|
sys.exit("Unable to parse enum value '%s'" % num)
|
|
|
|
num = inum
|
|
|
|
else:
|
|
|
|
num = next_num
|
|
|
|
|
2024-02-29 14:32:35 +08:00
|
|
|
c_namespace[name] = num
|
2022-06-29 16:02:05 +08:00
|
|
|
tmp_prod = tmp_prod.replace('\u0040valuenum\u0040', str(num))
|
|
|
|
next_num = int(num) + 1
|
|
|
|
|
2024-02-29 14:32:35 +08:00
|
|
|
if private:
|
|
|
|
continue
|
|
|
|
|
2022-06-29 16:02:05 +08:00
|
|
|
tmp_prod = tmp_prod.replace('\u0040VALUENAME\u0040', name)
|
|
|
|
tmp_prod = tmp_prod.replace('\u0040valuenick\u0040', nick)
|
|
|
|
if flags:
|
|
|
|
tmp_prod = tmp_prod.replace('\u0040type\u0040', 'flags')
|
|
|
|
else:
|
|
|
|
tmp_prod = tmp_prod.replace('\u0040type\u0040', 'enum')
|
|
|
|
if flags:
|
|
|
|
tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Flags')
|
|
|
|
else:
|
|
|
|
tmp_prod = tmp_prod.replace('\u0040Type\u0040', 'Enum')
|
|
|
|
if flags:
|
|
|
|
tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'FLAGS')
|
|
|
|
else:
|
|
|
|
tmp_prod = tmp_prod.replace('\u0040TYPE\u0040', 'ENUM')
|
|
|
|
tmp_prod = tmp_prod.rstrip()
|
|
|
|
|
|
|
|
write_output(tmp_prod)
|
|
|
|
|
|
|
|
if len(vtail) > 0:
|
|
|
|
prod = vtail
|
|
|
|
prod = prod.replace('\u0040enum_name\u0040', enumsym)
|
|
|
|
prod = prod.replace('\u0040EnumName\u0040', enumname)
|
|
|
|
prod = prod.replace('\u0040ENUMSHORT\u0040', enumshort)
|
|
|
|
prod = prod.replace('\u0040ENUMNAME\u0040', enumlong)
|
|
|
|
prod = prod.replace('\u0040ENUMPREFIX\u0040', enumname_prefix)
|
2023-02-14 16:00:02 +08:00
|
|
|
prod = prod.replace('\u0040enumsince\u0040', enumsince)
|
2022-06-29 16:02:05 +08:00
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040type\u0040', 'flags')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040type\u0040', 'enum')
|
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040Type\u0040', 'Flags')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040Type\u0040', 'Enum')
|
|
|
|
if flags:
|
|
|
|
prod = prod.replace('\u0040TYPE\u0040', 'FLAGS')
|
|
|
|
else:
|
|
|
|
prod = prod.replace('\u0040TYPE\u0040', 'ENUM')
|
|
|
|
prod = replace_specials(prod)
|
|
|
|
write_output(prod)
|
|
|
|
|
|
|
|
for fname in sorted(options.args):
|
|
|
|
process_file(fname)
|
|
|
|
|
|
|
|
if len(ftail) > 0:
|
|
|
|
prod = ftail
|
|
|
|
warn_if_filename_basename_used('file-tail', prod)
|
|
|
|
prod = replace_specials(prod)
|
|
|
|
write_output(prod)
|
|
|
|
|
|
|
|
# put auto-generation comment
|
|
|
|
comment = comment_tmpl
|
|
|
|
comment = comment.replace('\u0040comment\u0040', 'Generated data ends here')
|
|
|
|
write_output("\n" + comment + "\n")
|
|
|
|
|
|
|
|
if tmpfile is not None:
|
|
|
|
tmpfilename = tmpfile.name
|
|
|
|
tmpfile.close()
|
|
|
|
|
|
|
|
try:
|
|
|
|
os.unlink(options.output)
|
|
|
|
except OSError as error:
|
|
|
|
if error.errno != errno.ENOENT:
|
|
|
|
raise error
|
|
|
|
|
|
|
|
os.rename(tmpfilename, options.output)
|