| # Copyright 2022 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 collections | 
 | import importlib.util | 
 | import os | 
 | import re | 
 | import sys | 
 | from typing import Dict, List | 
 |  | 
 | from json_data_generator.util import (GetDirNameFromPath, GetFileNameFromPath, | 
 |                                       GetFileNameWithoutExtensionFromPath, | 
 |                                       JoinPath) | 
 |  | 
 | _FILE_PATH = os.path.dirname(os.path.realpath(__file__)) | 
 |  | 
 | _JSON5_PATH = os.path.join(_FILE_PATH, os.pardir, os.pardir, 'third_party', | 
 |                            'pyjson5', 'src') | 
 | sys.path.insert(1, _JSON5_PATH) | 
 | import json5 | 
 |  | 
 | _JINJA2_PATH = os.path.join(_FILE_PATH, os.pardir, os.pardir, 'third_party') | 
 | sys.path.insert(1, _JINJA2_PATH) | 
 | import jinja2 | 
 |  | 
 |  | 
 | class JSONDataGenerator(object): | 
 |     '''A generic json data generator.''' | 
 |  | 
 |     def __init__(self, out_dir: str): | 
 |         self.out_dir = out_dir | 
 |         self.model: Dict = {} | 
 |         # Store all json sources used in the generator. | 
 |         self.sources: List[str] = list() | 
 |  | 
 |     def AddJSONFilesToModel(self, paths: List[str]): | 
 |         '''Adds one or more JSON files to the model.''' | 
 |         for path in paths: | 
 |             try: | 
 |                 with open(path, 'r') as f: | 
 |                     self.AddJSONToModel(path, f.read()) | 
 |                     self.sources.append(path) | 
 |             except ValueError as err: | 
 |                 raise ValueError('\n%s:\n    %s' % (path, err)) | 
 |  | 
 |     def AddJSONToModel(self, json_path: str, json_string: str): | 
 |         '''Adds a |json_string| with data to the model. | 
 |         Every json file is added to |self.model| with the original file | 
 |         name as the key. | 
 |         ''' | 
 |         data = json5.loads(json_string, | 
 |                            object_pairs_hook=collections.OrderedDict) | 
 |         # Use the json file name as the key of the loaded json data. | 
 |         key = GetFileNameWithoutExtensionFromPath(json_path) | 
 |         self.model[key] = data | 
 |  | 
 |     def GetGlobals(self, template_path: str): | 
 |         file_name_without_ext = GetFileNameWithoutExtensionFromPath( | 
 |             template_path) | 
 |         out_file_path = JoinPath(self.out_dir, file_name_without_ext) | 
 |         return { | 
 |             'model': self.model, | 
 |             'source_json_files': self.sources, | 
 |             'out_file_path': out_file_path, | 
 |         } | 
 |  | 
 |     def GetFilters(self): | 
 |         return { | 
 |             'to_header_guard': self._ToHeaderGuard, | 
 |         } | 
 |  | 
 |     def RenderTemplate(self, | 
 |                        path_to_template: str, | 
 |                        path_to_template_helper: str = None): | 
 |         template_dir = GetDirNameFromPath(path_to_template) | 
 |         template_name = GetFileNameFromPath(path_to_template) | 
 |         jinja_env = jinja2.Environment( | 
 |             loader=jinja2.FileSystemLoader(template_dir), | 
 |             keep_trailing_newline=True) | 
 |         jinja_env.globals.update(self.GetGlobals(path_to_template)) | 
 |         jinja_env.filters.update(self.GetFilters()) | 
 |         if path_to_template_helper: | 
 |             template_helper_module = self._LoadTemplateHelper( | 
 |                 path_to_template_helper) | 
 |             jinja_env.globals.update( | 
 |                 template_helper_module.get_custom_globals(self.model)) | 
 |             jinja_env.filters.update( | 
 |                 template_helper_module.get_custom_filters(self.model)) | 
 |         template = jinja_env.get_template(template_name) | 
 |         return template.render() | 
 |  | 
 |     def _LoadTemplateHelper(self, path_to_template_helper: str): | 
 |         template_helper_dir = GetDirNameFromPath(path_to_template_helper) | 
 |         try: | 
 |             sys.path.append(template_helper_dir) | 
 |             spec = importlib.util.spec_from_file_location( | 
 |                 path_to_template_helper, path_to_template_helper) | 
 |             module = importlib.util.module_from_spec(spec) | 
 |             spec.loader.exec_module(module) | 
 |             return module | 
 |         finally: | 
 |             # Restore sys.path to what it was before. | 
 |             sys.path.remove(template_helper_dir) | 
 |  | 
 |     def _ToHeaderGuard(self, path: str): | 
 |         return re.sub(r'[\/\.\-]+', '_', path.upper()) |