flasklet 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flasklet-0.3.0 → flasklet-0.3.2}/PKG-INFO +1 -1
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/render/block.py +1 -1
- flasklet-0.3.2/flasklet/render/macro.py +94 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/render/partial.py +1 -1
- {flasklet-0.3.0 → flasklet-0.3.2}/pyproject.toml +1 -1
- flasklet-0.3.0/flasklet/render/macro.py +0 -108
- {flasklet-0.3.0 → flasklet-0.3.2}/LICENSE +0 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/README.md +0 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/__init__.py +0 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/blueprints.py +0 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/flashing.py +0 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/imports.py +0 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/py.typed +0 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/render/__init__.py +0 -0
- {flasklet-0.3.0 → flasklet-0.3.2}/flasklet/render/jinja.py +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
5
|
+
|
|
6
|
+
from jinja2 import Template
|
|
7
|
+
from jinja2.nodes import Macro, Name, Output, TemplateData
|
|
8
|
+
from markupsafe import Markup
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from jinja2.nodes import Expr
|
|
12
|
+
|
|
13
|
+
from flasklet.render.jinja import load_jinja_env
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _extract_macro_source(template_source: str, macro_name: str) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Extracts the source code of a macro from the template source code.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Pattern to match the macro definition, including parameters and content
|
|
22
|
+
macro_pattern = re.compile(
|
|
23
|
+
r"{%\s*macro\s+"
|
|
24
|
+
+ re.escape(macro_name)
|
|
25
|
+
+ r"\s*\((.*?)\)\s*%}(.*?)"
|
|
26
|
+
+ r"{%\s*endmacro\s*%}",
|
|
27
|
+
re.DOTALL,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if not (match := macro_pattern.search(template_source)):
|
|
31
|
+
msg = f"Macro '{macro_name}' not found in template."
|
|
32
|
+
raise ValueError(msg)
|
|
33
|
+
|
|
34
|
+
# Return macro source code, including the {% macro %} and {% endmacro %} lines
|
|
35
|
+
return match.group(0)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _flatten_macro_source_nodes(nodes: list[Expr]) -> str:
|
|
39
|
+
"""Flattens the nodes of a macro's body into a single string of source code."""
|
|
40
|
+
res = ""
|
|
41
|
+
for node in nodes:
|
|
42
|
+
if isinstance(node, TemplateData):
|
|
43
|
+
res += node.data
|
|
44
|
+
elif isinstance(node, Name):
|
|
45
|
+
res += r"{{ " + node.name + r" }}"
|
|
46
|
+
else:
|
|
47
|
+
msg = f"Unsupported node type: {type(node)}"
|
|
48
|
+
raise TypeError(msg)
|
|
49
|
+
return res
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def render_macro_unsafe(template_name: str, macro_name: str, **kwargs: Any) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Render a specific macro from a given template.
|
|
55
|
+
It returns unescaped HTML, meaning that it is not safe to use with user input.
|
|
56
|
+
"""
|
|
57
|
+
env, env_loader = load_jinja_env()
|
|
58
|
+
|
|
59
|
+
template_source, _, _ = env_loader.get_source(env, template_name)
|
|
60
|
+
parsed_template = env.parse(template_source)
|
|
61
|
+
macros = parsed_template.find_all(Macro)
|
|
62
|
+
|
|
63
|
+
if not (macro := next((m for m in macros if m.name == macro_name), None)):
|
|
64
|
+
msg = f"Macro {macro_name} not found in template {template_name}"
|
|
65
|
+
raise ValueError(msg)
|
|
66
|
+
|
|
67
|
+
macro_source = _flatten_macro_source_nodes(cast("Output", macro.body[0]).nodes)
|
|
68
|
+
|
|
69
|
+
return Template(macro_source).render(**kwargs, g=env.globals)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def render_macro(template_name: str, macro_name: str, **kwargs: Any) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Render a specific macro from a given template.
|
|
75
|
+
"""
|
|
76
|
+
env, env_loader = load_jinja_env()
|
|
77
|
+
|
|
78
|
+
# Load the template source code
|
|
79
|
+
template_source, _, _ = env_loader.get_source(env, template_name)
|
|
80
|
+
|
|
81
|
+
# Extract the macro source code
|
|
82
|
+
macro_source = _extract_macro_source(template_source, macro_name)
|
|
83
|
+
|
|
84
|
+
# Create a new template that includes the macro and calls it
|
|
85
|
+
macro_call_template_source = macro_source + "\n{{ " + macro_name + "()" + " }}"
|
|
86
|
+
|
|
87
|
+
# Compile the new template
|
|
88
|
+
macro_template = env.from_string(macro_call_template_source)
|
|
89
|
+
|
|
90
|
+
# Render the template with the provided arguments
|
|
91
|
+
rendered = macro_template.render(**kwargs)
|
|
92
|
+
|
|
93
|
+
# Return the rendered macro content
|
|
94
|
+
return Markup(rendered) # noqa: S704
|
|
@@ -4,7 +4,7 @@ from flask import render_template
|
|
|
4
4
|
from markupsafe import Markup
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def render_partial(template_name: str, **context: Any) ->
|
|
7
|
+
def render_partial(template_name: str, **context: Any) -> str:
|
|
8
8
|
"""
|
|
9
9
|
Render a full template as safe markup, for embedding inside another template.
|
|
10
10
|
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
from jinja2 import Template
|
|
4
|
-
from markupsafe import Markup
|
|
5
|
-
|
|
6
|
-
from flasklet.render.jinja import load_jinja_env
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _get_macro(template: Template, macro_name: str, template_name: str) -> Any:
|
|
10
|
-
"""Look up a callable macro on a compiled template, or raise if absent."""
|
|
11
|
-
macro = getattr(template.module, macro_name, None)
|
|
12
|
-
if not callable(macro):
|
|
13
|
-
msg = f"Macro {macro_name!r} not found in template {template_name!r}"
|
|
14
|
-
raise TypeError(msg)
|
|
15
|
-
return macro
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def render_macro(template_name: str, macro_name: str, **kwargs: Any) -> Markup:
|
|
19
|
-
"""
|
|
20
|
-
Render a single macro from a template, calling it with the given arguments.
|
|
21
|
-
|
|
22
|
-
The macro is looked up on the compiled template's module and called with
|
|
23
|
-
`**kwargs` as its parameters, so all Jinja features (filters, expressions,
|
|
24
|
-
control flow, autoescaping) behave exactly as they do in normal rendering.
|
|
25
|
-
Values interpolated inside the macro are HTML-escaped, so this is safe to
|
|
26
|
-
call with untrusted `**kwargs`.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
template_name: Name of the template that defines the macro.
|
|
30
|
-
macro_name: Name of the macro to call.
|
|
31
|
-
**kwargs: Arguments passed to the macro's parameters.
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
The rendered macro output as safe `Markup`.
|
|
35
|
-
|
|
36
|
-
Raises:
|
|
37
|
-
ValueError: If the template has no macro named `macro_name`.
|
|
38
|
-
|
|
39
|
-
Example:
|
|
40
|
-
Template "components/button.html":
|
|
41
|
-
|
|
42
|
-
```jinja
|
|
43
|
-
{% macro button(label, kind="primary") %}
|
|
44
|
-
<button class="{{ kind }}">{{ label }}</button>
|
|
45
|
-
{% endmacro %}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
In Python:
|
|
49
|
-
|
|
50
|
-
>>> render_macro("components/button.html", "button", label="Save")
|
|
51
|
-
Markup('<button class="primary">Save</button>')
|
|
52
|
-
|
|
53
|
-
In a Jinja template:
|
|
54
|
-
|
|
55
|
-
```jinja
|
|
56
|
-
{{ render_macro("components/button.html", "button", label="Save") }}
|
|
57
|
-
```
|
|
58
|
-
"""
|
|
59
|
-
env, _ = load_jinja_env()
|
|
60
|
-
|
|
61
|
-
macro = _get_macro(env.get_template(template_name), macro_name, template_name)
|
|
62
|
-
|
|
63
|
-
return Markup(macro(**kwargs)) # noqa: S704
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def render_macro_unsafe(template_name: str, macro_name: str, **kwargs: Any) -> Markup:
|
|
67
|
-
"""
|
|
68
|
-
Render a single macro with autoescaping disabled.
|
|
69
|
-
|
|
70
|
-
Like `render_macro`, but values interpolated inside the macro are NOT
|
|
71
|
-
HTML-escaped, so the macro can emit raw markup passed in via `**kwargs`. The
|
|
72
|
-
result is still returned as `Markup` so it embeds into a parent template
|
|
73
|
-
without being re-escaped.
|
|
74
|
-
|
|
75
|
-
Because interpolated values are not escaped, this is unsafe to call with
|
|
76
|
-
untrusted `**kwargs`. Prefer `render_macro` unless you specifically need to
|
|
77
|
-
pass through trusted HTML.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
template_name: Name of the template that defines the macro.
|
|
81
|
-
macro_name: Name of the macro to call.
|
|
82
|
-
**kwargs: Arguments passed to the macro's parameters.
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
The rendered macro output as `Markup`, with its contents left unescaped.
|
|
86
|
-
|
|
87
|
-
Raises:
|
|
88
|
-
ValueError: If the template has no macro named `macro_name`.
|
|
89
|
-
|
|
90
|
-
Example:
|
|
91
|
-
Template "components/raw.html":
|
|
92
|
-
|
|
93
|
-
```jinja
|
|
94
|
-
{% macro raw(html) %}<div>{{ html }}</div>{% endmacro %}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
In Python:
|
|
98
|
-
|
|
99
|
-
>>> render_macro_unsafe("components/raw.html", "raw", html="<b>hi</b>")
|
|
100
|
-
Markup('<div><b>hi</b></div>')
|
|
101
|
-
"""
|
|
102
|
-
env, env_loader = load_jinja_env()
|
|
103
|
-
|
|
104
|
-
source, _, _ = env_loader.get_source(env, template_name)
|
|
105
|
-
unsafe_template = env.overlay(autoescape=False).from_string(source)
|
|
106
|
-
macro = _get_macro(unsafe_template, macro_name, template_name)
|
|
107
|
-
|
|
108
|
-
return Markup(macro(**kwargs)) # noqa: S704
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|