# Copyright 2022 The Chromium Authors
# 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', encoding='utf-8') 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())