Index: components/variations/service/generate_ui_string_overrider.py |
diff --git a/components/variations/service/generate_ui_string_overrider.py b/components/variations/service/generate_ui_string_overrider.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..288272cfb5657288af381e752efb2be324298a83 |
--- /dev/null |
+++ b/components/variations/service/generate_ui_string_overrider.py |
@@ -0,0 +1,292 @@ |
+#!/usr/bin/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 argparse |
+import collections |
+import hashlib |
+import operator |
+import os |
+import re |
+import sys |
+ |
+SCRIPT_NAME = "generate_ui_string_overrider.py" |
+RESOURCE_EXTRACT_REGEX = re.compile('^#define (\S*) (\d*)$', re.MULTILINE) |
+ |
+class Error(Exception): |
+ """Base error class for all exceptions in generated_resources_map.""" |
+ |
+ |
+class HashCollisionError(Error): |
+ """Multiple resource names hash to the same value.""" |
+ |
+ |
+Resource = collections.namedtuple("Resource", ['hash', 'name', 'index']) |
+ |
+ |
+def _HashName(name): |
+ """Returns the hash id for a name. |
+ |
+ Args: |
+ name: The name to hash. |
+ |
+ Returns: |
+ An int that is at most 32 bits. |
+ """ |
+ md5hash = hashlib.md5() |
+ md5hash.update(name) |
+ return int(md5hash.hexdigest()[:8], 16) |
+ |
+ |
+def _GetNameIndexPairsIter(string_to_scan): |
+ """Gets an iterator of the resource name and index pairs of the given string. |
+ |
+ Scans the input string for lines of the form "#define NAME INDEX" and returns |
+ an iterator over all matching (NAME, INDEX) pairs. |
+ |
+ Args: |
+ string_to_scan: The input string to scan. |
+ |
+ Yields: |
+ A tuple of name and index. |
+ """ |
+ for match in RESOURCE_EXTRACT_REGEX.finditer(string_to_scan): |
+ yield match.group(1, 2) |
+ |
+ |
+def _GetResourceListFromString(resources_content): |
+ """Produces a list of |Resource| objects from a string. |
+ |
+ The input string contains lines of the form "#define NAME INDEX". The returned |
+ list is sorted primarily by hash, then name, and then index. |
+ |
+ Args: |
+ resources_content: The input string to process, contains lines of the form |
+ "#define NAME INDEX". |
+ |
+ Returns: |
+ A sorted list of |Resource| objects. |
+ """ |
+ resources = [Resource(_HashName(name), name, index) for name, index in |
+ _GetNameIndexPairsIter(resources_content)] |
+ |
+ # The default |Resource| order makes |resources| sorted by the hash, then |
+ # name, then index. |
+ resources.sort() |
+ |
+ return resources |
+ |
+ |
+def _CheckForHashCollisions(sorted_resource_list): |
+ """Checks a sorted list of |Resource| objects for hash collisions. |
+ |
+ Args: |
+ sorted_resource_list: A sorted list of |Resource| objects. |
+ |
+ Returns: |
+ A set of all |Resource| objects with collisions. |
+ """ |
+ collisions = set() |
+ for i in xrange(len(sorted_resource_list) - 1): |
+ resource = sorted_resource_list[i] |
+ next_resource = sorted_resource_list[i+1] |
+ if resource.hash == next_resource.hash: |
+ collisions.add(resource) |
+ collisions.add(next_resource) |
+ |
+ return collisions |
+ |
+ |
+def _GenDataArray( |
+ resources, entry_pattern, array_name, array_type, data_getter): |
+ """Generates a C++ statement defining a literal array containing the hashes. |
+ |
+ Args: |
+ resources: A sorted list of |Resource| objects. |
+ entry_pattern: A pattern to be used to generate each entry in the array. The |
+ pattern is expected to have a place for data and one for a comment, in |
+ that order. |
+ array_name: The name of the array being generated. |
+ array_type: The type of the array being generated. |
+ data_getter: A function that gets the array data from a |Resource| object. |
+ |
+ Returns: |
+ A string containing a C++ statement defining the an array. |
+ """ |
+ lines = [entry_pattern % (data_getter(r), r.name) for r in resources] |
+ pattern = """const %(type)s %(name)s[] = { |
+%(content)s |
+}; |
+""" |
+ return pattern % {'type': array_type, |
+ 'name': array_name, |
+ 'content': '\n'.join(lines)} |
+ |
+ |
+def _GenerateNamespacePrefixAndSuffix(namespace): |
+ """Generates the namespace prefix and suffix for |namespace|. |
+ |
+ Args: |
+ namespace: A string corresponding to the namespace name. May be empty. |
+ |
+ Returns: |
+ A tuple of strings corresponding to the namespace prefix and suffix for |
+ putting the code in the corresponding namespace in C++. If namespace is |
+ the empty string, both returned strings are empty too. |
+ """ |
+ if not namespace: |
+ return "", "" |
+ return "namespace %s {\n\n" % namespace, "\n} // namespace %s\n" % namespace |
+ |
+ |
+def _GenerateSourceFileContent(resources_content, namespace, header_filename): |
+ """Generates the .cc content from the given generated grit headers content. |
+ |
+ Args: |
+ resources_content: The input string to process, contains lines of the form |
+ "#define NAME INDEX". |
+ |
+ namespace: The namespace in which the generated code should be scoped. If |
+ not defined, then the code will be in the global namespace. |
+ |
+ header_filename: Path to the corresponding .h. |
+ |
+ Returns: |
+ .cc file content implementing the CreateUIStringOverrider() factory. |
+ """ |
+ hashed_tuples = _GetResourceListFromString(resources_content) |
+ |
+ collisions = _CheckForHashCollisions(hashed_tuples) |
+ if collisions: |
+ error_message = "\n".join( |
+ ["hash: %i, name: %s" % (i[0], i[1]) for i in sorted(collisions)]) |
+ error_message = ("\nThe following names had hash collisions " |
+ "(sorted by the hash value):\n%s\n" %(error_message)) |
+ raise HashCollisionError(error_message) |
+ |
+ hashes_array = _GenDataArray( |
+ hashed_tuples, " %iU, // %s", 'kResourceHashes', 'uint32_t', |
+ operator.attrgetter('hash')) |
+ indices_array = _GenDataArray( |
+ hashed_tuples, " %s, // %s", 'kResourceIndices', 'int', |
+ operator.attrgetter('index')) |
+ |
+ namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix( |
+ namespace) |
+ |
+ return ( |
+ "// This file was generated by %(script_name)s. Do not edit.\n" |
+ "\n" |
+ "#include \"%(header_filename)s\"\n\n" |
+ "%(namespace_prefix)s" |
+ "namespace {\n\n" |
+ "const size_t kNumResources = %(num_resources)i;\n\n" |
+ "%(hashes_array)s" |
+ "\n" |
+ "%(indices_array)s" |
+ "\n" |
+ "} // namespace\n" |
+ "\n" |
+ "variations::UIStringOverrider CreateUIStringOverrider() {\n" |
+ " return variations::UIStringOverrider(\n" |
+ " kResourceHashes, kResourceIndices, kNumResources);\n" |
+ "}\n" |
+ "%(namespace_suffix)s") % { |
+ 'script_name': SCRIPT_NAME, |
+ 'header_filename': header_filename, |
+ 'namespace_prefix': namespace_prefix, |
+ 'num_resources': len(hashed_tuples), |
+ 'hashes_array': hashes_array, |
+ 'indices_array': indices_array, |
+ 'namespace_suffix': namespace_suffix, |
+ } |
+ |
+ |
+def _GenerateHeaderFileContent(namespace, header_filename): |
+ """Generates the .h for to the .cc generated by _GenerateSourceFileContent. |
+ |
+ Args: |
+ namespace: The namespace in which the generated code should be scoped. If |
+ not defined, then the code will be in the global namespace. |
+ |
+ header_filename: Path to the corresponding .h. Used to generate the include |
+ guards. |
+ |
+ Returns: |
+ .cc file content implementing the CreateUIStringOverrider() factory. |
+ """ |
+ |
+ include_guard = re.sub('[^A-Z]', '_', header_filename.upper()) + '_' |
+ namespace_prefix, namespace_suffix = _GenerateNamespacePrefixAndSuffix( |
+ namespace) |
+ |
+ return ( |
+ "// This file was generated by %(script_name)s. Do not edit.\n" |
+ "\n" |
+ "#ifndef %(include_guard)s\n" |
+ "#define %(include_guard)s\n" |
+ "\n" |
+ "#include \"components/variations/service/ui_string_overrider.h\"\n\n" |
+ "%(namespace_prefix)s" |
+ "// Returns an initialized UIStringOverrider.\n" |
+ "variations::UIStringOverrider CreateUIStringOverrider();\n" |
+ "%(namespace_suffix)s" |
+ "\n" |
+ "#endif // %(include_guard)s\n" |
+ ) % { |
+ 'script_name': SCRIPT_NAME, |
+ 'include_guard': include_guard, |
+ 'namespace_prefix': namespace_prefix, |
+ 'namespace_suffix': namespace_suffix, |
+ } |
+ |
+ |
+def main(): |
+ arg_parser = argparse.ArgumentParser( |
+ description="Generate UIStringOverrider factory from resources headers " |
+ "generated by grit.") |
+ arg_parser.add_argument( |
+ "--output_dir", "-o", required=True, |
+ help="Base directory to for generated files.") |
+ arg_parser.add_argument( |
+ "--source_filename", "-S", required=True, |
+ help="File name of the generated source file.") |
+ arg_parser.add_argument( |
+ "--header_filename", "-H", required=True, |
+ help="File name of the generated header file.") |
+ arg_parser.add_argument( |
+ "--namespace", "-N", default="", |
+ help="Namespace of the generated factory function (code will be in " |
+ "the global namespace if this is omitted).") |
+ arg_parser.add_argument( |
+ "--test_support", "-t", action="store_true", default=False, |
+ help="Make internal variables accessible for testing.") |
+ arg_parser.add_argument( |
+ "inputs", metavar="FILENAME", nargs="+", |
+ help="Path to resources header file generated by grit.") |
+ arguments = arg_parser.parse_args() |
+ |
+ generated_resources_h = "" |
+ for resources_file in arguments.inputs: |
+ with open(resources_file, "r") as resources: |
+ generated_resources_h += resources.read() |
+ |
+ if len(generated_resources_h) == 0: |
+ raise Error("No content loaded for %s." % (resources_file)) |
+ |
+ source_file_content = _GenerateSourceFileContent( |
+ generated_resources_h, arguments.namespace, arguments.header_filename) |
+ header_file_content = _GenerateHeaderFileContent( |
+ arguments.namespace, arguments.header_filename) |
+ |
+ with open(os.path.join( |
+ arguments.output_dir, arguments.source_filename), "w") as generated_file: |
+ generated_file.write(source_file_content) |
+ with open(os.path.join( |
+ arguments.output_dir, arguments.header_filename), "w") as generated_file: |
+ generated_file.write(header_file_content) |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |