import jinja2 import jinja2.filters import re import typing from json_loader import BEnumCollection, BEnum from utils import CKParts import utils class RenderUtils: """Possible used functions for jinja when rendering templates""" TRANTABLE_ESCAPE_STRING: typing.ClassVar[dict[int, str]] = str.maketrans({ "\\": "\\\\", "\t": "\\t", "\b": "\\b", "\n": "\\n", "\r": "\\r", "\f": "\\f", "\"": "\\\"", }) @staticmethod def escape_string(strl: str) -> str: """ Escape string Escape all characters which are invalid in string quote. :param strl: The string need to be escaped. :return: The escaped string. """ return strl.translate(RenderUtils.TRANTABLE_ESCAPE_STRING) TRANTABLE_REMOVE_EOL: typing.ClassVar[dict[int, str]] = str.maketrans({ "\n": "", "\r": "", }) @staticmethod def remove_eol(strl: str) -> str: """ Remove EOL of given string. When rendering code, adding line comment is a common case. However, comment may have EOL. So when adding this to line comment, we need to remove all EOL. """ return strl.translate(RenderUtils.TRANTABLE_REMOVE_EOL) REGEX_PY_TO_LITERAL_NUMBER: typing.ClassVar[re.Pattern] = re.compile("[ulUL]+$") @staticmethod def to_py_num_literal(numstr: str) -> str: """ Convert given string into Python number literal style. Number literal declaration in C++ is different with Python. Previous one allow adding suffix like "UL" to indicate its type, but Python don't allow this. So this function actually just remove "UL" suffix. This function is only served for Python code generation. :param numstr: The captured number. :return: The Python style number string. """ return RenderUtils.REGEX_PY_TO_LITERAL_NUMBER.sub("", numstr, 1) REGEX_PY_EXT_HUMANRDABLE_ENTRY_NAME: typing.ClassVar[re.Pattern] = re.compile("^[a-zA-Z0-9]+_") @staticmethod def underline_to_camel(entry_name: str) -> str: """ Convert given capital underlying name into camel name. This function is only served for Python code generation. As you noticed, almost entries of CK enums are fully capital and splitted by underline. This is really not good for human reading, especially those who are not programmer. So this function will try give these programmer-oriented entry name a human readable name as its display name. However, this extract method is not perfect. It simply do some split and replacement so the generated content may still not good for reader. :param entry_name: The name of enum entry :return: A human readable entry name. No guaranteen that return value is must human readable. """ # remove first part (any content before underline '_') entry_name = RenderUtils.REGEX_PY_EXT_HUMANRDABLE_ENTRY_NAME.sub("", entry_name, 1) # then convert result into camel return ' '.join(map(lambda s: s[0:1].upper() + s[1:].lower(), entry_name.split('_'))) class TemplateRender: """Render templates to code files""" __loader: jinja2.BaseLoader __environment: jinja2.Environment def __init__(self) -> None: self.__loader = jinja2.FileSystemLoader(utils.get_template_directory()) self.__environment = jinja2.Environment(loader=self.__loader) # prepare filters self.__environment.filters['some_or_blank'] = lambda s: "" if s is None else s self.__environment.filters['escape_string'] = lambda s: RenderUtils.escape_string(s) self.__environment.filters['block_comment'] = lambda s, fmt: jinja2.filters.do_indent(s, fmt, True, True) self.__environment.filters['line_comment'] = lambda s: RenderUtils.remove_eol(s) def __render(self, template_name: str, dest_filename: str, payload: BEnumCollection, extra: dict[str, typing.Any] = {}) -> None: # prepare template argument template_argument: dict[str, typing.Any] = { 'payload': payload, 'extra': extra, 'utils': RenderUtils, } # fetch template template = self.__environment.get_template(str(template_name)) # render template and save with open(utils.get_output_file_path(dest_filename), 'w', encoding='utf-8') as f: f.write(template.render(**template_argument)) def __wrap_single_enum(self, benum: BEnum) -> BEnumCollection: return BEnumCollection([benum, ]) # region: C++ Header and Sources def render_cpp_enums(self, filename: str, benums: BEnumCollection) -> None: self.__render('generic.hpp.jinja', filename, benums) def render_cpp_enum(self, filename: str, benum: BEnum) -> None: self.render_cpp_enums(filename, self.__wrap_single_enum(benum)) def render_cpp_enum_docstrings(self, hpp_filename: str, cpp_filename: str, benums: BEnumCollection, parts: CKParts) -> None: self.__render('generic.docstring.hpp.jinja', hpp_filename, benums, {'parts': parts}) self.__render('generic.docstring.cpp.jinja', cpp_filename, benums, {'parts': parts}) def render_cpp_enum_docstring(self, hpp_filename: str, cpp_filename: str, benum: BEnum, parts: CKParts) -> None: self.render_cpp_enum_docstrings(hpp_filename, cpp_filename, self.__wrap_single_enum(benum), parts) def render_cpp_ckerror_docstring(self, hpp_filename: str, cpp_filename: str, benums: BEnum) -> None: self.__render('CKERROR.docstring.hpp.jinja', hpp_filename, self.__wrap_single_enum(benums)) self.__render('CKERROR.docstring.cpp.jinja', cpp_filename, self.__wrap_single_enum(benums)) def render_cpp_ckclassid_docstring(self, hpp_filename: str, cpp_filename: str, benums: BEnum) -> None: self.__render('CK_CLASSID.docstring.hpp.jinja', hpp_filename, self.__wrap_single_enum(benums)) self.__render('CK_CLASSID.docstring.cpp.jinja', cpp_filename, self.__wrap_single_enum(benums)) # endregion # region: Python def render_py_enums(self, filename: str, benums: BEnumCollection) -> None: self.__render('generic.py.jinja', filename, benums) def render_py_enum(self, filename: str, benum: BEnum) -> None: self.render_py_enums(filename, self.__wrap_single_enum(benum)) def render_py_enum_docstrings(self, filename: str, benums: BEnumCollection) -> None: self.__render('generic.docstring.py.jinja', filename, benums) def render_py_enum_docstring(self, filename: str, benum: BEnum) -> None: self.render_py_enum_docstrings(filename, self.__wrap_single_enum(benum)) # endregion # region: C# def render_cs_enums(self, filename: str, benums: BEnumCollection) -> None: self.__render('generic.cs.jinja', filename, benums) def render_cs_enum(self, filename: str, benum: BEnum) -> None: self.render_cs_enums(filename, self.__wrap_single_enum(benum)) def render_cs_enum_docstrings(self, filename: str, benums: BEnumCollection) -> None: self.__render('generic.docstring.cs.jinja', filename, benums) def render_cs_enum_docstring(self, filename: str, benum: BEnum) -> None: self.render_cs_enum_docstrings(filename, self.__wrap_single_enum(benum)) # endregion # region: Rust def render_rs_enums(self, filename: str, benums: BEnumCollection) -> None: self.__render('generic.rs.jinja', filename, benums) def render_rs_enum(self, filename: str, benum: BEnum) -> None: self.render_rs_enums(filename, self.__wrap_single_enum(benum)) def render_rs_enum_docstrings(self, filename: str, benums: BEnumCollection) -> None: self.__render('generic.docstring.rs.jinja', filename, benums) def render_rs_enum_docstring(self, filename: str, benum: BEnum) -> None: self.render_rs_enum_docstrings(filename, self.__wrap_single_enum(benum)) # endregion