File: jinja_helpers.py

package info (click to toggle)
openxr-sdk-source 1.0.14~dfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 6,564 kB
  • sloc: python: 16,103; cpp: 12,052; ansic: 8,813; xml: 3,480; sh: 410; makefile: 338; ruby: 247
file content (160 lines) | stat: -rw-r--r-- 5,528 bytes parent folder | download | duplicates (2)
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