text: # Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
  # reserved. Use of this source code is governed by a BSD-style license that
  # can be found in the LICENSE file.
  
  from __future__ import absolute_import
  from __future__ import print_function
  from file_util import *
  import os
  import re
  import shutil
  import string
  import sys
  import textwrap
  import time
  import itertools
  import hashlib
  
  
  class cef_api_hash:
    """ CEF API hash calculator """
  
    def __init__(self, headerdir, debugdir=None, verbose=False):
      if headerdir is None or len(headerdir) == 0:
        raise AssertionError("headerdir is not specified")
  
      self.__headerdir = headerdir
      self.__debugdir = debugdir
      self.__verbose = verbose
      self.__debug_enabled = not (self.__debugdir is
                                  None) and len(self.__debugdir) > 0
  
      self.platforms = ["windows", "macosx", "linux"]
  
      self.platform_files = {
          # List of includes_win_capi from cef_paths2.gypi.
          "windows": [
              "internal/cef_types_win.h",
          ],
          # List of includes_mac_capi from cef_paths2.gypi.
          "macosx": [
              "internal/cef_types_mac.h",
          ],
          # List of includes_linux_capi from cef_paths2.gypi.
          "linux": [
              "internal/cef_types_linux.h",
          ]
      }
  
      self.included_files = []
  
      # List of include/ and include/internal/ files from cef_paths2.gypi.
      self.excluded_files = [
          # includes_common
          "cef_api_hash.h",
          "cef_base.h",
          "cef_config.h",
          "cef_version.h",
          "internal/cef_export.h",
          "internal/cef_ptr.h",
          "internal/cef_string_wrappers.h",
          "internal/cef_types_wrappers.h",
          # includes_win
          "cef_sandbox_win.h",
          "internal/cef_win.h",
          # includes_mac
          "cef_application_mac.h",
          "cef_sandbox_mac.h",
          "internal/cef_mac.h",
          # includes_linux
          "internal/cef_linux.h",
      ]
  
    def calculate(self):
      filenames = [
          filename for filename in self.__get_filenames()
          if not filename in self.excluded_files
      ]
  
      objects = []
      for filename in filenames:
        if self.__verbose:
          print("Processing " + filename + "...")
        content = read_file(os.path.join(self.__headerdir, filename), True)
        platforms = list([
            p for p in self.platforms if self.__is_platform_filename(filename, p)
        ])
  
        # Parse cef_string.h happens in special case: grab only defined CEF_STRING_TYPE_xxx declaration
        content_objects = None
        if filename == "internal/cef_string.h":
          content_objects = self.__parse_string_type(content)
        else:
          content_objects = self.__parse_objects(content)
  
        for o in content_objects:
          o["text"] = self.__prepare_text(o["text"])
          o["platforms"] = platforms
          o["filename"] = filename
          objects.append(o)
  
      # objects will be sorted including filename, to make stable universal hashes
      objects = sorted(objects, key=lambda o: o["name"] + "@" + o["filename"])
  
      if self.__debug_enabled:
        namelen = max([len(o["name"]) for o in objects])
        filenamelen = max([len(o["filename"]) for o in objects])
        dumpsig = []
        for o in objects:
          dumpsig.append(
              format(o["name"], str(namelen) + "s") + "|" + format(
                  o["filename"], "" + str(filenamelen) + "s") + "|" + o["text"])
        self.__write_debug_file("objects.txt", dumpsig)
  
      revisions = {}
  
      for platform in itertools.chain(["universal"], self.platforms):
        sig = self.__get_final_sig(objects, platform)
        if self.__debug_enabled:
          self.__write_debug_file(platform + ".sig", sig)
        revstr = hashlib.sha1(sig.encode('utf-8')).hexdigest()
        revisions[platform] = revstr
  
      return revisions
  
    def __parse_objects(self, content):
      """ Returns array of objects in content file. """
      objects = []
      content = re.sub("//.*\n", "", content)
  
      # function declarations
      for m in re.finditer(
          "\nCEF_EXPORT\s+?.*?\s+?(\w+)\s*?\(.*?\)\s*?;",
          content,
          flags=re.DOTALL):
        object = {"name": m.group(1), "text": m.group(0).strip()}
        objects.append(object)
  
      # structs
      for m in re.finditer(
          "\ntypedef\s+?struct\s+?(\w+)\s+?\{.*?\}\s+?(\w+)\s*?;",
          content,
          flags=re.DOTALL):
        object = {"name": m.group(2), "text": m.group(0).strip()}
        objects.append(object)
  
      # enums
      for m in re.finditer(
          "\nenum\s+?(\w+)\s+?\{.*?\}\s*?;", content, flags=re.DOTALL):
        object = {"name": m.group(1), "text": m.group(0).strip()}
        objects.append(object)
  
      # typedefs
      for m in re.finditer("\ntypedef\s+?.*?\s+(\w+);", content, flags=0):
        object = {"name": m.group(1), "text": m.group(0).strip()}
        objects.append(object)
  
      return objects
  
    def __parse_string_type(self, content):
      """ Grab defined CEF_STRING_TYPE_xxx """
      objects = []
      for m in re.finditer(
          "\n\s*?#\s*?define\s+?(CEF_STRING_TYPE_\w+)\s+?.*?\n", content,
          flags=0):
        object = {
            "name": m.group(1),
            "text": m.group(0),
        }
        objects.append(object)
      return objects
  
    def __prepare_text(self, text):
      text = text.strip()
      text = re.sub("\s+", " ", text)
      text = re.sub("\(\s+", "(", text)
      return text
  
    def __get_final_sig(self, objects, platform):
      sig = []
  
      for o in objects:
        if platform == "universal" or platform in o["platforms"]:
          sig.append(o["text"])
  
      return "\n".join(sig)
  
    def __get_filenames(self):
      """ Returns file names to be processed, relative to headerdir """
      headers = [
          os.path.join(self.__headerdir, filename)
          for filename in self.included_files
      ]
      headers = itertools.chain(
          headers, get_files(os.path.join(self.__headerdir, "capi", "*.h")))
      headers = itertools.chain(
          headers, get_files(os.path.join(self.__headerdir, "internal", "*.h")))
  
      for v in self.platform_files.values():
        headers = itertools.chain(headers,
                                  [os.path.join(self.__headerdir, f) for f in v])
  
      normalized = [
          os.path.relpath(filename, self.__headerdir) for filename in headers
      ]
      normalized = [f.replace('\\', '/').lower() for f in normalized]
  
      return list(set(normalized))
  
    def __is_platform_filename(self, filename, platform):
      if platform == "universal":
        return True
      if not platform in self.platform_files:
        return False
      listed = False
      for p in self.platforms:
        if filename in self.platform_files[p]:
          if p == platform:
            return True
          else:
            listed = True
      return not listed
  
    def __write_debug_file(self, filename, content):
      make_dir(self.__debugdir)
      outfile = os.path.join(self.__debugdir, filename)
      dir = os.path.dirname(outfile)
      make_dir(dir)
      if not isinstance(content, basestring):
        content = "\n".join(content)
      write_file(outfile, content)
  
  
  if __name__ == "__main__":
    from optparse import OptionParser
    import time
  
    disc = """
      This utility calculates CEF API hash.
      """
  
    parser = OptionParser(description=disc)
    parser.add_option(
        '--cpp-header-dir',
        dest='cppheaderdir',
        metavar='DIR',
        help='input directory for C++ header files [required]')
    parser.add_option(
        '--debug-dir',
        dest='debugdir',
        metavar='DIR',
        help='intermediate directory for easy debugging')
    parser.add_option(
        '-v',
        '--verbose',
        action='store_true',
        dest='verbose',
        default=False,
        help='output detailed status information')
    (options, args) = parser.parse_args()
  
    # the cppheader option is required
    if options.cppheaderdir is None:
      parser.print_help(sys.stdout)
      sys.exit()
  
    # calculate
    c_start_time = time.time()
  
    calc = cef_api_hash(options.cppheaderdir, options.debugdir, options.verbose)
    revisions = calc.calculate()
  
    c_completed_in = time.time() - c_start_time
  
    print("{")
    for k in sorted(revisions.keys()):
      print(format("\"" + k + "\"", ">12s") + ": \"" + revisions[k] + "\"")
    print("}")
    # print
    # print 'Completed in: ' + str(c_completed_in)
    # print
  
    # print "Press any key to continue...";
    # sys.stdin.readline();
  