Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Unified 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, 3 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 side-by-side diff with in-line comments
Download patch
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())

Powered by Google App Engine
This is Rietveld 408576698