Chromium Code Reviews
[email protected] (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(114)

Side by Side Diff: components/variations/service/generate_ui_string_overrider.py

Issue 1374773002: Componentize script to generate UI string overrides mapping. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@get-resources-index
Patch Set: Rebase and fix //components/variations:unit_tests build with gn Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import argparse
7 import collections
8 import hashlib
9 import operator
10 import os
11 import re
12 import sys
13
14 SCRIPT_NAME = "generate_ui_string_overrider.py"
15 RESOURCE_EXTRACT_REGEX = re.compile('^#define (\S*) (\d*)$', re.MULTILINE)
16
17 class Error(Exception):
18 """Base error class for all exceptions in generated_resources_map."""
19
20
21 class HashCollisionError(Error):
22 """Multiple resource names hash to the same value."""
23
24
25 Resource = collections.namedtuple("Resource", ['hash', 'name', 'index'])
26
27
28 def _HashName(name):
29 """Returns the hash id for a name.
30
31 Args:
32 name: The name to hash.
33
34 Returns:
35 An int that is at most 32 bits.
36 """
37 md5hash = hashlib.md5()
38 md5hash.update(name)
39 return int(md5hash.hexdigest()[:8], 16)
40
41
42 def _GetNameIndexPairsIter(string_to_scan):
43 """Gets an iterator of the resource name and index pairs of the given string.
44
45 Scans the input string for lines of the form "#define NAME INDEX" and returns
46 an iterator over all matching (NAME, INDEX) pairs.
47
48 Args:
49 string_to_scan: The input string to scan.
50
51 Yields:
52 A tuple of name and index.
53 """
54 for match in RESOURCE_EXTRACT_REGEX.finditer(string_to_scan):
55 yield match.group(1, 2)
56
57
58 def _GetResourceListFromString(resources_content):
59 """Produces a list of |Resource| objects from a string.
60
61 The input string contains lines of the form "#define NAME INDEX". The returned
62 list is sorted primarily by hash, then name, and then index.
63
64 Args:
65 resources_content: The input string to process, contains lines of the form
66 "#define NAME INDEX".
67
68 Returns:
69 A sorted list of |Resource| objects.
70 """
71 resources = [Resource(_HashName(name), name, index) for name, index in
72 _GetNameIndexPairsIter(resources_content)]
73
74 # The default |Resource| order makes |resources| sorted by the hash, then
75 # name, then index.
76 resources.sort()
77
78 return resources
79
80
81 def _CheckForHashCollisions(sorted_resource_list):
82 """Checks a sorted list of |Resource| objects for hash collisions.
83
84 Args:
85 sorted_resource_list: A sorted list of |Resource| objects.
86
87 Returns:
88 A set of all |Resource| objects with collisions.
89 """
90 collisions = set()
91 for i in xrange(len(sorted_resource_list) - 1):
92 resource = sorted_resource_list[i]
93 next_resource = sorted_resource_list[i+1]
94 if resource.hash == next_resource.hash:
95 collisions.add(resource)
96 collisions.add(next_resource)
97
98 return collisions
99
100
101 def _GenDataArray(
102 resources, entry_pattern, array_name, array_type, data_getter):
103 """Generates a C++ statement defining a literal array containing the hashes.
104
105 Args:
106 resources: A sorted list of |Resource| objects.
107 entry_pattern: A pattern to be used to generate each entry in the array. The
108 pattern is expected to have a place for data and one for a comment, in
109 that order.
110 array_name: The name of the array being generated.
111 array_type: The type of the array being generated.
112 data_getter: A function that gets the array data from a |Resource| object.
113
114 Returns:
115 A string containing a C++ statement defining the an array.
116 """
117 lines = [entry_pattern % (data_getter(r), r.name) for r in resources]
118 pattern = """const %(type)s %(name)s[] = {
119 %(content)s
120 };
121 """
122 return pattern % {'type': array_type,
123 'name': array_name,
124 'content': '\n'.join(lines)}
125
126
127 def _GenerateNamespacePrefixAndSuffix(namespace):
128 """Generates the namespace prefix and suffix for |namespace|.
129
130 Args:
131 namespace: A string corresponding to the namespace name. May be empty.
132
133 Returns:
134 A tuple of strings corresponding to the namespace prefix and suffix for
135 putting the code in the corresponding namespace in C++. If namespace is
136 the empty string, both returned strings are empty too.
137 """
138 if not namespace:
139 return "", ""
140 return "namespace %s {\n\n" % namespace, "\n} // namespace %s\n" % namespace
141
142
143 def _GenerateSourceFileContent(resources_content, namespace, header_filename):
144 """Generates the .cc content from the given generated grit headers content.
145
146 Args:
147 resources_content: The input string to process, contains lines of the form
148 "#define NAME INDEX".
149
150 namespace: The namespace in which the generated code should be scoped. If
151 not defined, then the code will be in the global namespace.
152
153 header_filename: Path to the corresponding .h.
154
155 Returns:
156 .cc file content implementing the CreateUIStringOverrider() factory.
157 """
158 hashed_tuples = _GetResourceListFromString(resources_content)
159
160 collisions = _CheckForHashCollisions(hashed_tuples)
161 if collisions:
162 error_message = "\n".join(
163 ["hash: %i, name: %s" % (i[0], i[1]) for i in sorted(collisions)])
164 error_message = ("\nThe following names had hash collisions "
165 "(sorted by the hash value):\n%s\n" %(error_message))
166 raise HashCollisionError(error_message)
167
168 hashes_array = _GenDataArray(
169 hashed_tuples, " %iU, // %s", 'kResourceHashes', 'uint32_t',
170 operator.attrgetter('hash'))
171 indices_array = _GenDataArray(
172 hashed_tuples, " %s, // %s", 'kResourceIndices', 'int',
173 operator.attrgetter('index'))
174
175 namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix(
176 namespace)
177
178 return (
179 "// This file was generated by %(script_name)s. Do not edit.\n"
180 "\n"
181 "#include \"%(header_filename)s\"\n\n"
182 "%(namespace_prefix)s"
183 "namespace {\n\n"
184 "const size_t kNumResources = %(num_resources)i;\n\n"
185 "%(hashes_array)s"
186 "\n"
187 "%(indices_array)s"
188 "\n"
189 "} // namespace\n"
190 "\n"
191 "variations::UIStringOverrider CreateUIStringOverrider() {\n"
192 " return variations::UIStringOverrider(\n"
193 " kResourceHashes, kResourceIndices, kNumResources);\n"
194 "}\n"
195 "%(namespace_suffix)s") % {
196 'script_name': SCRIPT_NAME,
197 'header_filename': header_filename,
198 'namespace_prefix': namespace_prefix,
199 'num_resources': len(hashed_tuples),
200 'hashes_array': hashes_array,
201 'indices_array': indices_array,
202 'namespace_suffix': namespace_suffix,
203 }
204
205
206 def _GenerateHeaderFileContent(namespace, header_filename):
207 """Generates the .h for to the .cc generated by _GenerateSourceFileContent.
208
209 Args:
210 namespace: The namespace in which the generated code should be scoped. If
211 not defined, then the code will be in the global namespace.
212
213 header_filename: Path to the corresponding .h. Used to generate the include
214 guards.
215
216 Returns:
217 .cc file content implementing the CreateUIStringOverrider() factory.
218 """
219
220 include_guard = re.sub('[^A-Z]', '_', header_filename.upper()) + '_'
221 namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix(
222 namespace)
223
224 return (
225 "// This file was generated by %(script_name)s. Do not edit.\n"
226 "\n"
227 "#ifndef %(include_guard)s\n"
228 "#define %(include_guard)s\n"
229 "\n"
230 "#include \"components/variations/service/ui_string_overrider.h\"\n\n"
231 "%(namespace_prefix)s"
232 "// Returns an initialized UIStringOverrider.\n"
233 "variations::UIStringOverrider CreateUIStringOverrider();\n"
234 "%(namespace_suffix)s"
235 "\n"
236 "#endif // %(include_guard)s\n"
237 ) % {
238 'script_name': SCRIPT_NAME,
239 'include_guard': include_guard,
240 'namespace_prefix': namespace_prefix,
241 'namespace_suffix': namespace_suffix,
242 }
243
244
245 def main():
246 arg_parser = argparse.ArgumentParser(
247 description="Generate UIStringOverrider factory from resources headers "
248 "generated by grit.")
249 arg_parser.add_argument(
250 "--output_dir", "-o", required=True,
251 help="Base directory to for generated files.")
252 arg_parser.add_argument(
253 "--source_filename", "-S", required=True,
254 help="File name of the generated source file.")
255 arg_parser.add_argument(
256 "--header_filename", "-H", required=True,
257 help="File name of the generated header file.")
258 arg_parser.add_argument(
259 "--namespace", "-N", default="",
260 help="Namespace of the generated factory function (code will be in "
261 "the global namespace if this is omitted).")
262 arg_parser.add_argument(
263 "--test_support", "-t", action="store_true", default=False,
264 help="Make internal variables accessible for testing.")
265 arg_parser.add_argument(
266 "inputs", metavar="FILENAME", nargs="+",
267 help="Path to resources header file generated by grit.")
268 arguments = arg_parser.parse_args()
269
270 generated_resources_h = ""
271 for resources_file in arguments.inputs:
272 with open(resources_file, "r") as resources:
273 generated_resources_h += resources.read()
274
275 if len(generated_resources_h) == 0:
276 raise Error("No content loaded for %s." % (resources_file))
277
278 source_file_content = _GenerateSourceFileContent(
279 generated_resources_h, arguments.namespace, arguments.header_filename)
280 header_file_content = _GenerateHeaderFileContent(
281 arguments.namespace, arguments.header_filename)
282
283 with open(os.path.join(
284 arguments.output_dir, arguments.source_filename), "w") as generated_file:
285 generated_file.write(source_file_content)
286 with open(os.path.join(
287 arguments.output_dir, arguments.header_filename), "w") as generated_file:
288 generated_file.write(header_file_content)
289
290
291 if __name__ == '__main__':
292 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698