1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
#!/usr/bin/python3 -i
#
# Copyright (c) 2019-2021, The Khronos Group Inc.
# Copyright (c) 2019 Collabora, Ltd.
#
# SPDX-License-Identifier: Apache-2.0
"""Provides functionality to use Jinja2 when generating C/C++ code, while eliminating the need to import Jinja2 from any other file."""
import re
from pathlib import Path
_ADDED_TO_PATH = False
def _add_to_path():
global _ADDED_TO_PATH
if not _ADDED_TO_PATH:
import sys
# Find Jinja2 in source tree, as last resort.
sys.path.append(
str(
Path(__file__).resolve().parent.parent.parent / "external" /
"python"))
_ADDED_TO_PATH = True
_WHITESPACE = re.compile(r"[\s\n]+")
def _undecorate(name):
"""Undecorate a name by removing the leading Xr and making it lowercase."""
lower = name.lower()
assert(lower.startswith('xr'))
return lower[2:]
def _quote_string(s):
return '"{}"'.format(s)
def _base_name(name):
return name[2:]
def _collapse_whitespace(s):
return _WHITESPACE.sub(" ", s)
def _protect_begin(entity):
if entity.protect_value:
return "#if {}".format(entity.protect_string)
return ""
def _protect_end(entity):
if entity.protect_value:
return "#endif // {}".format(entity.protect_string)
return ""
def make_jinja_environment(file_with_templates_as_sibs=None, search_path=None):
"""Create a Jinja2 environment customized to generate C/C++ headers/code for Khronos APIs.
Delimiters have been changed from Jinja2 defaults to permit better interoperability with
editors and other tooling expecting C/C++ code, by combining them with comments:
- Blocks are bracketed like /*% block_contents %*/ instead of {% block_contents %}
- Variable outputs are bracketed like /*{ var }*/ instead of {{ var }}
- Line statements start with //# instead of just #
- Line comments start with //## instead of just ##
Other details:
- autoescape is turned off because this isn't HTML.
- trailing newline kept
- blocks are trimmed for easier markup.
- the loader is a file system loader, building a search path from file_with_templates_as_sibs
(if your template is a sibling of your source file, just pass file_with_templates_as_sibs=__file__),
and search_path (an iterable if you want more control)
Provided filters:
- quote_string - wrap something in double-quotes
- undecorate - same as the generator utility function: remove leading two-character API prefix and make lowercase.
- base_name - just removes leading two-character API prefix
- collapse_whitespace - minimizes internal whitespace with a regex
Provided functions used as globals:
- protect_begin(entity) and protect_end(entity) - use in a pair, and pass an entity
(function/command, struct, etc), and if it is noted with protect="something" in the XML,
the appropriate #if and #endif will be printed (for protect_begin and protect_end respectively.)
You may further add globals and filters to the returned environment.
"""
_add_to_path()
from jinja2 import Environment, FileSystemLoader
search_paths = []
if file_with_templates_as_sibs:
search_paths.append(
str(Path(file_with_templates_as_sibs).parent))
if search_path:
search_paths.extend(search_path)
env = Environment(keep_trailing_newline=True,
trim_blocks=True,
block_start_string="/*%",
block_end_string="%*/",
variable_start_string="/*{",
variable_end_string="}*/",
line_statement_prefix="//#",
line_comment_prefix="//##",
autoescape=False,
loader=FileSystemLoader(search_paths))
env.filters['quote_string'] = _quote_string
env.filters['undecorate'] = _undecorate
env.filters['base_name'] = _base_name
env.filters['collapse_whitespace'] = _collapse_whitespace
env.globals['protect_begin'] = _protect_begin
env.globals['protect_end'] = _protect_end
return env
class JinjaTemplate:
def __init__(self, env, fn):
"""Load and parse a Jinja2 template given a Jinja2 environment and the template file name.
Create the environment using make_jinja_environment().
Syntax errors are caught, have their details printed, then are re-raised (to stop execution).
"""
_add_to_path()
from jinja2 import TemplateSyntaxError
try:
self.template = env.get_template(fn)
except TemplateSyntaxError as e:
print("Jinja2 template syntax error during parse: {}:{} error: {}".
format(e.filename, e.lineno, e.message))
raise e
def render(self, *args, **kwargs):
"""Render the Jinja2 template with the provided context.
All arguments are passed through; this just wraps the Jinja2 template render method
to handle syntax error exceptions so that Jinja2 does not need to be imported anywhere
but this file.
"""
_add_to_path()
from jinja2 import TemplateSyntaxError
try:
return self.template.render(*args, **kwargs)
except TemplateSyntaxError as e:
print(
"Jinja2 template syntax error during render: {}:{} error: {}".
format(e.filename, e.lineno, e.message))
raise e
|