blob: 6a1d5c1e4c39fa54a1c1b2550356a162c2dcb173 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import re
import optparse
import os
from string import Template
import sys
from util import build_utils
class EnumDefinition(object):
def __init__(self, class_name=None, class_package=None, entries=None):
self.class_name = class_name
self.class_package = class_package
self.entries = collections.OrderedDict(entries or [])
self.prefix_to_strip = ''
def AppendEntry(self, key, value):
if key in self.entries:
raise Exception('Multiple definitions of key %s found.' % key)
self.entries[key] = value
def Finalize(self):
self._Validate()
self._AssignEntryIndices()
self._StripPrefix()
def _Validate(self):
assert self.class_name
assert self.class_package
assert self.entries
def _AssignEntryIndices(self):
# Enums, if given no value, are given the value of the previous enum + 1.
if not all(self.entries.values()):
prev_enum_value = -1
for key, value in self.entries.iteritems():
if not value:
self.entries[key] = prev_enum_value + 1
elif value in self.entries:
self.entries[key] = self.entries[value]
else:
try:
self.entries[key] = int(value)
except ValueError:
raise Exception('Could not interpret integer from enum value "%s" '
'for key %s.' % (value, key))
prev_enum_value = self.entries[key]
def _StripPrefix(self):
if not self.prefix_to_strip:
prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', self.class_name).upper()
prefix_to_strip += '_'
if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]):
prefix_to_strip = ''
else:
prefix_to_strip = self.prefix_to_strip
entries = ((k.replace(prefix_to_strip, '', 1), v) for (k, v) in
self.entries.iteritems())
self.entries = collections.OrderedDict(entries)
class HeaderParser(object):
single_line_comment_re = re.compile(r'\s*//')
multi_line_comment_start_re = re.compile(r'\s*/\*')
enum_start_re = re.compile(r'^\s*enum\s+(\w+)\s+{\s*$')
enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?')
enum_end_re = re.compile(r'^\s*}\s*;\s*$')
generator_directive_re = re.compile(
r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
def __init__(self, lines):
self._lines = lines
self._enum_definitions = []
self._in_enum = False
self._current_definition = None
self._generator_directives = {}
def ParseDefinitions(self):
for line in self._lines:
self._ParseLine(line)
return self._enum_definitions
def _ParseLine(self, line):
if not self._in_enum:
self._ParseRegularLine(line)
else:
self._ParseEnumLine(line)
def _ParseEnumLine(self, line):
if HeaderParser.single_line_comment_re.match(line):
return
if HeaderParser.multi_line_comment_start_re.match(line):
raise Exception('Multi-line comments in enums are not supported.')
enum_end = HeaderParser.enum_end_re.match(line)
enum_entry = HeaderParser.enum_line_re.match(line)
if enum_end:
self._ApplyGeneratorDirectives()
self._current_definition.Finalize()
self._enum_definitions.append(self._current_definition)
self._in_enum = False
elif enum_entry:
enum_key = enum_entry.groups()[0]
enum_value = enum_entry.groups()[2]
self._current_definition.AppendEntry(enum_key, enum_value)
def _GetCurrentEnumPackageName(self):
return self._generator_directives.get('ENUM_PACKAGE')
def _GetCurrentEnumPrefixToStrip(self):
return self._generator_directives.get('PREFIX_TO_STRIP', '')
def _ApplyGeneratorDirectives(self):
current_definition = self._current_definition
current_definition.class_package = self._GetCurrentEnumPackageName()
current_definition.prefix_to_strip = self._GetCurrentEnumPrefixToStrip()
self._generator_directives = {}
def _ParseRegularLine(self, line):
enum_start = HeaderParser.enum_start_re.match(line)
generator_directive = HeaderParser.generator_directive_re.match(line)
if enum_start:
if not self._GetCurrentEnumPackageName():
return
self._current_definition = EnumDefinition()
self._current_definition.class_name = enum_start.groups()[0]
self._in_enum = True
elif generator_directive:
directive_name = generator_directive.groups()[0]
directive_value = generator_directive.groups()[1]
self._generator_directives[directive_name] = directive_value
def GetScriptName():
script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
build_index = script_components.index('build')
return os.sep.join(script_components[build_index:])
def DoGenerate(options, source_paths):
output_paths = []
for source_path in source_paths:
enum_definitions = DoParseHeaderFile(source_path)
for enum_definition in enum_definitions:
package_path = enum_definition.class_package.replace('.', os.path.sep)
file_name = enum_definition.class_name + '.java'
output_path = os.path.join(options.output_dir, package_path, file_name)
output_paths.append(output_path)
if not options.print_output_only:
build_utils.MakeDirectory(os.path.dirname(output_path))
DoWriteOutput(source_path, output_path, enum_definition)
return output_paths
def DoParseHeaderFile(path):
with open(path) as f:
return HeaderParser(f.readlines()).ParseDefinitions()
def GenerateOutput(source_path, enum_definition):
template = Template("""
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is autogenerated by
// ${SCRIPT_NAME}
// From
// ${SOURCE_PATH}
package ${PACKAGE};
public class ${CLASS_NAME} {
${ENUM_ENTRIES}
}
""")
enum_template = Template(' public static final int ${NAME} = ${VALUE};')
enum_entries_string = []
for enum_name, enum_value in enum_definition.entries.iteritems():
values = {
'NAME': enum_name,
'VALUE': enum_value,
}
enum_entries_string.append(enum_template.substitute(values))
enum_entries_string = '\n'.join(enum_entries_string)
values = {
'CLASS_NAME': enum_definition.class_name,
'ENUM_ENTRIES': enum_entries_string,
'PACKAGE': enum_definition.class_package,
'SCRIPT_NAME': GetScriptName(),
'SOURCE_PATH': source_path,
}
return template.substitute(values)
def DoWriteOutput(source_path, output_path, enum_definition):
with open(output_path, 'w') as out_file:
out_file.write(GenerateOutput(source_path, enum_definition))
def AssertFilesList(output_paths, assert_files_list):
actual = set(output_paths)
expected = set(assert_files_list)
if not actual == expected:
need_to_add = list(actual - expected)
need_to_remove = list(expected - actual)
raise Exception('Output files list does not match expectations. Please '
'add %s and remove %s.' % (need_to_add, need_to_remove))
def DoMain(argv):
parser = optparse.OptionParser()
parser.add_option('--assert_file', action="append", default=[],
dest="assert_files_list", help='Assert that the given '
'file is an output. There can be multiple occurrences of '
'this flag.')
parser.add_option('--output_dir', help='Base path for generated files.')
parser.add_option('--print_output_only', help='Only print output paths.',
action='store_true')
options, args = parser.parse_args(argv)
output_paths = DoGenerate(options, args)
if options.assert_files_list:
AssertFilesList(output_paths, options.assert_files_list)
return " ".join(output_paths)
if __name__ == '__main__':
DoMain(sys.argv[1:])