OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # |
| 3 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. |
| 6 |
| 7 import collections |
| 8 import re |
| 9 import optparse |
| 10 import os |
| 11 from string import Template |
| 12 import sys |
| 13 |
| 14 from util import build_utils |
| 15 |
| 16 class EnumDefinition(object): |
| 17 def __init__(self, class_name=None, class_package=None, entries=None): |
| 18 self.class_name = class_name |
| 19 self.class_package = class_package |
| 20 self.entries = collections.OrderedDict(entries or []) |
| 21 self.prefix_to_strip = '' |
| 22 |
| 23 def AppendEntry(self, key, value): |
| 24 if key in self.entries: |
| 25 raise Exception('Multiple definitions of key %s found.' % key) |
| 26 self.entries[key] = value |
| 27 |
| 28 def Finalize(self): |
| 29 self._Validate() |
| 30 self._AssignEntryIndices() |
| 31 self._StripPrefix() |
| 32 |
| 33 def _Validate(self): |
| 34 assert self.class_name |
| 35 assert self.class_package |
| 36 assert self.entries |
| 37 |
| 38 def _AssignEntryIndices(self): |
| 39 # Supporting the same set enum value assignments the compiler does is rather |
| 40 # complicated, so we limit ourselves to these cases: |
| 41 # - all the enum constants have values assigned, |
| 42 # - enum constants reference other enum constants or have no value assigned. |
| 43 |
| 44 if not all(self.entries.values()): |
| 45 index = 0 |
| 46 for key, value in self.entries.iteritems(): |
| 47 if not value: |
| 48 self.entries[key] = index |
| 49 index = index + 1 |
| 50 elif value in self.entries: |
| 51 self.entries[key] = self.entries[value] |
| 52 else: |
| 53 raise Exception('You can only reference other enum constants unless ' |
| 54 'you assign values to all of the constants.') |
| 55 |
| 56 def _StripPrefix(self): |
| 57 if not self.prefix_to_strip: |
| 58 prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', self.class_name).upper() |
| 59 prefix_to_strip += '_' |
| 60 if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]): |
| 61 prefix_to_strip = '' |
| 62 else: |
| 63 prefix_to_strip = self.prefix_to_strip |
| 64 entries = ((k.replace(prefix_to_strip, '', 1), v) for (k, v) in |
| 65 self.entries.iteritems()) |
| 66 self.entries = collections.OrderedDict(entries) |
| 67 |
| 68 class HeaderParser(object): |
| 69 single_line_comment_re = re.compile(r'\s*//') |
| 70 multi_line_comment_start_re = re.compile(r'\s*/\*') |
| 71 enum_start_re = re.compile(r'^\s*enum\s+(\w+)\s+{\s*$') |
| 72 enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?\s*$') |
| 73 enum_end_re = re.compile(r'^\s*}\s*;\s*$') |
| 74 generator_directive_re = re.compile( |
| 75 r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$') |
| 76 |
| 77 def __init__(self, lines): |
| 78 self._lines = lines |
| 79 self._enum_definitions = [] |
| 80 self._in_enum = False |
| 81 self._current_definition = None |
| 82 self._generator_directives = {} |
| 83 |
| 84 def ParseDefinitions(self): |
| 85 for line in self._lines: |
| 86 self._ParseLine(line) |
| 87 return self._enum_definitions |
| 88 |
| 89 def _ParseLine(self, line): |
| 90 if not self._in_enum: |
| 91 self._ParseRegularLine(line) |
| 92 else: |
| 93 self._ParseEnumLine(line) |
| 94 |
| 95 def _ParseEnumLine(self, line): |
| 96 if HeaderParser.single_line_comment_re.match(line): |
| 97 return |
| 98 if HeaderParser.multi_line_comment_start_re.match(line): |
| 99 raise Exception('Multi-line comments in enums are not supported.') |
| 100 enum_end = HeaderParser.enum_end_re.match(line) |
| 101 enum_entry = HeaderParser.enum_line_re.match(line) |
| 102 if enum_end: |
| 103 self._ApplyGeneratorDirectives() |
| 104 self._current_definition.Finalize() |
| 105 self._enum_definitions.append(self._current_definition) |
| 106 self._in_enum = False |
| 107 elif enum_entry: |
| 108 enum_key = enum_entry.groups()[0] |
| 109 enum_value = enum_entry.groups()[2] |
| 110 self._current_definition.AppendEntry(enum_key, enum_value) |
| 111 |
| 112 def _GetCurrentEnumPackageName(self): |
| 113 return self._generator_directives.get('ENUM_PACKAGE') |
| 114 |
| 115 def _GetCurrentEnumPrefixToStrip(self): |
| 116 return self._generator_directives.get('PREFIX_TO_STRIP', '') |
| 117 |
| 118 def _ApplyGeneratorDirectives(self): |
| 119 current_definition = self._current_definition |
| 120 current_definition.class_package = self._GetCurrentEnumPackageName() |
| 121 current_definition.prefix_to_strip = self._GetCurrentEnumPrefixToStrip() |
| 122 self._generator_directives = {} |
| 123 |
| 124 def _ParseRegularLine(self, line): |
| 125 enum_start = HeaderParser.enum_start_re.match(line) |
| 126 generator_directive = HeaderParser.generator_directive_re.match(line) |
| 127 if enum_start: |
| 128 if not self._GetCurrentEnumPackageName(): |
| 129 return |
| 130 self._current_definition = EnumDefinition() |
| 131 self._current_definition.class_name = enum_start.groups()[0] |
| 132 self._in_enum = True |
| 133 elif generator_directive: |
| 134 directive_name = generator_directive.groups()[0] |
| 135 directive_value = generator_directive.groups()[1] |
| 136 self._generator_directives[directive_name] = directive_value |
| 137 |
| 138 |
| 139 def GetScriptName(): |
| 140 script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) |
| 141 build_index = script_components.index('build') |
| 142 return os.sep.join(script_components[build_index:]) |
| 143 |
| 144 |
| 145 def DoGenerate(options, source_paths): |
| 146 output_paths = [] |
| 147 for source_path in source_paths: |
| 148 enum_definitions = DoParseHeaderFile(source_path) |
| 149 for enum_definition in enum_definitions: |
| 150 package_path = enum_definition.class_package.replace('.', os.path.sep) |
| 151 file_name = enum_definition.class_name + '.java' |
| 152 output_path = os.path.join(options.output_dir, package_path, file_name) |
| 153 output_paths.append(output_path) |
| 154 if not options.print_output_only: |
| 155 build_utils.MakeDirectory(os.path.dirname(output_path)) |
| 156 DoWriteOutput(source_path, output_path, enum_definition) |
| 157 return output_paths |
| 158 |
| 159 |
| 160 def DoParseHeaderFile(path): |
| 161 with open(path) as f: |
| 162 return HeaderParser(f.readlines()).ParseDefinitions() |
| 163 |
| 164 |
| 165 def GenerateOutput(source_path, enum_definition): |
| 166 template = Template(""" |
| 167 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 168 // Use of this source code is governed by a BSD-style license that can be |
| 169 // found in the LICENSE file. |
| 170 |
| 171 // This file is autogenerated by |
| 172 // ${SCRIPT_NAME} |
| 173 // From |
| 174 // ${SOURCE_PATH} |
| 175 |
| 176 package ${PACKAGE}; |
| 177 |
| 178 public class ${CLASS_NAME} { |
| 179 ${ENUM_ENTRIES} |
| 180 } |
| 181 """) |
| 182 |
| 183 enum_template = Template(' public static final int ${NAME} = ${VALUE};') |
| 184 enum_entries_string = [] |
| 185 for enum_name, enum_value in enum_definition.entries.iteritems(): |
| 186 values = { |
| 187 'NAME': enum_name, |
| 188 'VALUE': enum_value, |
| 189 } |
| 190 enum_entries_string.append(enum_template.substitute(values)) |
| 191 enum_entries_string = '\n'.join(enum_entries_string) |
| 192 |
| 193 values = { |
| 194 'CLASS_NAME': enum_definition.class_name, |
| 195 'ENUM_ENTRIES': enum_entries_string, |
| 196 'PACKAGE': enum_definition.class_package, |
| 197 'SCRIPT_NAME': GetScriptName(), |
| 198 'SOURCE_PATH': source_path, |
| 199 } |
| 200 return template.substitute(values) |
| 201 |
| 202 |
| 203 def DoWriteOutput(source_path, output_path, enum_definition): |
| 204 with open(output_path, 'w') as out_file: |
| 205 out_file.write(GenerateOutput(source_path, enum_definition)) |
| 206 |
| 207 def AssertFilesList(output_paths, assert_files_list): |
| 208 actual = set(output_paths) |
| 209 expected = set(assert_files_list) |
| 210 if not actual == expected: |
| 211 need_to_add = list(actual - expected) |
| 212 need_to_remove = list(expected - actual) |
| 213 raise Exception('Output files list does not match expectations. Please ' |
| 214 'add %s and remove %s.' % (need_to_add, need_to_remove)) |
| 215 |
| 216 def DoMain(argv): |
| 217 parser = optparse.OptionParser() |
| 218 |
| 219 parser.add_option('--assert_file', action="append", default=[], |
| 220 dest="assert_files_list", help='Assert that the given ' |
| 221 'file is an output. There can be multiple occurrences of ' |
| 222 'this flag.') |
| 223 parser.add_option('--output_dir', help='Base path for generated files.') |
| 224 parser.add_option('--print_output_only', help='Only print output paths.', |
| 225 action='store_true') |
| 226 |
| 227 options, args = parser.parse_args(argv) |
| 228 |
| 229 output_paths = DoGenerate(options, args) |
| 230 |
| 231 if options.assert_files_list: |
| 232 AssertFilesList(output_paths, options.assert_files_list) |
| 233 |
| 234 return " ".join(output_paths) |
| 235 |
| 236 if __name__ == '__main__': |
| 237 DoMain(sys.argv[1:]) |
OLD | NEW |