# 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())