2ebf362ced2ed2d371063d42fcd330b8d8f4dfd54da93eab551b86860bb15b4e
Browse files- lib/python3.11/site-packages/jinja2/__pycache__/idtracking.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/lexer.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/loaders.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/meta.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/nativetypes.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/nodes.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/optimizer.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/parser.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/runtime.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/sandbox.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/tests.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/utils.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/__pycache__/visitor.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/jinja2/_identifier.py +6 -0
- lib/python3.11/site-packages/jinja2/async_utils.py +84 -0
- lib/python3.11/site-packages/jinja2/bccache.py +406 -0
- lib/python3.11/site-packages/jinja2/compiler.py +1957 -0
- lib/python3.11/site-packages/jinja2/constants.py +20 -0
- lib/python3.11/site-packages/jinja2/debug.py +191 -0
- lib/python3.11/site-packages/jinja2/defaults.py +48 -0
- lib/python3.11/site-packages/jinja2/environment.py +1667 -0
- lib/python3.11/site-packages/jinja2/exceptions.py +166 -0
- lib/python3.11/site-packages/jinja2/ext.py +859 -0
- lib/python3.11/site-packages/jinja2/filters.py +1840 -0
- lib/python3.11/site-packages/jinja2/idtracking.py +318 -0
- lib/python3.11/site-packages/jinja2/lexer.py +866 -0
- lib/python3.11/site-packages/jinja2/loaders.py +661 -0
- lib/python3.11/site-packages/jinja2/meta.py +111 -0
- lib/python3.11/site-packages/jinja2/nativetypes.py +130 -0
- lib/python3.11/site-packages/jinja2/nodes.py +1204 -0
- lib/python3.11/site-packages/jinja2/optimizer.py +47 -0
- lib/python3.11/site-packages/jinja2/parser.py +1032 -0
- lib/python3.11/site-packages/jinja2/py.typed +0 -0
- lib/python3.11/site-packages/jinja2/runtime.py +1053 -0
- lib/python3.11/site-packages/jinja2/sandbox.py +428 -0
- lib/python3.11/site-packages/jinja2/tests.py +255 -0
- lib/python3.11/site-packages/jinja2/utils.py +755 -0
- lib/python3.11/site-packages/jinja2/visitor.py +92 -0
- lib/python3.11/site-packages/llvmlite/__init__.py +3 -0
- lib/python3.11/site-packages/llvmlite/__pycache__/__init__.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/llvmlite/__pycache__/_version.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/llvmlite/__pycache__/utils.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/llvmlite/_version.py +11 -0
- lib/python3.11/site-packages/llvmlite/binding/__init__.py +17 -0
- lib/python3.11/site-packages/llvmlite/binding/__pycache__/__init__.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/llvmlite/binding/__pycache__/analysis.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/llvmlite/binding/__pycache__/common.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/llvmlite/binding/__pycache__/context.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/llvmlite/binding/__pycache__/dylib.cpython-311.pyc +0 -0
- lib/python3.11/site-packages/llvmlite/binding/__pycache__/executionengine.cpython-311.pyc +0 -0
lib/python3.11/site-packages/jinja2/__pycache__/idtracking.cpython-311.pyc
ADDED
Binary file (19.6 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/lexer.cpython-311.pyc
ADDED
Binary file (35.7 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/loaders.cpython-311.pyc
ADDED
Binary file (33.1 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/meta.cpython-311.pyc
ADDED
Binary file (5.73 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/nativetypes.cpython-311.pyc
ADDED
Binary file (8 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/nodes.cpython-311.pyc
ADDED
Binary file (64.5 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/optimizer.cpython-311.pyc
ADDED
Binary file (2.89 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/parser.cpython-311.pyc
ADDED
Binary file (59.3 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/runtime.cpython-311.pyc
ADDED
Binary file (50.7 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/sandbox.cpython-311.pyc
ADDED
Binary file (18.9 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/tests.cpython-311.pyc
ADDED
Binary file (9.28 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/utils.cpython-311.pyc
ADDED
Binary file (37.1 kB). View file
|
|
lib/python3.11/site-packages/jinja2/__pycache__/visitor.cpython-311.pyc
ADDED
Binary file (5.75 kB). View file
|
|
lib/python3.11/site-packages/jinja2/_identifier.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
|
3 |
+
# generated by scripts/generate_identifier_pattern.py
|
4 |
+
pattern = re.compile(
|
5 |
+
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
|
6 |
+
)
|
lib/python3.11/site-packages/jinja2/async_utils.py
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import inspect
|
2 |
+
import typing as t
|
3 |
+
from functools import WRAPPER_ASSIGNMENTS
|
4 |
+
from functools import wraps
|
5 |
+
|
6 |
+
from .utils import _PassArg
|
7 |
+
from .utils import pass_eval_context
|
8 |
+
|
9 |
+
V = t.TypeVar("V")
|
10 |
+
|
11 |
+
|
12 |
+
def async_variant(normal_func): # type: ignore
|
13 |
+
def decorator(async_func): # type: ignore
|
14 |
+
pass_arg = _PassArg.from_obj(normal_func)
|
15 |
+
need_eval_context = pass_arg is None
|
16 |
+
|
17 |
+
if pass_arg is _PassArg.environment:
|
18 |
+
|
19 |
+
def is_async(args: t.Any) -> bool:
|
20 |
+
return t.cast(bool, args[0].is_async)
|
21 |
+
|
22 |
+
else:
|
23 |
+
|
24 |
+
def is_async(args: t.Any) -> bool:
|
25 |
+
return t.cast(bool, args[0].environment.is_async)
|
26 |
+
|
27 |
+
# Take the doc and annotations from the sync function, but the
|
28 |
+
# name from the async function. Pallets-Sphinx-Themes
|
29 |
+
# build_function_directive expects __wrapped__ to point to the
|
30 |
+
# sync function.
|
31 |
+
async_func_attrs = ("__module__", "__name__", "__qualname__")
|
32 |
+
normal_func_attrs = tuple(set(WRAPPER_ASSIGNMENTS).difference(async_func_attrs))
|
33 |
+
|
34 |
+
@wraps(normal_func, assigned=normal_func_attrs)
|
35 |
+
@wraps(async_func, assigned=async_func_attrs, updated=())
|
36 |
+
def wrapper(*args, **kwargs): # type: ignore
|
37 |
+
b = is_async(args)
|
38 |
+
|
39 |
+
if need_eval_context:
|
40 |
+
args = args[1:]
|
41 |
+
|
42 |
+
if b:
|
43 |
+
return async_func(*args, **kwargs)
|
44 |
+
|
45 |
+
return normal_func(*args, **kwargs)
|
46 |
+
|
47 |
+
if need_eval_context:
|
48 |
+
wrapper = pass_eval_context(wrapper)
|
49 |
+
|
50 |
+
wrapper.jinja_async_variant = True
|
51 |
+
return wrapper
|
52 |
+
|
53 |
+
return decorator
|
54 |
+
|
55 |
+
|
56 |
+
_common_primitives = {int, float, bool, str, list, dict, tuple, type(None)}
|
57 |
+
|
58 |
+
|
59 |
+
async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V":
|
60 |
+
# Avoid a costly call to isawaitable
|
61 |
+
if type(value) in _common_primitives:
|
62 |
+
return t.cast("V", value)
|
63 |
+
|
64 |
+
if inspect.isawaitable(value):
|
65 |
+
return await t.cast("t.Awaitable[V]", value)
|
66 |
+
|
67 |
+
return t.cast("V", value)
|
68 |
+
|
69 |
+
|
70 |
+
async def auto_aiter(
|
71 |
+
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
72 |
+
) -> "t.AsyncIterator[V]":
|
73 |
+
if hasattr(iterable, "__aiter__"):
|
74 |
+
async for item in t.cast("t.AsyncIterable[V]", iterable):
|
75 |
+
yield item
|
76 |
+
else:
|
77 |
+
for item in t.cast("t.Iterable[V]", iterable):
|
78 |
+
yield item
|
79 |
+
|
80 |
+
|
81 |
+
async def auto_to_list(
|
82 |
+
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
83 |
+
) -> t.List["V"]:
|
84 |
+
return [x async for x in auto_aiter(value)]
|
lib/python3.11/site-packages/jinja2/bccache.py
ADDED
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""The optional bytecode cache system. This is useful if you have very
|
2 |
+
complex template situations and the compilation of all those templates
|
3 |
+
slows down your application too much.
|
4 |
+
|
5 |
+
Situations where this is useful are often forking web applications that
|
6 |
+
are initialized on the first request.
|
7 |
+
"""
|
8 |
+
import errno
|
9 |
+
import fnmatch
|
10 |
+
import marshal
|
11 |
+
import os
|
12 |
+
import pickle
|
13 |
+
import stat
|
14 |
+
import sys
|
15 |
+
import tempfile
|
16 |
+
import typing as t
|
17 |
+
from hashlib import sha1
|
18 |
+
from io import BytesIO
|
19 |
+
from types import CodeType
|
20 |
+
|
21 |
+
if t.TYPE_CHECKING:
|
22 |
+
import typing_extensions as te
|
23 |
+
from .environment import Environment
|
24 |
+
|
25 |
+
class _MemcachedClient(te.Protocol):
|
26 |
+
def get(self, key: str) -> bytes:
|
27 |
+
...
|
28 |
+
|
29 |
+
def set(self, key: str, value: bytes, timeout: t.Optional[int] = None) -> None:
|
30 |
+
...
|
31 |
+
|
32 |
+
|
33 |
+
bc_version = 5
|
34 |
+
# Magic bytes to identify Jinja bytecode cache files. Contains the
|
35 |
+
# Python major and minor version to avoid loading incompatible bytecode
|
36 |
+
# if a project upgrades its Python version.
|
37 |
+
bc_magic = (
|
38 |
+
b"j2"
|
39 |
+
+ pickle.dumps(bc_version, 2)
|
40 |
+
+ pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2)
|
41 |
+
)
|
42 |
+
|
43 |
+
|
44 |
+
class Bucket:
|
45 |
+
"""Buckets are used to store the bytecode for one template. It's created
|
46 |
+
and initialized by the bytecode cache and passed to the loading functions.
|
47 |
+
|
48 |
+
The buckets get an internal checksum from the cache assigned and use this
|
49 |
+
to automatically reject outdated cache material. Individual bytecode
|
50 |
+
cache subclasses don't have to care about cache invalidation.
|
51 |
+
"""
|
52 |
+
|
53 |
+
def __init__(self, environment: "Environment", key: str, checksum: str) -> None:
|
54 |
+
self.environment = environment
|
55 |
+
self.key = key
|
56 |
+
self.checksum = checksum
|
57 |
+
self.reset()
|
58 |
+
|
59 |
+
def reset(self) -> None:
|
60 |
+
"""Resets the bucket (unloads the bytecode)."""
|
61 |
+
self.code: t.Optional[CodeType] = None
|
62 |
+
|
63 |
+
def load_bytecode(self, f: t.BinaryIO) -> None:
|
64 |
+
"""Loads bytecode from a file or file like object."""
|
65 |
+
# make sure the magic header is correct
|
66 |
+
magic = f.read(len(bc_magic))
|
67 |
+
if magic != bc_magic:
|
68 |
+
self.reset()
|
69 |
+
return
|
70 |
+
# the source code of the file changed, we need to reload
|
71 |
+
checksum = pickle.load(f)
|
72 |
+
if self.checksum != checksum:
|
73 |
+
self.reset()
|
74 |
+
return
|
75 |
+
# if marshal_load fails then we need to reload
|
76 |
+
try:
|
77 |
+
self.code = marshal.load(f)
|
78 |
+
except (EOFError, ValueError, TypeError):
|
79 |
+
self.reset()
|
80 |
+
return
|
81 |
+
|
82 |
+
def write_bytecode(self, f: t.IO[bytes]) -> None:
|
83 |
+
"""Dump the bytecode into the file or file like object passed."""
|
84 |
+
if self.code is None:
|
85 |
+
raise TypeError("can't write empty bucket")
|
86 |
+
f.write(bc_magic)
|
87 |
+
pickle.dump(self.checksum, f, 2)
|
88 |
+
marshal.dump(self.code, f)
|
89 |
+
|
90 |
+
def bytecode_from_string(self, string: bytes) -> None:
|
91 |
+
"""Load bytecode from bytes."""
|
92 |
+
self.load_bytecode(BytesIO(string))
|
93 |
+
|
94 |
+
def bytecode_to_string(self) -> bytes:
|
95 |
+
"""Return the bytecode as bytes."""
|
96 |
+
out = BytesIO()
|
97 |
+
self.write_bytecode(out)
|
98 |
+
return out.getvalue()
|
99 |
+
|
100 |
+
|
101 |
+
class BytecodeCache:
|
102 |
+
"""To implement your own bytecode cache you have to subclass this class
|
103 |
+
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
|
104 |
+
these methods are passed a :class:`~jinja2.bccache.Bucket`.
|
105 |
+
|
106 |
+
A very basic bytecode cache that saves the bytecode on the file system::
|
107 |
+
|
108 |
+
from os import path
|
109 |
+
|
110 |
+
class MyCache(BytecodeCache):
|
111 |
+
|
112 |
+
def __init__(self, directory):
|
113 |
+
self.directory = directory
|
114 |
+
|
115 |
+
def load_bytecode(self, bucket):
|
116 |
+
filename = path.join(self.directory, bucket.key)
|
117 |
+
if path.exists(filename):
|
118 |
+
with open(filename, 'rb') as f:
|
119 |
+
bucket.load_bytecode(f)
|
120 |
+
|
121 |
+
def dump_bytecode(self, bucket):
|
122 |
+
filename = path.join(self.directory, bucket.key)
|
123 |
+
with open(filename, 'wb') as f:
|
124 |
+
bucket.write_bytecode(f)
|
125 |
+
|
126 |
+
A more advanced version of a filesystem based bytecode cache is part of
|
127 |
+
Jinja.
|
128 |
+
"""
|
129 |
+
|
130 |
+
def load_bytecode(self, bucket: Bucket) -> None:
|
131 |
+
"""Subclasses have to override this method to load bytecode into a
|
132 |
+
bucket. If they are not able to find code in the cache for the
|
133 |
+
bucket, it must not do anything.
|
134 |
+
"""
|
135 |
+
raise NotImplementedError()
|
136 |
+
|
137 |
+
def dump_bytecode(self, bucket: Bucket) -> None:
|
138 |
+
"""Subclasses have to override this method to write the bytecode
|
139 |
+
from a bucket back to the cache. If it unable to do so it must not
|
140 |
+
fail silently but raise an exception.
|
141 |
+
"""
|
142 |
+
raise NotImplementedError()
|
143 |
+
|
144 |
+
def clear(self) -> None:
|
145 |
+
"""Clears the cache. This method is not used by Jinja but should be
|
146 |
+
implemented to allow applications to clear the bytecode cache used
|
147 |
+
by a particular environment.
|
148 |
+
"""
|
149 |
+
|
150 |
+
def get_cache_key(
|
151 |
+
self, name: str, filename: t.Optional[t.Union[str]] = None
|
152 |
+
) -> str:
|
153 |
+
"""Returns the unique hash key for this template name."""
|
154 |
+
hash = sha1(name.encode("utf-8"))
|
155 |
+
|
156 |
+
if filename is not None:
|
157 |
+
hash.update(f"|{filename}".encode())
|
158 |
+
|
159 |
+
return hash.hexdigest()
|
160 |
+
|
161 |
+
def get_source_checksum(self, source: str) -> str:
|
162 |
+
"""Returns a checksum for the source."""
|
163 |
+
return sha1(source.encode("utf-8")).hexdigest()
|
164 |
+
|
165 |
+
def get_bucket(
|
166 |
+
self,
|
167 |
+
environment: "Environment",
|
168 |
+
name: str,
|
169 |
+
filename: t.Optional[str],
|
170 |
+
source: str,
|
171 |
+
) -> Bucket:
|
172 |
+
"""Return a cache bucket for the given template. All arguments are
|
173 |
+
mandatory but filename may be `None`.
|
174 |
+
"""
|
175 |
+
key = self.get_cache_key(name, filename)
|
176 |
+
checksum = self.get_source_checksum(source)
|
177 |
+
bucket = Bucket(environment, key, checksum)
|
178 |
+
self.load_bytecode(bucket)
|
179 |
+
return bucket
|
180 |
+
|
181 |
+
def set_bucket(self, bucket: Bucket) -> None:
|
182 |
+
"""Put the bucket into the cache."""
|
183 |
+
self.dump_bytecode(bucket)
|
184 |
+
|
185 |
+
|
186 |
+
class FileSystemBytecodeCache(BytecodeCache):
|
187 |
+
"""A bytecode cache that stores bytecode on the filesystem. It accepts
|
188 |
+
two arguments: The directory where the cache items are stored and a
|
189 |
+
pattern string that is used to build the filename.
|
190 |
+
|
191 |
+
If no directory is specified a default cache directory is selected. On
|
192 |
+
Windows the user's temp directory is used, on UNIX systems a directory
|
193 |
+
is created for the user in the system temp directory.
|
194 |
+
|
195 |
+
The pattern can be used to have multiple separate caches operate on the
|
196 |
+
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
|
197 |
+
is replaced with the cache key.
|
198 |
+
|
199 |
+
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
|
200 |
+
|
201 |
+
This bytecode cache supports clearing of the cache using the clear method.
|
202 |
+
"""
|
203 |
+
|
204 |
+
def __init__(
|
205 |
+
self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
|
206 |
+
) -> None:
|
207 |
+
if directory is None:
|
208 |
+
directory = self._get_default_cache_dir()
|
209 |
+
self.directory = directory
|
210 |
+
self.pattern = pattern
|
211 |
+
|
212 |
+
def _get_default_cache_dir(self) -> str:
|
213 |
+
def _unsafe_dir() -> "te.NoReturn":
|
214 |
+
raise RuntimeError(
|
215 |
+
"Cannot determine safe temp directory. You "
|
216 |
+
"need to explicitly provide one."
|
217 |
+
)
|
218 |
+
|
219 |
+
tmpdir = tempfile.gettempdir()
|
220 |
+
|
221 |
+
# On windows the temporary directory is used specific unless
|
222 |
+
# explicitly forced otherwise. We can just use that.
|
223 |
+
if os.name == "nt":
|
224 |
+
return tmpdir
|
225 |
+
if not hasattr(os, "getuid"):
|
226 |
+
_unsafe_dir()
|
227 |
+
|
228 |
+
dirname = f"_jinja2-cache-{os.getuid()}"
|
229 |
+
actual_dir = os.path.join(tmpdir, dirname)
|
230 |
+
|
231 |
+
try:
|
232 |
+
os.mkdir(actual_dir, stat.S_IRWXU)
|
233 |
+
except OSError as e:
|
234 |
+
if e.errno != errno.EEXIST:
|
235 |
+
raise
|
236 |
+
try:
|
237 |
+
os.chmod(actual_dir, stat.S_IRWXU)
|
238 |
+
actual_dir_stat = os.lstat(actual_dir)
|
239 |
+
if (
|
240 |
+
actual_dir_stat.st_uid != os.getuid()
|
241 |
+
or not stat.S_ISDIR(actual_dir_stat.st_mode)
|
242 |
+
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
|
243 |
+
):
|
244 |
+
_unsafe_dir()
|
245 |
+
except OSError as e:
|
246 |
+
if e.errno != errno.EEXIST:
|
247 |
+
raise
|
248 |
+
|
249 |
+
actual_dir_stat = os.lstat(actual_dir)
|
250 |
+
if (
|
251 |
+
actual_dir_stat.st_uid != os.getuid()
|
252 |
+
or not stat.S_ISDIR(actual_dir_stat.st_mode)
|
253 |
+
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
|
254 |
+
):
|
255 |
+
_unsafe_dir()
|
256 |
+
|
257 |
+
return actual_dir
|
258 |
+
|
259 |
+
def _get_cache_filename(self, bucket: Bucket) -> str:
|
260 |
+
return os.path.join(self.directory, self.pattern % (bucket.key,))
|
261 |
+
|
262 |
+
def load_bytecode(self, bucket: Bucket) -> None:
|
263 |
+
filename = self._get_cache_filename(bucket)
|
264 |
+
|
265 |
+
# Don't test for existence before opening the file, since the
|
266 |
+
# file could disappear after the test before the open.
|
267 |
+
try:
|
268 |
+
f = open(filename, "rb")
|
269 |
+
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
270 |
+
# PermissionError can occur on Windows when an operation is
|
271 |
+
# in progress, such as calling clear().
|
272 |
+
return
|
273 |
+
|
274 |
+
with f:
|
275 |
+
bucket.load_bytecode(f)
|
276 |
+
|
277 |
+
def dump_bytecode(self, bucket: Bucket) -> None:
|
278 |
+
# Write to a temporary file, then rename to the real name after
|
279 |
+
# writing. This avoids another process reading the file before
|
280 |
+
# it is fully written.
|
281 |
+
name = self._get_cache_filename(bucket)
|
282 |
+
f = tempfile.NamedTemporaryFile(
|
283 |
+
mode="wb",
|
284 |
+
dir=os.path.dirname(name),
|
285 |
+
prefix=os.path.basename(name),
|
286 |
+
suffix=".tmp",
|
287 |
+
delete=False,
|
288 |
+
)
|
289 |
+
|
290 |
+
def remove_silent() -> None:
|
291 |
+
try:
|
292 |
+
os.remove(f.name)
|
293 |
+
except OSError:
|
294 |
+
# Another process may have called clear(). On Windows,
|
295 |
+
# another program may be holding the file open.
|
296 |
+
pass
|
297 |
+
|
298 |
+
try:
|
299 |
+
with f:
|
300 |
+
bucket.write_bytecode(f)
|
301 |
+
except BaseException:
|
302 |
+
remove_silent()
|
303 |
+
raise
|
304 |
+
|
305 |
+
try:
|
306 |
+
os.replace(f.name, name)
|
307 |
+
except OSError:
|
308 |
+
# Another process may have called clear(). On Windows,
|
309 |
+
# another program may be holding the file open.
|
310 |
+
remove_silent()
|
311 |
+
except BaseException:
|
312 |
+
remove_silent()
|
313 |
+
raise
|
314 |
+
|
315 |
+
def clear(self) -> None:
|
316 |
+
# imported lazily here because google app-engine doesn't support
|
317 |
+
# write access on the file system and the function does not exist
|
318 |
+
# normally.
|
319 |
+
from os import remove
|
320 |
+
|
321 |
+
files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",))
|
322 |
+
for filename in files:
|
323 |
+
try:
|
324 |
+
remove(os.path.join(self.directory, filename))
|
325 |
+
except OSError:
|
326 |
+
pass
|
327 |
+
|
328 |
+
|
329 |
+
class MemcachedBytecodeCache(BytecodeCache):
|
330 |
+
"""This class implements a bytecode cache that uses a memcache cache for
|
331 |
+
storing the information. It does not enforce a specific memcache library
|
332 |
+
(tummy's memcache or cmemcache) but will accept any class that provides
|
333 |
+
the minimal interface required.
|
334 |
+
|
335 |
+
Libraries compatible with this class:
|
336 |
+
|
337 |
+
- `cachelib <https://github.com/pallets/cachelib>`_
|
338 |
+
- `python-memcached <https://pypi.org/project/python-memcached/>`_
|
339 |
+
|
340 |
+
(Unfortunately the django cache interface is not compatible because it
|
341 |
+
does not support storing binary data, only text. You can however pass
|
342 |
+
the underlying cache client to the bytecode cache which is available
|
343 |
+
as `django.core.cache.cache._client`.)
|
344 |
+
|
345 |
+
The minimal interface for the client passed to the constructor is this:
|
346 |
+
|
347 |
+
.. class:: MinimalClientInterface
|
348 |
+
|
349 |
+
.. method:: set(key, value[, timeout])
|
350 |
+
|
351 |
+
Stores the bytecode in the cache. `value` is a string and
|
352 |
+
`timeout` the timeout of the key. If timeout is not provided
|
353 |
+
a default timeout or no timeout should be assumed, if it's
|
354 |
+
provided it's an integer with the number of seconds the cache
|
355 |
+
item should exist.
|
356 |
+
|
357 |
+
.. method:: get(key)
|
358 |
+
|
359 |
+
Returns the value for the cache key. If the item does not
|
360 |
+
exist in the cache the return value must be `None`.
|
361 |
+
|
362 |
+
The other arguments to the constructor are the prefix for all keys that
|
363 |
+
is added before the actual cache key and the timeout for the bytecode in
|
364 |
+
the cache system. We recommend a high (or no) timeout.
|
365 |
+
|
366 |
+
This bytecode cache does not support clearing of used items in the cache.
|
367 |
+
The clear method is a no-operation function.
|
368 |
+
|
369 |
+
.. versionadded:: 2.7
|
370 |
+
Added support for ignoring memcache errors through the
|
371 |
+
`ignore_memcache_errors` parameter.
|
372 |
+
"""
|
373 |
+
|
374 |
+
def __init__(
|
375 |
+
self,
|
376 |
+
client: "_MemcachedClient",
|
377 |
+
prefix: str = "jinja2/bytecode/",
|
378 |
+
timeout: t.Optional[int] = None,
|
379 |
+
ignore_memcache_errors: bool = True,
|
380 |
+
):
|
381 |
+
self.client = client
|
382 |
+
self.prefix = prefix
|
383 |
+
self.timeout = timeout
|
384 |
+
self.ignore_memcache_errors = ignore_memcache_errors
|
385 |
+
|
386 |
+
def load_bytecode(self, bucket: Bucket) -> None:
|
387 |
+
try:
|
388 |
+
code = self.client.get(self.prefix + bucket.key)
|
389 |
+
except Exception:
|
390 |
+
if not self.ignore_memcache_errors:
|
391 |
+
raise
|
392 |
+
else:
|
393 |
+
bucket.bytecode_from_string(code)
|
394 |
+
|
395 |
+
def dump_bytecode(self, bucket: Bucket) -> None:
|
396 |
+
key = self.prefix + bucket.key
|
397 |
+
value = bucket.bytecode_to_string()
|
398 |
+
|
399 |
+
try:
|
400 |
+
if self.timeout is not None:
|
401 |
+
self.client.set(key, value, self.timeout)
|
402 |
+
else:
|
403 |
+
self.client.set(key, value)
|
404 |
+
except Exception:
|
405 |
+
if not self.ignore_memcache_errors:
|
406 |
+
raise
|
lib/python3.11/site-packages/jinja2/compiler.py
ADDED
@@ -0,0 +1,1957 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Compiles nodes from the parser into Python code."""
|
2 |
+
import typing as t
|
3 |
+
from contextlib import contextmanager
|
4 |
+
from functools import update_wrapper
|
5 |
+
from io import StringIO
|
6 |
+
from itertools import chain
|
7 |
+
from keyword import iskeyword as is_python_keyword
|
8 |
+
|
9 |
+
from markupsafe import escape
|
10 |
+
from markupsafe import Markup
|
11 |
+
|
12 |
+
from . import nodes
|
13 |
+
from .exceptions import TemplateAssertionError
|
14 |
+
from .idtracking import Symbols
|
15 |
+
from .idtracking import VAR_LOAD_ALIAS
|
16 |
+
from .idtracking import VAR_LOAD_PARAMETER
|
17 |
+
from .idtracking import VAR_LOAD_RESOLVE
|
18 |
+
from .idtracking import VAR_LOAD_UNDEFINED
|
19 |
+
from .nodes import EvalContext
|
20 |
+
from .optimizer import Optimizer
|
21 |
+
from .utils import _PassArg
|
22 |
+
from .utils import concat
|
23 |
+
from .visitor import NodeVisitor
|
24 |
+
|
25 |
+
if t.TYPE_CHECKING:
|
26 |
+
import typing_extensions as te
|
27 |
+
from .environment import Environment
|
28 |
+
|
29 |
+
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
30 |
+
|
31 |
+
operators = {
|
32 |
+
"eq": "==",
|
33 |
+
"ne": "!=",
|
34 |
+
"gt": ">",
|
35 |
+
"gteq": ">=",
|
36 |
+
"lt": "<",
|
37 |
+
"lteq": "<=",
|
38 |
+
"in": "in",
|
39 |
+
"notin": "not in",
|
40 |
+
}
|
41 |
+
|
42 |
+
|
43 |
+
def optimizeconst(f: F) -> F:
|
44 |
+
def new_func(
|
45 |
+
self: "CodeGenerator", node: nodes.Expr, frame: "Frame", **kwargs: t.Any
|
46 |
+
) -> t.Any:
|
47 |
+
# Only optimize if the frame is not volatile
|
48 |
+
if self.optimizer is not None and not frame.eval_ctx.volatile:
|
49 |
+
new_node = self.optimizer.visit(node, frame.eval_ctx)
|
50 |
+
|
51 |
+
if new_node != node:
|
52 |
+
return self.visit(new_node, frame)
|
53 |
+
|
54 |
+
return f(self, node, frame, **kwargs)
|
55 |
+
|
56 |
+
return update_wrapper(t.cast(F, new_func), f)
|
57 |
+
|
58 |
+
|
59 |
+
def _make_binop(op: str) -> t.Callable[["CodeGenerator", nodes.BinExpr, "Frame"], None]:
|
60 |
+
@optimizeconst
|
61 |
+
def visitor(self: "CodeGenerator", node: nodes.BinExpr, frame: Frame) -> None:
|
62 |
+
if (
|
63 |
+
self.environment.sandboxed
|
64 |
+
and op in self.environment.intercepted_binops # type: ignore
|
65 |
+
):
|
66 |
+
self.write(f"environment.call_binop(context, {op!r}, ")
|
67 |
+
self.visit(node.left, frame)
|
68 |
+
self.write(", ")
|
69 |
+
self.visit(node.right, frame)
|
70 |
+
else:
|
71 |
+
self.write("(")
|
72 |
+
self.visit(node.left, frame)
|
73 |
+
self.write(f" {op} ")
|
74 |
+
self.visit(node.right, frame)
|
75 |
+
|
76 |
+
self.write(")")
|
77 |
+
|
78 |
+
return visitor
|
79 |
+
|
80 |
+
|
81 |
+
def _make_unop(
|
82 |
+
op: str,
|
83 |
+
) -> t.Callable[["CodeGenerator", nodes.UnaryExpr, "Frame"], None]:
|
84 |
+
@optimizeconst
|
85 |
+
def visitor(self: "CodeGenerator", node: nodes.UnaryExpr, frame: Frame) -> None:
|
86 |
+
if (
|
87 |
+
self.environment.sandboxed
|
88 |
+
and op in self.environment.intercepted_unops # type: ignore
|
89 |
+
):
|
90 |
+
self.write(f"environment.call_unop(context, {op!r}, ")
|
91 |
+
self.visit(node.node, frame)
|
92 |
+
else:
|
93 |
+
self.write("(" + op)
|
94 |
+
self.visit(node.node, frame)
|
95 |
+
|
96 |
+
self.write(")")
|
97 |
+
|
98 |
+
return visitor
|
99 |
+
|
100 |
+
|
101 |
+
def generate(
|
102 |
+
node: nodes.Template,
|
103 |
+
environment: "Environment",
|
104 |
+
name: t.Optional[str],
|
105 |
+
filename: t.Optional[str],
|
106 |
+
stream: t.Optional[t.TextIO] = None,
|
107 |
+
defer_init: bool = False,
|
108 |
+
optimized: bool = True,
|
109 |
+
) -> t.Optional[str]:
|
110 |
+
"""Generate the python source for a node tree."""
|
111 |
+
if not isinstance(node, nodes.Template):
|
112 |
+
raise TypeError("Can't compile non template nodes")
|
113 |
+
|
114 |
+
generator = environment.code_generator_class(
|
115 |
+
environment, name, filename, stream, defer_init, optimized
|
116 |
+
)
|
117 |
+
generator.visit(node)
|
118 |
+
|
119 |
+
if stream is None:
|
120 |
+
return generator.stream.getvalue() # type: ignore
|
121 |
+
|
122 |
+
return None
|
123 |
+
|
124 |
+
|
125 |
+
def has_safe_repr(value: t.Any) -> bool:
|
126 |
+
"""Does the node have a safe representation?"""
|
127 |
+
if value is None or value is NotImplemented or value is Ellipsis:
|
128 |
+
return True
|
129 |
+
|
130 |
+
if type(value) in {bool, int, float, complex, range, str, Markup}:
|
131 |
+
return True
|
132 |
+
|
133 |
+
if type(value) in {tuple, list, set, frozenset}:
|
134 |
+
return all(has_safe_repr(v) for v in value)
|
135 |
+
|
136 |
+
if type(value) is dict:
|
137 |
+
return all(has_safe_repr(k) and has_safe_repr(v) for k, v in value.items())
|
138 |
+
|
139 |
+
return False
|
140 |
+
|
141 |
+
|
142 |
+
def find_undeclared(
|
143 |
+
nodes: t.Iterable[nodes.Node], names: t.Iterable[str]
|
144 |
+
) -> t.Set[str]:
|
145 |
+
"""Check if the names passed are accessed undeclared. The return value
|
146 |
+
is a set of all the undeclared names from the sequence of names found.
|
147 |
+
"""
|
148 |
+
visitor = UndeclaredNameVisitor(names)
|
149 |
+
try:
|
150 |
+
for node in nodes:
|
151 |
+
visitor.visit(node)
|
152 |
+
except VisitorExit:
|
153 |
+
pass
|
154 |
+
return visitor.undeclared
|
155 |
+
|
156 |
+
|
157 |
+
class MacroRef:
|
158 |
+
def __init__(self, node: t.Union[nodes.Macro, nodes.CallBlock]) -> None:
|
159 |
+
self.node = node
|
160 |
+
self.accesses_caller = False
|
161 |
+
self.accesses_kwargs = False
|
162 |
+
self.accesses_varargs = False
|
163 |
+
|
164 |
+
|
165 |
+
class Frame:
|
166 |
+
"""Holds compile time information for us."""
|
167 |
+
|
168 |
+
def __init__(
|
169 |
+
self,
|
170 |
+
eval_ctx: EvalContext,
|
171 |
+
parent: t.Optional["Frame"] = None,
|
172 |
+
level: t.Optional[int] = None,
|
173 |
+
) -> None:
|
174 |
+
self.eval_ctx = eval_ctx
|
175 |
+
|
176 |
+
# the parent of this frame
|
177 |
+
self.parent = parent
|
178 |
+
|
179 |
+
if parent is None:
|
180 |
+
self.symbols = Symbols(level=level)
|
181 |
+
|
182 |
+
# in some dynamic inheritance situations the compiler needs to add
|
183 |
+
# write tests around output statements.
|
184 |
+
self.require_output_check = False
|
185 |
+
|
186 |
+
# inside some tags we are using a buffer rather than yield statements.
|
187 |
+
# this for example affects {% filter %} or {% macro %}. If a frame
|
188 |
+
# is buffered this variable points to the name of the list used as
|
189 |
+
# buffer.
|
190 |
+
self.buffer: t.Optional[str] = None
|
191 |
+
|
192 |
+
# the name of the block we're in, otherwise None.
|
193 |
+
self.block: t.Optional[str] = None
|
194 |
+
|
195 |
+
else:
|
196 |
+
self.symbols = Symbols(parent.symbols, level=level)
|
197 |
+
self.require_output_check = parent.require_output_check
|
198 |
+
self.buffer = parent.buffer
|
199 |
+
self.block = parent.block
|
200 |
+
|
201 |
+
# a toplevel frame is the root + soft frames such as if conditions.
|
202 |
+
self.toplevel = False
|
203 |
+
|
204 |
+
# the root frame is basically just the outermost frame, so no if
|
205 |
+
# conditions. This information is used to optimize inheritance
|
206 |
+
# situations.
|
207 |
+
self.rootlevel = False
|
208 |
+
|
209 |
+
# variables set inside of loops and blocks should not affect outer frames,
|
210 |
+
# but they still needs to be kept track of as part of the active context.
|
211 |
+
self.loop_frame = False
|
212 |
+
self.block_frame = False
|
213 |
+
|
214 |
+
# track whether the frame is being used in an if-statement or conditional
|
215 |
+
# expression as it determines which errors should be raised during runtime
|
216 |
+
# or compile time.
|
217 |
+
self.soft_frame = False
|
218 |
+
|
219 |
+
def copy(self) -> "Frame":
|
220 |
+
"""Create a copy of the current one."""
|
221 |
+
rv = object.__new__(self.__class__)
|
222 |
+
rv.__dict__.update(self.__dict__)
|
223 |
+
rv.symbols = self.symbols.copy()
|
224 |
+
return rv
|
225 |
+
|
226 |
+
def inner(self, isolated: bool = False) -> "Frame":
|
227 |
+
"""Return an inner frame."""
|
228 |
+
if isolated:
|
229 |
+
return Frame(self.eval_ctx, level=self.symbols.level + 1)
|
230 |
+
return Frame(self.eval_ctx, self)
|
231 |
+
|
232 |
+
def soft(self) -> "Frame":
|
233 |
+
"""Return a soft frame. A soft frame may not be modified as
|
234 |
+
standalone thing as it shares the resources with the frame it
|
235 |
+
was created of, but it's not a rootlevel frame any longer.
|
236 |
+
|
237 |
+
This is only used to implement if-statements and conditional
|
238 |
+
expressions.
|
239 |
+
"""
|
240 |
+
rv = self.copy()
|
241 |
+
rv.rootlevel = False
|
242 |
+
rv.soft_frame = True
|
243 |
+
return rv
|
244 |
+
|
245 |
+
__copy__ = copy
|
246 |
+
|
247 |
+
|
248 |
+
class VisitorExit(RuntimeError):
|
249 |
+
"""Exception used by the `UndeclaredNameVisitor` to signal a stop."""
|
250 |
+
|
251 |
+
|
252 |
+
class DependencyFinderVisitor(NodeVisitor):
|
253 |
+
"""A visitor that collects filter and test calls."""
|
254 |
+
|
255 |
+
def __init__(self) -> None:
|
256 |
+
self.filters: t.Set[str] = set()
|
257 |
+
self.tests: t.Set[str] = set()
|
258 |
+
|
259 |
+
def visit_Filter(self, node: nodes.Filter) -> None:
|
260 |
+
self.generic_visit(node)
|
261 |
+
self.filters.add(node.name)
|
262 |
+
|
263 |
+
def visit_Test(self, node: nodes.Test) -> None:
|
264 |
+
self.generic_visit(node)
|
265 |
+
self.tests.add(node.name)
|
266 |
+
|
267 |
+
def visit_Block(self, node: nodes.Block) -> None:
|
268 |
+
"""Stop visiting at blocks."""
|
269 |
+
|
270 |
+
|
271 |
+
class UndeclaredNameVisitor(NodeVisitor):
|
272 |
+
"""A visitor that checks if a name is accessed without being
|
273 |
+
declared. This is different from the frame visitor as it will
|
274 |
+
not stop at closure frames.
|
275 |
+
"""
|
276 |
+
|
277 |
+
def __init__(self, names: t.Iterable[str]) -> None:
|
278 |
+
self.names = set(names)
|
279 |
+
self.undeclared: t.Set[str] = set()
|
280 |
+
|
281 |
+
def visit_Name(self, node: nodes.Name) -> None:
|
282 |
+
if node.ctx == "load" and node.name in self.names:
|
283 |
+
self.undeclared.add(node.name)
|
284 |
+
if self.undeclared == self.names:
|
285 |
+
raise VisitorExit()
|
286 |
+
else:
|
287 |
+
self.names.discard(node.name)
|
288 |
+
|
289 |
+
def visit_Block(self, node: nodes.Block) -> None:
|
290 |
+
"""Stop visiting a blocks."""
|
291 |
+
|
292 |
+
|
293 |
+
class CompilerExit(Exception):
|
294 |
+
"""Raised if the compiler encountered a situation where it just
|
295 |
+
doesn't make sense to further process the code. Any block that
|
296 |
+
raises such an exception is not further processed.
|
297 |
+
"""
|
298 |
+
|
299 |
+
|
300 |
+
class CodeGenerator(NodeVisitor):
|
301 |
+
def __init__(
|
302 |
+
self,
|
303 |
+
environment: "Environment",
|
304 |
+
name: t.Optional[str],
|
305 |
+
filename: t.Optional[str],
|
306 |
+
stream: t.Optional[t.TextIO] = None,
|
307 |
+
defer_init: bool = False,
|
308 |
+
optimized: bool = True,
|
309 |
+
) -> None:
|
310 |
+
if stream is None:
|
311 |
+
stream = StringIO()
|
312 |
+
self.environment = environment
|
313 |
+
self.name = name
|
314 |
+
self.filename = filename
|
315 |
+
self.stream = stream
|
316 |
+
self.created_block_context = False
|
317 |
+
self.defer_init = defer_init
|
318 |
+
self.optimizer: t.Optional[Optimizer] = None
|
319 |
+
|
320 |
+
if optimized:
|
321 |
+
self.optimizer = Optimizer(environment)
|
322 |
+
|
323 |
+
# aliases for imports
|
324 |
+
self.import_aliases: t.Dict[str, str] = {}
|
325 |
+
|
326 |
+
# a registry for all blocks. Because blocks are moved out
|
327 |
+
# into the global python scope they are registered here
|
328 |
+
self.blocks: t.Dict[str, nodes.Block] = {}
|
329 |
+
|
330 |
+
# the number of extends statements so far
|
331 |
+
self.extends_so_far = 0
|
332 |
+
|
333 |
+
# some templates have a rootlevel extends. In this case we
|
334 |
+
# can safely assume that we're a child template and do some
|
335 |
+
# more optimizations.
|
336 |
+
self.has_known_extends = False
|
337 |
+
|
338 |
+
# the current line number
|
339 |
+
self.code_lineno = 1
|
340 |
+
|
341 |
+
# registry of all filters and tests (global, not block local)
|
342 |
+
self.tests: t.Dict[str, str] = {}
|
343 |
+
self.filters: t.Dict[str, str] = {}
|
344 |
+
|
345 |
+
# the debug information
|
346 |
+
self.debug_info: t.List[t.Tuple[int, int]] = []
|
347 |
+
self._write_debug_info: t.Optional[int] = None
|
348 |
+
|
349 |
+
# the number of new lines before the next write()
|
350 |
+
self._new_lines = 0
|
351 |
+
|
352 |
+
# the line number of the last written statement
|
353 |
+
self._last_line = 0
|
354 |
+
|
355 |
+
# true if nothing was written so far.
|
356 |
+
self._first_write = True
|
357 |
+
|
358 |
+
# used by the `temporary_identifier` method to get new
|
359 |
+
# unique, temporary identifier
|
360 |
+
self._last_identifier = 0
|
361 |
+
|
362 |
+
# the current indentation
|
363 |
+
self._indentation = 0
|
364 |
+
|
365 |
+
# Tracks toplevel assignments
|
366 |
+
self._assign_stack: t.List[t.Set[str]] = []
|
367 |
+
|
368 |
+
# Tracks parameter definition blocks
|
369 |
+
self._param_def_block: t.List[t.Set[str]] = []
|
370 |
+
|
371 |
+
# Tracks the current context.
|
372 |
+
self._context_reference_stack = ["context"]
|
373 |
+
|
374 |
+
@property
|
375 |
+
def optimized(self) -> bool:
|
376 |
+
return self.optimizer is not None
|
377 |
+
|
378 |
+
# -- Various compilation helpers
|
379 |
+
|
380 |
+
def fail(self, msg: str, lineno: int) -> "te.NoReturn":
|
381 |
+
"""Fail with a :exc:`TemplateAssertionError`."""
|
382 |
+
raise TemplateAssertionError(msg, lineno, self.name, self.filename)
|
383 |
+
|
384 |
+
def temporary_identifier(self) -> str:
|
385 |
+
"""Get a new unique identifier."""
|
386 |
+
self._last_identifier += 1
|
387 |
+
return f"t_{self._last_identifier}"
|
388 |
+
|
389 |
+
def buffer(self, frame: Frame) -> None:
|
390 |
+
"""Enable buffering for the frame from that point onwards."""
|
391 |
+
frame.buffer = self.temporary_identifier()
|
392 |
+
self.writeline(f"{frame.buffer} = []")
|
393 |
+
|
394 |
+
def return_buffer_contents(
|
395 |
+
self, frame: Frame, force_unescaped: bool = False
|
396 |
+
) -> None:
|
397 |
+
"""Return the buffer contents of the frame."""
|
398 |
+
if not force_unescaped:
|
399 |
+
if frame.eval_ctx.volatile:
|
400 |
+
self.writeline("if context.eval_ctx.autoescape:")
|
401 |
+
self.indent()
|
402 |
+
self.writeline(f"return Markup(concat({frame.buffer}))")
|
403 |
+
self.outdent()
|
404 |
+
self.writeline("else:")
|
405 |
+
self.indent()
|
406 |
+
self.writeline(f"return concat({frame.buffer})")
|
407 |
+
self.outdent()
|
408 |
+
return
|
409 |
+
elif frame.eval_ctx.autoescape:
|
410 |
+
self.writeline(f"return Markup(concat({frame.buffer}))")
|
411 |
+
return
|
412 |
+
self.writeline(f"return concat({frame.buffer})")
|
413 |
+
|
414 |
+
def indent(self) -> None:
|
415 |
+
"""Indent by one."""
|
416 |
+
self._indentation += 1
|
417 |
+
|
418 |
+
def outdent(self, step: int = 1) -> None:
|
419 |
+
"""Outdent by step."""
|
420 |
+
self._indentation -= step
|
421 |
+
|
422 |
+
def start_write(self, frame: Frame, node: t.Optional[nodes.Node] = None) -> None:
|
423 |
+
"""Yield or write into the frame buffer."""
|
424 |
+
if frame.buffer is None:
|
425 |
+
self.writeline("yield ", node)
|
426 |
+
else:
|
427 |
+
self.writeline(f"{frame.buffer}.append(", node)
|
428 |
+
|
429 |
+
def end_write(self, frame: Frame) -> None:
|
430 |
+
"""End the writing process started by `start_write`."""
|
431 |
+
if frame.buffer is not None:
|
432 |
+
self.write(")")
|
433 |
+
|
434 |
+
def simple_write(
|
435 |
+
self, s: str, frame: Frame, node: t.Optional[nodes.Node] = None
|
436 |
+
) -> None:
|
437 |
+
"""Simple shortcut for start_write + write + end_write."""
|
438 |
+
self.start_write(frame, node)
|
439 |
+
self.write(s)
|
440 |
+
self.end_write(frame)
|
441 |
+
|
442 |
+
def blockvisit(self, nodes: t.Iterable[nodes.Node], frame: Frame) -> None:
|
443 |
+
"""Visit a list of nodes as block in a frame. If the current frame
|
444 |
+
is no buffer a dummy ``if 0: yield None`` is written automatically.
|
445 |
+
"""
|
446 |
+
try:
|
447 |
+
self.writeline("pass")
|
448 |
+
for node in nodes:
|
449 |
+
self.visit(node, frame)
|
450 |
+
except CompilerExit:
|
451 |
+
pass
|
452 |
+
|
453 |
+
def write(self, x: str) -> None:
|
454 |
+
"""Write a string into the output stream."""
|
455 |
+
if self._new_lines:
|
456 |
+
if not self._first_write:
|
457 |
+
self.stream.write("\n" * self._new_lines)
|
458 |
+
self.code_lineno += self._new_lines
|
459 |
+
if self._write_debug_info is not None:
|
460 |
+
self.debug_info.append((self._write_debug_info, self.code_lineno))
|
461 |
+
self._write_debug_info = None
|
462 |
+
self._first_write = False
|
463 |
+
self.stream.write(" " * self._indentation)
|
464 |
+
self._new_lines = 0
|
465 |
+
self.stream.write(x)
|
466 |
+
|
467 |
+
def writeline(
|
468 |
+
self, x: str, node: t.Optional[nodes.Node] = None, extra: int = 0
|
469 |
+
) -> None:
|
470 |
+
"""Combination of newline and write."""
|
471 |
+
self.newline(node, extra)
|
472 |
+
self.write(x)
|
473 |
+
|
474 |
+
def newline(self, node: t.Optional[nodes.Node] = None, extra: int = 0) -> None:
|
475 |
+
"""Add one or more newlines before the next write."""
|
476 |
+
self._new_lines = max(self._new_lines, 1 + extra)
|
477 |
+
if node is not None and node.lineno != self._last_line:
|
478 |
+
self._write_debug_info = node.lineno
|
479 |
+
self._last_line = node.lineno
|
480 |
+
|
481 |
+
def signature(
|
482 |
+
self,
|
483 |
+
node: t.Union[nodes.Call, nodes.Filter, nodes.Test],
|
484 |
+
frame: Frame,
|
485 |
+
extra_kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
|
486 |
+
) -> None:
|
487 |
+
"""Writes a function call to the stream for the current node.
|
488 |
+
A leading comma is added automatically. The extra keyword
|
489 |
+
arguments may not include python keywords otherwise a syntax
|
490 |
+
error could occur. The extra keyword arguments should be given
|
491 |
+
as python dict.
|
492 |
+
"""
|
493 |
+
# if any of the given keyword arguments is a python keyword
|
494 |
+
# we have to make sure that no invalid call is created.
|
495 |
+
kwarg_workaround = any(
|
496 |
+
is_python_keyword(t.cast(str, k))
|
497 |
+
for k in chain((x.key for x in node.kwargs), extra_kwargs or ())
|
498 |
+
)
|
499 |
+
|
500 |
+
for arg in node.args:
|
501 |
+
self.write(", ")
|
502 |
+
self.visit(arg, frame)
|
503 |
+
|
504 |
+
if not kwarg_workaround:
|
505 |
+
for kwarg in node.kwargs:
|
506 |
+
self.write(", ")
|
507 |
+
self.visit(kwarg, frame)
|
508 |
+
if extra_kwargs is not None:
|
509 |
+
for key, value in extra_kwargs.items():
|
510 |
+
self.write(f", {key}={value}")
|
511 |
+
if node.dyn_args:
|
512 |
+
self.write(", *")
|
513 |
+
self.visit(node.dyn_args, frame)
|
514 |
+
|
515 |
+
if kwarg_workaround:
|
516 |
+
if node.dyn_kwargs is not None:
|
517 |
+
self.write(", **dict({")
|
518 |
+
else:
|
519 |
+
self.write(", **{")
|
520 |
+
for kwarg in node.kwargs:
|
521 |
+
self.write(f"{kwarg.key!r}: ")
|
522 |
+
self.visit(kwarg.value, frame)
|
523 |
+
self.write(", ")
|
524 |
+
if extra_kwargs is not None:
|
525 |
+
for key, value in extra_kwargs.items():
|
526 |
+
self.write(f"{key!r}: {value}, ")
|
527 |
+
if node.dyn_kwargs is not None:
|
528 |
+
self.write("}, **")
|
529 |
+
self.visit(node.dyn_kwargs, frame)
|
530 |
+
self.write(")")
|
531 |
+
else:
|
532 |
+
self.write("}")
|
533 |
+
|
534 |
+
elif node.dyn_kwargs is not None:
|
535 |
+
self.write(", **")
|
536 |
+
self.visit(node.dyn_kwargs, frame)
|
537 |
+
|
538 |
+
def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None:
|
539 |
+
"""Find all filter and test names used in the template and
|
540 |
+
assign them to variables in the compiled namespace. Checking
|
541 |
+
that the names are registered with the environment is done when
|
542 |
+
compiling the Filter and Test nodes. If the node is in an If or
|
543 |
+
CondExpr node, the check is done at runtime instead.
|
544 |
+
|
545 |
+
.. versionchanged:: 3.0
|
546 |
+
Filters and tests in If and CondExpr nodes are checked at
|
547 |
+
runtime instead of compile time.
|
548 |
+
"""
|
549 |
+
visitor = DependencyFinderVisitor()
|
550 |
+
|
551 |
+
for node in nodes:
|
552 |
+
visitor.visit(node)
|
553 |
+
|
554 |
+
for id_map, names, dependency in (self.filters, visitor.filters, "filters"), (
|
555 |
+
self.tests,
|
556 |
+
visitor.tests,
|
557 |
+
"tests",
|
558 |
+
):
|
559 |
+
for name in sorted(names):
|
560 |
+
if name not in id_map:
|
561 |
+
id_map[name] = self.temporary_identifier()
|
562 |
+
|
563 |
+
# add check during runtime that dependencies used inside of executed
|
564 |
+
# blocks are defined, as this step may be skipped during compile time
|
565 |
+
self.writeline("try:")
|
566 |
+
self.indent()
|
567 |
+
self.writeline(f"{id_map[name]} = environment.{dependency}[{name!r}]")
|
568 |
+
self.outdent()
|
569 |
+
self.writeline("except KeyError:")
|
570 |
+
self.indent()
|
571 |
+
self.writeline("@internalcode")
|
572 |
+
self.writeline(f"def {id_map[name]}(*unused):")
|
573 |
+
self.indent()
|
574 |
+
self.writeline(
|
575 |
+
f'raise TemplateRuntimeError("No {dependency[:-1]}'
|
576 |
+
f' named {name!r} found.")'
|
577 |
+
)
|
578 |
+
self.outdent()
|
579 |
+
self.outdent()
|
580 |
+
|
581 |
+
def enter_frame(self, frame: Frame) -> None:
|
582 |
+
undefs = []
|
583 |
+
for target, (action, param) in frame.symbols.loads.items():
|
584 |
+
if action == VAR_LOAD_PARAMETER:
|
585 |
+
pass
|
586 |
+
elif action == VAR_LOAD_RESOLVE:
|
587 |
+
self.writeline(f"{target} = {self.get_resolve_func()}({param!r})")
|
588 |
+
elif action == VAR_LOAD_ALIAS:
|
589 |
+
self.writeline(f"{target} = {param}")
|
590 |
+
elif action == VAR_LOAD_UNDEFINED:
|
591 |
+
undefs.append(target)
|
592 |
+
else:
|
593 |
+
raise NotImplementedError("unknown load instruction")
|
594 |
+
if undefs:
|
595 |
+
self.writeline(f"{' = '.join(undefs)} = missing")
|
596 |
+
|
597 |
+
def leave_frame(self, frame: Frame, with_python_scope: bool = False) -> None:
|
598 |
+
if not with_python_scope:
|
599 |
+
undefs = []
|
600 |
+
for target in frame.symbols.loads:
|
601 |
+
undefs.append(target)
|
602 |
+
if undefs:
|
603 |
+
self.writeline(f"{' = '.join(undefs)} = missing")
|
604 |
+
|
605 |
+
def choose_async(self, async_value: str = "async ", sync_value: str = "") -> str:
|
606 |
+
return async_value if self.environment.is_async else sync_value
|
607 |
+
|
608 |
+
def func(self, name: str) -> str:
|
609 |
+
return f"{self.choose_async()}def {name}"
|
610 |
+
|
611 |
+
def macro_body(
|
612 |
+
self, node: t.Union[nodes.Macro, nodes.CallBlock], frame: Frame
|
613 |
+
) -> t.Tuple[Frame, MacroRef]:
|
614 |
+
"""Dump the function def of a macro or call block."""
|
615 |
+
frame = frame.inner()
|
616 |
+
frame.symbols.analyze_node(node)
|
617 |
+
macro_ref = MacroRef(node)
|
618 |
+
|
619 |
+
explicit_caller = None
|
620 |
+
skip_special_params = set()
|
621 |
+
args = []
|
622 |
+
|
623 |
+
for idx, arg in enumerate(node.args):
|
624 |
+
if arg.name == "caller":
|
625 |
+
explicit_caller = idx
|
626 |
+
if arg.name in ("kwargs", "varargs"):
|
627 |
+
skip_special_params.add(arg.name)
|
628 |
+
args.append(frame.symbols.ref(arg.name))
|
629 |
+
|
630 |
+
undeclared = find_undeclared(node.body, ("caller", "kwargs", "varargs"))
|
631 |
+
|
632 |
+
if "caller" in undeclared:
|
633 |
+
# In older Jinja versions there was a bug that allowed caller
|
634 |
+
# to retain the special behavior even if it was mentioned in
|
635 |
+
# the argument list. However thankfully this was only really
|
636 |
+
# working if it was the last argument. So we are explicitly
|
637 |
+
# checking this now and error out if it is anywhere else in
|
638 |
+
# the argument list.
|
639 |
+
if explicit_caller is not None:
|
640 |
+
try:
|
641 |
+
node.defaults[explicit_caller - len(node.args)]
|
642 |
+
except IndexError:
|
643 |
+
self.fail(
|
644 |
+
"When defining macros or call blocks the "
|
645 |
+
'special "caller" argument must be omitted '
|
646 |
+
"or be given a default.",
|
647 |
+
node.lineno,
|
648 |
+
)
|
649 |
+
else:
|
650 |
+
args.append(frame.symbols.declare_parameter("caller"))
|
651 |
+
macro_ref.accesses_caller = True
|
652 |
+
if "kwargs" in undeclared and "kwargs" not in skip_special_params:
|
653 |
+
args.append(frame.symbols.declare_parameter("kwargs"))
|
654 |
+
macro_ref.accesses_kwargs = True
|
655 |
+
if "varargs" in undeclared and "varargs" not in skip_special_params:
|
656 |
+
args.append(frame.symbols.declare_parameter("varargs"))
|
657 |
+
macro_ref.accesses_varargs = True
|
658 |
+
|
659 |
+
# macros are delayed, they never require output checks
|
660 |
+
frame.require_output_check = False
|
661 |
+
frame.symbols.analyze_node(node)
|
662 |
+
self.writeline(f"{self.func('macro')}({', '.join(args)}):", node)
|
663 |
+
self.indent()
|
664 |
+
|
665 |
+
self.buffer(frame)
|
666 |
+
self.enter_frame(frame)
|
667 |
+
|
668 |
+
self.push_parameter_definitions(frame)
|
669 |
+
for idx, arg in enumerate(node.args):
|
670 |
+
ref = frame.symbols.ref(arg.name)
|
671 |
+
self.writeline(f"if {ref} is missing:")
|
672 |
+
self.indent()
|
673 |
+
try:
|
674 |
+
default = node.defaults[idx - len(node.args)]
|
675 |
+
except IndexError:
|
676 |
+
self.writeline(
|
677 |
+
f'{ref} = undefined("parameter {arg.name!r} was not provided",'
|
678 |
+
f" name={arg.name!r})"
|
679 |
+
)
|
680 |
+
else:
|
681 |
+
self.writeline(f"{ref} = ")
|
682 |
+
self.visit(default, frame)
|
683 |
+
self.mark_parameter_stored(ref)
|
684 |
+
self.outdent()
|
685 |
+
self.pop_parameter_definitions()
|
686 |
+
|
687 |
+
self.blockvisit(node.body, frame)
|
688 |
+
self.return_buffer_contents(frame, force_unescaped=True)
|
689 |
+
self.leave_frame(frame, with_python_scope=True)
|
690 |
+
self.outdent()
|
691 |
+
|
692 |
+
return frame, macro_ref
|
693 |
+
|
694 |
+
def macro_def(self, macro_ref: MacroRef, frame: Frame) -> None:
|
695 |
+
"""Dump the macro definition for the def created by macro_body."""
|
696 |
+
arg_tuple = ", ".join(repr(x.name) for x in macro_ref.node.args)
|
697 |
+
name = getattr(macro_ref.node, "name", None)
|
698 |
+
if len(macro_ref.node.args) == 1:
|
699 |
+
arg_tuple += ","
|
700 |
+
self.write(
|
701 |
+
f"Macro(environment, macro, {name!r}, ({arg_tuple}),"
|
702 |
+
f" {macro_ref.accesses_kwargs!r}, {macro_ref.accesses_varargs!r},"
|
703 |
+
f" {macro_ref.accesses_caller!r}, context.eval_ctx.autoescape)"
|
704 |
+
)
|
705 |
+
|
706 |
+
def position(self, node: nodes.Node) -> str:
|
707 |
+
"""Return a human readable position for the node."""
|
708 |
+
rv = f"line {node.lineno}"
|
709 |
+
if self.name is not None:
|
710 |
+
rv = f"{rv} in {self.name!r}"
|
711 |
+
return rv
|
712 |
+
|
713 |
+
def dump_local_context(self, frame: Frame) -> str:
|
714 |
+
items_kv = ", ".join(
|
715 |
+
f"{name!r}: {target}"
|
716 |
+
for name, target in frame.symbols.dump_stores().items()
|
717 |
+
)
|
718 |
+
return f"{{{items_kv}}}"
|
719 |
+
|
720 |
+
def write_commons(self) -> None:
|
721 |
+
"""Writes a common preamble that is used by root and block functions.
|
722 |
+
Primarily this sets up common local helpers and enforces a generator
|
723 |
+
through a dead branch.
|
724 |
+
"""
|
725 |
+
self.writeline("resolve = context.resolve_or_missing")
|
726 |
+
self.writeline("undefined = environment.undefined")
|
727 |
+
self.writeline("concat = environment.concat")
|
728 |
+
# always use the standard Undefined class for the implicit else of
|
729 |
+
# conditional expressions
|
730 |
+
self.writeline("cond_expr_undefined = Undefined")
|
731 |
+
self.writeline("if 0: yield None")
|
732 |
+
|
733 |
+
def push_parameter_definitions(self, frame: Frame) -> None:
|
734 |
+
"""Pushes all parameter targets from the given frame into a local
|
735 |
+
stack that permits tracking of yet to be assigned parameters. In
|
736 |
+
particular this enables the optimization from `visit_Name` to skip
|
737 |
+
undefined expressions for parameters in macros as macros can reference
|
738 |
+
otherwise unbound parameters.
|
739 |
+
"""
|
740 |
+
self._param_def_block.append(frame.symbols.dump_param_targets())
|
741 |
+
|
742 |
+
def pop_parameter_definitions(self) -> None:
|
743 |
+
"""Pops the current parameter definitions set."""
|
744 |
+
self._param_def_block.pop()
|
745 |
+
|
746 |
+
def mark_parameter_stored(self, target: str) -> None:
|
747 |
+
"""Marks a parameter in the current parameter definitions as stored.
|
748 |
+
This will skip the enforced undefined checks.
|
749 |
+
"""
|
750 |
+
if self._param_def_block:
|
751 |
+
self._param_def_block[-1].discard(target)
|
752 |
+
|
753 |
+
def push_context_reference(self, target: str) -> None:
|
754 |
+
self._context_reference_stack.append(target)
|
755 |
+
|
756 |
+
def pop_context_reference(self) -> None:
|
757 |
+
self._context_reference_stack.pop()
|
758 |
+
|
759 |
+
def get_context_ref(self) -> str:
|
760 |
+
return self._context_reference_stack[-1]
|
761 |
+
|
762 |
+
def get_resolve_func(self) -> str:
|
763 |
+
target = self._context_reference_stack[-1]
|
764 |
+
if target == "context":
|
765 |
+
return "resolve"
|
766 |
+
return f"{target}.resolve"
|
767 |
+
|
768 |
+
def derive_context(self, frame: Frame) -> str:
|
769 |
+
return f"{self.get_context_ref()}.derived({self.dump_local_context(frame)})"
|
770 |
+
|
771 |
+
def parameter_is_undeclared(self, target: str) -> bool:
|
772 |
+
"""Checks if a given target is an undeclared parameter."""
|
773 |
+
if not self._param_def_block:
|
774 |
+
return False
|
775 |
+
return target in self._param_def_block[-1]
|
776 |
+
|
777 |
+
def push_assign_tracking(self) -> None:
|
778 |
+
"""Pushes a new layer for assignment tracking."""
|
779 |
+
self._assign_stack.append(set())
|
780 |
+
|
781 |
+
def pop_assign_tracking(self, frame: Frame) -> None:
|
782 |
+
"""Pops the topmost level for assignment tracking and updates the
|
783 |
+
context variables if necessary.
|
784 |
+
"""
|
785 |
+
vars = self._assign_stack.pop()
|
786 |
+
if (
|
787 |
+
not frame.block_frame
|
788 |
+
and not frame.loop_frame
|
789 |
+
and not frame.toplevel
|
790 |
+
or not vars
|
791 |
+
):
|
792 |
+
return
|
793 |
+
public_names = [x for x in vars if x[:1] != "_"]
|
794 |
+
if len(vars) == 1:
|
795 |
+
name = next(iter(vars))
|
796 |
+
ref = frame.symbols.ref(name)
|
797 |
+
if frame.loop_frame:
|
798 |
+
self.writeline(f"_loop_vars[{name!r}] = {ref}")
|
799 |
+
return
|
800 |
+
if frame.block_frame:
|
801 |
+
self.writeline(f"_block_vars[{name!r}] = {ref}")
|
802 |
+
return
|
803 |
+
self.writeline(f"context.vars[{name!r}] = {ref}")
|
804 |
+
else:
|
805 |
+
if frame.loop_frame:
|
806 |
+
self.writeline("_loop_vars.update({")
|
807 |
+
elif frame.block_frame:
|
808 |
+
self.writeline("_block_vars.update({")
|
809 |
+
else:
|
810 |
+
self.writeline("context.vars.update({")
|
811 |
+
for idx, name in enumerate(vars):
|
812 |
+
if idx:
|
813 |
+
self.write(", ")
|
814 |
+
ref = frame.symbols.ref(name)
|
815 |
+
self.write(f"{name!r}: {ref}")
|
816 |
+
self.write("})")
|
817 |
+
if not frame.block_frame and not frame.loop_frame and public_names:
|
818 |
+
if len(public_names) == 1:
|
819 |
+
self.writeline(f"context.exported_vars.add({public_names[0]!r})")
|
820 |
+
else:
|
821 |
+
names_str = ", ".join(map(repr, public_names))
|
822 |
+
self.writeline(f"context.exported_vars.update(({names_str}))")
|
823 |
+
|
824 |
+
# -- Statement Visitors
|
825 |
+
|
826 |
+
def visit_Template(
|
827 |
+
self, node: nodes.Template, frame: t.Optional[Frame] = None
|
828 |
+
) -> None:
|
829 |
+
assert frame is None, "no root frame allowed"
|
830 |
+
eval_ctx = EvalContext(self.environment, self.name)
|
831 |
+
|
832 |
+
from .runtime import exported, async_exported
|
833 |
+
|
834 |
+
if self.environment.is_async:
|
835 |
+
exported_names = sorted(exported + async_exported)
|
836 |
+
else:
|
837 |
+
exported_names = sorted(exported)
|
838 |
+
|
839 |
+
self.writeline("from jinja2.runtime import " + ", ".join(exported_names))
|
840 |
+
|
841 |
+
# if we want a deferred initialization we cannot move the
|
842 |
+
# environment into a local name
|
843 |
+
envenv = "" if self.defer_init else ", environment=environment"
|
844 |
+
|
845 |
+
# do we have an extends tag at all? If not, we can save some
|
846 |
+
# overhead by just not processing any inheritance code.
|
847 |
+
have_extends = node.find(nodes.Extends) is not None
|
848 |
+
|
849 |
+
# find all blocks
|
850 |
+
for block in node.find_all(nodes.Block):
|
851 |
+
if block.name in self.blocks:
|
852 |
+
self.fail(f"block {block.name!r} defined twice", block.lineno)
|
853 |
+
self.blocks[block.name] = block
|
854 |
+
|
855 |
+
# find all imports and import them
|
856 |
+
for import_ in node.find_all(nodes.ImportedName):
|
857 |
+
if import_.importname not in self.import_aliases:
|
858 |
+
imp = import_.importname
|
859 |
+
self.import_aliases[imp] = alias = self.temporary_identifier()
|
860 |
+
if "." in imp:
|
861 |
+
module, obj = imp.rsplit(".", 1)
|
862 |
+
self.writeline(f"from {module} import {obj} as {alias}")
|
863 |
+
else:
|
864 |
+
self.writeline(f"import {imp} as {alias}")
|
865 |
+
|
866 |
+
# add the load name
|
867 |
+
self.writeline(f"name = {self.name!r}")
|
868 |
+
|
869 |
+
# generate the root render function.
|
870 |
+
self.writeline(
|
871 |
+
f"{self.func('root')}(context, missing=missing{envenv}):", extra=1
|
872 |
+
)
|
873 |
+
self.indent()
|
874 |
+
self.write_commons()
|
875 |
+
|
876 |
+
# process the root
|
877 |
+
frame = Frame(eval_ctx)
|
878 |
+
if "self" in find_undeclared(node.body, ("self",)):
|
879 |
+
ref = frame.symbols.declare_parameter("self")
|
880 |
+
self.writeline(f"{ref} = TemplateReference(context)")
|
881 |
+
frame.symbols.analyze_node(node)
|
882 |
+
frame.toplevel = frame.rootlevel = True
|
883 |
+
frame.require_output_check = have_extends and not self.has_known_extends
|
884 |
+
if have_extends:
|
885 |
+
self.writeline("parent_template = None")
|
886 |
+
self.enter_frame(frame)
|
887 |
+
self.pull_dependencies(node.body)
|
888 |
+
self.blockvisit(node.body, frame)
|
889 |
+
self.leave_frame(frame, with_python_scope=True)
|
890 |
+
self.outdent()
|
891 |
+
|
892 |
+
# make sure that the parent root is called.
|
893 |
+
if have_extends:
|
894 |
+
if not self.has_known_extends:
|
895 |
+
self.indent()
|
896 |
+
self.writeline("if parent_template is not None:")
|
897 |
+
self.indent()
|
898 |
+
if not self.environment.is_async:
|
899 |
+
self.writeline("yield from parent_template.root_render_func(context)")
|
900 |
+
else:
|
901 |
+
self.writeline(
|
902 |
+
"async for event in parent_template.root_render_func(context):"
|
903 |
+
)
|
904 |
+
self.indent()
|
905 |
+
self.writeline("yield event")
|
906 |
+
self.outdent()
|
907 |
+
self.outdent(1 + (not self.has_known_extends))
|
908 |
+
|
909 |
+
# at this point we now have the blocks collected and can visit them too.
|
910 |
+
for name, block in self.blocks.items():
|
911 |
+
self.writeline(
|
912 |
+
f"{self.func('block_' + name)}(context, missing=missing{envenv}):",
|
913 |
+
block,
|
914 |
+
1,
|
915 |
+
)
|
916 |
+
self.indent()
|
917 |
+
self.write_commons()
|
918 |
+
# It's important that we do not make this frame a child of the
|
919 |
+
# toplevel template. This would cause a variety of
|
920 |
+
# interesting issues with identifier tracking.
|
921 |
+
block_frame = Frame(eval_ctx)
|
922 |
+
block_frame.block_frame = True
|
923 |
+
undeclared = find_undeclared(block.body, ("self", "super"))
|
924 |
+
if "self" in undeclared:
|
925 |
+
ref = block_frame.symbols.declare_parameter("self")
|
926 |
+
self.writeline(f"{ref} = TemplateReference(context)")
|
927 |
+
if "super" in undeclared:
|
928 |
+
ref = block_frame.symbols.declare_parameter("super")
|
929 |
+
self.writeline(f"{ref} = context.super({name!r}, block_{name})")
|
930 |
+
block_frame.symbols.analyze_node(block)
|
931 |
+
block_frame.block = name
|
932 |
+
self.writeline("_block_vars = {}")
|
933 |
+
self.enter_frame(block_frame)
|
934 |
+
self.pull_dependencies(block.body)
|
935 |
+
self.blockvisit(block.body, block_frame)
|
936 |
+
self.leave_frame(block_frame, with_python_scope=True)
|
937 |
+
self.outdent()
|
938 |
+
|
939 |
+
blocks_kv_str = ", ".join(f"{x!r}: block_{x}" for x in self.blocks)
|
940 |
+
self.writeline(f"blocks = {{{blocks_kv_str}}}", extra=1)
|
941 |
+
debug_kv_str = "&".join(f"{k}={v}" for k, v in self.debug_info)
|
942 |
+
self.writeline(f"debug_info = {debug_kv_str!r}")
|
943 |
+
|
944 |
+
def visit_Block(self, node: nodes.Block, frame: Frame) -> None:
|
945 |
+
"""Call a block and register it for the template."""
|
946 |
+
level = 0
|
947 |
+
if frame.toplevel:
|
948 |
+
# if we know that we are a child template, there is no need to
|
949 |
+
# check if we are one
|
950 |
+
if self.has_known_extends:
|
951 |
+
return
|
952 |
+
if self.extends_so_far > 0:
|
953 |
+
self.writeline("if parent_template is None:")
|
954 |
+
self.indent()
|
955 |
+
level += 1
|
956 |
+
|
957 |
+
if node.scoped:
|
958 |
+
context = self.derive_context(frame)
|
959 |
+
else:
|
960 |
+
context = self.get_context_ref()
|
961 |
+
|
962 |
+
if node.required:
|
963 |
+
self.writeline(f"if len(context.blocks[{node.name!r}]) <= 1:", node)
|
964 |
+
self.indent()
|
965 |
+
self.writeline(
|
966 |
+
f'raise TemplateRuntimeError("Required block {node.name!r} not found")',
|
967 |
+
node,
|
968 |
+
)
|
969 |
+
self.outdent()
|
970 |
+
|
971 |
+
if not self.environment.is_async and frame.buffer is None:
|
972 |
+
self.writeline(
|
973 |
+
f"yield from context.blocks[{node.name!r}][0]({context})", node
|
974 |
+
)
|
975 |
+
else:
|
976 |
+
self.writeline(
|
977 |
+
f"{self.choose_async()}for event in"
|
978 |
+
f" context.blocks[{node.name!r}][0]({context}):",
|
979 |
+
node,
|
980 |
+
)
|
981 |
+
self.indent()
|
982 |
+
self.simple_write("event", frame)
|
983 |
+
self.outdent()
|
984 |
+
|
985 |
+
self.outdent(level)
|
986 |
+
|
987 |
+
def visit_Extends(self, node: nodes.Extends, frame: Frame) -> None:
|
988 |
+
"""Calls the extender."""
|
989 |
+
if not frame.toplevel:
|
990 |
+
self.fail("cannot use extend from a non top-level scope", node.lineno)
|
991 |
+
|
992 |
+
# if the number of extends statements in general is zero so
|
993 |
+
# far, we don't have to add a check if something extended
|
994 |
+
# the template before this one.
|
995 |
+
if self.extends_so_far > 0:
|
996 |
+
|
997 |
+
# if we have a known extends we just add a template runtime
|
998 |
+
# error into the generated code. We could catch that at compile
|
999 |
+
# time too, but i welcome it not to confuse users by throwing the
|
1000 |
+
# same error at different times just "because we can".
|
1001 |
+
if not self.has_known_extends:
|
1002 |
+
self.writeline("if parent_template is not None:")
|
1003 |
+
self.indent()
|
1004 |
+
self.writeline('raise TemplateRuntimeError("extended multiple times")')
|
1005 |
+
|
1006 |
+
# if we have a known extends already we don't need that code here
|
1007 |
+
# as we know that the template execution will end here.
|
1008 |
+
if self.has_known_extends:
|
1009 |
+
raise CompilerExit()
|
1010 |
+
else:
|
1011 |
+
self.outdent()
|
1012 |
+
|
1013 |
+
self.writeline("parent_template = environment.get_template(", node)
|
1014 |
+
self.visit(node.template, frame)
|
1015 |
+
self.write(f", {self.name!r})")
|
1016 |
+
self.writeline("for name, parent_block in parent_template.blocks.items():")
|
1017 |
+
self.indent()
|
1018 |
+
self.writeline("context.blocks.setdefault(name, []).append(parent_block)")
|
1019 |
+
self.outdent()
|
1020 |
+
|
1021 |
+
# if this extends statement was in the root level we can take
|
1022 |
+
# advantage of that information and simplify the generated code
|
1023 |
+
# in the top level from this point onwards
|
1024 |
+
if frame.rootlevel:
|
1025 |
+
self.has_known_extends = True
|
1026 |
+
|
1027 |
+
# and now we have one more
|
1028 |
+
self.extends_so_far += 1
|
1029 |
+
|
1030 |
+
def visit_Include(self, node: nodes.Include, frame: Frame) -> None:
|
1031 |
+
"""Handles includes."""
|
1032 |
+
if node.ignore_missing:
|
1033 |
+
self.writeline("try:")
|
1034 |
+
self.indent()
|
1035 |
+
|
1036 |
+
func_name = "get_or_select_template"
|
1037 |
+
if isinstance(node.template, nodes.Const):
|
1038 |
+
if isinstance(node.template.value, str):
|
1039 |
+
func_name = "get_template"
|
1040 |
+
elif isinstance(node.template.value, (tuple, list)):
|
1041 |
+
func_name = "select_template"
|
1042 |
+
elif isinstance(node.template, (nodes.Tuple, nodes.List)):
|
1043 |
+
func_name = "select_template"
|
1044 |
+
|
1045 |
+
self.writeline(f"template = environment.{func_name}(", node)
|
1046 |
+
self.visit(node.template, frame)
|
1047 |
+
self.write(f", {self.name!r})")
|
1048 |
+
if node.ignore_missing:
|
1049 |
+
self.outdent()
|
1050 |
+
self.writeline("except TemplateNotFound:")
|
1051 |
+
self.indent()
|
1052 |
+
self.writeline("pass")
|
1053 |
+
self.outdent()
|
1054 |
+
self.writeline("else:")
|
1055 |
+
self.indent()
|
1056 |
+
|
1057 |
+
skip_event_yield = False
|
1058 |
+
if node.with_context:
|
1059 |
+
self.writeline(
|
1060 |
+
f"{self.choose_async()}for event in template.root_render_func("
|
1061 |
+
"template.new_context(context.get_all(), True,"
|
1062 |
+
f" {self.dump_local_context(frame)})):"
|
1063 |
+
)
|
1064 |
+
elif self.environment.is_async:
|
1065 |
+
self.writeline(
|
1066 |
+
"for event in (await template._get_default_module_async())"
|
1067 |
+
"._body_stream:"
|
1068 |
+
)
|
1069 |
+
else:
|
1070 |
+
self.writeline("yield from template._get_default_module()._body_stream")
|
1071 |
+
skip_event_yield = True
|
1072 |
+
|
1073 |
+
if not skip_event_yield:
|
1074 |
+
self.indent()
|
1075 |
+
self.simple_write("event", frame)
|
1076 |
+
self.outdent()
|
1077 |
+
|
1078 |
+
if node.ignore_missing:
|
1079 |
+
self.outdent()
|
1080 |
+
|
1081 |
+
def _import_common(
|
1082 |
+
self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame
|
1083 |
+
) -> None:
|
1084 |
+
self.write(f"{self.choose_async('await ')}environment.get_template(")
|
1085 |
+
self.visit(node.template, frame)
|
1086 |
+
self.write(f", {self.name!r}).")
|
1087 |
+
|
1088 |
+
if node.with_context:
|
1089 |
+
f_name = f"make_module{self.choose_async('_async')}"
|
1090 |
+
self.write(
|
1091 |
+
f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})"
|
1092 |
+
)
|
1093 |
+
else:
|
1094 |
+
self.write(f"_get_default_module{self.choose_async('_async')}(context)")
|
1095 |
+
|
1096 |
+
def visit_Import(self, node: nodes.Import, frame: Frame) -> None:
|
1097 |
+
"""Visit regular imports."""
|
1098 |
+
self.writeline(f"{frame.symbols.ref(node.target)} = ", node)
|
1099 |
+
if frame.toplevel:
|
1100 |
+
self.write(f"context.vars[{node.target!r}] = ")
|
1101 |
+
|
1102 |
+
self._import_common(node, frame)
|
1103 |
+
|
1104 |
+
if frame.toplevel and not node.target.startswith("_"):
|
1105 |
+
self.writeline(f"context.exported_vars.discard({node.target!r})")
|
1106 |
+
|
1107 |
+
def visit_FromImport(self, node: nodes.FromImport, frame: Frame) -> None:
|
1108 |
+
"""Visit named imports."""
|
1109 |
+
self.newline(node)
|
1110 |
+
self.write("included_template = ")
|
1111 |
+
self._import_common(node, frame)
|
1112 |
+
var_names = []
|
1113 |
+
discarded_names = []
|
1114 |
+
for name in node.names:
|
1115 |
+
if isinstance(name, tuple):
|
1116 |
+
name, alias = name
|
1117 |
+
else:
|
1118 |
+
alias = name
|
1119 |
+
self.writeline(
|
1120 |
+
f"{frame.symbols.ref(alias)} ="
|
1121 |
+
f" getattr(included_template, {name!r}, missing)"
|
1122 |
+
)
|
1123 |
+
self.writeline(f"if {frame.symbols.ref(alias)} is missing:")
|
1124 |
+
self.indent()
|
1125 |
+
message = (
|
1126 |
+
"the template {included_template.__name__!r}"
|
1127 |
+
f" (imported on {self.position(node)})"
|
1128 |
+
f" does not export the requested name {name!r}"
|
1129 |
+
)
|
1130 |
+
self.writeline(
|
1131 |
+
f"{frame.symbols.ref(alias)} = undefined(f{message!r}, name={name!r})"
|
1132 |
+
)
|
1133 |
+
self.outdent()
|
1134 |
+
if frame.toplevel:
|
1135 |
+
var_names.append(alias)
|
1136 |
+
if not alias.startswith("_"):
|
1137 |
+
discarded_names.append(alias)
|
1138 |
+
|
1139 |
+
if var_names:
|
1140 |
+
if len(var_names) == 1:
|
1141 |
+
name = var_names[0]
|
1142 |
+
self.writeline(f"context.vars[{name!r}] = {frame.symbols.ref(name)}")
|
1143 |
+
else:
|
1144 |
+
names_kv = ", ".join(
|
1145 |
+
f"{name!r}: {frame.symbols.ref(name)}" for name in var_names
|
1146 |
+
)
|
1147 |
+
self.writeline(f"context.vars.update({{{names_kv}}})")
|
1148 |
+
if discarded_names:
|
1149 |
+
if len(discarded_names) == 1:
|
1150 |
+
self.writeline(f"context.exported_vars.discard({discarded_names[0]!r})")
|
1151 |
+
else:
|
1152 |
+
names_str = ", ".join(map(repr, discarded_names))
|
1153 |
+
self.writeline(
|
1154 |
+
f"context.exported_vars.difference_update(({names_str}))"
|
1155 |
+
)
|
1156 |
+
|
1157 |
+
def visit_For(self, node: nodes.For, frame: Frame) -> None:
|
1158 |
+
loop_frame = frame.inner()
|
1159 |
+
loop_frame.loop_frame = True
|
1160 |
+
test_frame = frame.inner()
|
1161 |
+
else_frame = frame.inner()
|
1162 |
+
|
1163 |
+
# try to figure out if we have an extended loop. An extended loop
|
1164 |
+
# is necessary if the loop is in recursive mode if the special loop
|
1165 |
+
# variable is accessed in the body if the body is a scoped block.
|
1166 |
+
extended_loop = (
|
1167 |
+
node.recursive
|
1168 |
+
or "loop"
|
1169 |
+
in find_undeclared(node.iter_child_nodes(only=("body",)), ("loop",))
|
1170 |
+
or any(block.scoped for block in node.find_all(nodes.Block))
|
1171 |
+
)
|
1172 |
+
|
1173 |
+
loop_ref = None
|
1174 |
+
if extended_loop:
|
1175 |
+
loop_ref = loop_frame.symbols.declare_parameter("loop")
|
1176 |
+
|
1177 |
+
loop_frame.symbols.analyze_node(node, for_branch="body")
|
1178 |
+
if node.else_:
|
1179 |
+
else_frame.symbols.analyze_node(node, for_branch="else")
|
1180 |
+
|
1181 |
+
if node.test:
|
1182 |
+
loop_filter_func = self.temporary_identifier()
|
1183 |
+
test_frame.symbols.analyze_node(node, for_branch="test")
|
1184 |
+
self.writeline(f"{self.func(loop_filter_func)}(fiter):", node.test)
|
1185 |
+
self.indent()
|
1186 |
+
self.enter_frame(test_frame)
|
1187 |
+
self.writeline(self.choose_async("async for ", "for "))
|
1188 |
+
self.visit(node.target, loop_frame)
|
1189 |
+
self.write(" in ")
|
1190 |
+
self.write(self.choose_async("auto_aiter(fiter)", "fiter"))
|
1191 |
+
self.write(":")
|
1192 |
+
self.indent()
|
1193 |
+
self.writeline("if ", node.test)
|
1194 |
+
self.visit(node.test, test_frame)
|
1195 |
+
self.write(":")
|
1196 |
+
self.indent()
|
1197 |
+
self.writeline("yield ")
|
1198 |
+
self.visit(node.target, loop_frame)
|
1199 |
+
self.outdent(3)
|
1200 |
+
self.leave_frame(test_frame, with_python_scope=True)
|
1201 |
+
|
1202 |
+
# if we don't have an recursive loop we have to find the shadowed
|
1203 |
+
# variables at that point. Because loops can be nested but the loop
|
1204 |
+
# variable is a special one we have to enforce aliasing for it.
|
1205 |
+
if node.recursive:
|
1206 |
+
self.writeline(
|
1207 |
+
f"{self.func('loop')}(reciter, loop_render_func, depth=0):", node
|
1208 |
+
)
|
1209 |
+
self.indent()
|
1210 |
+
self.buffer(loop_frame)
|
1211 |
+
|
1212 |
+
# Use the same buffer for the else frame
|
1213 |
+
else_frame.buffer = loop_frame.buffer
|
1214 |
+
|
1215 |
+
# make sure the loop variable is a special one and raise a template
|
1216 |
+
# assertion error if a loop tries to write to loop
|
1217 |
+
if extended_loop:
|
1218 |
+
self.writeline(f"{loop_ref} = missing")
|
1219 |
+
|
1220 |
+
for name in node.find_all(nodes.Name):
|
1221 |
+
if name.ctx == "store" and name.name == "loop":
|
1222 |
+
self.fail(
|
1223 |
+
"Can't assign to special loop variable in for-loop target",
|
1224 |
+
name.lineno,
|
1225 |
+
)
|
1226 |
+
|
1227 |
+
if node.else_:
|
1228 |
+
iteration_indicator = self.temporary_identifier()
|
1229 |
+
self.writeline(f"{iteration_indicator} = 1")
|
1230 |
+
|
1231 |
+
self.writeline(self.choose_async("async for ", "for "), node)
|
1232 |
+
self.visit(node.target, loop_frame)
|
1233 |
+
if extended_loop:
|
1234 |
+
self.write(f", {loop_ref} in {self.choose_async('Async')}LoopContext(")
|
1235 |
+
else:
|
1236 |
+
self.write(" in ")
|
1237 |
+
|
1238 |
+
if node.test:
|
1239 |
+
self.write(f"{loop_filter_func}(")
|
1240 |
+
if node.recursive:
|
1241 |
+
self.write("reciter")
|
1242 |
+
else:
|
1243 |
+
if self.environment.is_async and not extended_loop:
|
1244 |
+
self.write("auto_aiter(")
|
1245 |
+
self.visit(node.iter, frame)
|
1246 |
+
if self.environment.is_async and not extended_loop:
|
1247 |
+
self.write(")")
|
1248 |
+
if node.test:
|
1249 |
+
self.write(")")
|
1250 |
+
|
1251 |
+
if node.recursive:
|
1252 |
+
self.write(", undefined, loop_render_func, depth):")
|
1253 |
+
else:
|
1254 |
+
self.write(", undefined):" if extended_loop else ":")
|
1255 |
+
|
1256 |
+
self.indent()
|
1257 |
+
self.enter_frame(loop_frame)
|
1258 |
+
|
1259 |
+
self.writeline("_loop_vars = {}")
|
1260 |
+
self.blockvisit(node.body, loop_frame)
|
1261 |
+
if node.else_:
|
1262 |
+
self.writeline(f"{iteration_indicator} = 0")
|
1263 |
+
self.outdent()
|
1264 |
+
self.leave_frame(
|
1265 |
+
loop_frame, with_python_scope=node.recursive and not node.else_
|
1266 |
+
)
|
1267 |
+
|
1268 |
+
if node.else_:
|
1269 |
+
self.writeline(f"if {iteration_indicator}:")
|
1270 |
+
self.indent()
|
1271 |
+
self.enter_frame(else_frame)
|
1272 |
+
self.blockvisit(node.else_, else_frame)
|
1273 |
+
self.leave_frame(else_frame)
|
1274 |
+
self.outdent()
|
1275 |
+
|
1276 |
+
# if the node was recursive we have to return the buffer contents
|
1277 |
+
# and start the iteration code
|
1278 |
+
if node.recursive:
|
1279 |
+
self.return_buffer_contents(loop_frame)
|
1280 |
+
self.outdent()
|
1281 |
+
self.start_write(frame, node)
|
1282 |
+
self.write(f"{self.choose_async('await ')}loop(")
|
1283 |
+
if self.environment.is_async:
|
1284 |
+
self.write("auto_aiter(")
|
1285 |
+
self.visit(node.iter, frame)
|
1286 |
+
if self.environment.is_async:
|
1287 |
+
self.write(")")
|
1288 |
+
self.write(", loop)")
|
1289 |
+
self.end_write(frame)
|
1290 |
+
|
1291 |
+
# at the end of the iteration, clear any assignments made in the
|
1292 |
+
# loop from the top level
|
1293 |
+
if self._assign_stack:
|
1294 |
+
self._assign_stack[-1].difference_update(loop_frame.symbols.stores)
|
1295 |
+
|
1296 |
+
def visit_If(self, node: nodes.If, frame: Frame) -> None:
|
1297 |
+
if_frame = frame.soft()
|
1298 |
+
self.writeline("if ", node)
|
1299 |
+
self.visit(node.test, if_frame)
|
1300 |
+
self.write(":")
|
1301 |
+
self.indent()
|
1302 |
+
self.blockvisit(node.body, if_frame)
|
1303 |
+
self.outdent()
|
1304 |
+
for elif_ in node.elif_:
|
1305 |
+
self.writeline("elif ", elif_)
|
1306 |
+
self.visit(elif_.test, if_frame)
|
1307 |
+
self.write(":")
|
1308 |
+
self.indent()
|
1309 |
+
self.blockvisit(elif_.body, if_frame)
|
1310 |
+
self.outdent()
|
1311 |
+
if node.else_:
|
1312 |
+
self.writeline("else:")
|
1313 |
+
self.indent()
|
1314 |
+
self.blockvisit(node.else_, if_frame)
|
1315 |
+
self.outdent()
|
1316 |
+
|
1317 |
+
def visit_Macro(self, node: nodes.Macro, frame: Frame) -> None:
|
1318 |
+
macro_frame, macro_ref = self.macro_body(node, frame)
|
1319 |
+
self.newline()
|
1320 |
+
if frame.toplevel:
|
1321 |
+
if not node.name.startswith("_"):
|
1322 |
+
self.write(f"context.exported_vars.add({node.name!r})")
|
1323 |
+
self.writeline(f"context.vars[{node.name!r}] = ")
|
1324 |
+
self.write(f"{frame.symbols.ref(node.name)} = ")
|
1325 |
+
self.macro_def(macro_ref, macro_frame)
|
1326 |
+
|
1327 |
+
def visit_CallBlock(self, node: nodes.CallBlock, frame: Frame) -> None:
|
1328 |
+
call_frame, macro_ref = self.macro_body(node, frame)
|
1329 |
+
self.writeline("caller = ")
|
1330 |
+
self.macro_def(macro_ref, call_frame)
|
1331 |
+
self.start_write(frame, node)
|
1332 |
+
self.visit_Call(node.call, frame, forward_caller=True)
|
1333 |
+
self.end_write(frame)
|
1334 |
+
|
1335 |
+
def visit_FilterBlock(self, node: nodes.FilterBlock, frame: Frame) -> None:
|
1336 |
+
filter_frame = frame.inner()
|
1337 |
+
filter_frame.symbols.analyze_node(node)
|
1338 |
+
self.enter_frame(filter_frame)
|
1339 |
+
self.buffer(filter_frame)
|
1340 |
+
self.blockvisit(node.body, filter_frame)
|
1341 |
+
self.start_write(frame, node)
|
1342 |
+
self.visit_Filter(node.filter, filter_frame)
|
1343 |
+
self.end_write(frame)
|
1344 |
+
self.leave_frame(filter_frame)
|
1345 |
+
|
1346 |
+
def visit_With(self, node: nodes.With, frame: Frame) -> None:
|
1347 |
+
with_frame = frame.inner()
|
1348 |
+
with_frame.symbols.analyze_node(node)
|
1349 |
+
self.enter_frame(with_frame)
|
1350 |
+
for target, expr in zip(node.targets, node.values):
|
1351 |
+
self.newline()
|
1352 |
+
self.visit(target, with_frame)
|
1353 |
+
self.write(" = ")
|
1354 |
+
self.visit(expr, frame)
|
1355 |
+
self.blockvisit(node.body, with_frame)
|
1356 |
+
self.leave_frame(with_frame)
|
1357 |
+
|
1358 |
+
def visit_ExprStmt(self, node: nodes.ExprStmt, frame: Frame) -> None:
|
1359 |
+
self.newline(node)
|
1360 |
+
self.visit(node.node, frame)
|
1361 |
+
|
1362 |
+
class _FinalizeInfo(t.NamedTuple):
|
1363 |
+
const: t.Optional[t.Callable[..., str]]
|
1364 |
+
src: t.Optional[str]
|
1365 |
+
|
1366 |
+
@staticmethod
|
1367 |
+
def _default_finalize(value: t.Any) -> t.Any:
|
1368 |
+
"""The default finalize function if the environment isn't
|
1369 |
+
configured with one. Or, if the environment has one, this is
|
1370 |
+
called on that function's output for constants.
|
1371 |
+
"""
|
1372 |
+
return str(value)
|
1373 |
+
|
1374 |
+
_finalize: t.Optional[_FinalizeInfo] = None
|
1375 |
+
|
1376 |
+
def _make_finalize(self) -> _FinalizeInfo:
|
1377 |
+
"""Build the finalize function to be used on constants and at
|
1378 |
+
runtime. Cached so it's only created once for all output nodes.
|
1379 |
+
|
1380 |
+
Returns a ``namedtuple`` with the following attributes:
|
1381 |
+
|
1382 |
+
``const``
|
1383 |
+
A function to finalize constant data at compile time.
|
1384 |
+
|
1385 |
+
``src``
|
1386 |
+
Source code to output around nodes to be evaluated at
|
1387 |
+
runtime.
|
1388 |
+
"""
|
1389 |
+
if self._finalize is not None:
|
1390 |
+
return self._finalize
|
1391 |
+
|
1392 |
+
finalize: t.Optional[t.Callable[..., t.Any]]
|
1393 |
+
finalize = default = self._default_finalize
|
1394 |
+
src = None
|
1395 |
+
|
1396 |
+
if self.environment.finalize:
|
1397 |
+
src = "environment.finalize("
|
1398 |
+
env_finalize = self.environment.finalize
|
1399 |
+
pass_arg = {
|
1400 |
+
_PassArg.context: "context",
|
1401 |
+
_PassArg.eval_context: "context.eval_ctx",
|
1402 |
+
_PassArg.environment: "environment",
|
1403 |
+
}.get(
|
1404 |
+
_PassArg.from_obj(env_finalize) # type: ignore
|
1405 |
+
)
|
1406 |
+
finalize = None
|
1407 |
+
|
1408 |
+
if pass_arg is None:
|
1409 |
+
|
1410 |
+
def finalize(value: t.Any) -> t.Any:
|
1411 |
+
return default(env_finalize(value))
|
1412 |
+
|
1413 |
+
else:
|
1414 |
+
src = f"{src}{pass_arg}, "
|
1415 |
+
|
1416 |
+
if pass_arg == "environment":
|
1417 |
+
|
1418 |
+
def finalize(value: t.Any) -> t.Any:
|
1419 |
+
return default(env_finalize(self.environment, value))
|
1420 |
+
|
1421 |
+
self._finalize = self._FinalizeInfo(finalize, src)
|
1422 |
+
return self._finalize
|
1423 |
+
|
1424 |
+
def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
|
1425 |
+
"""Given a group of constant values converted from ``Output``
|
1426 |
+
child nodes, produce a string to write to the template module
|
1427 |
+
source.
|
1428 |
+
"""
|
1429 |
+
return repr(concat(group))
|
1430 |
+
|
1431 |
+
def _output_child_to_const(
|
1432 |
+
self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
|
1433 |
+
) -> str:
|
1434 |
+
"""Try to optimize a child of an ``Output`` node by trying to
|
1435 |
+
convert it to constant, finalized data at compile time.
|
1436 |
+
|
1437 |
+
If :exc:`Impossible` is raised, the node is not constant and
|
1438 |
+
will be evaluated at runtime. Any other exception will also be
|
1439 |
+
evaluated at runtime for easier debugging.
|
1440 |
+
"""
|
1441 |
+
const = node.as_const(frame.eval_ctx)
|
1442 |
+
|
1443 |
+
if frame.eval_ctx.autoescape:
|
1444 |
+
const = escape(const)
|
1445 |
+
|
1446 |
+
# Template data doesn't go through finalize.
|
1447 |
+
if isinstance(node, nodes.TemplateData):
|
1448 |
+
return str(const)
|
1449 |
+
|
1450 |
+
return finalize.const(const) # type: ignore
|
1451 |
+
|
1452 |
+
def _output_child_pre(
|
1453 |
+
self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
|
1454 |
+
) -> None:
|
1455 |
+
"""Output extra source code before visiting a child of an
|
1456 |
+
``Output`` node.
|
1457 |
+
"""
|
1458 |
+
if frame.eval_ctx.volatile:
|
1459 |
+
self.write("(escape if context.eval_ctx.autoescape else str)(")
|
1460 |
+
elif frame.eval_ctx.autoescape:
|
1461 |
+
self.write("escape(")
|
1462 |
+
else:
|
1463 |
+
self.write("str(")
|
1464 |
+
|
1465 |
+
if finalize.src is not None:
|
1466 |
+
self.write(finalize.src)
|
1467 |
+
|
1468 |
+
def _output_child_post(
|
1469 |
+
self, node: nodes.Expr, frame: Frame, finalize: _FinalizeInfo
|
1470 |
+
) -> None:
|
1471 |
+
"""Output extra source code after visiting a child of an
|
1472 |
+
``Output`` node.
|
1473 |
+
"""
|
1474 |
+
self.write(")")
|
1475 |
+
|
1476 |
+
if finalize.src is not None:
|
1477 |
+
self.write(")")
|
1478 |
+
|
1479 |
+
def visit_Output(self, node: nodes.Output, frame: Frame) -> None:
|
1480 |
+
# If an extends is active, don't render outside a block.
|
1481 |
+
if frame.require_output_check:
|
1482 |
+
# A top-level extends is known to exist at compile time.
|
1483 |
+
if self.has_known_extends:
|
1484 |
+
return
|
1485 |
+
|
1486 |
+
self.writeline("if parent_template is None:")
|
1487 |
+
self.indent()
|
1488 |
+
|
1489 |
+
finalize = self._make_finalize()
|
1490 |
+
body: t.List[t.Union[t.List[t.Any], nodes.Expr]] = []
|
1491 |
+
|
1492 |
+
# Evaluate constants at compile time if possible. Each item in
|
1493 |
+
# body will be either a list of static data or a node to be
|
1494 |
+
# evaluated at runtime.
|
1495 |
+
for child in node.nodes:
|
1496 |
+
try:
|
1497 |
+
if not (
|
1498 |
+
# If the finalize function requires runtime context,
|
1499 |
+
# constants can't be evaluated at compile time.
|
1500 |
+
finalize.const
|
1501 |
+
# Unless it's basic template data that won't be
|
1502 |
+
# finalized anyway.
|
1503 |
+
or isinstance(child, nodes.TemplateData)
|
1504 |
+
):
|
1505 |
+
raise nodes.Impossible()
|
1506 |
+
|
1507 |
+
const = self._output_child_to_const(child, frame, finalize)
|
1508 |
+
except (nodes.Impossible, Exception):
|
1509 |
+
# The node was not constant and needs to be evaluated at
|
1510 |
+
# runtime. Or another error was raised, which is easier
|
1511 |
+
# to debug at runtime.
|
1512 |
+
body.append(child)
|
1513 |
+
continue
|
1514 |
+
|
1515 |
+
if body and isinstance(body[-1], list):
|
1516 |
+
body[-1].append(const)
|
1517 |
+
else:
|
1518 |
+
body.append([const])
|
1519 |
+
|
1520 |
+
if frame.buffer is not None:
|
1521 |
+
if len(body) == 1:
|
1522 |
+
self.writeline(f"{frame.buffer}.append(")
|
1523 |
+
else:
|
1524 |
+
self.writeline(f"{frame.buffer}.extend((")
|
1525 |
+
|
1526 |
+
self.indent()
|
1527 |
+
|
1528 |
+
for item in body:
|
1529 |
+
if isinstance(item, list):
|
1530 |
+
# A group of constant data to join and output.
|
1531 |
+
val = self._output_const_repr(item)
|
1532 |
+
|
1533 |
+
if frame.buffer is None:
|
1534 |
+
self.writeline("yield " + val)
|
1535 |
+
else:
|
1536 |
+
self.writeline(val + ",")
|
1537 |
+
else:
|
1538 |
+
if frame.buffer is None:
|
1539 |
+
self.writeline("yield ", item)
|
1540 |
+
else:
|
1541 |
+
self.newline(item)
|
1542 |
+
|
1543 |
+
# A node to be evaluated at runtime.
|
1544 |
+
self._output_child_pre(item, frame, finalize)
|
1545 |
+
self.visit(item, frame)
|
1546 |
+
self._output_child_post(item, frame, finalize)
|
1547 |
+
|
1548 |
+
if frame.buffer is not None:
|
1549 |
+
self.write(",")
|
1550 |
+
|
1551 |
+
if frame.buffer is not None:
|
1552 |
+
self.outdent()
|
1553 |
+
self.writeline(")" if len(body) == 1 else "))")
|
1554 |
+
|
1555 |
+
if frame.require_output_check:
|
1556 |
+
self.outdent()
|
1557 |
+
|
1558 |
+
def visit_Assign(self, node: nodes.Assign, frame: Frame) -> None:
|
1559 |
+
self.push_assign_tracking()
|
1560 |
+
self.newline(node)
|
1561 |
+
self.visit(node.target, frame)
|
1562 |
+
self.write(" = ")
|
1563 |
+
self.visit(node.node, frame)
|
1564 |
+
self.pop_assign_tracking(frame)
|
1565 |
+
|
1566 |
+
def visit_AssignBlock(self, node: nodes.AssignBlock, frame: Frame) -> None:
|
1567 |
+
self.push_assign_tracking()
|
1568 |
+
block_frame = frame.inner()
|
1569 |
+
# This is a special case. Since a set block always captures we
|
1570 |
+
# will disable output checks. This way one can use set blocks
|
1571 |
+
# toplevel even in extended templates.
|
1572 |
+
block_frame.require_output_check = False
|
1573 |
+
block_frame.symbols.analyze_node(node)
|
1574 |
+
self.enter_frame(block_frame)
|
1575 |
+
self.buffer(block_frame)
|
1576 |
+
self.blockvisit(node.body, block_frame)
|
1577 |
+
self.newline(node)
|
1578 |
+
self.visit(node.target, frame)
|
1579 |
+
self.write(" = (Markup if context.eval_ctx.autoescape else identity)(")
|
1580 |
+
if node.filter is not None:
|
1581 |
+
self.visit_Filter(node.filter, block_frame)
|
1582 |
+
else:
|
1583 |
+
self.write(f"concat({block_frame.buffer})")
|
1584 |
+
self.write(")")
|
1585 |
+
self.pop_assign_tracking(frame)
|
1586 |
+
self.leave_frame(block_frame)
|
1587 |
+
|
1588 |
+
# -- Expression Visitors
|
1589 |
+
|
1590 |
+
def visit_Name(self, node: nodes.Name, frame: Frame) -> None:
|
1591 |
+
if node.ctx == "store" and (
|
1592 |
+
frame.toplevel or frame.loop_frame or frame.block_frame
|
1593 |
+
):
|
1594 |
+
if self._assign_stack:
|
1595 |
+
self._assign_stack[-1].add(node.name)
|
1596 |
+
ref = frame.symbols.ref(node.name)
|
1597 |
+
|
1598 |
+
# If we are looking up a variable we might have to deal with the
|
1599 |
+
# case where it's undefined. We can skip that case if the load
|
1600 |
+
# instruction indicates a parameter which are always defined.
|
1601 |
+
if node.ctx == "load":
|
1602 |
+
load = frame.symbols.find_load(ref)
|
1603 |
+
if not (
|
1604 |
+
load is not None
|
1605 |
+
and load[0] == VAR_LOAD_PARAMETER
|
1606 |
+
and not self.parameter_is_undeclared(ref)
|
1607 |
+
):
|
1608 |
+
self.write(
|
1609 |
+
f"(undefined(name={node.name!r}) if {ref} is missing else {ref})"
|
1610 |
+
)
|
1611 |
+
return
|
1612 |
+
|
1613 |
+
self.write(ref)
|
1614 |
+
|
1615 |
+
def visit_NSRef(self, node: nodes.NSRef, frame: Frame) -> None:
|
1616 |
+
# NSRefs can only be used to store values; since they use the normal
|
1617 |
+
# `foo.bar` notation they will be parsed as a normal attribute access
|
1618 |
+
# when used anywhere but in a `set` context
|
1619 |
+
ref = frame.symbols.ref(node.name)
|
1620 |
+
self.writeline(f"if not isinstance({ref}, Namespace):")
|
1621 |
+
self.indent()
|
1622 |
+
self.writeline(
|
1623 |
+
"raise TemplateRuntimeError"
|
1624 |
+
'("cannot assign attribute on non-namespace object")'
|
1625 |
+
)
|
1626 |
+
self.outdent()
|
1627 |
+
self.writeline(f"{ref}[{node.attr!r}]")
|
1628 |
+
|
1629 |
+
def visit_Const(self, node: nodes.Const, frame: Frame) -> None:
|
1630 |
+
val = node.as_const(frame.eval_ctx)
|
1631 |
+
if isinstance(val, float):
|
1632 |
+
self.write(str(val))
|
1633 |
+
else:
|
1634 |
+
self.write(repr(val))
|
1635 |
+
|
1636 |
+
def visit_TemplateData(self, node: nodes.TemplateData, frame: Frame) -> None:
|
1637 |
+
try:
|
1638 |
+
self.write(repr(node.as_const(frame.eval_ctx)))
|
1639 |
+
except nodes.Impossible:
|
1640 |
+
self.write(
|
1641 |
+
f"(Markup if context.eval_ctx.autoescape else identity)({node.data!r})"
|
1642 |
+
)
|
1643 |
+
|
1644 |
+
def visit_Tuple(self, node: nodes.Tuple, frame: Frame) -> None:
|
1645 |
+
self.write("(")
|
1646 |
+
idx = -1
|
1647 |
+
for idx, item in enumerate(node.items):
|
1648 |
+
if idx:
|
1649 |
+
self.write(", ")
|
1650 |
+
self.visit(item, frame)
|
1651 |
+
self.write(",)" if idx == 0 else ")")
|
1652 |
+
|
1653 |
+
def visit_List(self, node: nodes.List, frame: Frame) -> None:
|
1654 |
+
self.write("[")
|
1655 |
+
for idx, item in enumerate(node.items):
|
1656 |
+
if idx:
|
1657 |
+
self.write(", ")
|
1658 |
+
self.visit(item, frame)
|
1659 |
+
self.write("]")
|
1660 |
+
|
1661 |
+
def visit_Dict(self, node: nodes.Dict, frame: Frame) -> None:
|
1662 |
+
self.write("{")
|
1663 |
+
for idx, item in enumerate(node.items):
|
1664 |
+
if idx:
|
1665 |
+
self.write(", ")
|
1666 |
+
self.visit(item.key, frame)
|
1667 |
+
self.write(": ")
|
1668 |
+
self.visit(item.value, frame)
|
1669 |
+
self.write("}")
|
1670 |
+
|
1671 |
+
visit_Add = _make_binop("+")
|
1672 |
+
visit_Sub = _make_binop("-")
|
1673 |
+
visit_Mul = _make_binop("*")
|
1674 |
+
visit_Div = _make_binop("/")
|
1675 |
+
visit_FloorDiv = _make_binop("//")
|
1676 |
+
visit_Pow = _make_binop("**")
|
1677 |
+
visit_Mod = _make_binop("%")
|
1678 |
+
visit_And = _make_binop("and")
|
1679 |
+
visit_Or = _make_binop("or")
|
1680 |
+
visit_Pos = _make_unop("+")
|
1681 |
+
visit_Neg = _make_unop("-")
|
1682 |
+
visit_Not = _make_unop("not ")
|
1683 |
+
|
1684 |
+
@optimizeconst
|
1685 |
+
def visit_Concat(self, node: nodes.Concat, frame: Frame) -> None:
|
1686 |
+
if frame.eval_ctx.volatile:
|
1687 |
+
func_name = "(markup_join if context.eval_ctx.volatile else str_join)"
|
1688 |
+
elif frame.eval_ctx.autoescape:
|
1689 |
+
func_name = "markup_join"
|
1690 |
+
else:
|
1691 |
+
func_name = "str_join"
|
1692 |
+
self.write(f"{func_name}((")
|
1693 |
+
for arg in node.nodes:
|
1694 |
+
self.visit(arg, frame)
|
1695 |
+
self.write(", ")
|
1696 |
+
self.write("))")
|
1697 |
+
|
1698 |
+
@optimizeconst
|
1699 |
+
def visit_Compare(self, node: nodes.Compare, frame: Frame) -> None:
|
1700 |
+
self.write("(")
|
1701 |
+
self.visit(node.expr, frame)
|
1702 |
+
for op in node.ops:
|
1703 |
+
self.visit(op, frame)
|
1704 |
+
self.write(")")
|
1705 |
+
|
1706 |
+
def visit_Operand(self, node: nodes.Operand, frame: Frame) -> None:
|
1707 |
+
self.write(f" {operators[node.op]} ")
|
1708 |
+
self.visit(node.expr, frame)
|
1709 |
+
|
1710 |
+
@optimizeconst
|
1711 |
+
def visit_Getattr(self, node: nodes.Getattr, frame: Frame) -> None:
|
1712 |
+
if self.environment.is_async:
|
1713 |
+
self.write("(await auto_await(")
|
1714 |
+
|
1715 |
+
self.write("environment.getattr(")
|
1716 |
+
self.visit(node.node, frame)
|
1717 |
+
self.write(f", {node.attr!r})")
|
1718 |
+
|
1719 |
+
if self.environment.is_async:
|
1720 |
+
self.write("))")
|
1721 |
+
|
1722 |
+
@optimizeconst
|
1723 |
+
def visit_Getitem(self, node: nodes.Getitem, frame: Frame) -> None:
|
1724 |
+
# slices bypass the environment getitem method.
|
1725 |
+
if isinstance(node.arg, nodes.Slice):
|
1726 |
+
self.visit(node.node, frame)
|
1727 |
+
self.write("[")
|
1728 |
+
self.visit(node.arg, frame)
|
1729 |
+
self.write("]")
|
1730 |
+
else:
|
1731 |
+
if self.environment.is_async:
|
1732 |
+
self.write("(await auto_await(")
|
1733 |
+
|
1734 |
+
self.write("environment.getitem(")
|
1735 |
+
self.visit(node.node, frame)
|
1736 |
+
self.write(", ")
|
1737 |
+
self.visit(node.arg, frame)
|
1738 |
+
self.write(")")
|
1739 |
+
|
1740 |
+
if self.environment.is_async:
|
1741 |
+
self.write("))")
|
1742 |
+
|
1743 |
+
def visit_Slice(self, node: nodes.Slice, frame: Frame) -> None:
|
1744 |
+
if node.start is not None:
|
1745 |
+
self.visit(node.start, frame)
|
1746 |
+
self.write(":")
|
1747 |
+
if node.stop is not None:
|
1748 |
+
self.visit(node.stop, frame)
|
1749 |
+
if node.step is not None:
|
1750 |
+
self.write(":")
|
1751 |
+
self.visit(node.step, frame)
|
1752 |
+
|
1753 |
+
@contextmanager
|
1754 |
+
def _filter_test_common(
|
1755 |
+
self, node: t.Union[nodes.Filter, nodes.Test], frame: Frame, is_filter: bool
|
1756 |
+
) -> t.Iterator[None]:
|
1757 |
+
if self.environment.is_async:
|
1758 |
+
self.write("(await auto_await(")
|
1759 |
+
|
1760 |
+
if is_filter:
|
1761 |
+
self.write(f"{self.filters[node.name]}(")
|
1762 |
+
func = self.environment.filters.get(node.name)
|
1763 |
+
else:
|
1764 |
+
self.write(f"{self.tests[node.name]}(")
|
1765 |
+
func = self.environment.tests.get(node.name)
|
1766 |
+
|
1767 |
+
# When inside an If or CondExpr frame, allow the filter to be
|
1768 |
+
# undefined at compile time and only raise an error if it's
|
1769 |
+
# actually called at runtime. See pull_dependencies.
|
1770 |
+
if func is None and not frame.soft_frame:
|
1771 |
+
type_name = "filter" if is_filter else "test"
|
1772 |
+
self.fail(f"No {type_name} named {node.name!r}.", node.lineno)
|
1773 |
+
|
1774 |
+
pass_arg = {
|
1775 |
+
_PassArg.context: "context",
|
1776 |
+
_PassArg.eval_context: "context.eval_ctx",
|
1777 |
+
_PassArg.environment: "environment",
|
1778 |
+
}.get(
|
1779 |
+
_PassArg.from_obj(func) # type: ignore
|
1780 |
+
)
|
1781 |
+
|
1782 |
+
if pass_arg is not None:
|
1783 |
+
self.write(f"{pass_arg}, ")
|
1784 |
+
|
1785 |
+
# Back to the visitor function to handle visiting the target of
|
1786 |
+
# the filter or test.
|
1787 |
+
yield
|
1788 |
+
|
1789 |
+
self.signature(node, frame)
|
1790 |
+
self.write(")")
|
1791 |
+
|
1792 |
+
if self.environment.is_async:
|
1793 |
+
self.write("))")
|
1794 |
+
|
1795 |
+
@optimizeconst
|
1796 |
+
def visit_Filter(self, node: nodes.Filter, frame: Frame) -> None:
|
1797 |
+
with self._filter_test_common(node, frame, True):
|
1798 |
+
# if the filter node is None we are inside a filter block
|
1799 |
+
# and want to write to the current buffer
|
1800 |
+
if node.node is not None:
|
1801 |
+
self.visit(node.node, frame)
|
1802 |
+
elif frame.eval_ctx.volatile:
|
1803 |
+
self.write(
|
1804 |
+
f"(Markup(concat({frame.buffer}))"
|
1805 |
+
f" if context.eval_ctx.autoescape else concat({frame.buffer}))"
|
1806 |
+
)
|
1807 |
+
elif frame.eval_ctx.autoescape:
|
1808 |
+
self.write(f"Markup(concat({frame.buffer}))")
|
1809 |
+
else:
|
1810 |
+
self.write(f"concat({frame.buffer})")
|
1811 |
+
|
1812 |
+
@optimizeconst
|
1813 |
+
def visit_Test(self, node: nodes.Test, frame: Frame) -> None:
|
1814 |
+
with self._filter_test_common(node, frame, False):
|
1815 |
+
self.visit(node.node, frame)
|
1816 |
+
|
1817 |
+
@optimizeconst
|
1818 |
+
def visit_CondExpr(self, node: nodes.CondExpr, frame: Frame) -> None:
|
1819 |
+
frame = frame.soft()
|
1820 |
+
|
1821 |
+
def write_expr2() -> None:
|
1822 |
+
if node.expr2 is not None:
|
1823 |
+
self.visit(node.expr2, frame)
|
1824 |
+
return
|
1825 |
+
|
1826 |
+
self.write(
|
1827 |
+
f'cond_expr_undefined("the inline if-expression on'
|
1828 |
+
f" {self.position(node)} evaluated to false and no else"
|
1829 |
+
f' section was defined.")'
|
1830 |
+
)
|
1831 |
+
|
1832 |
+
self.write("(")
|
1833 |
+
self.visit(node.expr1, frame)
|
1834 |
+
self.write(" if ")
|
1835 |
+
self.visit(node.test, frame)
|
1836 |
+
self.write(" else ")
|
1837 |
+
write_expr2()
|
1838 |
+
self.write(")")
|
1839 |
+
|
1840 |
+
@optimizeconst
|
1841 |
+
def visit_Call(
|
1842 |
+
self, node: nodes.Call, frame: Frame, forward_caller: bool = False
|
1843 |
+
) -> None:
|
1844 |
+
if self.environment.is_async:
|
1845 |
+
self.write("(await auto_await(")
|
1846 |
+
if self.environment.sandboxed:
|
1847 |
+
self.write("environment.call(context, ")
|
1848 |
+
else:
|
1849 |
+
self.write("context.call(")
|
1850 |
+
self.visit(node.node, frame)
|
1851 |
+
extra_kwargs = {"caller": "caller"} if forward_caller else None
|
1852 |
+
loop_kwargs = {"_loop_vars": "_loop_vars"} if frame.loop_frame else {}
|
1853 |
+
block_kwargs = {"_block_vars": "_block_vars"} if frame.block_frame else {}
|
1854 |
+
if extra_kwargs:
|
1855 |
+
extra_kwargs.update(loop_kwargs, **block_kwargs)
|
1856 |
+
elif loop_kwargs or block_kwargs:
|
1857 |
+
extra_kwargs = dict(loop_kwargs, **block_kwargs)
|
1858 |
+
self.signature(node, frame, extra_kwargs)
|
1859 |
+
self.write(")")
|
1860 |
+
if self.environment.is_async:
|
1861 |
+
self.write("))")
|
1862 |
+
|
1863 |
+
def visit_Keyword(self, node: nodes.Keyword, frame: Frame) -> None:
|
1864 |
+
self.write(node.key + "=")
|
1865 |
+
self.visit(node.value, frame)
|
1866 |
+
|
1867 |
+
# -- Unused nodes for extensions
|
1868 |
+
|
1869 |
+
def visit_MarkSafe(self, node: nodes.MarkSafe, frame: Frame) -> None:
|
1870 |
+
self.write("Markup(")
|
1871 |
+
self.visit(node.expr, frame)
|
1872 |
+
self.write(")")
|
1873 |
+
|
1874 |
+
def visit_MarkSafeIfAutoescape(
|
1875 |
+
self, node: nodes.MarkSafeIfAutoescape, frame: Frame
|
1876 |
+
) -> None:
|
1877 |
+
self.write("(Markup if context.eval_ctx.autoescape else identity)(")
|
1878 |
+
self.visit(node.expr, frame)
|
1879 |
+
self.write(")")
|
1880 |
+
|
1881 |
+
def visit_EnvironmentAttribute(
|
1882 |
+
self, node: nodes.EnvironmentAttribute, frame: Frame
|
1883 |
+
) -> None:
|
1884 |
+
self.write("environment." + node.name)
|
1885 |
+
|
1886 |
+
def visit_ExtensionAttribute(
|
1887 |
+
self, node: nodes.ExtensionAttribute, frame: Frame
|
1888 |
+
) -> None:
|
1889 |
+
self.write(f"environment.extensions[{node.identifier!r}].{node.name}")
|
1890 |
+
|
1891 |
+
def visit_ImportedName(self, node: nodes.ImportedName, frame: Frame) -> None:
|
1892 |
+
self.write(self.import_aliases[node.importname])
|
1893 |
+
|
1894 |
+
def visit_InternalName(self, node: nodes.InternalName, frame: Frame) -> None:
|
1895 |
+
self.write(node.name)
|
1896 |
+
|
1897 |
+
def visit_ContextReference(
|
1898 |
+
self, node: nodes.ContextReference, frame: Frame
|
1899 |
+
) -> None:
|
1900 |
+
self.write("context")
|
1901 |
+
|
1902 |
+
def visit_DerivedContextReference(
|
1903 |
+
self, node: nodes.DerivedContextReference, frame: Frame
|
1904 |
+
) -> None:
|
1905 |
+
self.write(self.derive_context(frame))
|
1906 |
+
|
1907 |
+
def visit_Continue(self, node: nodes.Continue, frame: Frame) -> None:
|
1908 |
+
self.writeline("continue", node)
|
1909 |
+
|
1910 |
+
def visit_Break(self, node: nodes.Break, frame: Frame) -> None:
|
1911 |
+
self.writeline("break", node)
|
1912 |
+
|
1913 |
+
def visit_Scope(self, node: nodes.Scope, frame: Frame) -> None:
|
1914 |
+
scope_frame = frame.inner()
|
1915 |
+
scope_frame.symbols.analyze_node(node)
|
1916 |
+
self.enter_frame(scope_frame)
|
1917 |
+
self.blockvisit(node.body, scope_frame)
|
1918 |
+
self.leave_frame(scope_frame)
|
1919 |
+
|
1920 |
+
def visit_OverlayScope(self, node: nodes.OverlayScope, frame: Frame) -> None:
|
1921 |
+
ctx = self.temporary_identifier()
|
1922 |
+
self.writeline(f"{ctx} = {self.derive_context(frame)}")
|
1923 |
+
self.writeline(f"{ctx}.vars = ")
|
1924 |
+
self.visit(node.context, frame)
|
1925 |
+
self.push_context_reference(ctx)
|
1926 |
+
|
1927 |
+
scope_frame = frame.inner(isolated=True)
|
1928 |
+
scope_frame.symbols.analyze_node(node)
|
1929 |
+
self.enter_frame(scope_frame)
|
1930 |
+
self.blockvisit(node.body, scope_frame)
|
1931 |
+
self.leave_frame(scope_frame)
|
1932 |
+
self.pop_context_reference()
|
1933 |
+
|
1934 |
+
def visit_EvalContextModifier(
|
1935 |
+
self, node: nodes.EvalContextModifier, frame: Frame
|
1936 |
+
) -> None:
|
1937 |
+
for keyword in node.options:
|
1938 |
+
self.writeline(f"context.eval_ctx.{keyword.key} = ")
|
1939 |
+
self.visit(keyword.value, frame)
|
1940 |
+
try:
|
1941 |
+
val = keyword.value.as_const(frame.eval_ctx)
|
1942 |
+
except nodes.Impossible:
|
1943 |
+
frame.eval_ctx.volatile = True
|
1944 |
+
else:
|
1945 |
+
setattr(frame.eval_ctx, keyword.key, val)
|
1946 |
+
|
1947 |
+
def visit_ScopedEvalContextModifier(
|
1948 |
+
self, node: nodes.ScopedEvalContextModifier, frame: Frame
|
1949 |
+
) -> None:
|
1950 |
+
old_ctx_name = self.temporary_identifier()
|
1951 |
+
saved_ctx = frame.eval_ctx.save()
|
1952 |
+
self.writeline(f"{old_ctx_name} = context.eval_ctx.save()")
|
1953 |
+
self.visit_EvalContextModifier(node, frame)
|
1954 |
+
for child in node.body:
|
1955 |
+
self.visit(child, frame)
|
1956 |
+
frame.eval_ctx.revert(saved_ctx)
|
1957 |
+
self.writeline(f"context.eval_ctx.revert({old_ctx_name})")
|
lib/python3.11/site-packages/jinja2/constants.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#: list of lorem ipsum words used by the lipsum() helper function
|
2 |
+
LOREM_IPSUM_WORDS = """\
|
3 |
+
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
|
4 |
+
auctor augue bibendum blandit class commodo condimentum congue consectetuer
|
5 |
+
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
|
6 |
+
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
|
7 |
+
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
|
8 |
+
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
|
9 |
+
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
|
10 |
+
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
|
11 |
+
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
|
12 |
+
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
|
13 |
+
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
|
14 |
+
penatibus per pharetra phasellus placerat platea porta porttitor posuere
|
15 |
+
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
|
16 |
+
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
|
17 |
+
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
|
18 |
+
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
|
19 |
+
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
|
20 |
+
viverra volutpat vulputate"""
|
lib/python3.11/site-packages/jinja2/debug.py
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import typing as t
|
3 |
+
from types import CodeType
|
4 |
+
from types import TracebackType
|
5 |
+
|
6 |
+
from .exceptions import TemplateSyntaxError
|
7 |
+
from .utils import internal_code
|
8 |
+
from .utils import missing
|
9 |
+
|
10 |
+
if t.TYPE_CHECKING:
|
11 |
+
from .runtime import Context
|
12 |
+
|
13 |
+
|
14 |
+
def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
|
15 |
+
"""Rewrite the current exception to replace any tracebacks from
|
16 |
+
within compiled template code with tracebacks that look like they
|
17 |
+
came from the template source.
|
18 |
+
|
19 |
+
This must be called within an ``except`` block.
|
20 |
+
|
21 |
+
:param source: For ``TemplateSyntaxError``, the original source if
|
22 |
+
known.
|
23 |
+
:return: The original exception with the rewritten traceback.
|
24 |
+
"""
|
25 |
+
_, exc_value, tb = sys.exc_info()
|
26 |
+
exc_value = t.cast(BaseException, exc_value)
|
27 |
+
tb = t.cast(TracebackType, tb)
|
28 |
+
|
29 |
+
if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
|
30 |
+
exc_value.translated = True
|
31 |
+
exc_value.source = source
|
32 |
+
# Remove the old traceback, otherwise the frames from the
|
33 |
+
# compiler still show up.
|
34 |
+
exc_value.with_traceback(None)
|
35 |
+
# Outside of runtime, so the frame isn't executing template
|
36 |
+
# code, but it still needs to point at the template.
|
37 |
+
tb = fake_traceback(
|
38 |
+
exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno
|
39 |
+
)
|
40 |
+
else:
|
41 |
+
# Skip the frame for the render function.
|
42 |
+
tb = tb.tb_next
|
43 |
+
|
44 |
+
stack = []
|
45 |
+
|
46 |
+
# Build the stack of traceback object, replacing any in template
|
47 |
+
# code with the source file and line information.
|
48 |
+
while tb is not None:
|
49 |
+
# Skip frames decorated with @internalcode. These are internal
|
50 |
+
# calls that aren't useful in template debugging output.
|
51 |
+
if tb.tb_frame.f_code in internal_code:
|
52 |
+
tb = tb.tb_next
|
53 |
+
continue
|
54 |
+
|
55 |
+
template = tb.tb_frame.f_globals.get("__jinja_template__")
|
56 |
+
|
57 |
+
if template is not None:
|
58 |
+
lineno = template.get_corresponding_lineno(tb.tb_lineno)
|
59 |
+
fake_tb = fake_traceback(exc_value, tb, template.filename, lineno)
|
60 |
+
stack.append(fake_tb)
|
61 |
+
else:
|
62 |
+
stack.append(tb)
|
63 |
+
|
64 |
+
tb = tb.tb_next
|
65 |
+
|
66 |
+
tb_next = None
|
67 |
+
|
68 |
+
# Assign tb_next in reverse to avoid circular references.
|
69 |
+
for tb in reversed(stack):
|
70 |
+
tb.tb_next = tb_next
|
71 |
+
tb_next = tb
|
72 |
+
|
73 |
+
return exc_value.with_traceback(tb_next)
|
74 |
+
|
75 |
+
|
76 |
+
def fake_traceback( # type: ignore
|
77 |
+
exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int
|
78 |
+
) -> TracebackType:
|
79 |
+
"""Produce a new traceback object that looks like it came from the
|
80 |
+
template source instead of the compiled code. The filename, line
|
81 |
+
number, and location name will point to the template, and the local
|
82 |
+
variables will be the current template context.
|
83 |
+
|
84 |
+
:param exc_value: The original exception to be re-raised to create
|
85 |
+
the new traceback.
|
86 |
+
:param tb: The original traceback to get the local variables and
|
87 |
+
code info from.
|
88 |
+
:param filename: The template filename.
|
89 |
+
:param lineno: The line number in the template source.
|
90 |
+
"""
|
91 |
+
if tb is not None:
|
92 |
+
# Replace the real locals with the context that would be
|
93 |
+
# available at that point in the template.
|
94 |
+
locals = get_template_locals(tb.tb_frame.f_locals)
|
95 |
+
locals.pop("__jinja_exception__", None)
|
96 |
+
else:
|
97 |
+
locals = {}
|
98 |
+
|
99 |
+
globals = {
|
100 |
+
"__name__": filename,
|
101 |
+
"__file__": filename,
|
102 |
+
"__jinja_exception__": exc_value,
|
103 |
+
}
|
104 |
+
# Raise an exception at the correct line number.
|
105 |
+
code: CodeType = compile(
|
106 |
+
"\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec"
|
107 |
+
)
|
108 |
+
|
109 |
+
# Build a new code object that points to the template file and
|
110 |
+
# replaces the location with a block name.
|
111 |
+
location = "template"
|
112 |
+
|
113 |
+
if tb is not None:
|
114 |
+
function = tb.tb_frame.f_code.co_name
|
115 |
+
|
116 |
+
if function == "root":
|
117 |
+
location = "top-level template code"
|
118 |
+
elif function.startswith("block_"):
|
119 |
+
location = f"block {function[6:]!r}"
|
120 |
+
|
121 |
+
if sys.version_info >= (3, 8):
|
122 |
+
code = code.replace(co_name=location)
|
123 |
+
else:
|
124 |
+
code = CodeType(
|
125 |
+
code.co_argcount,
|
126 |
+
code.co_kwonlyargcount,
|
127 |
+
code.co_nlocals,
|
128 |
+
code.co_stacksize,
|
129 |
+
code.co_flags,
|
130 |
+
code.co_code,
|
131 |
+
code.co_consts,
|
132 |
+
code.co_names,
|
133 |
+
code.co_varnames,
|
134 |
+
code.co_filename,
|
135 |
+
location,
|
136 |
+
code.co_firstlineno,
|
137 |
+
code.co_lnotab,
|
138 |
+
code.co_freevars,
|
139 |
+
code.co_cellvars,
|
140 |
+
)
|
141 |
+
|
142 |
+
# Execute the new code, which is guaranteed to raise, and return
|
143 |
+
# the new traceback without this frame.
|
144 |
+
try:
|
145 |
+
exec(code, globals, locals)
|
146 |
+
except BaseException:
|
147 |
+
return sys.exc_info()[2].tb_next # type: ignore
|
148 |
+
|
149 |
+
|
150 |
+
def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]:
|
151 |
+
"""Based on the runtime locals, get the context that would be
|
152 |
+
available at that point in the template.
|
153 |
+
"""
|
154 |
+
# Start with the current template context.
|
155 |
+
ctx: "t.Optional[Context]" = real_locals.get("context")
|
156 |
+
|
157 |
+
if ctx is not None:
|
158 |
+
data: t.Dict[str, t.Any] = ctx.get_all().copy()
|
159 |
+
else:
|
160 |
+
data = {}
|
161 |
+
|
162 |
+
# Might be in a derived context that only sets local variables
|
163 |
+
# rather than pushing a context. Local variables follow the scheme
|
164 |
+
# l_depth_name. Find the highest-depth local that has a value for
|
165 |
+
# each name.
|
166 |
+
local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {}
|
167 |
+
|
168 |
+
for name, value in real_locals.items():
|
169 |
+
if not name.startswith("l_") or value is missing:
|
170 |
+
# Not a template variable, or no longer relevant.
|
171 |
+
continue
|
172 |
+
|
173 |
+
try:
|
174 |
+
_, depth_str, name = name.split("_", 2)
|
175 |
+
depth = int(depth_str)
|
176 |
+
except ValueError:
|
177 |
+
continue
|
178 |
+
|
179 |
+
cur_depth = local_overrides.get(name, (-1,))[0]
|
180 |
+
|
181 |
+
if cur_depth < depth:
|
182 |
+
local_overrides[name] = (depth, value)
|
183 |
+
|
184 |
+
# Modify the context with any derived context.
|
185 |
+
for name, (_, value) in local_overrides.items():
|
186 |
+
if value is missing:
|
187 |
+
data.pop(name, None)
|
188 |
+
else:
|
189 |
+
data[name] = value
|
190 |
+
|
191 |
+
return data
|
lib/python3.11/site-packages/jinja2/defaults.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import typing as t
|
2 |
+
|
3 |
+
from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401
|
4 |
+
from .tests import TESTS as DEFAULT_TESTS # noqa: F401
|
5 |
+
from .utils import Cycler
|
6 |
+
from .utils import generate_lorem_ipsum
|
7 |
+
from .utils import Joiner
|
8 |
+
from .utils import Namespace
|
9 |
+
|
10 |
+
if t.TYPE_CHECKING:
|
11 |
+
import typing_extensions as te
|
12 |
+
|
13 |
+
# defaults for the parser / lexer
|
14 |
+
BLOCK_START_STRING = "{%"
|
15 |
+
BLOCK_END_STRING = "%}"
|
16 |
+
VARIABLE_START_STRING = "{{"
|
17 |
+
VARIABLE_END_STRING = "}}"
|
18 |
+
COMMENT_START_STRING = "{#"
|
19 |
+
COMMENT_END_STRING = "#}"
|
20 |
+
LINE_STATEMENT_PREFIX: t.Optional[str] = None
|
21 |
+
LINE_COMMENT_PREFIX: t.Optional[str] = None
|
22 |
+
TRIM_BLOCKS = False
|
23 |
+
LSTRIP_BLOCKS = False
|
24 |
+
NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n"
|
25 |
+
KEEP_TRAILING_NEWLINE = False
|
26 |
+
|
27 |
+
# default filters, tests and namespace
|
28 |
+
|
29 |
+
DEFAULT_NAMESPACE = {
|
30 |
+
"range": range,
|
31 |
+
"dict": dict,
|
32 |
+
"lipsum": generate_lorem_ipsum,
|
33 |
+
"cycler": Cycler,
|
34 |
+
"joiner": Joiner,
|
35 |
+
"namespace": Namespace,
|
36 |
+
}
|
37 |
+
|
38 |
+
# default policies
|
39 |
+
DEFAULT_POLICIES: t.Dict[str, t.Any] = {
|
40 |
+
"compiler.ascii_str": True,
|
41 |
+
"urlize.rel": "noopener",
|
42 |
+
"urlize.target": None,
|
43 |
+
"urlize.extra_schemes": None,
|
44 |
+
"truncate.leeway": 5,
|
45 |
+
"json.dumps_function": None,
|
46 |
+
"json.dumps_kwargs": {"sort_keys": True},
|
47 |
+
"ext.i18n.trimmed": False,
|
48 |
+
}
|
lib/python3.11/site-packages/jinja2/environment.py
ADDED
@@ -0,0 +1,1667 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Classes for managing templates and their runtime and compile time
|
2 |
+
options.
|
3 |
+
"""
|
4 |
+
import os
|
5 |
+
import typing
|
6 |
+
import typing as t
|
7 |
+
import weakref
|
8 |
+
from collections import ChainMap
|
9 |
+
from functools import lru_cache
|
10 |
+
from functools import partial
|
11 |
+
from functools import reduce
|
12 |
+
from types import CodeType
|
13 |
+
|
14 |
+
from markupsafe import Markup
|
15 |
+
|
16 |
+
from . import nodes
|
17 |
+
from .compiler import CodeGenerator
|
18 |
+
from .compiler import generate
|
19 |
+
from .defaults import BLOCK_END_STRING
|
20 |
+
from .defaults import BLOCK_START_STRING
|
21 |
+
from .defaults import COMMENT_END_STRING
|
22 |
+
from .defaults import COMMENT_START_STRING
|
23 |
+
from .defaults import DEFAULT_FILTERS
|
24 |
+
from .defaults import DEFAULT_NAMESPACE
|
25 |
+
from .defaults import DEFAULT_POLICIES
|
26 |
+
from .defaults import DEFAULT_TESTS
|
27 |
+
from .defaults import KEEP_TRAILING_NEWLINE
|
28 |
+
from .defaults import LINE_COMMENT_PREFIX
|
29 |
+
from .defaults import LINE_STATEMENT_PREFIX
|
30 |
+
from .defaults import LSTRIP_BLOCKS
|
31 |
+
from .defaults import NEWLINE_SEQUENCE
|
32 |
+
from .defaults import TRIM_BLOCKS
|
33 |
+
from .defaults import VARIABLE_END_STRING
|
34 |
+
from .defaults import VARIABLE_START_STRING
|
35 |
+
from .exceptions import TemplateNotFound
|
36 |
+
from .exceptions import TemplateRuntimeError
|
37 |
+
from .exceptions import TemplatesNotFound
|
38 |
+
from .exceptions import TemplateSyntaxError
|
39 |
+
from .exceptions import UndefinedError
|
40 |
+
from .lexer import get_lexer
|
41 |
+
from .lexer import Lexer
|
42 |
+
from .lexer import TokenStream
|
43 |
+
from .nodes import EvalContext
|
44 |
+
from .parser import Parser
|
45 |
+
from .runtime import Context
|
46 |
+
from .runtime import new_context
|
47 |
+
from .runtime import Undefined
|
48 |
+
from .utils import _PassArg
|
49 |
+
from .utils import concat
|
50 |
+
from .utils import consume
|
51 |
+
from .utils import import_string
|
52 |
+
from .utils import internalcode
|
53 |
+
from .utils import LRUCache
|
54 |
+
from .utils import missing
|
55 |
+
|
56 |
+
if t.TYPE_CHECKING:
|
57 |
+
import typing_extensions as te
|
58 |
+
from .bccache import BytecodeCache
|
59 |
+
from .ext import Extension
|
60 |
+
from .loaders import BaseLoader
|
61 |
+
|
62 |
+
_env_bound = t.TypeVar("_env_bound", bound="Environment")
|
63 |
+
|
64 |
+
|
65 |
+
# for direct template usage we have up to ten living environments
|
66 |
+
@lru_cache(maxsize=10)
|
67 |
+
def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound:
|
68 |
+
"""Return a new spontaneous environment. A spontaneous environment
|
69 |
+
is used for templates created directly rather than through an
|
70 |
+
existing environment.
|
71 |
+
|
72 |
+
:param cls: Environment class to create.
|
73 |
+
:param args: Positional arguments passed to environment.
|
74 |
+
"""
|
75 |
+
env = cls(*args)
|
76 |
+
env.shared = True
|
77 |
+
return env
|
78 |
+
|
79 |
+
|
80 |
+
def create_cache(
|
81 |
+
size: int,
|
82 |
+
) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]:
|
83 |
+
"""Return the cache class for the given size."""
|
84 |
+
if size == 0:
|
85 |
+
return None
|
86 |
+
|
87 |
+
if size < 0:
|
88 |
+
return {}
|
89 |
+
|
90 |
+
return LRUCache(size) # type: ignore
|
91 |
+
|
92 |
+
|
93 |
+
def copy_cache(
|
94 |
+
cache: t.Optional[t.MutableMapping],
|
95 |
+
) -> t.Optional[t.MutableMapping[t.Tuple[weakref.ref, str], "Template"]]:
|
96 |
+
"""Create an empty copy of the given cache."""
|
97 |
+
if cache is None:
|
98 |
+
return None
|
99 |
+
|
100 |
+
if type(cache) is dict:
|
101 |
+
return {}
|
102 |
+
|
103 |
+
return LRUCache(cache.capacity) # type: ignore
|
104 |
+
|
105 |
+
|
106 |
+
def load_extensions(
|
107 |
+
environment: "Environment",
|
108 |
+
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]],
|
109 |
+
) -> t.Dict[str, "Extension"]:
|
110 |
+
"""Load the extensions from the list and bind it to the environment.
|
111 |
+
Returns a dict of instantiated extensions.
|
112 |
+
"""
|
113 |
+
result = {}
|
114 |
+
|
115 |
+
for extension in extensions:
|
116 |
+
if isinstance(extension, str):
|
117 |
+
extension = t.cast(t.Type["Extension"], import_string(extension))
|
118 |
+
|
119 |
+
result[extension.identifier] = extension(environment)
|
120 |
+
|
121 |
+
return result
|
122 |
+
|
123 |
+
|
124 |
+
def _environment_config_check(environment: "Environment") -> "Environment":
|
125 |
+
"""Perform a sanity check on the environment."""
|
126 |
+
assert issubclass(
|
127 |
+
environment.undefined, Undefined
|
128 |
+
), "'undefined' must be a subclass of 'jinja2.Undefined'."
|
129 |
+
assert (
|
130 |
+
environment.block_start_string
|
131 |
+
!= environment.variable_start_string
|
132 |
+
!= environment.comment_start_string
|
133 |
+
), "block, variable and comment start strings must be different."
|
134 |
+
assert environment.newline_sequence in {
|
135 |
+
"\r",
|
136 |
+
"\r\n",
|
137 |
+
"\n",
|
138 |
+
}, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'."
|
139 |
+
return environment
|
140 |
+
|
141 |
+
|
142 |
+
class Environment:
|
143 |
+
r"""The core component of Jinja is the `Environment`. It contains
|
144 |
+
important shared variables like configuration, filters, tests,
|
145 |
+
globals and others. Instances of this class may be modified if
|
146 |
+
they are not shared and if no template was loaded so far.
|
147 |
+
Modifications on environments after the first template was loaded
|
148 |
+
will lead to surprising effects and undefined behavior.
|
149 |
+
|
150 |
+
Here are the possible initialization parameters:
|
151 |
+
|
152 |
+
`block_start_string`
|
153 |
+
The string marking the beginning of a block. Defaults to ``'{%'``.
|
154 |
+
|
155 |
+
`block_end_string`
|
156 |
+
The string marking the end of a block. Defaults to ``'%}'``.
|
157 |
+
|
158 |
+
`variable_start_string`
|
159 |
+
The string marking the beginning of a print statement.
|
160 |
+
Defaults to ``'{{'``.
|
161 |
+
|
162 |
+
`variable_end_string`
|
163 |
+
The string marking the end of a print statement. Defaults to
|
164 |
+
``'}}'``.
|
165 |
+
|
166 |
+
`comment_start_string`
|
167 |
+
The string marking the beginning of a comment. Defaults to ``'{#'``.
|
168 |
+
|
169 |
+
`comment_end_string`
|
170 |
+
The string marking the end of a comment. Defaults to ``'#}'``.
|
171 |
+
|
172 |
+
`line_statement_prefix`
|
173 |
+
If given and a string, this will be used as prefix for line based
|
174 |
+
statements. See also :ref:`line-statements`.
|
175 |
+
|
176 |
+
`line_comment_prefix`
|
177 |
+
If given and a string, this will be used as prefix for line based
|
178 |
+
comments. See also :ref:`line-statements`.
|
179 |
+
|
180 |
+
.. versionadded:: 2.2
|
181 |
+
|
182 |
+
`trim_blocks`
|
183 |
+
If this is set to ``True`` the first newline after a block is
|
184 |
+
removed (block, not variable tag!). Defaults to `False`.
|
185 |
+
|
186 |
+
`lstrip_blocks`
|
187 |
+
If this is set to ``True`` leading spaces and tabs are stripped
|
188 |
+
from the start of a line to a block. Defaults to `False`.
|
189 |
+
|
190 |
+
`newline_sequence`
|
191 |
+
The sequence that starts a newline. Must be one of ``'\r'``,
|
192 |
+
``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a
|
193 |
+
useful default for Linux and OS X systems as well as web
|
194 |
+
applications.
|
195 |
+
|
196 |
+
`keep_trailing_newline`
|
197 |
+
Preserve the trailing newline when rendering templates.
|
198 |
+
The default is ``False``, which causes a single newline,
|
199 |
+
if present, to be stripped from the end of the template.
|
200 |
+
|
201 |
+
.. versionadded:: 2.7
|
202 |
+
|
203 |
+
`extensions`
|
204 |
+
List of Jinja extensions to use. This can either be import paths
|
205 |
+
as strings or extension classes. For more information have a
|
206 |
+
look at :ref:`the extensions documentation <jinja-extensions>`.
|
207 |
+
|
208 |
+
`optimized`
|
209 |
+
should the optimizer be enabled? Default is ``True``.
|
210 |
+
|
211 |
+
`undefined`
|
212 |
+
:class:`Undefined` or a subclass of it that is used to represent
|
213 |
+
undefined values in the template.
|
214 |
+
|
215 |
+
`finalize`
|
216 |
+
A callable that can be used to process the result of a variable
|
217 |
+
expression before it is output. For example one can convert
|
218 |
+
``None`` implicitly into an empty string here.
|
219 |
+
|
220 |
+
`autoescape`
|
221 |
+
If set to ``True`` the XML/HTML autoescaping feature is enabled by
|
222 |
+
default. For more details about autoescaping see
|
223 |
+
:class:`~markupsafe.Markup`. As of Jinja 2.4 this can also
|
224 |
+
be a callable that is passed the template name and has to
|
225 |
+
return ``True`` or ``False`` depending on autoescape should be
|
226 |
+
enabled by default.
|
227 |
+
|
228 |
+
.. versionchanged:: 2.4
|
229 |
+
`autoescape` can now be a function
|
230 |
+
|
231 |
+
`loader`
|
232 |
+
The template loader for this environment.
|
233 |
+
|
234 |
+
`cache_size`
|
235 |
+
The size of the cache. Per default this is ``400`` which means
|
236 |
+
that if more than 400 templates are loaded the loader will clean
|
237 |
+
out the least recently used template. If the cache size is set to
|
238 |
+
``0`` templates are recompiled all the time, if the cache size is
|
239 |
+
``-1`` the cache will not be cleaned.
|
240 |
+
|
241 |
+
.. versionchanged:: 2.8
|
242 |
+
The cache size was increased to 400 from a low 50.
|
243 |
+
|
244 |
+
`auto_reload`
|
245 |
+
Some loaders load templates from locations where the template
|
246 |
+
sources may change (ie: file system or database). If
|
247 |
+
``auto_reload`` is set to ``True`` (default) every time a template is
|
248 |
+
requested the loader checks if the source changed and if yes, it
|
249 |
+
will reload the template. For higher performance it's possible to
|
250 |
+
disable that.
|
251 |
+
|
252 |
+
`bytecode_cache`
|
253 |
+
If set to a bytecode cache object, this object will provide a
|
254 |
+
cache for the internal Jinja bytecode so that templates don't
|
255 |
+
have to be parsed if they were not changed.
|
256 |
+
|
257 |
+
See :ref:`bytecode-cache` for more information.
|
258 |
+
|
259 |
+
`enable_async`
|
260 |
+
If set to true this enables async template execution which
|
261 |
+
allows using async functions and generators.
|
262 |
+
"""
|
263 |
+
|
264 |
+
#: if this environment is sandboxed. Modifying this variable won't make
|
265 |
+
#: the environment sandboxed though. For a real sandboxed environment
|
266 |
+
#: have a look at jinja2.sandbox. This flag alone controls the code
|
267 |
+
#: generation by the compiler.
|
268 |
+
sandboxed = False
|
269 |
+
|
270 |
+
#: True if the environment is just an overlay
|
271 |
+
overlayed = False
|
272 |
+
|
273 |
+
#: the environment this environment is linked to if it is an overlay
|
274 |
+
linked_to: t.Optional["Environment"] = None
|
275 |
+
|
276 |
+
#: shared environments have this set to `True`. A shared environment
|
277 |
+
#: must not be modified
|
278 |
+
shared = False
|
279 |
+
|
280 |
+
#: the class that is used for code generation. See
|
281 |
+
#: :class:`~jinja2.compiler.CodeGenerator` for more information.
|
282 |
+
code_generator_class: t.Type["CodeGenerator"] = CodeGenerator
|
283 |
+
|
284 |
+
concat = "".join
|
285 |
+
|
286 |
+
#: the context class that is used for templates. See
|
287 |
+
#: :class:`~jinja2.runtime.Context` for more information.
|
288 |
+
context_class: t.Type[Context] = Context
|
289 |
+
|
290 |
+
template_class: t.Type["Template"]
|
291 |
+
|
292 |
+
def __init__(
|
293 |
+
self,
|
294 |
+
block_start_string: str = BLOCK_START_STRING,
|
295 |
+
block_end_string: str = BLOCK_END_STRING,
|
296 |
+
variable_start_string: str = VARIABLE_START_STRING,
|
297 |
+
variable_end_string: str = VARIABLE_END_STRING,
|
298 |
+
comment_start_string: str = COMMENT_START_STRING,
|
299 |
+
comment_end_string: str = COMMENT_END_STRING,
|
300 |
+
line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
|
301 |
+
line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
|
302 |
+
trim_blocks: bool = TRIM_BLOCKS,
|
303 |
+
lstrip_blocks: bool = LSTRIP_BLOCKS,
|
304 |
+
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
|
305 |
+
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
|
306 |
+
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
|
307 |
+
optimized: bool = True,
|
308 |
+
undefined: t.Type[Undefined] = Undefined,
|
309 |
+
finalize: t.Optional[t.Callable[..., t.Any]] = None,
|
310 |
+
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
|
311 |
+
loader: t.Optional["BaseLoader"] = None,
|
312 |
+
cache_size: int = 400,
|
313 |
+
auto_reload: bool = True,
|
314 |
+
bytecode_cache: t.Optional["BytecodeCache"] = None,
|
315 |
+
enable_async: bool = False,
|
316 |
+
):
|
317 |
+
# !!Important notice!!
|
318 |
+
# The constructor accepts quite a few arguments that should be
|
319 |
+
# passed by keyword rather than position. However it's important to
|
320 |
+
# not change the order of arguments because it's used at least
|
321 |
+
# internally in those cases:
|
322 |
+
# - spontaneous environments (i18n extension and Template)
|
323 |
+
# - unittests
|
324 |
+
# If parameter changes are required only add parameters at the end
|
325 |
+
# and don't change the arguments (or the defaults!) of the arguments
|
326 |
+
# existing already.
|
327 |
+
|
328 |
+
# lexer / parser information
|
329 |
+
self.block_start_string = block_start_string
|
330 |
+
self.block_end_string = block_end_string
|
331 |
+
self.variable_start_string = variable_start_string
|
332 |
+
self.variable_end_string = variable_end_string
|
333 |
+
self.comment_start_string = comment_start_string
|
334 |
+
self.comment_end_string = comment_end_string
|
335 |
+
self.line_statement_prefix = line_statement_prefix
|
336 |
+
self.line_comment_prefix = line_comment_prefix
|
337 |
+
self.trim_blocks = trim_blocks
|
338 |
+
self.lstrip_blocks = lstrip_blocks
|
339 |
+
self.newline_sequence = newline_sequence
|
340 |
+
self.keep_trailing_newline = keep_trailing_newline
|
341 |
+
|
342 |
+
# runtime information
|
343 |
+
self.undefined: t.Type[Undefined] = undefined
|
344 |
+
self.optimized = optimized
|
345 |
+
self.finalize = finalize
|
346 |
+
self.autoescape = autoescape
|
347 |
+
|
348 |
+
# defaults
|
349 |
+
self.filters = DEFAULT_FILTERS.copy()
|
350 |
+
self.tests = DEFAULT_TESTS.copy()
|
351 |
+
self.globals = DEFAULT_NAMESPACE.copy()
|
352 |
+
|
353 |
+
# set the loader provided
|
354 |
+
self.loader = loader
|
355 |
+
self.cache = create_cache(cache_size)
|
356 |
+
self.bytecode_cache = bytecode_cache
|
357 |
+
self.auto_reload = auto_reload
|
358 |
+
|
359 |
+
# configurable policies
|
360 |
+
self.policies = DEFAULT_POLICIES.copy()
|
361 |
+
|
362 |
+
# load extensions
|
363 |
+
self.extensions = load_extensions(self, extensions)
|
364 |
+
|
365 |
+
self.is_async = enable_async
|
366 |
+
_environment_config_check(self)
|
367 |
+
|
368 |
+
def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None:
|
369 |
+
"""Adds an extension after the environment was created.
|
370 |
+
|
371 |
+
.. versionadded:: 2.5
|
372 |
+
"""
|
373 |
+
self.extensions.update(load_extensions(self, [extension]))
|
374 |
+
|
375 |
+
def extend(self, **attributes: t.Any) -> None:
|
376 |
+
"""Add the items to the instance of the environment if they do not exist
|
377 |
+
yet. This is used by :ref:`extensions <writing-extensions>` to register
|
378 |
+
callbacks and configuration values without breaking inheritance.
|
379 |
+
"""
|
380 |
+
for key, value in attributes.items():
|
381 |
+
if not hasattr(self, key):
|
382 |
+
setattr(self, key, value)
|
383 |
+
|
384 |
+
def overlay(
|
385 |
+
self,
|
386 |
+
block_start_string: str = missing,
|
387 |
+
block_end_string: str = missing,
|
388 |
+
variable_start_string: str = missing,
|
389 |
+
variable_end_string: str = missing,
|
390 |
+
comment_start_string: str = missing,
|
391 |
+
comment_end_string: str = missing,
|
392 |
+
line_statement_prefix: t.Optional[str] = missing,
|
393 |
+
line_comment_prefix: t.Optional[str] = missing,
|
394 |
+
trim_blocks: bool = missing,
|
395 |
+
lstrip_blocks: bool = missing,
|
396 |
+
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing,
|
397 |
+
keep_trailing_newline: bool = missing,
|
398 |
+
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing,
|
399 |
+
optimized: bool = missing,
|
400 |
+
undefined: t.Type[Undefined] = missing,
|
401 |
+
finalize: t.Optional[t.Callable[..., t.Any]] = missing,
|
402 |
+
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing,
|
403 |
+
loader: t.Optional["BaseLoader"] = missing,
|
404 |
+
cache_size: int = missing,
|
405 |
+
auto_reload: bool = missing,
|
406 |
+
bytecode_cache: t.Optional["BytecodeCache"] = missing,
|
407 |
+
enable_async: bool = False,
|
408 |
+
) -> "Environment":
|
409 |
+
"""Create a new overlay environment that shares all the data with the
|
410 |
+
current environment except for cache and the overridden attributes.
|
411 |
+
Extensions cannot be removed for an overlayed environment. An overlayed
|
412 |
+
environment automatically gets all the extensions of the environment it
|
413 |
+
is linked to plus optional extra extensions.
|
414 |
+
|
415 |
+
Creating overlays should happen after the initial environment was set
|
416 |
+
up completely. Not all attributes are truly linked, some are just
|
417 |
+
copied over so modifications on the original environment may not shine
|
418 |
+
through.
|
419 |
+
|
420 |
+
.. versionchanged:: 3.1.2
|
421 |
+
Added the ``newline_sequence``,, ``keep_trailing_newline``,
|
422 |
+
and ``enable_async`` parameters to match ``__init__``.
|
423 |
+
"""
|
424 |
+
args = dict(locals())
|
425 |
+
del args["self"], args["cache_size"], args["extensions"], args["enable_async"]
|
426 |
+
|
427 |
+
rv = object.__new__(self.__class__)
|
428 |
+
rv.__dict__.update(self.__dict__)
|
429 |
+
rv.overlayed = True
|
430 |
+
rv.linked_to = self
|
431 |
+
|
432 |
+
for key, value in args.items():
|
433 |
+
if value is not missing:
|
434 |
+
setattr(rv, key, value)
|
435 |
+
|
436 |
+
if cache_size is not missing:
|
437 |
+
rv.cache = create_cache(cache_size)
|
438 |
+
else:
|
439 |
+
rv.cache = copy_cache(self.cache)
|
440 |
+
|
441 |
+
rv.extensions = {}
|
442 |
+
for key, value in self.extensions.items():
|
443 |
+
rv.extensions[key] = value.bind(rv)
|
444 |
+
if extensions is not missing:
|
445 |
+
rv.extensions.update(load_extensions(rv, extensions))
|
446 |
+
|
447 |
+
if enable_async is not missing:
|
448 |
+
rv.is_async = enable_async
|
449 |
+
|
450 |
+
return _environment_config_check(rv)
|
451 |
+
|
452 |
+
@property
|
453 |
+
def lexer(self) -> Lexer:
|
454 |
+
"""The lexer for this environment."""
|
455 |
+
return get_lexer(self)
|
456 |
+
|
457 |
+
def iter_extensions(self) -> t.Iterator["Extension"]:
|
458 |
+
"""Iterates over the extensions by priority."""
|
459 |
+
return iter(sorted(self.extensions.values(), key=lambda x: x.priority))
|
460 |
+
|
461 |
+
def getitem(
|
462 |
+
self, obj: t.Any, argument: t.Union[str, t.Any]
|
463 |
+
) -> t.Union[t.Any, Undefined]:
|
464 |
+
"""Get an item or attribute of an object but prefer the item."""
|
465 |
+
try:
|
466 |
+
return obj[argument]
|
467 |
+
except (AttributeError, TypeError, LookupError):
|
468 |
+
if isinstance(argument, str):
|
469 |
+
try:
|
470 |
+
attr = str(argument)
|
471 |
+
except Exception:
|
472 |
+
pass
|
473 |
+
else:
|
474 |
+
try:
|
475 |
+
return getattr(obj, attr)
|
476 |
+
except AttributeError:
|
477 |
+
pass
|
478 |
+
return self.undefined(obj=obj, name=argument)
|
479 |
+
|
480 |
+
def getattr(self, obj: t.Any, attribute: str) -> t.Any:
|
481 |
+
"""Get an item or attribute of an object but prefer the attribute.
|
482 |
+
Unlike :meth:`getitem` the attribute *must* be a string.
|
483 |
+
"""
|
484 |
+
try:
|
485 |
+
return getattr(obj, attribute)
|
486 |
+
except AttributeError:
|
487 |
+
pass
|
488 |
+
try:
|
489 |
+
return obj[attribute]
|
490 |
+
except (TypeError, LookupError, AttributeError):
|
491 |
+
return self.undefined(obj=obj, name=attribute)
|
492 |
+
|
493 |
+
def _filter_test_common(
|
494 |
+
self,
|
495 |
+
name: t.Union[str, Undefined],
|
496 |
+
value: t.Any,
|
497 |
+
args: t.Optional[t.Sequence[t.Any]],
|
498 |
+
kwargs: t.Optional[t.Mapping[str, t.Any]],
|
499 |
+
context: t.Optional[Context],
|
500 |
+
eval_ctx: t.Optional[EvalContext],
|
501 |
+
is_filter: bool,
|
502 |
+
) -> t.Any:
|
503 |
+
if is_filter:
|
504 |
+
env_map = self.filters
|
505 |
+
type_name = "filter"
|
506 |
+
else:
|
507 |
+
env_map = self.tests
|
508 |
+
type_name = "test"
|
509 |
+
|
510 |
+
func = env_map.get(name) # type: ignore
|
511 |
+
|
512 |
+
if func is None:
|
513 |
+
msg = f"No {type_name} named {name!r}."
|
514 |
+
|
515 |
+
if isinstance(name, Undefined):
|
516 |
+
try:
|
517 |
+
name._fail_with_undefined_error()
|
518 |
+
except Exception as e:
|
519 |
+
msg = f"{msg} ({e}; did you forget to quote the callable name?)"
|
520 |
+
|
521 |
+
raise TemplateRuntimeError(msg)
|
522 |
+
|
523 |
+
args = [value, *(args if args is not None else ())]
|
524 |
+
kwargs = kwargs if kwargs is not None else {}
|
525 |
+
pass_arg = _PassArg.from_obj(func)
|
526 |
+
|
527 |
+
if pass_arg is _PassArg.context:
|
528 |
+
if context is None:
|
529 |
+
raise TemplateRuntimeError(
|
530 |
+
f"Attempted to invoke a context {type_name} without context."
|
531 |
+
)
|
532 |
+
|
533 |
+
args.insert(0, context)
|
534 |
+
elif pass_arg is _PassArg.eval_context:
|
535 |
+
if eval_ctx is None:
|
536 |
+
if context is not None:
|
537 |
+
eval_ctx = context.eval_ctx
|
538 |
+
else:
|
539 |
+
eval_ctx = EvalContext(self)
|
540 |
+
|
541 |
+
args.insert(0, eval_ctx)
|
542 |
+
elif pass_arg is _PassArg.environment:
|
543 |
+
args.insert(0, self)
|
544 |
+
|
545 |
+
return func(*args, **kwargs)
|
546 |
+
|
547 |
+
def call_filter(
|
548 |
+
self,
|
549 |
+
name: str,
|
550 |
+
value: t.Any,
|
551 |
+
args: t.Optional[t.Sequence[t.Any]] = None,
|
552 |
+
kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
|
553 |
+
context: t.Optional[Context] = None,
|
554 |
+
eval_ctx: t.Optional[EvalContext] = None,
|
555 |
+
) -> t.Any:
|
556 |
+
"""Invoke a filter on a value the same way the compiler does.
|
557 |
+
|
558 |
+
This might return a coroutine if the filter is running from an
|
559 |
+
environment in async mode and the filter supports async
|
560 |
+
execution. It's your responsibility to await this if needed.
|
561 |
+
|
562 |
+
.. versionadded:: 2.7
|
563 |
+
"""
|
564 |
+
return self._filter_test_common(
|
565 |
+
name, value, args, kwargs, context, eval_ctx, True
|
566 |
+
)
|
567 |
+
|
568 |
+
def call_test(
|
569 |
+
self,
|
570 |
+
name: str,
|
571 |
+
value: t.Any,
|
572 |
+
args: t.Optional[t.Sequence[t.Any]] = None,
|
573 |
+
kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
|
574 |
+
context: t.Optional[Context] = None,
|
575 |
+
eval_ctx: t.Optional[EvalContext] = None,
|
576 |
+
) -> t.Any:
|
577 |
+
"""Invoke a test on a value the same way the compiler does.
|
578 |
+
|
579 |
+
This might return a coroutine if the test is running from an
|
580 |
+
environment in async mode and the test supports async execution.
|
581 |
+
It's your responsibility to await this if needed.
|
582 |
+
|
583 |
+
.. versionchanged:: 3.0
|
584 |
+
Tests support ``@pass_context``, etc. decorators. Added
|
585 |
+
the ``context`` and ``eval_ctx`` parameters.
|
586 |
+
|
587 |
+
.. versionadded:: 2.7
|
588 |
+
"""
|
589 |
+
return self._filter_test_common(
|
590 |
+
name, value, args, kwargs, context, eval_ctx, False
|
591 |
+
)
|
592 |
+
|
593 |
+
@internalcode
|
594 |
+
def parse(
|
595 |
+
self,
|
596 |
+
source: str,
|
597 |
+
name: t.Optional[str] = None,
|
598 |
+
filename: t.Optional[str] = None,
|
599 |
+
) -> nodes.Template:
|
600 |
+
"""Parse the sourcecode and return the abstract syntax tree. This
|
601 |
+
tree of nodes is used by the compiler to convert the template into
|
602 |
+
executable source- or bytecode. This is useful for debugging or to
|
603 |
+
extract information from templates.
|
604 |
+
|
605 |
+
If you are :ref:`developing Jinja extensions <writing-extensions>`
|
606 |
+
this gives you a good overview of the node tree generated.
|
607 |
+
"""
|
608 |
+
try:
|
609 |
+
return self._parse(source, name, filename)
|
610 |
+
except TemplateSyntaxError:
|
611 |
+
self.handle_exception(source=source)
|
612 |
+
|
613 |
+
def _parse(
|
614 |
+
self, source: str, name: t.Optional[str], filename: t.Optional[str]
|
615 |
+
) -> nodes.Template:
|
616 |
+
"""Internal parsing function used by `parse` and `compile`."""
|
617 |
+
return Parser(self, source, name, filename).parse()
|
618 |
+
|
619 |
+
def lex(
|
620 |
+
self,
|
621 |
+
source: str,
|
622 |
+
name: t.Optional[str] = None,
|
623 |
+
filename: t.Optional[str] = None,
|
624 |
+
) -> t.Iterator[t.Tuple[int, str, str]]:
|
625 |
+
"""Lex the given sourcecode and return a generator that yields
|
626 |
+
tokens as tuples in the form ``(lineno, token_type, value)``.
|
627 |
+
This can be useful for :ref:`extension development <writing-extensions>`
|
628 |
+
and debugging templates.
|
629 |
+
|
630 |
+
This does not perform preprocessing. If you want the preprocessing
|
631 |
+
of the extensions to be applied you have to filter source through
|
632 |
+
the :meth:`preprocess` method.
|
633 |
+
"""
|
634 |
+
source = str(source)
|
635 |
+
try:
|
636 |
+
return self.lexer.tokeniter(source, name, filename)
|
637 |
+
except TemplateSyntaxError:
|
638 |
+
self.handle_exception(source=source)
|
639 |
+
|
640 |
+
def preprocess(
|
641 |
+
self,
|
642 |
+
source: str,
|
643 |
+
name: t.Optional[str] = None,
|
644 |
+
filename: t.Optional[str] = None,
|
645 |
+
) -> str:
|
646 |
+
"""Preprocesses the source with all extensions. This is automatically
|
647 |
+
called for all parsing and compiling methods but *not* for :meth:`lex`
|
648 |
+
because there you usually only want the actual source tokenized.
|
649 |
+
"""
|
650 |
+
return reduce(
|
651 |
+
lambda s, e: e.preprocess(s, name, filename),
|
652 |
+
self.iter_extensions(),
|
653 |
+
str(source),
|
654 |
+
)
|
655 |
+
|
656 |
+
def _tokenize(
|
657 |
+
self,
|
658 |
+
source: str,
|
659 |
+
name: t.Optional[str],
|
660 |
+
filename: t.Optional[str] = None,
|
661 |
+
state: t.Optional[str] = None,
|
662 |
+
) -> TokenStream:
|
663 |
+
"""Called by the parser to do the preprocessing and filtering
|
664 |
+
for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
|
665 |
+
"""
|
666 |
+
source = self.preprocess(source, name, filename)
|
667 |
+
stream = self.lexer.tokenize(source, name, filename, state)
|
668 |
+
|
669 |
+
for ext in self.iter_extensions():
|
670 |
+
stream = ext.filter_stream(stream) # type: ignore
|
671 |
+
|
672 |
+
if not isinstance(stream, TokenStream):
|
673 |
+
stream = TokenStream(stream, name, filename) # type: ignore
|
674 |
+
|
675 |
+
return stream
|
676 |
+
|
677 |
+
def _generate(
|
678 |
+
self,
|
679 |
+
source: nodes.Template,
|
680 |
+
name: t.Optional[str],
|
681 |
+
filename: t.Optional[str],
|
682 |
+
defer_init: bool = False,
|
683 |
+
) -> str:
|
684 |
+
"""Internal hook that can be overridden to hook a different generate
|
685 |
+
method in.
|
686 |
+
|
687 |
+
.. versionadded:: 2.5
|
688 |
+
"""
|
689 |
+
return generate( # type: ignore
|
690 |
+
source,
|
691 |
+
self,
|
692 |
+
name,
|
693 |
+
filename,
|
694 |
+
defer_init=defer_init,
|
695 |
+
optimized=self.optimized,
|
696 |
+
)
|
697 |
+
|
698 |
+
def _compile(self, source: str, filename: str) -> CodeType:
|
699 |
+
"""Internal hook that can be overridden to hook a different compile
|
700 |
+
method in.
|
701 |
+
|
702 |
+
.. versionadded:: 2.5
|
703 |
+
"""
|
704 |
+
return compile(source, filename, "exec") # type: ignore
|
705 |
+
|
706 |
+
@typing.overload
|
707 |
+
def compile( # type: ignore
|
708 |
+
self,
|
709 |
+
source: t.Union[str, nodes.Template],
|
710 |
+
name: t.Optional[str] = None,
|
711 |
+
filename: t.Optional[str] = None,
|
712 |
+
raw: "te.Literal[False]" = False,
|
713 |
+
defer_init: bool = False,
|
714 |
+
) -> CodeType:
|
715 |
+
...
|
716 |
+
|
717 |
+
@typing.overload
|
718 |
+
def compile(
|
719 |
+
self,
|
720 |
+
source: t.Union[str, nodes.Template],
|
721 |
+
name: t.Optional[str] = None,
|
722 |
+
filename: t.Optional[str] = None,
|
723 |
+
raw: "te.Literal[True]" = ...,
|
724 |
+
defer_init: bool = False,
|
725 |
+
) -> str:
|
726 |
+
...
|
727 |
+
|
728 |
+
@internalcode
|
729 |
+
def compile(
|
730 |
+
self,
|
731 |
+
source: t.Union[str, nodes.Template],
|
732 |
+
name: t.Optional[str] = None,
|
733 |
+
filename: t.Optional[str] = None,
|
734 |
+
raw: bool = False,
|
735 |
+
defer_init: bool = False,
|
736 |
+
) -> t.Union[str, CodeType]:
|
737 |
+
"""Compile a node or template source code. The `name` parameter is
|
738 |
+
the load name of the template after it was joined using
|
739 |
+
:meth:`join_path` if necessary, not the filename on the file system.
|
740 |
+
the `filename` parameter is the estimated filename of the template on
|
741 |
+
the file system. If the template came from a database or memory this
|
742 |
+
can be omitted.
|
743 |
+
|
744 |
+
The return value of this method is a python code object. If the `raw`
|
745 |
+
parameter is `True` the return value will be a string with python
|
746 |
+
code equivalent to the bytecode returned otherwise. This method is
|
747 |
+
mainly used internally.
|
748 |
+
|
749 |
+
`defer_init` is use internally to aid the module code generator. This
|
750 |
+
causes the generated code to be able to import without the global
|
751 |
+
environment variable to be set.
|
752 |
+
|
753 |
+
.. versionadded:: 2.4
|
754 |
+
`defer_init` parameter added.
|
755 |
+
"""
|
756 |
+
source_hint = None
|
757 |
+
try:
|
758 |
+
if isinstance(source, str):
|
759 |
+
source_hint = source
|
760 |
+
source = self._parse(source, name, filename)
|
761 |
+
source = self._generate(source, name, filename, defer_init=defer_init)
|
762 |
+
if raw:
|
763 |
+
return source
|
764 |
+
if filename is None:
|
765 |
+
filename = "<template>"
|
766 |
+
return self._compile(source, filename)
|
767 |
+
except TemplateSyntaxError:
|
768 |
+
self.handle_exception(source=source_hint)
|
769 |
+
|
770 |
+
def compile_expression(
|
771 |
+
self, source: str, undefined_to_none: bool = True
|
772 |
+
) -> "TemplateExpression":
|
773 |
+
"""A handy helper method that returns a callable that accepts keyword
|
774 |
+
arguments that appear as variables in the expression. If called it
|
775 |
+
returns the result of the expression.
|
776 |
+
|
777 |
+
This is useful if applications want to use the same rules as Jinja
|
778 |
+
in template "configuration files" or similar situations.
|
779 |
+
|
780 |
+
Example usage:
|
781 |
+
|
782 |
+
>>> env = Environment()
|
783 |
+
>>> expr = env.compile_expression('foo == 42')
|
784 |
+
>>> expr(foo=23)
|
785 |
+
False
|
786 |
+
>>> expr(foo=42)
|
787 |
+
True
|
788 |
+
|
789 |
+
Per default the return value is converted to `None` if the
|
790 |
+
expression returns an undefined value. This can be changed
|
791 |
+
by setting `undefined_to_none` to `False`.
|
792 |
+
|
793 |
+
>>> env.compile_expression('var')() is None
|
794 |
+
True
|
795 |
+
>>> env.compile_expression('var', undefined_to_none=False)()
|
796 |
+
Undefined
|
797 |
+
|
798 |
+
.. versionadded:: 2.1
|
799 |
+
"""
|
800 |
+
parser = Parser(self, source, state="variable")
|
801 |
+
try:
|
802 |
+
expr = parser.parse_expression()
|
803 |
+
if not parser.stream.eos:
|
804 |
+
raise TemplateSyntaxError(
|
805 |
+
"chunk after expression", parser.stream.current.lineno, None, None
|
806 |
+
)
|
807 |
+
expr.set_environment(self)
|
808 |
+
except TemplateSyntaxError:
|
809 |
+
self.handle_exception(source=source)
|
810 |
+
|
811 |
+
body = [nodes.Assign(nodes.Name("result", "store"), expr, lineno=1)]
|
812 |
+
template = self.from_string(nodes.Template(body, lineno=1))
|
813 |
+
return TemplateExpression(template, undefined_to_none)
|
814 |
+
|
815 |
+
def compile_templates(
|
816 |
+
self,
|
817 |
+
target: t.Union[str, os.PathLike],
|
818 |
+
extensions: t.Optional[t.Collection[str]] = None,
|
819 |
+
filter_func: t.Optional[t.Callable[[str], bool]] = None,
|
820 |
+
zip: t.Optional[str] = "deflated",
|
821 |
+
log_function: t.Optional[t.Callable[[str], None]] = None,
|
822 |
+
ignore_errors: bool = True,
|
823 |
+
) -> None:
|
824 |
+
"""Finds all the templates the loader can find, compiles them
|
825 |
+
and stores them in `target`. If `zip` is `None`, instead of in a
|
826 |
+
zipfile, the templates will be stored in a directory.
|
827 |
+
By default a deflate zip algorithm is used. To switch to
|
828 |
+
the stored algorithm, `zip` can be set to ``'stored'``.
|
829 |
+
|
830 |
+
`extensions` and `filter_func` are passed to :meth:`list_templates`.
|
831 |
+
Each template returned will be compiled to the target folder or
|
832 |
+
zipfile.
|
833 |
+
|
834 |
+
By default template compilation errors are ignored. In case a
|
835 |
+
log function is provided, errors are logged. If you want template
|
836 |
+
syntax errors to abort the compilation you can set `ignore_errors`
|
837 |
+
to `False` and you will get an exception on syntax errors.
|
838 |
+
|
839 |
+
.. versionadded:: 2.4
|
840 |
+
"""
|
841 |
+
from .loaders import ModuleLoader
|
842 |
+
|
843 |
+
if log_function is None:
|
844 |
+
|
845 |
+
def log_function(x: str) -> None:
|
846 |
+
pass
|
847 |
+
|
848 |
+
assert log_function is not None
|
849 |
+
assert self.loader is not None, "No loader configured."
|
850 |
+
|
851 |
+
def write_file(filename: str, data: str) -> None:
|
852 |
+
if zip:
|
853 |
+
info = ZipInfo(filename)
|
854 |
+
info.external_attr = 0o755 << 16
|
855 |
+
zip_file.writestr(info, data)
|
856 |
+
else:
|
857 |
+
with open(os.path.join(target, filename), "wb") as f:
|
858 |
+
f.write(data.encode("utf8"))
|
859 |
+
|
860 |
+
if zip is not None:
|
861 |
+
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED
|
862 |
+
|
863 |
+
zip_file = ZipFile(
|
864 |
+
target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip]
|
865 |
+
)
|
866 |
+
log_function(f"Compiling into Zip archive {target!r}")
|
867 |
+
else:
|
868 |
+
if not os.path.isdir(target):
|
869 |
+
os.makedirs(target)
|
870 |
+
log_function(f"Compiling into folder {target!r}")
|
871 |
+
|
872 |
+
try:
|
873 |
+
for name in self.list_templates(extensions, filter_func):
|
874 |
+
source, filename, _ = self.loader.get_source(self, name)
|
875 |
+
try:
|
876 |
+
code = self.compile(source, name, filename, True, True)
|
877 |
+
except TemplateSyntaxError as e:
|
878 |
+
if not ignore_errors:
|
879 |
+
raise
|
880 |
+
log_function(f'Could not compile "{name}": {e}')
|
881 |
+
continue
|
882 |
+
|
883 |
+
filename = ModuleLoader.get_module_filename(name)
|
884 |
+
|
885 |
+
write_file(filename, code)
|
886 |
+
log_function(f'Compiled "{name}" as {filename}')
|
887 |
+
finally:
|
888 |
+
if zip:
|
889 |
+
zip_file.close()
|
890 |
+
|
891 |
+
log_function("Finished compiling templates")
|
892 |
+
|
893 |
+
def list_templates(
|
894 |
+
self,
|
895 |
+
extensions: t.Optional[t.Collection[str]] = None,
|
896 |
+
filter_func: t.Optional[t.Callable[[str], bool]] = None,
|
897 |
+
) -> t.List[str]:
|
898 |
+
"""Returns a list of templates for this environment. This requires
|
899 |
+
that the loader supports the loader's
|
900 |
+
:meth:`~BaseLoader.list_templates` method.
|
901 |
+
|
902 |
+
If there are other files in the template folder besides the
|
903 |
+
actual templates, the returned list can be filtered. There are two
|
904 |
+
ways: either `extensions` is set to a list of file extensions for
|
905 |
+
templates, or a `filter_func` can be provided which is a callable that
|
906 |
+
is passed a template name and should return `True` if it should end up
|
907 |
+
in the result list.
|
908 |
+
|
909 |
+
If the loader does not support that, a :exc:`TypeError` is raised.
|
910 |
+
|
911 |
+
.. versionadded:: 2.4
|
912 |
+
"""
|
913 |
+
assert self.loader is not None, "No loader configured."
|
914 |
+
names = self.loader.list_templates()
|
915 |
+
|
916 |
+
if extensions is not None:
|
917 |
+
if filter_func is not None:
|
918 |
+
raise TypeError(
|
919 |
+
"either extensions or filter_func can be passed, but not both"
|
920 |
+
)
|
921 |
+
|
922 |
+
def filter_func(x: str) -> bool:
|
923 |
+
return "." in x and x.rsplit(".", 1)[1] in extensions # type: ignore
|
924 |
+
|
925 |
+
if filter_func is not None:
|
926 |
+
names = [name for name in names if filter_func(name)]
|
927 |
+
|
928 |
+
return names
|
929 |
+
|
930 |
+
def handle_exception(self, source: t.Optional[str] = None) -> "te.NoReturn":
|
931 |
+
"""Exception handling helper. This is used internally to either raise
|
932 |
+
rewritten exceptions or return a rendered traceback for the template.
|
933 |
+
"""
|
934 |
+
from .debug import rewrite_traceback_stack
|
935 |
+
|
936 |
+
raise rewrite_traceback_stack(source=source)
|
937 |
+
|
938 |
+
def join_path(self, template: str, parent: str) -> str:
|
939 |
+
"""Join a template with the parent. By default all the lookups are
|
940 |
+
relative to the loader root so this method returns the `template`
|
941 |
+
parameter unchanged, but if the paths should be relative to the
|
942 |
+
parent template, this function can be used to calculate the real
|
943 |
+
template name.
|
944 |
+
|
945 |
+
Subclasses may override this method and implement template path
|
946 |
+
joining here.
|
947 |
+
"""
|
948 |
+
return template
|
949 |
+
|
950 |
+
@internalcode
|
951 |
+
def _load_template(
|
952 |
+
self, name: str, globals: t.Optional[t.MutableMapping[str, t.Any]]
|
953 |
+
) -> "Template":
|
954 |
+
if self.loader is None:
|
955 |
+
raise TypeError("no loader for this environment specified")
|
956 |
+
cache_key = (weakref.ref(self.loader), name)
|
957 |
+
if self.cache is not None:
|
958 |
+
template = self.cache.get(cache_key)
|
959 |
+
if template is not None and (
|
960 |
+
not self.auto_reload or template.is_up_to_date
|
961 |
+
):
|
962 |
+
# template.globals is a ChainMap, modifying it will only
|
963 |
+
# affect the template, not the environment globals.
|
964 |
+
if globals:
|
965 |
+
template.globals.update(globals)
|
966 |
+
|
967 |
+
return template
|
968 |
+
|
969 |
+
template = self.loader.load(self, name, self.make_globals(globals))
|
970 |
+
|
971 |
+
if self.cache is not None:
|
972 |
+
self.cache[cache_key] = template
|
973 |
+
return template
|
974 |
+
|
975 |
+
@internalcode
|
976 |
+
def get_template(
|
977 |
+
self,
|
978 |
+
name: t.Union[str, "Template"],
|
979 |
+
parent: t.Optional[str] = None,
|
980 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
981 |
+
) -> "Template":
|
982 |
+
"""Load a template by name with :attr:`loader` and return a
|
983 |
+
:class:`Template`. If the template does not exist a
|
984 |
+
:exc:`TemplateNotFound` exception is raised.
|
985 |
+
|
986 |
+
:param name: Name of the template to load. When loading
|
987 |
+
templates from the filesystem, "/" is used as the path
|
988 |
+
separator, even on Windows.
|
989 |
+
:param parent: The name of the parent template importing this
|
990 |
+
template. :meth:`join_path` can be used to implement name
|
991 |
+
transformations with this.
|
992 |
+
:param globals: Extend the environment :attr:`globals` with
|
993 |
+
these extra variables available for all renders of this
|
994 |
+
template. If the template has already been loaded and
|
995 |
+
cached, its globals are updated with any new items.
|
996 |
+
|
997 |
+
.. versionchanged:: 3.0
|
998 |
+
If a template is loaded from cache, ``globals`` will update
|
999 |
+
the template's globals instead of ignoring the new values.
|
1000 |
+
|
1001 |
+
.. versionchanged:: 2.4
|
1002 |
+
If ``name`` is a :class:`Template` object it is returned
|
1003 |
+
unchanged.
|
1004 |
+
"""
|
1005 |
+
if isinstance(name, Template):
|
1006 |
+
return name
|
1007 |
+
if parent is not None:
|
1008 |
+
name = self.join_path(name, parent)
|
1009 |
+
|
1010 |
+
return self._load_template(name, globals)
|
1011 |
+
|
1012 |
+
@internalcode
|
1013 |
+
def select_template(
|
1014 |
+
self,
|
1015 |
+
names: t.Iterable[t.Union[str, "Template"]],
|
1016 |
+
parent: t.Optional[str] = None,
|
1017 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
1018 |
+
) -> "Template":
|
1019 |
+
"""Like :meth:`get_template`, but tries loading multiple names.
|
1020 |
+
If none of the names can be loaded a :exc:`TemplatesNotFound`
|
1021 |
+
exception is raised.
|
1022 |
+
|
1023 |
+
:param names: List of template names to try loading in order.
|
1024 |
+
:param parent: The name of the parent template importing this
|
1025 |
+
template. :meth:`join_path` can be used to implement name
|
1026 |
+
transformations with this.
|
1027 |
+
:param globals: Extend the environment :attr:`globals` with
|
1028 |
+
these extra variables available for all renders of this
|
1029 |
+
template. If the template has already been loaded and
|
1030 |
+
cached, its globals are updated with any new items.
|
1031 |
+
|
1032 |
+
.. versionchanged:: 3.0
|
1033 |
+
If a template is loaded from cache, ``globals`` will update
|
1034 |
+
the template's globals instead of ignoring the new values.
|
1035 |
+
|
1036 |
+
.. versionchanged:: 2.11
|
1037 |
+
If ``names`` is :class:`Undefined`, an :exc:`UndefinedError`
|
1038 |
+
is raised instead. If no templates were found and ``names``
|
1039 |
+
contains :class:`Undefined`, the message is more helpful.
|
1040 |
+
|
1041 |
+
.. versionchanged:: 2.4
|
1042 |
+
If ``names`` contains a :class:`Template` object it is
|
1043 |
+
returned unchanged.
|
1044 |
+
|
1045 |
+
.. versionadded:: 2.3
|
1046 |
+
"""
|
1047 |
+
if isinstance(names, Undefined):
|
1048 |
+
names._fail_with_undefined_error()
|
1049 |
+
|
1050 |
+
if not names:
|
1051 |
+
raise TemplatesNotFound(
|
1052 |
+
message="Tried to select from an empty list of templates."
|
1053 |
+
)
|
1054 |
+
|
1055 |
+
for name in names:
|
1056 |
+
if isinstance(name, Template):
|
1057 |
+
return name
|
1058 |
+
if parent is not None:
|
1059 |
+
name = self.join_path(name, parent)
|
1060 |
+
try:
|
1061 |
+
return self._load_template(name, globals)
|
1062 |
+
except (TemplateNotFound, UndefinedError):
|
1063 |
+
pass
|
1064 |
+
raise TemplatesNotFound(names) # type: ignore
|
1065 |
+
|
1066 |
+
@internalcode
|
1067 |
+
def get_or_select_template(
|
1068 |
+
self,
|
1069 |
+
template_name_or_list: t.Union[
|
1070 |
+
str, "Template", t.List[t.Union[str, "Template"]]
|
1071 |
+
],
|
1072 |
+
parent: t.Optional[str] = None,
|
1073 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
1074 |
+
) -> "Template":
|
1075 |
+
"""Use :meth:`select_template` if an iterable of template names
|
1076 |
+
is given, or :meth:`get_template` if one name is given.
|
1077 |
+
|
1078 |
+
.. versionadded:: 2.3
|
1079 |
+
"""
|
1080 |
+
if isinstance(template_name_or_list, (str, Undefined)):
|
1081 |
+
return self.get_template(template_name_or_list, parent, globals)
|
1082 |
+
elif isinstance(template_name_or_list, Template):
|
1083 |
+
return template_name_or_list
|
1084 |
+
return self.select_template(template_name_or_list, parent, globals)
|
1085 |
+
|
1086 |
+
def from_string(
|
1087 |
+
self,
|
1088 |
+
source: t.Union[str, nodes.Template],
|
1089 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
1090 |
+
template_class: t.Optional[t.Type["Template"]] = None,
|
1091 |
+
) -> "Template":
|
1092 |
+
"""Load a template from a source string without using
|
1093 |
+
:attr:`loader`.
|
1094 |
+
|
1095 |
+
:param source: Jinja source to compile into a template.
|
1096 |
+
:param globals: Extend the environment :attr:`globals` with
|
1097 |
+
these extra variables available for all renders of this
|
1098 |
+
template. If the template has already been loaded and
|
1099 |
+
cached, its globals are updated with any new items.
|
1100 |
+
:param template_class: Return an instance of this
|
1101 |
+
:class:`Template` class.
|
1102 |
+
"""
|
1103 |
+
gs = self.make_globals(globals)
|
1104 |
+
cls = template_class or self.template_class
|
1105 |
+
return cls.from_code(self, self.compile(source), gs, None)
|
1106 |
+
|
1107 |
+
def make_globals(
|
1108 |
+
self, d: t.Optional[t.MutableMapping[str, t.Any]]
|
1109 |
+
) -> t.MutableMapping[str, t.Any]:
|
1110 |
+
"""Make the globals map for a template. Any given template
|
1111 |
+
globals overlay the environment :attr:`globals`.
|
1112 |
+
|
1113 |
+
Returns a :class:`collections.ChainMap`. This allows any changes
|
1114 |
+
to a template's globals to only affect that template, while
|
1115 |
+
changes to the environment's globals are still reflected.
|
1116 |
+
However, avoid modifying any globals after a template is loaded.
|
1117 |
+
|
1118 |
+
:param d: Dict of template-specific globals.
|
1119 |
+
|
1120 |
+
.. versionchanged:: 3.0
|
1121 |
+
Use :class:`collections.ChainMap` to always prevent mutating
|
1122 |
+
environment globals.
|
1123 |
+
"""
|
1124 |
+
if d is None:
|
1125 |
+
d = {}
|
1126 |
+
|
1127 |
+
return ChainMap(d, self.globals)
|
1128 |
+
|
1129 |
+
|
1130 |
+
class Template:
|
1131 |
+
"""A compiled template that can be rendered.
|
1132 |
+
|
1133 |
+
Use the methods on :class:`Environment` to create or load templates.
|
1134 |
+
The environment is used to configure how templates are compiled and
|
1135 |
+
behave.
|
1136 |
+
|
1137 |
+
It is also possible to create a template object directly. This is
|
1138 |
+
not usually recommended. The constructor takes most of the same
|
1139 |
+
arguments as :class:`Environment`. All templates created with the
|
1140 |
+
same environment arguments share the same ephemeral ``Environment``
|
1141 |
+
instance behind the scenes.
|
1142 |
+
|
1143 |
+
A template object should be considered immutable. Modifications on
|
1144 |
+
the object are not supported.
|
1145 |
+
"""
|
1146 |
+
|
1147 |
+
#: Type of environment to create when creating a template directly
|
1148 |
+
#: rather than through an existing environment.
|
1149 |
+
environment_class: t.Type[Environment] = Environment
|
1150 |
+
|
1151 |
+
environment: Environment
|
1152 |
+
globals: t.MutableMapping[str, t.Any]
|
1153 |
+
name: t.Optional[str]
|
1154 |
+
filename: t.Optional[str]
|
1155 |
+
blocks: t.Dict[str, t.Callable[[Context], t.Iterator[str]]]
|
1156 |
+
root_render_func: t.Callable[[Context], t.Iterator[str]]
|
1157 |
+
_module: t.Optional["TemplateModule"]
|
1158 |
+
_debug_info: str
|
1159 |
+
_uptodate: t.Optional[t.Callable[[], bool]]
|
1160 |
+
|
1161 |
+
def __new__(
|
1162 |
+
cls,
|
1163 |
+
source: t.Union[str, nodes.Template],
|
1164 |
+
block_start_string: str = BLOCK_START_STRING,
|
1165 |
+
block_end_string: str = BLOCK_END_STRING,
|
1166 |
+
variable_start_string: str = VARIABLE_START_STRING,
|
1167 |
+
variable_end_string: str = VARIABLE_END_STRING,
|
1168 |
+
comment_start_string: str = COMMENT_START_STRING,
|
1169 |
+
comment_end_string: str = COMMENT_END_STRING,
|
1170 |
+
line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
|
1171 |
+
line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
|
1172 |
+
trim_blocks: bool = TRIM_BLOCKS,
|
1173 |
+
lstrip_blocks: bool = LSTRIP_BLOCKS,
|
1174 |
+
newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
|
1175 |
+
keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
|
1176 |
+
extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
|
1177 |
+
optimized: bool = True,
|
1178 |
+
undefined: t.Type[Undefined] = Undefined,
|
1179 |
+
finalize: t.Optional[t.Callable[..., t.Any]] = None,
|
1180 |
+
autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
|
1181 |
+
enable_async: bool = False,
|
1182 |
+
) -> t.Any: # it returns a `Template`, but this breaks the sphinx build...
|
1183 |
+
env = get_spontaneous_environment(
|
1184 |
+
cls.environment_class, # type: ignore
|
1185 |
+
block_start_string,
|
1186 |
+
block_end_string,
|
1187 |
+
variable_start_string,
|
1188 |
+
variable_end_string,
|
1189 |
+
comment_start_string,
|
1190 |
+
comment_end_string,
|
1191 |
+
line_statement_prefix,
|
1192 |
+
line_comment_prefix,
|
1193 |
+
trim_blocks,
|
1194 |
+
lstrip_blocks,
|
1195 |
+
newline_sequence,
|
1196 |
+
keep_trailing_newline,
|
1197 |
+
frozenset(extensions),
|
1198 |
+
optimized,
|
1199 |
+
undefined, # type: ignore
|
1200 |
+
finalize,
|
1201 |
+
autoescape,
|
1202 |
+
None,
|
1203 |
+
0,
|
1204 |
+
False,
|
1205 |
+
None,
|
1206 |
+
enable_async,
|
1207 |
+
)
|
1208 |
+
return env.from_string(source, template_class=cls)
|
1209 |
+
|
1210 |
+
@classmethod
|
1211 |
+
def from_code(
|
1212 |
+
cls,
|
1213 |
+
environment: Environment,
|
1214 |
+
code: CodeType,
|
1215 |
+
globals: t.MutableMapping[str, t.Any],
|
1216 |
+
uptodate: t.Optional[t.Callable[[], bool]] = None,
|
1217 |
+
) -> "Template":
|
1218 |
+
"""Creates a template object from compiled code and the globals. This
|
1219 |
+
is used by the loaders and environment to create a template object.
|
1220 |
+
"""
|
1221 |
+
namespace = {"environment": environment, "__file__": code.co_filename}
|
1222 |
+
exec(code, namespace)
|
1223 |
+
rv = cls._from_namespace(environment, namespace, globals)
|
1224 |
+
rv._uptodate = uptodate
|
1225 |
+
return rv
|
1226 |
+
|
1227 |
+
@classmethod
|
1228 |
+
def from_module_dict(
|
1229 |
+
cls,
|
1230 |
+
environment: Environment,
|
1231 |
+
module_dict: t.MutableMapping[str, t.Any],
|
1232 |
+
globals: t.MutableMapping[str, t.Any],
|
1233 |
+
) -> "Template":
|
1234 |
+
"""Creates a template object from a module. This is used by the
|
1235 |
+
module loader to create a template object.
|
1236 |
+
|
1237 |
+
.. versionadded:: 2.4
|
1238 |
+
"""
|
1239 |
+
return cls._from_namespace(environment, module_dict, globals)
|
1240 |
+
|
1241 |
+
@classmethod
|
1242 |
+
def _from_namespace(
|
1243 |
+
cls,
|
1244 |
+
environment: Environment,
|
1245 |
+
namespace: t.MutableMapping[str, t.Any],
|
1246 |
+
globals: t.MutableMapping[str, t.Any],
|
1247 |
+
) -> "Template":
|
1248 |
+
t: "Template" = object.__new__(cls)
|
1249 |
+
t.environment = environment
|
1250 |
+
t.globals = globals
|
1251 |
+
t.name = namespace["name"]
|
1252 |
+
t.filename = namespace["__file__"]
|
1253 |
+
t.blocks = namespace["blocks"]
|
1254 |
+
|
1255 |
+
# render function and module
|
1256 |
+
t.root_render_func = namespace["root"] # type: ignore
|
1257 |
+
t._module = None
|
1258 |
+
|
1259 |
+
# debug and loader helpers
|
1260 |
+
t._debug_info = namespace["debug_info"]
|
1261 |
+
t._uptodate = None
|
1262 |
+
|
1263 |
+
# store the reference
|
1264 |
+
namespace["environment"] = environment
|
1265 |
+
namespace["__jinja_template__"] = t
|
1266 |
+
|
1267 |
+
return t
|
1268 |
+
|
1269 |
+
def render(self, *args: t.Any, **kwargs: t.Any) -> str:
|
1270 |
+
"""This method accepts the same arguments as the `dict` constructor:
|
1271 |
+
A dict, a dict subclass or some keyword arguments. If no arguments
|
1272 |
+
are given the context will be empty. These two calls do the same::
|
1273 |
+
|
1274 |
+
template.render(knights='that say nih')
|
1275 |
+
template.render({'knights': 'that say nih'})
|
1276 |
+
|
1277 |
+
This will return the rendered template as a string.
|
1278 |
+
"""
|
1279 |
+
if self.environment.is_async:
|
1280 |
+
import asyncio
|
1281 |
+
|
1282 |
+
close = False
|
1283 |
+
|
1284 |
+
try:
|
1285 |
+
loop = asyncio.get_running_loop()
|
1286 |
+
except RuntimeError:
|
1287 |
+
loop = asyncio.new_event_loop()
|
1288 |
+
close = True
|
1289 |
+
|
1290 |
+
try:
|
1291 |
+
return loop.run_until_complete(self.render_async(*args, **kwargs))
|
1292 |
+
finally:
|
1293 |
+
if close:
|
1294 |
+
loop.close()
|
1295 |
+
|
1296 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
1297 |
+
|
1298 |
+
try:
|
1299 |
+
return self.environment.concat(self.root_render_func(ctx)) # type: ignore
|
1300 |
+
except Exception:
|
1301 |
+
self.environment.handle_exception()
|
1302 |
+
|
1303 |
+
async def render_async(self, *args: t.Any, **kwargs: t.Any) -> str:
|
1304 |
+
"""This works similar to :meth:`render` but returns a coroutine
|
1305 |
+
that when awaited returns the entire rendered template string. This
|
1306 |
+
requires the async feature to be enabled.
|
1307 |
+
|
1308 |
+
Example usage::
|
1309 |
+
|
1310 |
+
await template.render_async(knights='that say nih; asynchronously')
|
1311 |
+
"""
|
1312 |
+
if not self.environment.is_async:
|
1313 |
+
raise RuntimeError(
|
1314 |
+
"The environment was not created with async mode enabled."
|
1315 |
+
)
|
1316 |
+
|
1317 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
1318 |
+
|
1319 |
+
try:
|
1320 |
+
return self.environment.concat( # type: ignore
|
1321 |
+
[n async for n in self.root_render_func(ctx)] # type: ignore
|
1322 |
+
)
|
1323 |
+
except Exception:
|
1324 |
+
return self.environment.handle_exception()
|
1325 |
+
|
1326 |
+
def stream(self, *args: t.Any, **kwargs: t.Any) -> "TemplateStream":
|
1327 |
+
"""Works exactly like :meth:`generate` but returns a
|
1328 |
+
:class:`TemplateStream`.
|
1329 |
+
"""
|
1330 |
+
return TemplateStream(self.generate(*args, **kwargs))
|
1331 |
+
|
1332 |
+
def generate(self, *args: t.Any, **kwargs: t.Any) -> t.Iterator[str]:
|
1333 |
+
"""For very large templates it can be useful to not render the whole
|
1334 |
+
template at once but evaluate each statement after another and yield
|
1335 |
+
piece for piece. This method basically does exactly that and returns
|
1336 |
+
a generator that yields one item after another as strings.
|
1337 |
+
|
1338 |
+
It accepts the same arguments as :meth:`render`.
|
1339 |
+
"""
|
1340 |
+
if self.environment.is_async:
|
1341 |
+
import asyncio
|
1342 |
+
|
1343 |
+
async def to_list() -> t.List[str]:
|
1344 |
+
return [x async for x in self.generate_async(*args, **kwargs)]
|
1345 |
+
|
1346 |
+
yield from asyncio.run(to_list())
|
1347 |
+
return
|
1348 |
+
|
1349 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
1350 |
+
|
1351 |
+
try:
|
1352 |
+
yield from self.root_render_func(ctx) # type: ignore
|
1353 |
+
except Exception:
|
1354 |
+
yield self.environment.handle_exception()
|
1355 |
+
|
1356 |
+
async def generate_async(
|
1357 |
+
self, *args: t.Any, **kwargs: t.Any
|
1358 |
+
) -> t.AsyncIterator[str]:
|
1359 |
+
"""An async version of :meth:`generate`. Works very similarly but
|
1360 |
+
returns an async iterator instead.
|
1361 |
+
"""
|
1362 |
+
if not self.environment.is_async:
|
1363 |
+
raise RuntimeError(
|
1364 |
+
"The environment was not created with async mode enabled."
|
1365 |
+
)
|
1366 |
+
|
1367 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
1368 |
+
|
1369 |
+
try:
|
1370 |
+
async for event in self.root_render_func(ctx): # type: ignore
|
1371 |
+
yield event
|
1372 |
+
except Exception:
|
1373 |
+
yield self.environment.handle_exception()
|
1374 |
+
|
1375 |
+
def new_context(
|
1376 |
+
self,
|
1377 |
+
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
1378 |
+
shared: bool = False,
|
1379 |
+
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
1380 |
+
) -> Context:
|
1381 |
+
"""Create a new :class:`Context` for this template. The vars
|
1382 |
+
provided will be passed to the template. Per default the globals
|
1383 |
+
are added to the context. If shared is set to `True` the data
|
1384 |
+
is passed as is to the context without adding the globals.
|
1385 |
+
|
1386 |
+
`locals` can be a dict of local variables for internal usage.
|
1387 |
+
"""
|
1388 |
+
return new_context(
|
1389 |
+
self.environment, self.name, self.blocks, vars, shared, self.globals, locals
|
1390 |
+
)
|
1391 |
+
|
1392 |
+
def make_module(
|
1393 |
+
self,
|
1394 |
+
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
1395 |
+
shared: bool = False,
|
1396 |
+
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
1397 |
+
) -> "TemplateModule":
|
1398 |
+
"""This method works like the :attr:`module` attribute when called
|
1399 |
+
without arguments but it will evaluate the template on every call
|
1400 |
+
rather than caching it. It's also possible to provide
|
1401 |
+
a dict which is then used as context. The arguments are the same
|
1402 |
+
as for the :meth:`new_context` method.
|
1403 |
+
"""
|
1404 |
+
ctx = self.new_context(vars, shared, locals)
|
1405 |
+
return TemplateModule(self, ctx)
|
1406 |
+
|
1407 |
+
async def make_module_async(
|
1408 |
+
self,
|
1409 |
+
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
1410 |
+
shared: bool = False,
|
1411 |
+
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
1412 |
+
) -> "TemplateModule":
|
1413 |
+
"""As template module creation can invoke template code for
|
1414 |
+
asynchronous executions this method must be used instead of the
|
1415 |
+
normal :meth:`make_module` one. Likewise the module attribute
|
1416 |
+
becomes unavailable in async mode.
|
1417 |
+
"""
|
1418 |
+
ctx = self.new_context(vars, shared, locals)
|
1419 |
+
return TemplateModule(
|
1420 |
+
self, ctx, [x async for x in self.root_render_func(ctx)] # type: ignore
|
1421 |
+
)
|
1422 |
+
|
1423 |
+
@internalcode
|
1424 |
+
def _get_default_module(self, ctx: t.Optional[Context] = None) -> "TemplateModule":
|
1425 |
+
"""If a context is passed in, this means that the template was
|
1426 |
+
imported. Imported templates have access to the current
|
1427 |
+
template's globals by default, but they can only be accessed via
|
1428 |
+
the context during runtime.
|
1429 |
+
|
1430 |
+
If there are new globals, we need to create a new module because
|
1431 |
+
the cached module is already rendered and will not have access
|
1432 |
+
to globals from the current context. This new module is not
|
1433 |
+
cached because the template can be imported elsewhere, and it
|
1434 |
+
should have access to only the current template's globals.
|
1435 |
+
"""
|
1436 |
+
if self.environment.is_async:
|
1437 |
+
raise RuntimeError("Module is not available in async mode.")
|
1438 |
+
|
1439 |
+
if ctx is not None:
|
1440 |
+
keys = ctx.globals_keys - self.globals.keys()
|
1441 |
+
|
1442 |
+
if keys:
|
1443 |
+
return self.make_module({k: ctx.parent[k] for k in keys})
|
1444 |
+
|
1445 |
+
if self._module is None:
|
1446 |
+
self._module = self.make_module()
|
1447 |
+
|
1448 |
+
return self._module
|
1449 |
+
|
1450 |
+
async def _get_default_module_async(
|
1451 |
+
self, ctx: t.Optional[Context] = None
|
1452 |
+
) -> "TemplateModule":
|
1453 |
+
if ctx is not None:
|
1454 |
+
keys = ctx.globals_keys - self.globals.keys()
|
1455 |
+
|
1456 |
+
if keys:
|
1457 |
+
return await self.make_module_async({k: ctx.parent[k] for k in keys})
|
1458 |
+
|
1459 |
+
if self._module is None:
|
1460 |
+
self._module = await self.make_module_async()
|
1461 |
+
|
1462 |
+
return self._module
|
1463 |
+
|
1464 |
+
@property
|
1465 |
+
def module(self) -> "TemplateModule":
|
1466 |
+
"""The template as module. This is used for imports in the
|
1467 |
+
template runtime but is also useful if one wants to access
|
1468 |
+
exported template variables from the Python layer:
|
1469 |
+
|
1470 |
+
>>> t = Template('{% macro foo() %}42{% endmacro %}23')
|
1471 |
+
>>> str(t.module)
|
1472 |
+
'23'
|
1473 |
+
>>> t.module.foo() == u'42'
|
1474 |
+
True
|
1475 |
+
|
1476 |
+
This attribute is not available if async mode is enabled.
|
1477 |
+
"""
|
1478 |
+
return self._get_default_module()
|
1479 |
+
|
1480 |
+
def get_corresponding_lineno(self, lineno: int) -> int:
|
1481 |
+
"""Return the source line number of a line number in the
|
1482 |
+
generated bytecode as they are not in sync.
|
1483 |
+
"""
|
1484 |
+
for template_line, code_line in reversed(self.debug_info):
|
1485 |
+
if code_line <= lineno:
|
1486 |
+
return template_line
|
1487 |
+
return 1
|
1488 |
+
|
1489 |
+
@property
|
1490 |
+
def is_up_to_date(self) -> bool:
|
1491 |
+
"""If this variable is `False` there is a newer version available."""
|
1492 |
+
if self._uptodate is None:
|
1493 |
+
return True
|
1494 |
+
return self._uptodate()
|
1495 |
+
|
1496 |
+
@property
|
1497 |
+
def debug_info(self) -> t.List[t.Tuple[int, int]]:
|
1498 |
+
"""The debug info mapping."""
|
1499 |
+
if self._debug_info:
|
1500 |
+
return [
|
1501 |
+
tuple(map(int, x.split("="))) # type: ignore
|
1502 |
+
for x in self._debug_info.split("&")
|
1503 |
+
]
|
1504 |
+
|
1505 |
+
return []
|
1506 |
+
|
1507 |
+
def __repr__(self) -> str:
|
1508 |
+
if self.name is None:
|
1509 |
+
name = f"memory:{id(self):x}"
|
1510 |
+
else:
|
1511 |
+
name = repr(self.name)
|
1512 |
+
return f"<{type(self).__name__} {name}>"
|
1513 |
+
|
1514 |
+
|
1515 |
+
class TemplateModule:
|
1516 |
+
"""Represents an imported template. All the exported names of the
|
1517 |
+
template are available as attributes on this object. Additionally
|
1518 |
+
converting it into a string renders the contents.
|
1519 |
+
"""
|
1520 |
+
|
1521 |
+
def __init__(
|
1522 |
+
self,
|
1523 |
+
template: Template,
|
1524 |
+
context: Context,
|
1525 |
+
body_stream: t.Optional[t.Iterable[str]] = None,
|
1526 |
+
) -> None:
|
1527 |
+
if body_stream is None:
|
1528 |
+
if context.environment.is_async:
|
1529 |
+
raise RuntimeError(
|
1530 |
+
"Async mode requires a body stream to be passed to"
|
1531 |
+
" a template module. Use the async methods of the"
|
1532 |
+
" API you are using."
|
1533 |
+
)
|
1534 |
+
|
1535 |
+
body_stream = list(template.root_render_func(context)) # type: ignore
|
1536 |
+
|
1537 |
+
self._body_stream = body_stream
|
1538 |
+
self.__dict__.update(context.get_exported())
|
1539 |
+
self.__name__ = template.name
|
1540 |
+
|
1541 |
+
def __html__(self) -> Markup:
|
1542 |
+
return Markup(concat(self._body_stream))
|
1543 |
+
|
1544 |
+
def __str__(self) -> str:
|
1545 |
+
return concat(self._body_stream)
|
1546 |
+
|
1547 |
+
def __repr__(self) -> str:
|
1548 |
+
if self.__name__ is None:
|
1549 |
+
name = f"memory:{id(self):x}"
|
1550 |
+
else:
|
1551 |
+
name = repr(self.__name__)
|
1552 |
+
return f"<{type(self).__name__} {name}>"
|
1553 |
+
|
1554 |
+
|
1555 |
+
class TemplateExpression:
|
1556 |
+
"""The :meth:`jinja2.Environment.compile_expression` method returns an
|
1557 |
+
instance of this object. It encapsulates the expression-like access
|
1558 |
+
to the template with an expression it wraps.
|
1559 |
+
"""
|
1560 |
+
|
1561 |
+
def __init__(self, template: Template, undefined_to_none: bool) -> None:
|
1562 |
+
self._template = template
|
1563 |
+
self._undefined_to_none = undefined_to_none
|
1564 |
+
|
1565 |
+
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]:
|
1566 |
+
context = self._template.new_context(dict(*args, **kwargs))
|
1567 |
+
consume(self._template.root_render_func(context)) # type: ignore
|
1568 |
+
rv = context.vars["result"]
|
1569 |
+
if self._undefined_to_none and isinstance(rv, Undefined):
|
1570 |
+
rv = None
|
1571 |
+
return rv
|
1572 |
+
|
1573 |
+
|
1574 |
+
class TemplateStream:
|
1575 |
+
"""A template stream works pretty much like an ordinary python generator
|
1576 |
+
but it can buffer multiple items to reduce the number of total iterations.
|
1577 |
+
Per default the output is unbuffered which means that for every unbuffered
|
1578 |
+
instruction in the template one string is yielded.
|
1579 |
+
|
1580 |
+
If buffering is enabled with a buffer size of 5, five items are combined
|
1581 |
+
into a new string. This is mainly useful if you are streaming
|
1582 |
+
big templates to a client via WSGI which flushes after each iteration.
|
1583 |
+
"""
|
1584 |
+
|
1585 |
+
def __init__(self, gen: t.Iterator[str]) -> None:
|
1586 |
+
self._gen = gen
|
1587 |
+
self.disable_buffering()
|
1588 |
+
|
1589 |
+
def dump(
|
1590 |
+
self,
|
1591 |
+
fp: t.Union[str, t.IO],
|
1592 |
+
encoding: t.Optional[str] = None,
|
1593 |
+
errors: t.Optional[str] = "strict",
|
1594 |
+
) -> None:
|
1595 |
+
"""Dump the complete stream into a file or file-like object.
|
1596 |
+
Per default strings are written, if you want to encode
|
1597 |
+
before writing specify an `encoding`.
|
1598 |
+
|
1599 |
+
Example usage::
|
1600 |
+
|
1601 |
+
Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
|
1602 |
+
"""
|
1603 |
+
close = False
|
1604 |
+
|
1605 |
+
if isinstance(fp, str):
|
1606 |
+
if encoding is None:
|
1607 |
+
encoding = "utf-8"
|
1608 |
+
|
1609 |
+
fp = open(fp, "wb")
|
1610 |
+
close = True
|
1611 |
+
try:
|
1612 |
+
if encoding is not None:
|
1613 |
+
iterable = (x.encode(encoding, errors) for x in self) # type: ignore
|
1614 |
+
else:
|
1615 |
+
iterable = self # type: ignore
|
1616 |
+
|
1617 |
+
if hasattr(fp, "writelines"):
|
1618 |
+
fp.writelines(iterable)
|
1619 |
+
else:
|
1620 |
+
for item in iterable:
|
1621 |
+
fp.write(item)
|
1622 |
+
finally:
|
1623 |
+
if close:
|
1624 |
+
fp.close()
|
1625 |
+
|
1626 |
+
def disable_buffering(self) -> None:
|
1627 |
+
"""Disable the output buffering."""
|
1628 |
+
self._next = partial(next, self._gen)
|
1629 |
+
self.buffered = False
|
1630 |
+
|
1631 |
+
def _buffered_generator(self, size: int) -> t.Iterator[str]:
|
1632 |
+
buf: t.List[str] = []
|
1633 |
+
c_size = 0
|
1634 |
+
push = buf.append
|
1635 |
+
|
1636 |
+
while True:
|
1637 |
+
try:
|
1638 |
+
while c_size < size:
|
1639 |
+
c = next(self._gen)
|
1640 |
+
push(c)
|
1641 |
+
if c:
|
1642 |
+
c_size += 1
|
1643 |
+
except StopIteration:
|
1644 |
+
if not c_size:
|
1645 |
+
return
|
1646 |
+
yield concat(buf)
|
1647 |
+
del buf[:]
|
1648 |
+
c_size = 0
|
1649 |
+
|
1650 |
+
def enable_buffering(self, size: int = 5) -> None:
|
1651 |
+
"""Enable buffering. Buffer `size` items before yielding them."""
|
1652 |
+
if size <= 1:
|
1653 |
+
raise ValueError("buffer size too small")
|
1654 |
+
|
1655 |
+
self.buffered = True
|
1656 |
+
self._next = partial(next, self._buffered_generator(size))
|
1657 |
+
|
1658 |
+
def __iter__(self) -> "TemplateStream":
|
1659 |
+
return self
|
1660 |
+
|
1661 |
+
def __next__(self) -> str:
|
1662 |
+
return self._next() # type: ignore
|
1663 |
+
|
1664 |
+
|
1665 |
+
# hook in default template class. if anyone reads this comment: ignore that
|
1666 |
+
# it's possible to use custom templates ;-)
|
1667 |
+
Environment.template_class = Template
|
lib/python3.11/site-packages/jinja2/exceptions.py
ADDED
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import typing as t
|
2 |
+
|
3 |
+
if t.TYPE_CHECKING:
|
4 |
+
from .runtime import Undefined
|
5 |
+
|
6 |
+
|
7 |
+
class TemplateError(Exception):
|
8 |
+
"""Baseclass for all template errors."""
|
9 |
+
|
10 |
+
def __init__(self, message: t.Optional[str] = None) -> None:
|
11 |
+
super().__init__(message)
|
12 |
+
|
13 |
+
@property
|
14 |
+
def message(self) -> t.Optional[str]:
|
15 |
+
return self.args[0] if self.args else None
|
16 |
+
|
17 |
+
|
18 |
+
class TemplateNotFound(IOError, LookupError, TemplateError):
|
19 |
+
"""Raised if a template does not exist.
|
20 |
+
|
21 |
+
.. versionchanged:: 2.11
|
22 |
+
If the given name is :class:`Undefined` and no message was
|
23 |
+
provided, an :exc:`UndefinedError` is raised.
|
24 |
+
"""
|
25 |
+
|
26 |
+
# Silence the Python warning about message being deprecated since
|
27 |
+
# it's not valid here.
|
28 |
+
message: t.Optional[str] = None
|
29 |
+
|
30 |
+
def __init__(
|
31 |
+
self,
|
32 |
+
name: t.Optional[t.Union[str, "Undefined"]],
|
33 |
+
message: t.Optional[str] = None,
|
34 |
+
) -> None:
|
35 |
+
IOError.__init__(self, name)
|
36 |
+
|
37 |
+
if message is None:
|
38 |
+
from .runtime import Undefined
|
39 |
+
|
40 |
+
if isinstance(name, Undefined):
|
41 |
+
name._fail_with_undefined_error()
|
42 |
+
|
43 |
+
message = name
|
44 |
+
|
45 |
+
self.message = message
|
46 |
+
self.name = name
|
47 |
+
self.templates = [name]
|
48 |
+
|
49 |
+
def __str__(self) -> str:
|
50 |
+
return str(self.message)
|
51 |
+
|
52 |
+
|
53 |
+
class TemplatesNotFound(TemplateNotFound):
|
54 |
+
"""Like :class:`TemplateNotFound` but raised if multiple templates
|
55 |
+
are selected. This is a subclass of :class:`TemplateNotFound`
|
56 |
+
exception, so just catching the base exception will catch both.
|
57 |
+
|
58 |
+
.. versionchanged:: 2.11
|
59 |
+
If a name in the list of names is :class:`Undefined`, a message
|
60 |
+
about it being undefined is shown rather than the empty string.
|
61 |
+
|
62 |
+
.. versionadded:: 2.2
|
63 |
+
"""
|
64 |
+
|
65 |
+
def __init__(
|
66 |
+
self,
|
67 |
+
names: t.Sequence[t.Union[str, "Undefined"]] = (),
|
68 |
+
message: t.Optional[str] = None,
|
69 |
+
) -> None:
|
70 |
+
if message is None:
|
71 |
+
from .runtime import Undefined
|
72 |
+
|
73 |
+
parts = []
|
74 |
+
|
75 |
+
for name in names:
|
76 |
+
if isinstance(name, Undefined):
|
77 |
+
parts.append(name._undefined_message)
|
78 |
+
else:
|
79 |
+
parts.append(name)
|
80 |
+
|
81 |
+
parts_str = ", ".join(map(str, parts))
|
82 |
+
message = f"none of the templates given were found: {parts_str}"
|
83 |
+
|
84 |
+
super().__init__(names[-1] if names else None, message)
|
85 |
+
self.templates = list(names)
|
86 |
+
|
87 |
+
|
88 |
+
class TemplateSyntaxError(TemplateError):
|
89 |
+
"""Raised to tell the user that there is a problem with the template."""
|
90 |
+
|
91 |
+
def __init__(
|
92 |
+
self,
|
93 |
+
message: str,
|
94 |
+
lineno: int,
|
95 |
+
name: t.Optional[str] = None,
|
96 |
+
filename: t.Optional[str] = None,
|
97 |
+
) -> None:
|
98 |
+
super().__init__(message)
|
99 |
+
self.lineno = lineno
|
100 |
+
self.name = name
|
101 |
+
self.filename = filename
|
102 |
+
self.source: t.Optional[str] = None
|
103 |
+
|
104 |
+
# this is set to True if the debug.translate_syntax_error
|
105 |
+
# function translated the syntax error into a new traceback
|
106 |
+
self.translated = False
|
107 |
+
|
108 |
+
def __str__(self) -> str:
|
109 |
+
# for translated errors we only return the message
|
110 |
+
if self.translated:
|
111 |
+
return t.cast(str, self.message)
|
112 |
+
|
113 |
+
# otherwise attach some stuff
|
114 |
+
location = f"line {self.lineno}"
|
115 |
+
name = self.filename or self.name
|
116 |
+
if name:
|
117 |
+
location = f'File "{name}", {location}'
|
118 |
+
lines = [t.cast(str, self.message), " " + location]
|
119 |
+
|
120 |
+
# if the source is set, add the line to the output
|
121 |
+
if self.source is not None:
|
122 |
+
try:
|
123 |
+
line = self.source.splitlines()[self.lineno - 1]
|
124 |
+
except IndexError:
|
125 |
+
pass
|
126 |
+
else:
|
127 |
+
lines.append(" " + line.strip())
|
128 |
+
|
129 |
+
return "\n".join(lines)
|
130 |
+
|
131 |
+
def __reduce__(self): # type: ignore
|
132 |
+
# https://bugs.python.org/issue1692335 Exceptions that take
|
133 |
+
# multiple required arguments have problems with pickling.
|
134 |
+
# Without this, raises TypeError: __init__() missing 1 required
|
135 |
+
# positional argument: 'lineno'
|
136 |
+
return self.__class__, (self.message, self.lineno, self.name, self.filename)
|
137 |
+
|
138 |
+
|
139 |
+
class TemplateAssertionError(TemplateSyntaxError):
|
140 |
+
"""Like a template syntax error, but covers cases where something in the
|
141 |
+
template caused an error at compile time that wasn't necessarily caused
|
142 |
+
by a syntax error. However it's a direct subclass of
|
143 |
+
:exc:`TemplateSyntaxError` and has the same attributes.
|
144 |
+
"""
|
145 |
+
|
146 |
+
|
147 |
+
class TemplateRuntimeError(TemplateError):
|
148 |
+
"""A generic runtime error in the template engine. Under some situations
|
149 |
+
Jinja may raise this exception.
|
150 |
+
"""
|
151 |
+
|
152 |
+
|
153 |
+
class UndefinedError(TemplateRuntimeError):
|
154 |
+
"""Raised if a template tries to operate on :class:`Undefined`."""
|
155 |
+
|
156 |
+
|
157 |
+
class SecurityError(TemplateRuntimeError):
|
158 |
+
"""Raised if a template tries to do something insecure if the
|
159 |
+
sandbox is enabled.
|
160 |
+
"""
|
161 |
+
|
162 |
+
|
163 |
+
class FilterArgumentError(TemplateRuntimeError):
|
164 |
+
"""This error is raised if a filter was called with inappropriate
|
165 |
+
arguments
|
166 |
+
"""
|
lib/python3.11/site-packages/jinja2/ext.py
ADDED
@@ -0,0 +1,859 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Extension API for adding custom tags and behavior."""
|
2 |
+
import pprint
|
3 |
+
import re
|
4 |
+
import typing as t
|
5 |
+
|
6 |
+
from markupsafe import Markup
|
7 |
+
|
8 |
+
from . import defaults
|
9 |
+
from . import nodes
|
10 |
+
from .environment import Environment
|
11 |
+
from .exceptions import TemplateAssertionError
|
12 |
+
from .exceptions import TemplateSyntaxError
|
13 |
+
from .runtime import concat # type: ignore
|
14 |
+
from .runtime import Context
|
15 |
+
from .runtime import Undefined
|
16 |
+
from .utils import import_string
|
17 |
+
from .utils import pass_context
|
18 |
+
|
19 |
+
if t.TYPE_CHECKING:
|
20 |
+
import typing_extensions as te
|
21 |
+
from .lexer import Token
|
22 |
+
from .lexer import TokenStream
|
23 |
+
from .parser import Parser
|
24 |
+
|
25 |
+
class _TranslationsBasic(te.Protocol):
|
26 |
+
def gettext(self, message: str) -> str:
|
27 |
+
...
|
28 |
+
|
29 |
+
def ngettext(self, singular: str, plural: str, n: int) -> str:
|
30 |
+
pass
|
31 |
+
|
32 |
+
class _TranslationsContext(_TranslationsBasic):
|
33 |
+
def pgettext(self, context: str, message: str) -> str:
|
34 |
+
...
|
35 |
+
|
36 |
+
def npgettext(self, context: str, singular: str, plural: str, n: int) -> str:
|
37 |
+
...
|
38 |
+
|
39 |
+
_SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]
|
40 |
+
|
41 |
+
|
42 |
+
# I18N functions available in Jinja templates. If the I18N library
|
43 |
+
# provides ugettext, it will be assigned to gettext.
|
44 |
+
GETTEXT_FUNCTIONS: t.Tuple[str, ...] = (
|
45 |
+
"_",
|
46 |
+
"gettext",
|
47 |
+
"ngettext",
|
48 |
+
"pgettext",
|
49 |
+
"npgettext",
|
50 |
+
)
|
51 |
+
_ws_re = re.compile(r"\s*\n\s*")
|
52 |
+
|
53 |
+
|
54 |
+
class Extension:
|
55 |
+
"""Extensions can be used to add extra functionality to the Jinja template
|
56 |
+
system at the parser level. Custom extensions are bound to an environment
|
57 |
+
but may not store environment specific data on `self`. The reason for
|
58 |
+
this is that an extension can be bound to another environment (for
|
59 |
+
overlays) by creating a copy and reassigning the `environment` attribute.
|
60 |
+
|
61 |
+
As extensions are created by the environment they cannot accept any
|
62 |
+
arguments for configuration. One may want to work around that by using
|
63 |
+
a factory function, but that is not possible as extensions are identified
|
64 |
+
by their import name. The correct way to configure the extension is
|
65 |
+
storing the configuration values on the environment. Because this way the
|
66 |
+
environment ends up acting as central configuration storage the
|
67 |
+
attributes may clash which is why extensions have to ensure that the names
|
68 |
+
they choose for configuration are not too generic. ``prefix`` for example
|
69 |
+
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
|
70 |
+
name as includes the name of the extension (fragment cache).
|
71 |
+
"""
|
72 |
+
|
73 |
+
identifier: t.ClassVar[str]
|
74 |
+
|
75 |
+
def __init_subclass__(cls) -> None:
|
76 |
+
cls.identifier = f"{cls.__module__}.{cls.__name__}"
|
77 |
+
|
78 |
+
#: if this extension parses this is the list of tags it's listening to.
|
79 |
+
tags: t.Set[str] = set()
|
80 |
+
|
81 |
+
#: the priority of that extension. This is especially useful for
|
82 |
+
#: extensions that preprocess values. A lower value means higher
|
83 |
+
#: priority.
|
84 |
+
#:
|
85 |
+
#: .. versionadded:: 2.4
|
86 |
+
priority = 100
|
87 |
+
|
88 |
+
def __init__(self, environment: Environment) -> None:
|
89 |
+
self.environment = environment
|
90 |
+
|
91 |
+
def bind(self, environment: Environment) -> "Extension":
|
92 |
+
"""Create a copy of this extension bound to another environment."""
|
93 |
+
rv = object.__new__(self.__class__)
|
94 |
+
rv.__dict__.update(self.__dict__)
|
95 |
+
rv.environment = environment
|
96 |
+
return rv
|
97 |
+
|
98 |
+
def preprocess(
|
99 |
+
self, source: str, name: t.Optional[str], filename: t.Optional[str] = None
|
100 |
+
) -> str:
|
101 |
+
"""This method is called before the actual lexing and can be used to
|
102 |
+
preprocess the source. The `filename` is optional. The return value
|
103 |
+
must be the preprocessed source.
|
104 |
+
"""
|
105 |
+
return source
|
106 |
+
|
107 |
+
def filter_stream(
|
108 |
+
self, stream: "TokenStream"
|
109 |
+
) -> t.Union["TokenStream", t.Iterable["Token"]]:
|
110 |
+
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
|
111 |
+
to filter tokens returned. This method has to return an iterable of
|
112 |
+
:class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
|
113 |
+
:class:`~jinja2.lexer.TokenStream`.
|
114 |
+
"""
|
115 |
+
return stream
|
116 |
+
|
117 |
+
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
118 |
+
"""If any of the :attr:`tags` matched this method is called with the
|
119 |
+
parser as first argument. The token the parser stream is pointing at
|
120 |
+
is the name token that matched. This method has to return one or a
|
121 |
+
list of multiple nodes.
|
122 |
+
"""
|
123 |
+
raise NotImplementedError()
|
124 |
+
|
125 |
+
def attr(
|
126 |
+
self, name: str, lineno: t.Optional[int] = None
|
127 |
+
) -> nodes.ExtensionAttribute:
|
128 |
+
"""Return an attribute node for the current extension. This is useful
|
129 |
+
to pass constants on extensions to generated template code.
|
130 |
+
|
131 |
+
::
|
132 |
+
|
133 |
+
self.attr('_my_attribute', lineno=lineno)
|
134 |
+
"""
|
135 |
+
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
|
136 |
+
|
137 |
+
def call_method(
|
138 |
+
self,
|
139 |
+
name: str,
|
140 |
+
args: t.Optional[t.List[nodes.Expr]] = None,
|
141 |
+
kwargs: t.Optional[t.List[nodes.Keyword]] = None,
|
142 |
+
dyn_args: t.Optional[nodes.Expr] = None,
|
143 |
+
dyn_kwargs: t.Optional[nodes.Expr] = None,
|
144 |
+
lineno: t.Optional[int] = None,
|
145 |
+
) -> nodes.Call:
|
146 |
+
"""Call a method of the extension. This is a shortcut for
|
147 |
+
:meth:`attr` + :class:`jinja2.nodes.Call`.
|
148 |
+
"""
|
149 |
+
if args is None:
|
150 |
+
args = []
|
151 |
+
if kwargs is None:
|
152 |
+
kwargs = []
|
153 |
+
return nodes.Call(
|
154 |
+
self.attr(name, lineno=lineno),
|
155 |
+
args,
|
156 |
+
kwargs,
|
157 |
+
dyn_args,
|
158 |
+
dyn_kwargs,
|
159 |
+
lineno=lineno,
|
160 |
+
)
|
161 |
+
|
162 |
+
|
163 |
+
@pass_context
|
164 |
+
def _gettext_alias(
|
165 |
+
__context: Context, *args: t.Any, **kwargs: t.Any
|
166 |
+
) -> t.Union[t.Any, Undefined]:
|
167 |
+
return __context.call(__context.resolve("gettext"), *args, **kwargs)
|
168 |
+
|
169 |
+
|
170 |
+
def _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]:
|
171 |
+
@pass_context
|
172 |
+
def gettext(__context: Context, __string: str, **variables: t.Any) -> str:
|
173 |
+
rv = __context.call(func, __string)
|
174 |
+
if __context.eval_ctx.autoescape:
|
175 |
+
rv = Markup(rv)
|
176 |
+
# Always treat as a format string, even if there are no
|
177 |
+
# variables. This makes translation strings more consistent
|
178 |
+
# and predictable. This requires escaping
|
179 |
+
return rv % variables # type: ignore
|
180 |
+
|
181 |
+
return gettext
|
182 |
+
|
183 |
+
|
184 |
+
def _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]:
|
185 |
+
@pass_context
|
186 |
+
def ngettext(
|
187 |
+
__context: Context,
|
188 |
+
__singular: str,
|
189 |
+
__plural: str,
|
190 |
+
__num: int,
|
191 |
+
**variables: t.Any,
|
192 |
+
) -> str:
|
193 |
+
variables.setdefault("num", __num)
|
194 |
+
rv = __context.call(func, __singular, __plural, __num)
|
195 |
+
if __context.eval_ctx.autoescape:
|
196 |
+
rv = Markup(rv)
|
197 |
+
# Always treat as a format string, see gettext comment above.
|
198 |
+
return rv % variables # type: ignore
|
199 |
+
|
200 |
+
return ngettext
|
201 |
+
|
202 |
+
|
203 |
+
def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]:
|
204 |
+
@pass_context
|
205 |
+
def pgettext(
|
206 |
+
__context: Context, __string_ctx: str, __string: str, **variables: t.Any
|
207 |
+
) -> str:
|
208 |
+
variables.setdefault("context", __string_ctx)
|
209 |
+
rv = __context.call(func, __string_ctx, __string)
|
210 |
+
|
211 |
+
if __context.eval_ctx.autoescape:
|
212 |
+
rv = Markup(rv)
|
213 |
+
|
214 |
+
# Always treat as a format string, see gettext comment above.
|
215 |
+
return rv % variables # type: ignore
|
216 |
+
|
217 |
+
return pgettext
|
218 |
+
|
219 |
+
|
220 |
+
def _make_new_npgettext(
|
221 |
+
func: t.Callable[[str, str, str, int], str]
|
222 |
+
) -> t.Callable[..., str]:
|
223 |
+
@pass_context
|
224 |
+
def npgettext(
|
225 |
+
__context: Context,
|
226 |
+
__string_ctx: str,
|
227 |
+
__singular: str,
|
228 |
+
__plural: str,
|
229 |
+
__num: int,
|
230 |
+
**variables: t.Any,
|
231 |
+
) -> str:
|
232 |
+
variables.setdefault("context", __string_ctx)
|
233 |
+
variables.setdefault("num", __num)
|
234 |
+
rv = __context.call(func, __string_ctx, __singular, __plural, __num)
|
235 |
+
|
236 |
+
if __context.eval_ctx.autoescape:
|
237 |
+
rv = Markup(rv)
|
238 |
+
|
239 |
+
# Always treat as a format string, see gettext comment above.
|
240 |
+
return rv % variables # type: ignore
|
241 |
+
|
242 |
+
return npgettext
|
243 |
+
|
244 |
+
|
245 |
+
class InternationalizationExtension(Extension):
|
246 |
+
"""This extension adds gettext support to Jinja."""
|
247 |
+
|
248 |
+
tags = {"trans"}
|
249 |
+
|
250 |
+
# TODO: the i18n extension is currently reevaluating values in a few
|
251 |
+
# situations. Take this example:
|
252 |
+
# {% trans count=something() %}{{ count }} foo{% pluralize
|
253 |
+
# %}{{ count }} fooss{% endtrans %}
|
254 |
+
# something is called twice here. One time for the gettext value and
|
255 |
+
# the other time for the n-parameter of the ngettext function.
|
256 |
+
|
257 |
+
def __init__(self, environment: Environment) -> None:
|
258 |
+
super().__init__(environment)
|
259 |
+
environment.globals["_"] = _gettext_alias
|
260 |
+
environment.extend(
|
261 |
+
install_gettext_translations=self._install,
|
262 |
+
install_null_translations=self._install_null,
|
263 |
+
install_gettext_callables=self._install_callables,
|
264 |
+
uninstall_gettext_translations=self._uninstall,
|
265 |
+
extract_translations=self._extract,
|
266 |
+
newstyle_gettext=False,
|
267 |
+
)
|
268 |
+
|
269 |
+
def _install(
|
270 |
+
self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None
|
271 |
+
) -> None:
|
272 |
+
# ugettext and ungettext are preferred in case the I18N library
|
273 |
+
# is providing compatibility with older Python versions.
|
274 |
+
gettext = getattr(translations, "ugettext", None)
|
275 |
+
if gettext is None:
|
276 |
+
gettext = translations.gettext
|
277 |
+
ngettext = getattr(translations, "ungettext", None)
|
278 |
+
if ngettext is None:
|
279 |
+
ngettext = translations.ngettext
|
280 |
+
|
281 |
+
pgettext = getattr(translations, "pgettext", None)
|
282 |
+
npgettext = getattr(translations, "npgettext", None)
|
283 |
+
self._install_callables(
|
284 |
+
gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
|
285 |
+
)
|
286 |
+
|
287 |
+
def _install_null(self, newstyle: t.Optional[bool] = None) -> None:
|
288 |
+
import gettext
|
289 |
+
|
290 |
+
translations = gettext.NullTranslations()
|
291 |
+
|
292 |
+
if hasattr(translations, "pgettext"):
|
293 |
+
# Python < 3.8
|
294 |
+
pgettext = translations.pgettext # type: ignore
|
295 |
+
else:
|
296 |
+
|
297 |
+
def pgettext(c: str, s: str) -> str:
|
298 |
+
return s
|
299 |
+
|
300 |
+
if hasattr(translations, "npgettext"):
|
301 |
+
npgettext = translations.npgettext # type: ignore
|
302 |
+
else:
|
303 |
+
|
304 |
+
def npgettext(c: str, s: str, p: str, n: int) -> str:
|
305 |
+
return s if n == 1 else p
|
306 |
+
|
307 |
+
self._install_callables(
|
308 |
+
gettext=translations.gettext,
|
309 |
+
ngettext=translations.ngettext,
|
310 |
+
newstyle=newstyle,
|
311 |
+
pgettext=pgettext,
|
312 |
+
npgettext=npgettext,
|
313 |
+
)
|
314 |
+
|
315 |
+
def _install_callables(
|
316 |
+
self,
|
317 |
+
gettext: t.Callable[[str], str],
|
318 |
+
ngettext: t.Callable[[str, str, int], str],
|
319 |
+
newstyle: t.Optional[bool] = None,
|
320 |
+
pgettext: t.Optional[t.Callable[[str, str], str]] = None,
|
321 |
+
npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None,
|
322 |
+
) -> None:
|
323 |
+
if newstyle is not None:
|
324 |
+
self.environment.newstyle_gettext = newstyle # type: ignore
|
325 |
+
if self.environment.newstyle_gettext: # type: ignore
|
326 |
+
gettext = _make_new_gettext(gettext)
|
327 |
+
ngettext = _make_new_ngettext(ngettext)
|
328 |
+
|
329 |
+
if pgettext is not None:
|
330 |
+
pgettext = _make_new_pgettext(pgettext)
|
331 |
+
|
332 |
+
if npgettext is not None:
|
333 |
+
npgettext = _make_new_npgettext(npgettext)
|
334 |
+
|
335 |
+
self.environment.globals.update(
|
336 |
+
gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext
|
337 |
+
)
|
338 |
+
|
339 |
+
def _uninstall(self, translations: "_SupportedTranslations") -> None:
|
340 |
+
for key in ("gettext", "ngettext", "pgettext", "npgettext"):
|
341 |
+
self.environment.globals.pop(key, None)
|
342 |
+
|
343 |
+
def _extract(
|
344 |
+
self,
|
345 |
+
source: t.Union[str, nodes.Template],
|
346 |
+
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
|
347 |
+
) -> t.Iterator[
|
348 |
+
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
|
349 |
+
]:
|
350 |
+
if isinstance(source, str):
|
351 |
+
source = self.environment.parse(source)
|
352 |
+
return extract_from_ast(source, gettext_functions)
|
353 |
+
|
354 |
+
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
355 |
+
"""Parse a translatable tag."""
|
356 |
+
lineno = next(parser.stream).lineno
|
357 |
+
|
358 |
+
context = None
|
359 |
+
context_token = parser.stream.next_if("string")
|
360 |
+
|
361 |
+
if context_token is not None:
|
362 |
+
context = context_token.value
|
363 |
+
|
364 |
+
# find all the variables referenced. Additionally a variable can be
|
365 |
+
# defined in the body of the trans block too, but this is checked at
|
366 |
+
# a later state.
|
367 |
+
plural_expr: t.Optional[nodes.Expr] = None
|
368 |
+
plural_expr_assignment: t.Optional[nodes.Assign] = None
|
369 |
+
num_called_num = False
|
370 |
+
variables: t.Dict[str, nodes.Expr] = {}
|
371 |
+
trimmed = None
|
372 |
+
while parser.stream.current.type != "block_end":
|
373 |
+
if variables:
|
374 |
+
parser.stream.expect("comma")
|
375 |
+
|
376 |
+
# skip colon for python compatibility
|
377 |
+
if parser.stream.skip_if("colon"):
|
378 |
+
break
|
379 |
+
|
380 |
+
token = parser.stream.expect("name")
|
381 |
+
if token.value in variables:
|
382 |
+
parser.fail(
|
383 |
+
f"translatable variable {token.value!r} defined twice.",
|
384 |
+
token.lineno,
|
385 |
+
exc=TemplateAssertionError,
|
386 |
+
)
|
387 |
+
|
388 |
+
# expressions
|
389 |
+
if parser.stream.current.type == "assign":
|
390 |
+
next(parser.stream)
|
391 |
+
variables[token.value] = var = parser.parse_expression()
|
392 |
+
elif trimmed is None and token.value in ("trimmed", "notrimmed"):
|
393 |
+
trimmed = token.value == "trimmed"
|
394 |
+
continue
|
395 |
+
else:
|
396 |
+
variables[token.value] = var = nodes.Name(token.value, "load")
|
397 |
+
|
398 |
+
if plural_expr is None:
|
399 |
+
if isinstance(var, nodes.Call):
|
400 |
+
plural_expr = nodes.Name("_trans", "load")
|
401 |
+
variables[token.value] = plural_expr
|
402 |
+
plural_expr_assignment = nodes.Assign(
|
403 |
+
nodes.Name("_trans", "store"), var
|
404 |
+
)
|
405 |
+
else:
|
406 |
+
plural_expr = var
|
407 |
+
num_called_num = token.value == "num"
|
408 |
+
|
409 |
+
parser.stream.expect("block_end")
|
410 |
+
|
411 |
+
plural = None
|
412 |
+
have_plural = False
|
413 |
+
referenced = set()
|
414 |
+
|
415 |
+
# now parse until endtrans or pluralize
|
416 |
+
singular_names, singular = self._parse_block(parser, True)
|
417 |
+
if singular_names:
|
418 |
+
referenced.update(singular_names)
|
419 |
+
if plural_expr is None:
|
420 |
+
plural_expr = nodes.Name(singular_names[0], "load")
|
421 |
+
num_called_num = singular_names[0] == "num"
|
422 |
+
|
423 |
+
# if we have a pluralize block, we parse that too
|
424 |
+
if parser.stream.current.test("name:pluralize"):
|
425 |
+
have_plural = True
|
426 |
+
next(parser.stream)
|
427 |
+
if parser.stream.current.type != "block_end":
|
428 |
+
token = parser.stream.expect("name")
|
429 |
+
if token.value not in variables:
|
430 |
+
parser.fail(
|
431 |
+
f"unknown variable {token.value!r} for pluralization",
|
432 |
+
token.lineno,
|
433 |
+
exc=TemplateAssertionError,
|
434 |
+
)
|
435 |
+
plural_expr = variables[token.value]
|
436 |
+
num_called_num = token.value == "num"
|
437 |
+
parser.stream.expect("block_end")
|
438 |
+
plural_names, plural = self._parse_block(parser, False)
|
439 |
+
next(parser.stream)
|
440 |
+
referenced.update(plural_names)
|
441 |
+
else:
|
442 |
+
next(parser.stream)
|
443 |
+
|
444 |
+
# register free names as simple name expressions
|
445 |
+
for name in referenced:
|
446 |
+
if name not in variables:
|
447 |
+
variables[name] = nodes.Name(name, "load")
|
448 |
+
|
449 |
+
if not have_plural:
|
450 |
+
plural_expr = None
|
451 |
+
elif plural_expr is None:
|
452 |
+
parser.fail("pluralize without variables", lineno)
|
453 |
+
|
454 |
+
if trimmed is None:
|
455 |
+
trimmed = self.environment.policies["ext.i18n.trimmed"]
|
456 |
+
if trimmed:
|
457 |
+
singular = self._trim_whitespace(singular)
|
458 |
+
if plural:
|
459 |
+
plural = self._trim_whitespace(plural)
|
460 |
+
|
461 |
+
node = self._make_node(
|
462 |
+
singular,
|
463 |
+
plural,
|
464 |
+
context,
|
465 |
+
variables,
|
466 |
+
plural_expr,
|
467 |
+
bool(referenced),
|
468 |
+
num_called_num and have_plural,
|
469 |
+
)
|
470 |
+
node.set_lineno(lineno)
|
471 |
+
if plural_expr_assignment is not None:
|
472 |
+
return [plural_expr_assignment, node]
|
473 |
+
else:
|
474 |
+
return node
|
475 |
+
|
476 |
+
def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str:
|
477 |
+
return _ws_re.sub(" ", string.strip())
|
478 |
+
|
479 |
+
def _parse_block(
|
480 |
+
self, parser: "Parser", allow_pluralize: bool
|
481 |
+
) -> t.Tuple[t.List[str], str]:
|
482 |
+
"""Parse until the next block tag with a given name."""
|
483 |
+
referenced = []
|
484 |
+
buf = []
|
485 |
+
|
486 |
+
while True:
|
487 |
+
if parser.stream.current.type == "data":
|
488 |
+
buf.append(parser.stream.current.value.replace("%", "%%"))
|
489 |
+
next(parser.stream)
|
490 |
+
elif parser.stream.current.type == "variable_begin":
|
491 |
+
next(parser.stream)
|
492 |
+
name = parser.stream.expect("name").value
|
493 |
+
referenced.append(name)
|
494 |
+
buf.append(f"%({name})s")
|
495 |
+
parser.stream.expect("variable_end")
|
496 |
+
elif parser.stream.current.type == "block_begin":
|
497 |
+
next(parser.stream)
|
498 |
+
if parser.stream.current.test("name:endtrans"):
|
499 |
+
break
|
500 |
+
elif parser.stream.current.test("name:pluralize"):
|
501 |
+
if allow_pluralize:
|
502 |
+
break
|
503 |
+
parser.fail(
|
504 |
+
"a translatable section can have only one pluralize section"
|
505 |
+
)
|
506 |
+
parser.fail(
|
507 |
+
"control structures in translatable sections are not allowed"
|
508 |
+
)
|
509 |
+
elif parser.stream.eos:
|
510 |
+
parser.fail("unclosed translation block")
|
511 |
+
else:
|
512 |
+
raise RuntimeError("internal parser error")
|
513 |
+
|
514 |
+
return referenced, concat(buf)
|
515 |
+
|
516 |
+
def _make_node(
|
517 |
+
self,
|
518 |
+
singular: str,
|
519 |
+
plural: t.Optional[str],
|
520 |
+
context: t.Optional[str],
|
521 |
+
variables: t.Dict[str, nodes.Expr],
|
522 |
+
plural_expr: t.Optional[nodes.Expr],
|
523 |
+
vars_referenced: bool,
|
524 |
+
num_called_num: bool,
|
525 |
+
) -> nodes.Output:
|
526 |
+
"""Generates a useful node from the data provided."""
|
527 |
+
newstyle = self.environment.newstyle_gettext # type: ignore
|
528 |
+
node: nodes.Expr
|
529 |
+
|
530 |
+
# no variables referenced? no need to escape for old style
|
531 |
+
# gettext invocations only if there are vars.
|
532 |
+
if not vars_referenced and not newstyle:
|
533 |
+
singular = singular.replace("%%", "%")
|
534 |
+
if plural:
|
535 |
+
plural = plural.replace("%%", "%")
|
536 |
+
|
537 |
+
func_name = "gettext"
|
538 |
+
func_args: t.List[nodes.Expr] = [nodes.Const(singular)]
|
539 |
+
|
540 |
+
if context is not None:
|
541 |
+
func_args.insert(0, nodes.Const(context))
|
542 |
+
func_name = f"p{func_name}"
|
543 |
+
|
544 |
+
if plural_expr is not None:
|
545 |
+
func_name = f"n{func_name}"
|
546 |
+
func_args.extend((nodes.Const(plural), plural_expr))
|
547 |
+
|
548 |
+
node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None)
|
549 |
+
|
550 |
+
# in case newstyle gettext is used, the method is powerful
|
551 |
+
# enough to handle the variable expansion and autoescape
|
552 |
+
# handling itself
|
553 |
+
if newstyle:
|
554 |
+
for key, value in variables.items():
|
555 |
+
# the function adds that later anyways in case num was
|
556 |
+
# called num, so just skip it.
|
557 |
+
if num_called_num and key == "num":
|
558 |
+
continue
|
559 |
+
node.kwargs.append(nodes.Keyword(key, value))
|
560 |
+
|
561 |
+
# otherwise do that here
|
562 |
+
else:
|
563 |
+
# mark the return value as safe if we are in an
|
564 |
+
# environment with autoescaping turned on
|
565 |
+
node = nodes.MarkSafeIfAutoescape(node)
|
566 |
+
if variables:
|
567 |
+
node = nodes.Mod(
|
568 |
+
node,
|
569 |
+
nodes.Dict(
|
570 |
+
[
|
571 |
+
nodes.Pair(nodes.Const(key), value)
|
572 |
+
for key, value in variables.items()
|
573 |
+
]
|
574 |
+
),
|
575 |
+
)
|
576 |
+
return nodes.Output([node])
|
577 |
+
|
578 |
+
|
579 |
+
class ExprStmtExtension(Extension):
|
580 |
+
"""Adds a `do` tag to Jinja that works like the print statement just
|
581 |
+
that it doesn't print the return value.
|
582 |
+
"""
|
583 |
+
|
584 |
+
tags = {"do"}
|
585 |
+
|
586 |
+
def parse(self, parser: "Parser") -> nodes.ExprStmt:
|
587 |
+
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
|
588 |
+
node.node = parser.parse_tuple()
|
589 |
+
return node
|
590 |
+
|
591 |
+
|
592 |
+
class LoopControlExtension(Extension):
|
593 |
+
"""Adds break and continue to the template engine."""
|
594 |
+
|
595 |
+
tags = {"break", "continue"}
|
596 |
+
|
597 |
+
def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]:
|
598 |
+
token = next(parser.stream)
|
599 |
+
if token.value == "break":
|
600 |
+
return nodes.Break(lineno=token.lineno)
|
601 |
+
return nodes.Continue(lineno=token.lineno)
|
602 |
+
|
603 |
+
|
604 |
+
class DebugExtension(Extension):
|
605 |
+
"""A ``{% debug %}`` tag that dumps the available variables,
|
606 |
+
filters, and tests.
|
607 |
+
|
608 |
+
.. code-block:: html+jinja
|
609 |
+
|
610 |
+
<pre>{% debug %}</pre>
|
611 |
+
|
612 |
+
.. code-block:: text
|
613 |
+
|
614 |
+
{'context': {'cycler': <class 'jinja2.utils.Cycler'>,
|
615 |
+
...,
|
616 |
+
'namespace': <class 'jinja2.utils.Namespace'>},
|
617 |
+
'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
|
618 |
+
..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
|
619 |
+
'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
|
620 |
+
..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}
|
621 |
+
|
622 |
+
.. versionadded:: 2.11.0
|
623 |
+
"""
|
624 |
+
|
625 |
+
tags = {"debug"}
|
626 |
+
|
627 |
+
def parse(self, parser: "Parser") -> nodes.Output:
|
628 |
+
lineno = parser.stream.expect("name:debug").lineno
|
629 |
+
context = nodes.ContextReference()
|
630 |
+
result = self.call_method("_render", [context], lineno=lineno)
|
631 |
+
return nodes.Output([result], lineno=lineno)
|
632 |
+
|
633 |
+
def _render(self, context: Context) -> str:
|
634 |
+
result = {
|
635 |
+
"context": context.get_all(),
|
636 |
+
"filters": sorted(self.environment.filters.keys()),
|
637 |
+
"tests": sorted(self.environment.tests.keys()),
|
638 |
+
}
|
639 |
+
|
640 |
+
# Set the depth since the intent is to show the top few names.
|
641 |
+
return pprint.pformat(result, depth=3, compact=True)
|
642 |
+
|
643 |
+
|
644 |
+
def extract_from_ast(
|
645 |
+
ast: nodes.Template,
|
646 |
+
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
|
647 |
+
babel_style: bool = True,
|
648 |
+
) -> t.Iterator[
|
649 |
+
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
|
650 |
+
]:
|
651 |
+
"""Extract localizable strings from the given template node. Per
|
652 |
+
default this function returns matches in babel style that means non string
|
653 |
+
parameters as well as keyword arguments are returned as `None`. This
|
654 |
+
allows Babel to figure out what you really meant if you are using
|
655 |
+
gettext functions that allow keyword arguments for placeholder expansion.
|
656 |
+
If you don't want that behavior set the `babel_style` parameter to `False`
|
657 |
+
which causes only strings to be returned and parameters are always stored
|
658 |
+
in tuples. As a consequence invalid gettext calls (calls without a single
|
659 |
+
string parameter or string parameters after non-string parameters) are
|
660 |
+
skipped.
|
661 |
+
|
662 |
+
This example explains the behavior:
|
663 |
+
|
664 |
+
>>> from jinja2 import Environment
|
665 |
+
>>> env = Environment()
|
666 |
+
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
|
667 |
+
>>> list(extract_from_ast(node))
|
668 |
+
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
|
669 |
+
>>> list(extract_from_ast(node, babel_style=False))
|
670 |
+
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
|
671 |
+
|
672 |
+
For every string found this function yields a ``(lineno, function,
|
673 |
+
message)`` tuple, where:
|
674 |
+
|
675 |
+
* ``lineno`` is the number of the line on which the string was found,
|
676 |
+
* ``function`` is the name of the ``gettext`` function used (if the
|
677 |
+
string was extracted from embedded Python code), and
|
678 |
+
* ``message`` is the string, or a tuple of strings for functions
|
679 |
+
with multiple string arguments.
|
680 |
+
|
681 |
+
This extraction function operates on the AST and is because of that unable
|
682 |
+
to extract any comments. For comment support you have to use the babel
|
683 |
+
extraction interface or extract comments yourself.
|
684 |
+
"""
|
685 |
+
out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]
|
686 |
+
|
687 |
+
for node in ast.find_all(nodes.Call):
|
688 |
+
if (
|
689 |
+
not isinstance(node.node, nodes.Name)
|
690 |
+
or node.node.name not in gettext_functions
|
691 |
+
):
|
692 |
+
continue
|
693 |
+
|
694 |
+
strings: t.List[t.Optional[str]] = []
|
695 |
+
|
696 |
+
for arg in node.args:
|
697 |
+
if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
|
698 |
+
strings.append(arg.value)
|
699 |
+
else:
|
700 |
+
strings.append(None)
|
701 |
+
|
702 |
+
for _ in node.kwargs:
|
703 |
+
strings.append(None)
|
704 |
+
if node.dyn_args is not None:
|
705 |
+
strings.append(None)
|
706 |
+
if node.dyn_kwargs is not None:
|
707 |
+
strings.append(None)
|
708 |
+
|
709 |
+
if not babel_style:
|
710 |
+
out = tuple(x for x in strings if x is not None)
|
711 |
+
|
712 |
+
if not out:
|
713 |
+
continue
|
714 |
+
else:
|
715 |
+
if len(strings) == 1:
|
716 |
+
out = strings[0]
|
717 |
+
else:
|
718 |
+
out = tuple(strings)
|
719 |
+
|
720 |
+
yield node.lineno, node.node.name, out
|
721 |
+
|
722 |
+
|
723 |
+
class _CommentFinder:
|
724 |
+
"""Helper class to find comments in a token stream. Can only
|
725 |
+
find comments for gettext calls forwards. Once the comment
|
726 |
+
from line 4 is found, a comment for line 1 will not return a
|
727 |
+
usable value.
|
728 |
+
"""
|
729 |
+
|
730 |
+
def __init__(
|
731 |
+
self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str]
|
732 |
+
) -> None:
|
733 |
+
self.tokens = tokens
|
734 |
+
self.comment_tags = comment_tags
|
735 |
+
self.offset = 0
|
736 |
+
self.last_lineno = 0
|
737 |
+
|
738 |
+
def find_backwards(self, offset: int) -> t.List[str]:
|
739 |
+
try:
|
740 |
+
for _, token_type, token_value in reversed(
|
741 |
+
self.tokens[self.offset : offset]
|
742 |
+
):
|
743 |
+
if token_type in ("comment", "linecomment"):
|
744 |
+
try:
|
745 |
+
prefix, comment = token_value.split(None, 1)
|
746 |
+
except ValueError:
|
747 |
+
continue
|
748 |
+
if prefix in self.comment_tags:
|
749 |
+
return [comment.rstrip()]
|
750 |
+
return []
|
751 |
+
finally:
|
752 |
+
self.offset = offset
|
753 |
+
|
754 |
+
def find_comments(self, lineno: int) -> t.List[str]:
|
755 |
+
if not self.comment_tags or self.last_lineno > lineno:
|
756 |
+
return []
|
757 |
+
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
|
758 |
+
if token_lineno > lineno:
|
759 |
+
return self.find_backwards(self.offset + idx)
|
760 |
+
return self.find_backwards(len(self.tokens))
|
761 |
+
|
762 |
+
|
763 |
+
def babel_extract(
|
764 |
+
fileobj: t.BinaryIO,
|
765 |
+
keywords: t.Sequence[str],
|
766 |
+
comment_tags: t.Sequence[str],
|
767 |
+
options: t.Dict[str, t.Any],
|
768 |
+
) -> t.Iterator[
|
769 |
+
t.Tuple[
|
770 |
+
int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str]
|
771 |
+
]
|
772 |
+
]:
|
773 |
+
"""Babel extraction method for Jinja templates.
|
774 |
+
|
775 |
+
.. versionchanged:: 2.3
|
776 |
+
Basic support for translation comments was added. If `comment_tags`
|
777 |
+
is now set to a list of keywords for extraction, the extractor will
|
778 |
+
try to find the best preceding comment that begins with one of the
|
779 |
+
keywords. For best results, make sure to not have more than one
|
780 |
+
gettext call in one line of code and the matching comment in the
|
781 |
+
same line or the line before.
|
782 |
+
|
783 |
+
.. versionchanged:: 2.5.1
|
784 |
+
The `newstyle_gettext` flag can be set to `True` to enable newstyle
|
785 |
+
gettext calls.
|
786 |
+
|
787 |
+
.. versionchanged:: 2.7
|
788 |
+
A `silent` option can now be provided. If set to `False` template
|
789 |
+
syntax errors are propagated instead of being ignored.
|
790 |
+
|
791 |
+
:param fileobj: the file-like object the messages should be extracted from
|
792 |
+
:param keywords: a list of keywords (i.e. function names) that should be
|
793 |
+
recognized as translation functions
|
794 |
+
:param comment_tags: a list of translator tags to search for and include
|
795 |
+
in the results.
|
796 |
+
:param options: a dictionary of additional options (optional)
|
797 |
+
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
|
798 |
+
(comments will be empty currently)
|
799 |
+
"""
|
800 |
+
extensions: t.Dict[t.Type[Extension], None] = {}
|
801 |
+
|
802 |
+
for extension_name in options.get("extensions", "").split(","):
|
803 |
+
extension_name = extension_name.strip()
|
804 |
+
|
805 |
+
if not extension_name:
|
806 |
+
continue
|
807 |
+
|
808 |
+
extensions[import_string(extension_name)] = None
|
809 |
+
|
810 |
+
if InternationalizationExtension not in extensions:
|
811 |
+
extensions[InternationalizationExtension] = None
|
812 |
+
|
813 |
+
def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool:
|
814 |
+
return options.get(key, str(default)).lower() in {"1", "on", "yes", "true"}
|
815 |
+
|
816 |
+
silent = getbool(options, "silent", True)
|
817 |
+
environment = Environment(
|
818 |
+
options.get("block_start_string", defaults.BLOCK_START_STRING),
|
819 |
+
options.get("block_end_string", defaults.BLOCK_END_STRING),
|
820 |
+
options.get("variable_start_string", defaults.VARIABLE_START_STRING),
|
821 |
+
options.get("variable_end_string", defaults.VARIABLE_END_STRING),
|
822 |
+
options.get("comment_start_string", defaults.COMMENT_START_STRING),
|
823 |
+
options.get("comment_end_string", defaults.COMMENT_END_STRING),
|
824 |
+
options.get("line_statement_prefix") or defaults.LINE_STATEMENT_PREFIX,
|
825 |
+
options.get("line_comment_prefix") or defaults.LINE_COMMENT_PREFIX,
|
826 |
+
getbool(options, "trim_blocks", defaults.TRIM_BLOCKS),
|
827 |
+
getbool(options, "lstrip_blocks", defaults.LSTRIP_BLOCKS),
|
828 |
+
defaults.NEWLINE_SEQUENCE,
|
829 |
+
getbool(options, "keep_trailing_newline", defaults.KEEP_TRAILING_NEWLINE),
|
830 |
+
tuple(extensions),
|
831 |
+
cache_size=0,
|
832 |
+
auto_reload=False,
|
833 |
+
)
|
834 |
+
|
835 |
+
if getbool(options, "trimmed"):
|
836 |
+
environment.policies["ext.i18n.trimmed"] = True
|
837 |
+
if getbool(options, "newstyle_gettext"):
|
838 |
+
environment.newstyle_gettext = True # type: ignore
|
839 |
+
|
840 |
+
source = fileobj.read().decode(options.get("encoding", "utf-8"))
|
841 |
+
try:
|
842 |
+
node = environment.parse(source)
|
843 |
+
tokens = list(environment.lex(environment.preprocess(source)))
|
844 |
+
except TemplateSyntaxError:
|
845 |
+
if not silent:
|
846 |
+
raise
|
847 |
+
# skip templates with syntax errors
|
848 |
+
return
|
849 |
+
|
850 |
+
finder = _CommentFinder(tokens, comment_tags)
|
851 |
+
for lineno, func, message in extract_from_ast(node, keywords):
|
852 |
+
yield lineno, func, message, finder.find_comments(lineno)
|
853 |
+
|
854 |
+
|
855 |
+
#: nicer import names
|
856 |
+
i18n = InternationalizationExtension
|
857 |
+
do = ExprStmtExtension
|
858 |
+
loopcontrols = LoopControlExtension
|
859 |
+
debug = DebugExtension
|
lib/python3.11/site-packages/jinja2/filters.py
ADDED
@@ -0,0 +1,1840 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Built-in template filters used with the ``|`` operator."""
|
2 |
+
import math
|
3 |
+
import random
|
4 |
+
import re
|
5 |
+
import typing
|
6 |
+
import typing as t
|
7 |
+
from collections import abc
|
8 |
+
from itertools import chain
|
9 |
+
from itertools import groupby
|
10 |
+
|
11 |
+
from markupsafe import escape
|
12 |
+
from markupsafe import Markup
|
13 |
+
from markupsafe import soft_str
|
14 |
+
|
15 |
+
from .async_utils import async_variant
|
16 |
+
from .async_utils import auto_aiter
|
17 |
+
from .async_utils import auto_await
|
18 |
+
from .async_utils import auto_to_list
|
19 |
+
from .exceptions import FilterArgumentError
|
20 |
+
from .runtime import Undefined
|
21 |
+
from .utils import htmlsafe_json_dumps
|
22 |
+
from .utils import pass_context
|
23 |
+
from .utils import pass_environment
|
24 |
+
from .utils import pass_eval_context
|
25 |
+
from .utils import pformat
|
26 |
+
from .utils import url_quote
|
27 |
+
from .utils import urlize
|
28 |
+
|
29 |
+
if t.TYPE_CHECKING:
|
30 |
+
import typing_extensions as te
|
31 |
+
from .environment import Environment
|
32 |
+
from .nodes import EvalContext
|
33 |
+
from .runtime import Context
|
34 |
+
from .sandbox import SandboxedEnvironment # noqa: F401
|
35 |
+
|
36 |
+
class HasHTML(te.Protocol):
|
37 |
+
def __html__(self) -> str:
|
38 |
+
pass
|
39 |
+
|
40 |
+
|
41 |
+
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
42 |
+
K = t.TypeVar("K")
|
43 |
+
V = t.TypeVar("V")
|
44 |
+
|
45 |
+
|
46 |
+
def ignore_case(value: V) -> V:
|
47 |
+
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings
|
48 |
+
to lowercase and returns other types as-is."""
|
49 |
+
if isinstance(value, str):
|
50 |
+
return t.cast(V, value.lower())
|
51 |
+
|
52 |
+
return value
|
53 |
+
|
54 |
+
|
55 |
+
def make_attrgetter(
|
56 |
+
environment: "Environment",
|
57 |
+
attribute: t.Optional[t.Union[str, int]],
|
58 |
+
postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None,
|
59 |
+
default: t.Optional[t.Any] = None,
|
60 |
+
) -> t.Callable[[t.Any], t.Any]:
|
61 |
+
"""Returns a callable that looks up the given attribute from a
|
62 |
+
passed object with the rules of the environment. Dots are allowed
|
63 |
+
to access attributes of attributes. Integer parts in paths are
|
64 |
+
looked up as integers.
|
65 |
+
"""
|
66 |
+
parts = _prepare_attribute_parts(attribute)
|
67 |
+
|
68 |
+
def attrgetter(item: t.Any) -> t.Any:
|
69 |
+
for part in parts:
|
70 |
+
item = environment.getitem(item, part)
|
71 |
+
|
72 |
+
if default is not None and isinstance(item, Undefined):
|
73 |
+
item = default
|
74 |
+
|
75 |
+
if postprocess is not None:
|
76 |
+
item = postprocess(item)
|
77 |
+
|
78 |
+
return item
|
79 |
+
|
80 |
+
return attrgetter
|
81 |
+
|
82 |
+
|
83 |
+
def make_multi_attrgetter(
|
84 |
+
environment: "Environment",
|
85 |
+
attribute: t.Optional[t.Union[str, int]],
|
86 |
+
postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None,
|
87 |
+
) -> t.Callable[[t.Any], t.List[t.Any]]:
|
88 |
+
"""Returns a callable that looks up the given comma separated
|
89 |
+
attributes from a passed object with the rules of the environment.
|
90 |
+
Dots are allowed to access attributes of each attribute. Integer
|
91 |
+
parts in paths are looked up as integers.
|
92 |
+
|
93 |
+
The value returned by the returned callable is a list of extracted
|
94 |
+
attribute values.
|
95 |
+
|
96 |
+
Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc.
|
97 |
+
"""
|
98 |
+
if isinstance(attribute, str):
|
99 |
+
split: t.Sequence[t.Union[str, int, None]] = attribute.split(",")
|
100 |
+
else:
|
101 |
+
split = [attribute]
|
102 |
+
|
103 |
+
parts = [_prepare_attribute_parts(item) for item in split]
|
104 |
+
|
105 |
+
def attrgetter(item: t.Any) -> t.List[t.Any]:
|
106 |
+
items = [None] * len(parts)
|
107 |
+
|
108 |
+
for i, attribute_part in enumerate(parts):
|
109 |
+
item_i = item
|
110 |
+
|
111 |
+
for part in attribute_part:
|
112 |
+
item_i = environment.getitem(item_i, part)
|
113 |
+
|
114 |
+
if postprocess is not None:
|
115 |
+
item_i = postprocess(item_i)
|
116 |
+
|
117 |
+
items[i] = item_i
|
118 |
+
|
119 |
+
return items
|
120 |
+
|
121 |
+
return attrgetter
|
122 |
+
|
123 |
+
|
124 |
+
def _prepare_attribute_parts(
|
125 |
+
attr: t.Optional[t.Union[str, int]]
|
126 |
+
) -> t.List[t.Union[str, int]]:
|
127 |
+
if attr is None:
|
128 |
+
return []
|
129 |
+
|
130 |
+
if isinstance(attr, str):
|
131 |
+
return [int(x) if x.isdigit() else x for x in attr.split(".")]
|
132 |
+
|
133 |
+
return [attr]
|
134 |
+
|
135 |
+
|
136 |
+
def do_forceescape(value: "t.Union[str, HasHTML]") -> Markup:
|
137 |
+
"""Enforce HTML escaping. This will probably double escape variables."""
|
138 |
+
if hasattr(value, "__html__"):
|
139 |
+
value = t.cast("HasHTML", value).__html__()
|
140 |
+
|
141 |
+
return escape(str(value))
|
142 |
+
|
143 |
+
|
144 |
+
def do_urlencode(
|
145 |
+
value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]]
|
146 |
+
) -> str:
|
147 |
+
"""Quote data for use in a URL path or query using UTF-8.
|
148 |
+
|
149 |
+
Basic wrapper around :func:`urllib.parse.quote` when given a
|
150 |
+
string, or :func:`urllib.parse.urlencode` for a dict or iterable.
|
151 |
+
|
152 |
+
:param value: Data to quote. A string will be quoted directly. A
|
153 |
+
dict or iterable of ``(key, value)`` pairs will be joined as a
|
154 |
+
query string.
|
155 |
+
|
156 |
+
When given a string, "/" is not quoted. HTTP servers treat "/" and
|
157 |
+
"%2F" equivalently in paths. If you need quoted slashes, use the
|
158 |
+
``|replace("/", "%2F")`` filter.
|
159 |
+
|
160 |
+
.. versionadded:: 2.7
|
161 |
+
"""
|
162 |
+
if isinstance(value, str) or not isinstance(value, abc.Iterable):
|
163 |
+
return url_quote(value)
|
164 |
+
|
165 |
+
if isinstance(value, dict):
|
166 |
+
items: t.Iterable[t.Tuple[str, t.Any]] = value.items()
|
167 |
+
else:
|
168 |
+
items = value # type: ignore
|
169 |
+
|
170 |
+
return "&".join(
|
171 |
+
f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items
|
172 |
+
)
|
173 |
+
|
174 |
+
|
175 |
+
@pass_eval_context
|
176 |
+
def do_replace(
|
177 |
+
eval_ctx: "EvalContext", s: str, old: str, new: str, count: t.Optional[int] = None
|
178 |
+
) -> str:
|
179 |
+
"""Return a copy of the value with all occurrences of a substring
|
180 |
+
replaced with a new one. The first argument is the substring
|
181 |
+
that should be replaced, the second is the replacement string.
|
182 |
+
If the optional third argument ``count`` is given, only the first
|
183 |
+
``count`` occurrences are replaced:
|
184 |
+
|
185 |
+
.. sourcecode:: jinja
|
186 |
+
|
187 |
+
{{ "Hello World"|replace("Hello", "Goodbye") }}
|
188 |
+
-> Goodbye World
|
189 |
+
|
190 |
+
{{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
|
191 |
+
-> d'oh, d'oh, aaargh
|
192 |
+
"""
|
193 |
+
if count is None:
|
194 |
+
count = -1
|
195 |
+
|
196 |
+
if not eval_ctx.autoescape:
|
197 |
+
return str(s).replace(str(old), str(new), count)
|
198 |
+
|
199 |
+
if (
|
200 |
+
hasattr(old, "__html__")
|
201 |
+
or hasattr(new, "__html__")
|
202 |
+
and not hasattr(s, "__html__")
|
203 |
+
):
|
204 |
+
s = escape(s)
|
205 |
+
else:
|
206 |
+
s = soft_str(s)
|
207 |
+
|
208 |
+
return s.replace(soft_str(old), soft_str(new), count)
|
209 |
+
|
210 |
+
|
211 |
+
def do_upper(s: str) -> str:
|
212 |
+
"""Convert a value to uppercase."""
|
213 |
+
return soft_str(s).upper()
|
214 |
+
|
215 |
+
|
216 |
+
def do_lower(s: str) -> str:
|
217 |
+
"""Convert a value to lowercase."""
|
218 |
+
return soft_str(s).lower()
|
219 |
+
|
220 |
+
|
221 |
+
def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K, V]]:
|
222 |
+
"""Return an iterator over the ``(key, value)`` items of a mapping.
|
223 |
+
|
224 |
+
``x|items`` is the same as ``x.items()``, except if ``x`` is
|
225 |
+
undefined an empty iterator is returned.
|
226 |
+
|
227 |
+
This filter is useful if you expect the template to be rendered with
|
228 |
+
an implementation of Jinja in another programming language that does
|
229 |
+
not have a ``.items()`` method on its mapping type.
|
230 |
+
|
231 |
+
.. code-block:: html+jinja
|
232 |
+
|
233 |
+
<dl>
|
234 |
+
{% for key, value in my_dict|items %}
|
235 |
+
<dt>{{ key }}
|
236 |
+
<dd>{{ value }}
|
237 |
+
{% endfor %}
|
238 |
+
</dl>
|
239 |
+
|
240 |
+
.. versionadded:: 3.1
|
241 |
+
"""
|
242 |
+
if isinstance(value, Undefined):
|
243 |
+
return
|
244 |
+
|
245 |
+
if not isinstance(value, abc.Mapping):
|
246 |
+
raise TypeError("Can only get item pairs from a mapping.")
|
247 |
+
|
248 |
+
yield from value.items()
|
249 |
+
|
250 |
+
|
251 |
+
@pass_eval_context
|
252 |
+
def do_xmlattr(
|
253 |
+
eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True
|
254 |
+
) -> str:
|
255 |
+
"""Create an SGML/XML attribute string based on the items in a dict.
|
256 |
+
All values that are neither `none` nor `undefined` are automatically
|
257 |
+
escaped:
|
258 |
+
|
259 |
+
.. sourcecode:: html+jinja
|
260 |
+
|
261 |
+
<ul{{ {'class': 'my_list', 'missing': none,
|
262 |
+
'id': 'list-%d'|format(variable)}|xmlattr }}>
|
263 |
+
...
|
264 |
+
</ul>
|
265 |
+
|
266 |
+
Results in something like this:
|
267 |
+
|
268 |
+
.. sourcecode:: html
|
269 |
+
|
270 |
+
<ul class="my_list" id="list-42">
|
271 |
+
...
|
272 |
+
</ul>
|
273 |
+
|
274 |
+
As you can see it automatically prepends a space in front of the item
|
275 |
+
if the filter returned something unless the second parameter is false.
|
276 |
+
"""
|
277 |
+
rv = " ".join(
|
278 |
+
f'{escape(key)}="{escape(value)}"'
|
279 |
+
for key, value in d.items()
|
280 |
+
if value is not None and not isinstance(value, Undefined)
|
281 |
+
)
|
282 |
+
|
283 |
+
if autospace and rv:
|
284 |
+
rv = " " + rv
|
285 |
+
|
286 |
+
if eval_ctx.autoescape:
|
287 |
+
rv = Markup(rv)
|
288 |
+
|
289 |
+
return rv
|
290 |
+
|
291 |
+
|
292 |
+
def do_capitalize(s: str) -> str:
|
293 |
+
"""Capitalize a value. The first character will be uppercase, all others
|
294 |
+
lowercase.
|
295 |
+
"""
|
296 |
+
return soft_str(s).capitalize()
|
297 |
+
|
298 |
+
|
299 |
+
_word_beginning_split_re = re.compile(r"([-\s({\[<]+)")
|
300 |
+
|
301 |
+
|
302 |
+
def do_title(s: str) -> str:
|
303 |
+
"""Return a titlecased version of the value. I.e. words will start with
|
304 |
+
uppercase letters, all remaining characters are lowercase.
|
305 |
+
"""
|
306 |
+
return "".join(
|
307 |
+
[
|
308 |
+
item[0].upper() + item[1:].lower()
|
309 |
+
for item in _word_beginning_split_re.split(soft_str(s))
|
310 |
+
if item
|
311 |
+
]
|
312 |
+
)
|
313 |
+
|
314 |
+
|
315 |
+
def do_dictsort(
|
316 |
+
value: t.Mapping[K, V],
|
317 |
+
case_sensitive: bool = False,
|
318 |
+
by: 'te.Literal["key", "value"]' = "key",
|
319 |
+
reverse: bool = False,
|
320 |
+
) -> t.List[t.Tuple[K, V]]:
|
321 |
+
"""Sort a dict and yield (key, value) pairs. Python dicts may not
|
322 |
+
be in the order you want to display them in, so sort them first.
|
323 |
+
|
324 |
+
.. sourcecode:: jinja
|
325 |
+
|
326 |
+
{% for key, value in mydict|dictsort %}
|
327 |
+
sort the dict by key, case insensitive
|
328 |
+
|
329 |
+
{% for key, value in mydict|dictsort(reverse=true) %}
|
330 |
+
sort the dict by key, case insensitive, reverse order
|
331 |
+
|
332 |
+
{% for key, value in mydict|dictsort(true) %}
|
333 |
+
sort the dict by key, case sensitive
|
334 |
+
|
335 |
+
{% for key, value in mydict|dictsort(false, 'value') %}
|
336 |
+
sort the dict by value, case insensitive
|
337 |
+
"""
|
338 |
+
if by == "key":
|
339 |
+
pos = 0
|
340 |
+
elif by == "value":
|
341 |
+
pos = 1
|
342 |
+
else:
|
343 |
+
raise FilterArgumentError('You can only sort by either "key" or "value"')
|
344 |
+
|
345 |
+
def sort_func(item: t.Tuple[t.Any, t.Any]) -> t.Any:
|
346 |
+
value = item[pos]
|
347 |
+
|
348 |
+
if not case_sensitive:
|
349 |
+
value = ignore_case(value)
|
350 |
+
|
351 |
+
return value
|
352 |
+
|
353 |
+
return sorted(value.items(), key=sort_func, reverse=reverse)
|
354 |
+
|
355 |
+
|
356 |
+
@pass_environment
|
357 |
+
def do_sort(
|
358 |
+
environment: "Environment",
|
359 |
+
value: "t.Iterable[V]",
|
360 |
+
reverse: bool = False,
|
361 |
+
case_sensitive: bool = False,
|
362 |
+
attribute: t.Optional[t.Union[str, int]] = None,
|
363 |
+
) -> "t.List[V]":
|
364 |
+
"""Sort an iterable using Python's :func:`sorted`.
|
365 |
+
|
366 |
+
.. sourcecode:: jinja
|
367 |
+
|
368 |
+
{% for city in cities|sort %}
|
369 |
+
...
|
370 |
+
{% endfor %}
|
371 |
+
|
372 |
+
:param reverse: Sort descending instead of ascending.
|
373 |
+
:param case_sensitive: When sorting strings, sort upper and lower
|
374 |
+
case separately.
|
375 |
+
:param attribute: When sorting objects or dicts, an attribute or
|
376 |
+
key to sort by. Can use dot notation like ``"address.city"``.
|
377 |
+
Can be a list of attributes like ``"age,name"``.
|
378 |
+
|
379 |
+
The sort is stable, it does not change the relative order of
|
380 |
+
elements that compare equal. This makes it is possible to chain
|
381 |
+
sorts on different attributes and ordering.
|
382 |
+
|
383 |
+
.. sourcecode:: jinja
|
384 |
+
|
385 |
+
{% for user in users|sort(attribute="name")
|
386 |
+
|sort(reverse=true, attribute="age") %}
|
387 |
+
...
|
388 |
+
{% endfor %}
|
389 |
+
|
390 |
+
As a shortcut to chaining when the direction is the same for all
|
391 |
+
attributes, pass a comma separate list of attributes.
|
392 |
+
|
393 |
+
.. sourcecode:: jinja
|
394 |
+
|
395 |
+
{% for user in users|sort(attribute="age,name") %}
|
396 |
+
...
|
397 |
+
{% endfor %}
|
398 |
+
|
399 |
+
.. versionchanged:: 2.11.0
|
400 |
+
The ``attribute`` parameter can be a comma separated list of
|
401 |
+
attributes, e.g. ``"age,name"``.
|
402 |
+
|
403 |
+
.. versionchanged:: 2.6
|
404 |
+
The ``attribute`` parameter was added.
|
405 |
+
"""
|
406 |
+
key_func = make_multi_attrgetter(
|
407 |
+
environment, attribute, postprocess=ignore_case if not case_sensitive else None
|
408 |
+
)
|
409 |
+
return sorted(value, key=key_func, reverse=reverse)
|
410 |
+
|
411 |
+
|
412 |
+
@pass_environment
|
413 |
+
def do_unique(
|
414 |
+
environment: "Environment",
|
415 |
+
value: "t.Iterable[V]",
|
416 |
+
case_sensitive: bool = False,
|
417 |
+
attribute: t.Optional[t.Union[str, int]] = None,
|
418 |
+
) -> "t.Iterator[V]":
|
419 |
+
"""Returns a list of unique items from the given iterable.
|
420 |
+
|
421 |
+
.. sourcecode:: jinja
|
422 |
+
|
423 |
+
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }}
|
424 |
+
-> ['foo', 'bar', 'foobar']
|
425 |
+
|
426 |
+
The unique items are yielded in the same order as their first occurrence in
|
427 |
+
the iterable passed to the filter.
|
428 |
+
|
429 |
+
:param case_sensitive: Treat upper and lower case strings as distinct.
|
430 |
+
:param attribute: Filter objects with unique values for this attribute.
|
431 |
+
"""
|
432 |
+
getter = make_attrgetter(
|
433 |
+
environment, attribute, postprocess=ignore_case if not case_sensitive else None
|
434 |
+
)
|
435 |
+
seen = set()
|
436 |
+
|
437 |
+
for item in value:
|
438 |
+
key = getter(item)
|
439 |
+
|
440 |
+
if key not in seen:
|
441 |
+
seen.add(key)
|
442 |
+
yield item
|
443 |
+
|
444 |
+
|
445 |
+
def _min_or_max(
|
446 |
+
environment: "Environment",
|
447 |
+
value: "t.Iterable[V]",
|
448 |
+
func: "t.Callable[..., V]",
|
449 |
+
case_sensitive: bool,
|
450 |
+
attribute: t.Optional[t.Union[str, int]],
|
451 |
+
) -> "t.Union[V, Undefined]":
|
452 |
+
it = iter(value)
|
453 |
+
|
454 |
+
try:
|
455 |
+
first = next(it)
|
456 |
+
except StopIteration:
|
457 |
+
return environment.undefined("No aggregated item, sequence was empty.")
|
458 |
+
|
459 |
+
key_func = make_attrgetter(
|
460 |
+
environment, attribute, postprocess=ignore_case if not case_sensitive else None
|
461 |
+
)
|
462 |
+
return func(chain([first], it), key=key_func)
|
463 |
+
|
464 |
+
|
465 |
+
@pass_environment
|
466 |
+
def do_min(
|
467 |
+
environment: "Environment",
|
468 |
+
value: "t.Iterable[V]",
|
469 |
+
case_sensitive: bool = False,
|
470 |
+
attribute: t.Optional[t.Union[str, int]] = None,
|
471 |
+
) -> "t.Union[V, Undefined]":
|
472 |
+
"""Return the smallest item from the sequence.
|
473 |
+
|
474 |
+
.. sourcecode:: jinja
|
475 |
+
|
476 |
+
{{ [1, 2, 3]|min }}
|
477 |
+
-> 1
|
478 |
+
|
479 |
+
:param case_sensitive: Treat upper and lower case strings as distinct.
|
480 |
+
:param attribute: Get the object with the min value of this attribute.
|
481 |
+
"""
|
482 |
+
return _min_or_max(environment, value, min, case_sensitive, attribute)
|
483 |
+
|
484 |
+
|
485 |
+
@pass_environment
|
486 |
+
def do_max(
|
487 |
+
environment: "Environment",
|
488 |
+
value: "t.Iterable[V]",
|
489 |
+
case_sensitive: bool = False,
|
490 |
+
attribute: t.Optional[t.Union[str, int]] = None,
|
491 |
+
) -> "t.Union[V, Undefined]":
|
492 |
+
"""Return the largest item from the sequence.
|
493 |
+
|
494 |
+
.. sourcecode:: jinja
|
495 |
+
|
496 |
+
{{ [1, 2, 3]|max }}
|
497 |
+
-> 3
|
498 |
+
|
499 |
+
:param case_sensitive: Treat upper and lower case strings as distinct.
|
500 |
+
:param attribute: Get the object with the max value of this attribute.
|
501 |
+
"""
|
502 |
+
return _min_or_max(environment, value, max, case_sensitive, attribute)
|
503 |
+
|
504 |
+
|
505 |
+
def do_default(
|
506 |
+
value: V,
|
507 |
+
default_value: V = "", # type: ignore
|
508 |
+
boolean: bool = False,
|
509 |
+
) -> V:
|
510 |
+
"""If the value is undefined it will return the passed default value,
|
511 |
+
otherwise the value of the variable:
|
512 |
+
|
513 |
+
.. sourcecode:: jinja
|
514 |
+
|
515 |
+
{{ my_variable|default('my_variable is not defined') }}
|
516 |
+
|
517 |
+
This will output the value of ``my_variable`` if the variable was
|
518 |
+
defined, otherwise ``'my_variable is not defined'``. If you want
|
519 |
+
to use default with variables that evaluate to false you have to
|
520 |
+
set the second parameter to `true`:
|
521 |
+
|
522 |
+
.. sourcecode:: jinja
|
523 |
+
|
524 |
+
{{ ''|default('the string was empty', true) }}
|
525 |
+
|
526 |
+
.. versionchanged:: 2.11
|
527 |
+
It's now possible to configure the :class:`~jinja2.Environment` with
|
528 |
+
:class:`~jinja2.ChainableUndefined` to make the `default` filter work
|
529 |
+
on nested elements and attributes that may contain undefined values
|
530 |
+
in the chain without getting an :exc:`~jinja2.UndefinedError`.
|
531 |
+
"""
|
532 |
+
if isinstance(value, Undefined) or (boolean and not value):
|
533 |
+
return default_value
|
534 |
+
|
535 |
+
return value
|
536 |
+
|
537 |
+
|
538 |
+
@pass_eval_context
|
539 |
+
def sync_do_join(
|
540 |
+
eval_ctx: "EvalContext",
|
541 |
+
value: t.Iterable,
|
542 |
+
d: str = "",
|
543 |
+
attribute: t.Optional[t.Union[str, int]] = None,
|
544 |
+
) -> str:
|
545 |
+
"""Return a string which is the concatenation of the strings in the
|
546 |
+
sequence. The separator between elements is an empty string per
|
547 |
+
default, you can define it with the optional parameter:
|
548 |
+
|
549 |
+
.. sourcecode:: jinja
|
550 |
+
|
551 |
+
{{ [1, 2, 3]|join('|') }}
|
552 |
+
-> 1|2|3
|
553 |
+
|
554 |
+
{{ [1, 2, 3]|join }}
|
555 |
+
-> 123
|
556 |
+
|
557 |
+
It is also possible to join certain attributes of an object:
|
558 |
+
|
559 |
+
.. sourcecode:: jinja
|
560 |
+
|
561 |
+
{{ users|join(', ', attribute='username') }}
|
562 |
+
|
563 |
+
.. versionadded:: 2.6
|
564 |
+
The `attribute` parameter was added.
|
565 |
+
"""
|
566 |
+
if attribute is not None:
|
567 |
+
value = map(make_attrgetter(eval_ctx.environment, attribute), value)
|
568 |
+
|
569 |
+
# no automatic escaping? joining is a lot easier then
|
570 |
+
if not eval_ctx.autoescape:
|
571 |
+
return str(d).join(map(str, value))
|
572 |
+
|
573 |
+
# if the delimiter doesn't have an html representation we check
|
574 |
+
# if any of the items has. If yes we do a coercion to Markup
|
575 |
+
if not hasattr(d, "__html__"):
|
576 |
+
value = list(value)
|
577 |
+
do_escape = False
|
578 |
+
|
579 |
+
for idx, item in enumerate(value):
|
580 |
+
if hasattr(item, "__html__"):
|
581 |
+
do_escape = True
|
582 |
+
else:
|
583 |
+
value[idx] = str(item)
|
584 |
+
|
585 |
+
if do_escape:
|
586 |
+
d = escape(d)
|
587 |
+
else:
|
588 |
+
d = str(d)
|
589 |
+
|
590 |
+
return d.join(value)
|
591 |
+
|
592 |
+
# no html involved, to normal joining
|
593 |
+
return soft_str(d).join(map(soft_str, value))
|
594 |
+
|
595 |
+
|
596 |
+
@async_variant(sync_do_join) # type: ignore
|
597 |
+
async def do_join(
|
598 |
+
eval_ctx: "EvalContext",
|
599 |
+
value: t.Union[t.AsyncIterable, t.Iterable],
|
600 |
+
d: str = "",
|
601 |
+
attribute: t.Optional[t.Union[str, int]] = None,
|
602 |
+
) -> str:
|
603 |
+
return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute)
|
604 |
+
|
605 |
+
|
606 |
+
def do_center(value: str, width: int = 80) -> str:
|
607 |
+
"""Centers the value in a field of a given width."""
|
608 |
+
return soft_str(value).center(width)
|
609 |
+
|
610 |
+
|
611 |
+
@pass_environment
|
612 |
+
def sync_do_first(
|
613 |
+
environment: "Environment", seq: "t.Iterable[V]"
|
614 |
+
) -> "t.Union[V, Undefined]":
|
615 |
+
"""Return the first item of a sequence."""
|
616 |
+
try:
|
617 |
+
return next(iter(seq))
|
618 |
+
except StopIteration:
|
619 |
+
return environment.undefined("No first item, sequence was empty.")
|
620 |
+
|
621 |
+
|
622 |
+
@async_variant(sync_do_first) # type: ignore
|
623 |
+
async def do_first(
|
624 |
+
environment: "Environment", seq: "t.Union[t.AsyncIterable[V], t.Iterable[V]]"
|
625 |
+
) -> "t.Union[V, Undefined]":
|
626 |
+
try:
|
627 |
+
return await auto_aiter(seq).__anext__()
|
628 |
+
except StopAsyncIteration:
|
629 |
+
return environment.undefined("No first item, sequence was empty.")
|
630 |
+
|
631 |
+
|
632 |
+
@pass_environment
|
633 |
+
def do_last(
|
634 |
+
environment: "Environment", seq: "t.Reversible[V]"
|
635 |
+
) -> "t.Union[V, Undefined]":
|
636 |
+
"""Return the last item of a sequence.
|
637 |
+
|
638 |
+
Note: Does not work with generators. You may want to explicitly
|
639 |
+
convert it to a list:
|
640 |
+
|
641 |
+
.. sourcecode:: jinja
|
642 |
+
|
643 |
+
{{ data | selectattr('name', '==', 'Jinja') | list | last }}
|
644 |
+
"""
|
645 |
+
try:
|
646 |
+
return next(iter(reversed(seq)))
|
647 |
+
except StopIteration:
|
648 |
+
return environment.undefined("No last item, sequence was empty.")
|
649 |
+
|
650 |
+
|
651 |
+
# No async do_last, it may not be safe in async mode.
|
652 |
+
|
653 |
+
|
654 |
+
@pass_context
|
655 |
+
def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined]":
|
656 |
+
"""Return a random item from the sequence."""
|
657 |
+
try:
|
658 |
+
return random.choice(seq)
|
659 |
+
except IndexError:
|
660 |
+
return context.environment.undefined("No random item, sequence was empty.")
|
661 |
+
|
662 |
+
|
663 |
+
def do_filesizeformat(value: t.Union[str, float, int], binary: bool = False) -> str:
|
664 |
+
"""Format the value like a 'human-readable' file size (i.e. 13 kB,
|
665 |
+
4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega,
|
666 |
+
Giga, etc.), if the second parameter is set to `True` the binary
|
667 |
+
prefixes are used (Mebi, Gibi).
|
668 |
+
"""
|
669 |
+
bytes = float(value)
|
670 |
+
base = 1024 if binary else 1000
|
671 |
+
prefixes = [
|
672 |
+
("KiB" if binary else "kB"),
|
673 |
+
("MiB" if binary else "MB"),
|
674 |
+
("GiB" if binary else "GB"),
|
675 |
+
("TiB" if binary else "TB"),
|
676 |
+
("PiB" if binary else "PB"),
|
677 |
+
("EiB" if binary else "EB"),
|
678 |
+
("ZiB" if binary else "ZB"),
|
679 |
+
("YiB" if binary else "YB"),
|
680 |
+
]
|
681 |
+
|
682 |
+
if bytes == 1:
|
683 |
+
return "1 Byte"
|
684 |
+
elif bytes < base:
|
685 |
+
return f"{int(bytes)} Bytes"
|
686 |
+
else:
|
687 |
+
for i, prefix in enumerate(prefixes):
|
688 |
+
unit = base ** (i + 2)
|
689 |
+
|
690 |
+
if bytes < unit:
|
691 |
+
return f"{base * bytes / unit:.1f} {prefix}"
|
692 |
+
|
693 |
+
return f"{base * bytes / unit:.1f} {prefix}"
|
694 |
+
|
695 |
+
|
696 |
+
def do_pprint(value: t.Any) -> str:
|
697 |
+
"""Pretty print a variable. Useful for debugging."""
|
698 |
+
return pformat(value)
|
699 |
+
|
700 |
+
|
701 |
+
_uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$")
|
702 |
+
|
703 |
+
|
704 |
+
@pass_eval_context
|
705 |
+
def do_urlize(
|
706 |
+
eval_ctx: "EvalContext",
|
707 |
+
value: str,
|
708 |
+
trim_url_limit: t.Optional[int] = None,
|
709 |
+
nofollow: bool = False,
|
710 |
+
target: t.Optional[str] = None,
|
711 |
+
rel: t.Optional[str] = None,
|
712 |
+
extra_schemes: t.Optional[t.Iterable[str]] = None,
|
713 |
+
) -> str:
|
714 |
+
"""Convert URLs in text into clickable links.
|
715 |
+
|
716 |
+
This may not recognize links in some situations. Usually, a more
|
717 |
+
comprehensive formatter, such as a Markdown library, is a better
|
718 |
+
choice.
|
719 |
+
|
720 |
+
Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
|
721 |
+
addresses. Links with trailing punctuation (periods, commas, closing
|
722 |
+
parentheses) and leading punctuation (opening parentheses) are
|
723 |
+
recognized excluding the punctuation. Email addresses that include
|
724 |
+
header fields are not recognized (for example,
|
725 |
+
``mailto:[email protected][email protected]``).
|
726 |
+
|
727 |
+
:param value: Original text containing URLs to link.
|
728 |
+
:param trim_url_limit: Shorten displayed URL values to this length.
|
729 |
+
:param nofollow: Add the ``rel=nofollow`` attribute to links.
|
730 |
+
:param target: Add the ``target`` attribute to links.
|
731 |
+
:param rel: Add the ``rel`` attribute to links.
|
732 |
+
:param extra_schemes: Recognize URLs that start with these schemes
|
733 |
+
in addition to the default behavior. Defaults to
|
734 |
+
``env.policies["urlize.extra_schemes"]``, which defaults to no
|
735 |
+
extra schemes.
|
736 |
+
|
737 |
+
.. versionchanged:: 3.0
|
738 |
+
The ``extra_schemes`` parameter was added.
|
739 |
+
|
740 |
+
.. versionchanged:: 3.0
|
741 |
+
Generate ``https://`` links for URLs without a scheme.
|
742 |
+
|
743 |
+
.. versionchanged:: 3.0
|
744 |
+
The parsing rules were updated. Recognize email addresses with
|
745 |
+
or without the ``mailto:`` scheme. Validate IP addresses. Ignore
|
746 |
+
parentheses and brackets in more cases.
|
747 |
+
|
748 |
+
.. versionchanged:: 2.8
|
749 |
+
The ``target`` parameter was added.
|
750 |
+
"""
|
751 |
+
policies = eval_ctx.environment.policies
|
752 |
+
rel_parts = set((rel or "").split())
|
753 |
+
|
754 |
+
if nofollow:
|
755 |
+
rel_parts.add("nofollow")
|
756 |
+
|
757 |
+
rel_parts.update((policies["urlize.rel"] or "").split())
|
758 |
+
rel = " ".join(sorted(rel_parts)) or None
|
759 |
+
|
760 |
+
if target is None:
|
761 |
+
target = policies["urlize.target"]
|
762 |
+
|
763 |
+
if extra_schemes is None:
|
764 |
+
extra_schemes = policies["urlize.extra_schemes"] or ()
|
765 |
+
|
766 |
+
for scheme in extra_schemes:
|
767 |
+
if _uri_scheme_re.fullmatch(scheme) is None:
|
768 |
+
raise FilterArgumentError(f"{scheme!r} is not a valid URI scheme prefix.")
|
769 |
+
|
770 |
+
rv = urlize(
|
771 |
+
value,
|
772 |
+
trim_url_limit=trim_url_limit,
|
773 |
+
rel=rel,
|
774 |
+
target=target,
|
775 |
+
extra_schemes=extra_schemes,
|
776 |
+
)
|
777 |
+
|
778 |
+
if eval_ctx.autoescape:
|
779 |
+
rv = Markup(rv)
|
780 |
+
|
781 |
+
return rv
|
782 |
+
|
783 |
+
|
784 |
+
def do_indent(
|
785 |
+
s: str, width: t.Union[int, str] = 4, first: bool = False, blank: bool = False
|
786 |
+
) -> str:
|
787 |
+
"""Return a copy of the string with each line indented by 4 spaces. The
|
788 |
+
first line and blank lines are not indented by default.
|
789 |
+
|
790 |
+
:param width: Number of spaces, or a string, to indent by.
|
791 |
+
:param first: Don't skip indenting the first line.
|
792 |
+
:param blank: Don't skip indenting empty lines.
|
793 |
+
|
794 |
+
.. versionchanged:: 3.0
|
795 |
+
``width`` can be a string.
|
796 |
+
|
797 |
+
.. versionchanged:: 2.10
|
798 |
+
Blank lines are not indented by default.
|
799 |
+
|
800 |
+
Rename the ``indentfirst`` argument to ``first``.
|
801 |
+
"""
|
802 |
+
if isinstance(width, str):
|
803 |
+
indention = width
|
804 |
+
else:
|
805 |
+
indention = " " * width
|
806 |
+
|
807 |
+
newline = "\n"
|
808 |
+
|
809 |
+
if isinstance(s, Markup):
|
810 |
+
indention = Markup(indention)
|
811 |
+
newline = Markup(newline)
|
812 |
+
|
813 |
+
s += newline # this quirk is necessary for splitlines method
|
814 |
+
|
815 |
+
if blank:
|
816 |
+
rv = (newline + indention).join(s.splitlines())
|
817 |
+
else:
|
818 |
+
lines = s.splitlines()
|
819 |
+
rv = lines.pop(0)
|
820 |
+
|
821 |
+
if lines:
|
822 |
+
rv += newline + newline.join(
|
823 |
+
indention + line if line else line for line in lines
|
824 |
+
)
|
825 |
+
|
826 |
+
if first:
|
827 |
+
rv = indention + rv
|
828 |
+
|
829 |
+
return rv
|
830 |
+
|
831 |
+
|
832 |
+
@pass_environment
|
833 |
+
def do_truncate(
|
834 |
+
env: "Environment",
|
835 |
+
s: str,
|
836 |
+
length: int = 255,
|
837 |
+
killwords: bool = False,
|
838 |
+
end: str = "...",
|
839 |
+
leeway: t.Optional[int] = None,
|
840 |
+
) -> str:
|
841 |
+
"""Return a truncated copy of the string. The length is specified
|
842 |
+
with the first parameter which defaults to ``255``. If the second
|
843 |
+
parameter is ``true`` the filter will cut the text at length. Otherwise
|
844 |
+
it will discard the last word. If the text was in fact
|
845 |
+
truncated it will append an ellipsis sign (``"..."``). If you want a
|
846 |
+
different ellipsis sign than ``"..."`` you can specify it using the
|
847 |
+
third parameter. Strings that only exceed the length by the tolerance
|
848 |
+
margin given in the fourth parameter will not be truncated.
|
849 |
+
|
850 |
+
.. sourcecode:: jinja
|
851 |
+
|
852 |
+
{{ "foo bar baz qux"|truncate(9) }}
|
853 |
+
-> "foo..."
|
854 |
+
{{ "foo bar baz qux"|truncate(9, True) }}
|
855 |
+
-> "foo ba..."
|
856 |
+
{{ "foo bar baz qux"|truncate(11) }}
|
857 |
+
-> "foo bar baz qux"
|
858 |
+
{{ "foo bar baz qux"|truncate(11, False, '...', 0) }}
|
859 |
+
-> "foo bar..."
|
860 |
+
|
861 |
+
The default leeway on newer Jinja versions is 5 and was 0 before but
|
862 |
+
can be reconfigured globally.
|
863 |
+
"""
|
864 |
+
if leeway is None:
|
865 |
+
leeway = env.policies["truncate.leeway"]
|
866 |
+
|
867 |
+
assert length >= len(end), f"expected length >= {len(end)}, got {length}"
|
868 |
+
assert leeway >= 0, f"expected leeway >= 0, got {leeway}"
|
869 |
+
|
870 |
+
if len(s) <= length + leeway:
|
871 |
+
return s
|
872 |
+
|
873 |
+
if killwords:
|
874 |
+
return s[: length - len(end)] + end
|
875 |
+
|
876 |
+
result = s[: length - len(end)].rsplit(" ", 1)[0]
|
877 |
+
return result + end
|
878 |
+
|
879 |
+
|
880 |
+
@pass_environment
|
881 |
+
def do_wordwrap(
|
882 |
+
environment: "Environment",
|
883 |
+
s: str,
|
884 |
+
width: int = 79,
|
885 |
+
break_long_words: bool = True,
|
886 |
+
wrapstring: t.Optional[str] = None,
|
887 |
+
break_on_hyphens: bool = True,
|
888 |
+
) -> str:
|
889 |
+
"""Wrap a string to the given width. Existing newlines are treated
|
890 |
+
as paragraphs to be wrapped separately.
|
891 |
+
|
892 |
+
:param s: Original text to wrap.
|
893 |
+
:param width: Maximum length of wrapped lines.
|
894 |
+
:param break_long_words: If a word is longer than ``width``, break
|
895 |
+
it across lines.
|
896 |
+
:param break_on_hyphens: If a word contains hyphens, it may be split
|
897 |
+
across lines.
|
898 |
+
:param wrapstring: String to join each wrapped line. Defaults to
|
899 |
+
:attr:`Environment.newline_sequence`.
|
900 |
+
|
901 |
+
.. versionchanged:: 2.11
|
902 |
+
Existing newlines are treated as paragraphs wrapped separately.
|
903 |
+
|
904 |
+
.. versionchanged:: 2.11
|
905 |
+
Added the ``break_on_hyphens`` parameter.
|
906 |
+
|
907 |
+
.. versionchanged:: 2.7
|
908 |
+
Added the ``wrapstring`` parameter.
|
909 |
+
"""
|
910 |
+
import textwrap
|
911 |
+
|
912 |
+
if wrapstring is None:
|
913 |
+
wrapstring = environment.newline_sequence
|
914 |
+
|
915 |
+
# textwrap.wrap doesn't consider existing newlines when wrapping.
|
916 |
+
# If the string has a newline before width, wrap will still insert
|
917 |
+
# a newline at width, resulting in a short line. Instead, split and
|
918 |
+
# wrap each paragraph individually.
|
919 |
+
return wrapstring.join(
|
920 |
+
[
|
921 |
+
wrapstring.join(
|
922 |
+
textwrap.wrap(
|
923 |
+
line,
|
924 |
+
width=width,
|
925 |
+
expand_tabs=False,
|
926 |
+
replace_whitespace=False,
|
927 |
+
break_long_words=break_long_words,
|
928 |
+
break_on_hyphens=break_on_hyphens,
|
929 |
+
)
|
930 |
+
)
|
931 |
+
for line in s.splitlines()
|
932 |
+
]
|
933 |
+
)
|
934 |
+
|
935 |
+
|
936 |
+
_word_re = re.compile(r"\w+")
|
937 |
+
|
938 |
+
|
939 |
+
def do_wordcount(s: str) -> int:
|
940 |
+
"""Count the words in that string."""
|
941 |
+
return len(_word_re.findall(soft_str(s)))
|
942 |
+
|
943 |
+
|
944 |
+
def do_int(value: t.Any, default: int = 0, base: int = 10) -> int:
|
945 |
+
"""Convert the value into an integer. If the
|
946 |
+
conversion doesn't work it will return ``0``. You can
|
947 |
+
override this default using the first parameter. You
|
948 |
+
can also override the default base (10) in the second
|
949 |
+
parameter, which handles input with prefixes such as
|
950 |
+
0b, 0o and 0x for bases 2, 8 and 16 respectively.
|
951 |
+
The base is ignored for decimal numbers and non-string values.
|
952 |
+
"""
|
953 |
+
try:
|
954 |
+
if isinstance(value, str):
|
955 |
+
return int(value, base)
|
956 |
+
|
957 |
+
return int(value)
|
958 |
+
except (TypeError, ValueError):
|
959 |
+
# this quirk is necessary so that "42.23"|int gives 42.
|
960 |
+
try:
|
961 |
+
return int(float(value))
|
962 |
+
except (TypeError, ValueError):
|
963 |
+
return default
|
964 |
+
|
965 |
+
|
966 |
+
def do_float(value: t.Any, default: float = 0.0) -> float:
|
967 |
+
"""Convert the value into a floating point number. If the
|
968 |
+
conversion doesn't work it will return ``0.0``. You can
|
969 |
+
override this default using the first parameter.
|
970 |
+
"""
|
971 |
+
try:
|
972 |
+
return float(value)
|
973 |
+
except (TypeError, ValueError):
|
974 |
+
return default
|
975 |
+
|
976 |
+
|
977 |
+
def do_format(value: str, *args: t.Any, **kwargs: t.Any) -> str:
|
978 |
+
"""Apply the given values to a `printf-style`_ format string, like
|
979 |
+
``string % values``.
|
980 |
+
|
981 |
+
.. sourcecode:: jinja
|
982 |
+
|
983 |
+
{{ "%s, %s!"|format(greeting, name) }}
|
984 |
+
Hello, World!
|
985 |
+
|
986 |
+
In most cases it should be more convenient and efficient to use the
|
987 |
+
``%`` operator or :meth:`str.format`.
|
988 |
+
|
989 |
+
.. code-block:: text
|
990 |
+
|
991 |
+
{{ "%s, %s!" % (greeting, name) }}
|
992 |
+
{{ "{}, {}!".format(greeting, name) }}
|
993 |
+
|
994 |
+
.. _printf-style: https://docs.python.org/library/stdtypes.html
|
995 |
+
#printf-style-string-formatting
|
996 |
+
"""
|
997 |
+
if args and kwargs:
|
998 |
+
raise FilterArgumentError(
|
999 |
+
"can't handle positional and keyword arguments at the same time"
|
1000 |
+
)
|
1001 |
+
|
1002 |
+
return soft_str(value) % (kwargs or args)
|
1003 |
+
|
1004 |
+
|
1005 |
+
def do_trim(value: str, chars: t.Optional[str] = None) -> str:
|
1006 |
+
"""Strip leading and trailing characters, by default whitespace."""
|
1007 |
+
return soft_str(value).strip(chars)
|
1008 |
+
|
1009 |
+
|
1010 |
+
def do_striptags(value: "t.Union[str, HasHTML]") -> str:
|
1011 |
+
"""Strip SGML/XML tags and replace adjacent whitespace by one space."""
|
1012 |
+
if hasattr(value, "__html__"):
|
1013 |
+
value = t.cast("HasHTML", value).__html__()
|
1014 |
+
|
1015 |
+
return Markup(str(value)).striptags()
|
1016 |
+
|
1017 |
+
|
1018 |
+
def sync_do_slice(
|
1019 |
+
value: "t.Collection[V]", slices: int, fill_with: "t.Optional[V]" = None
|
1020 |
+
) -> "t.Iterator[t.List[V]]":
|
1021 |
+
"""Slice an iterator and return a list of lists containing
|
1022 |
+
those items. Useful if you want to create a div containing
|
1023 |
+
three ul tags that represent columns:
|
1024 |
+
|
1025 |
+
.. sourcecode:: html+jinja
|
1026 |
+
|
1027 |
+
<div class="columnwrapper">
|
1028 |
+
{%- for column in items|slice(3) %}
|
1029 |
+
<ul class="column-{{ loop.index }}">
|
1030 |
+
{%- for item in column %}
|
1031 |
+
<li>{{ item }}</li>
|
1032 |
+
{%- endfor %}
|
1033 |
+
</ul>
|
1034 |
+
{%- endfor %}
|
1035 |
+
</div>
|
1036 |
+
|
1037 |
+
If you pass it a second argument it's used to fill missing
|
1038 |
+
values on the last iteration.
|
1039 |
+
"""
|
1040 |
+
seq = list(value)
|
1041 |
+
length = len(seq)
|
1042 |
+
items_per_slice = length // slices
|
1043 |
+
slices_with_extra = length % slices
|
1044 |
+
offset = 0
|
1045 |
+
|
1046 |
+
for slice_number in range(slices):
|
1047 |
+
start = offset + slice_number * items_per_slice
|
1048 |
+
|
1049 |
+
if slice_number < slices_with_extra:
|
1050 |
+
offset += 1
|
1051 |
+
|
1052 |
+
end = offset + (slice_number + 1) * items_per_slice
|
1053 |
+
tmp = seq[start:end]
|
1054 |
+
|
1055 |
+
if fill_with is not None and slice_number >= slices_with_extra:
|
1056 |
+
tmp.append(fill_with)
|
1057 |
+
|
1058 |
+
yield tmp
|
1059 |
+
|
1060 |
+
|
1061 |
+
@async_variant(sync_do_slice) # type: ignore
|
1062 |
+
async def do_slice(
|
1063 |
+
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
1064 |
+
slices: int,
|
1065 |
+
fill_with: t.Optional[t.Any] = None,
|
1066 |
+
) -> "t.Iterator[t.List[V]]":
|
1067 |
+
return sync_do_slice(await auto_to_list(value), slices, fill_with)
|
1068 |
+
|
1069 |
+
|
1070 |
+
def do_batch(
|
1071 |
+
value: "t.Iterable[V]", linecount: int, fill_with: "t.Optional[V]" = None
|
1072 |
+
) -> "t.Iterator[t.List[V]]":
|
1073 |
+
"""
|
1074 |
+
A filter that batches items. It works pretty much like `slice`
|
1075 |
+
just the other way round. It returns a list of lists with the
|
1076 |
+
given number of items. If you provide a second parameter this
|
1077 |
+
is used to fill up missing items. See this example:
|
1078 |
+
|
1079 |
+
.. sourcecode:: html+jinja
|
1080 |
+
|
1081 |
+
<table>
|
1082 |
+
{%- for row in items|batch(3, ' ') %}
|
1083 |
+
<tr>
|
1084 |
+
{%- for column in row %}
|
1085 |
+
<td>{{ column }}</td>
|
1086 |
+
{%- endfor %}
|
1087 |
+
</tr>
|
1088 |
+
{%- endfor %}
|
1089 |
+
</table>
|
1090 |
+
"""
|
1091 |
+
tmp: "t.List[V]" = []
|
1092 |
+
|
1093 |
+
for item in value:
|
1094 |
+
if len(tmp) == linecount:
|
1095 |
+
yield tmp
|
1096 |
+
tmp = []
|
1097 |
+
|
1098 |
+
tmp.append(item)
|
1099 |
+
|
1100 |
+
if tmp:
|
1101 |
+
if fill_with is not None and len(tmp) < linecount:
|
1102 |
+
tmp += [fill_with] * (linecount - len(tmp))
|
1103 |
+
|
1104 |
+
yield tmp
|
1105 |
+
|
1106 |
+
|
1107 |
+
def do_round(
|
1108 |
+
value: float,
|
1109 |
+
precision: int = 0,
|
1110 |
+
method: 'te.Literal["common", "ceil", "floor"]' = "common",
|
1111 |
+
) -> float:
|
1112 |
+
"""Round the number to a given precision. The first
|
1113 |
+
parameter specifies the precision (default is ``0``), the
|
1114 |
+
second the rounding method:
|
1115 |
+
|
1116 |
+
- ``'common'`` rounds either up or down
|
1117 |
+
- ``'ceil'`` always rounds up
|
1118 |
+
- ``'floor'`` always rounds down
|
1119 |
+
|
1120 |
+
If you don't specify a method ``'common'`` is used.
|
1121 |
+
|
1122 |
+
.. sourcecode:: jinja
|
1123 |
+
|
1124 |
+
{{ 42.55|round }}
|
1125 |
+
-> 43.0
|
1126 |
+
{{ 42.55|round(1, 'floor') }}
|
1127 |
+
-> 42.5
|
1128 |
+
|
1129 |
+
Note that even if rounded to 0 precision, a float is returned. If
|
1130 |
+
you need a real integer, pipe it through `int`:
|
1131 |
+
|
1132 |
+
.. sourcecode:: jinja
|
1133 |
+
|
1134 |
+
{{ 42.55|round|int }}
|
1135 |
+
-> 43
|
1136 |
+
"""
|
1137 |
+
if method not in {"common", "ceil", "floor"}:
|
1138 |
+
raise FilterArgumentError("method must be common, ceil or floor")
|
1139 |
+
|
1140 |
+
if method == "common":
|
1141 |
+
return round(value, precision)
|
1142 |
+
|
1143 |
+
func = getattr(math, method)
|
1144 |
+
return t.cast(float, func(value * (10**precision)) / (10**precision))
|
1145 |
+
|
1146 |
+
|
1147 |
+
class _GroupTuple(t.NamedTuple):
|
1148 |
+
grouper: t.Any
|
1149 |
+
list: t.List
|
1150 |
+
|
1151 |
+
# Use the regular tuple repr to hide this subclass if users print
|
1152 |
+
# out the value during debugging.
|
1153 |
+
def __repr__(self) -> str:
|
1154 |
+
return tuple.__repr__(self)
|
1155 |
+
|
1156 |
+
def __str__(self) -> str:
|
1157 |
+
return tuple.__str__(self)
|
1158 |
+
|
1159 |
+
|
1160 |
+
@pass_environment
|
1161 |
+
def sync_do_groupby(
|
1162 |
+
environment: "Environment",
|
1163 |
+
value: "t.Iterable[V]",
|
1164 |
+
attribute: t.Union[str, int],
|
1165 |
+
default: t.Optional[t.Any] = None,
|
1166 |
+
case_sensitive: bool = False,
|
1167 |
+
) -> "t.List[_GroupTuple]":
|
1168 |
+
"""Group a sequence of objects by an attribute using Python's
|
1169 |
+
:func:`itertools.groupby`. The attribute can use dot notation for
|
1170 |
+
nested access, like ``"address.city"``. Unlike Python's ``groupby``,
|
1171 |
+
the values are sorted first so only one group is returned for each
|
1172 |
+
unique value.
|
1173 |
+
|
1174 |
+
For example, a list of ``User`` objects with a ``city`` attribute
|
1175 |
+
can be rendered in groups. In this example, ``grouper`` refers to
|
1176 |
+
the ``city`` value of the group.
|
1177 |
+
|
1178 |
+
.. sourcecode:: html+jinja
|
1179 |
+
|
1180 |
+
<ul>{% for city, items in users|groupby("city") %}
|
1181 |
+
<li>{{ city }}
|
1182 |
+
<ul>{% for user in items %}
|
1183 |
+
<li>{{ user.name }}
|
1184 |
+
{% endfor %}</ul>
|
1185 |
+
</li>
|
1186 |
+
{% endfor %}</ul>
|
1187 |
+
|
1188 |
+
``groupby`` yields namedtuples of ``(grouper, list)``, which
|
1189 |
+
can be used instead of the tuple unpacking above. ``grouper`` is the
|
1190 |
+
value of the attribute, and ``list`` is the items with that value.
|
1191 |
+
|
1192 |
+
.. sourcecode:: html+jinja
|
1193 |
+
|
1194 |
+
<ul>{% for group in users|groupby("city") %}
|
1195 |
+
<li>{{ group.grouper }}: {{ group.list|join(", ") }}
|
1196 |
+
{% endfor %}</ul>
|
1197 |
+
|
1198 |
+
You can specify a ``default`` value to use if an object in the list
|
1199 |
+
does not have the given attribute.
|
1200 |
+
|
1201 |
+
.. sourcecode:: jinja
|
1202 |
+
|
1203 |
+
<ul>{% for city, items in users|groupby("city", default="NY") %}
|
1204 |
+
<li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li>
|
1205 |
+
{% endfor %}</ul>
|
1206 |
+
|
1207 |
+
Like the :func:`~jinja-filters.sort` filter, sorting and grouping is
|
1208 |
+
case-insensitive by default. The ``key`` for each group will have
|
1209 |
+
the case of the first item in that group of values. For example, if
|
1210 |
+
a list of users has cities ``["CA", "NY", "ca"]``, the "CA" group
|
1211 |
+
will have two values. This can be disabled by passing
|
1212 |
+
``case_sensitive=True``.
|
1213 |
+
|
1214 |
+
.. versionchanged:: 3.1
|
1215 |
+
Added the ``case_sensitive`` parameter. Sorting and grouping is
|
1216 |
+
case-insensitive by default, matching other filters that do
|
1217 |
+
comparisons.
|
1218 |
+
|
1219 |
+
.. versionchanged:: 3.0
|
1220 |
+
Added the ``default`` parameter.
|
1221 |
+
|
1222 |
+
.. versionchanged:: 2.6
|
1223 |
+
The attribute supports dot notation for nested access.
|
1224 |
+
"""
|
1225 |
+
expr = make_attrgetter(
|
1226 |
+
environment,
|
1227 |
+
attribute,
|
1228 |
+
postprocess=ignore_case if not case_sensitive else None,
|
1229 |
+
default=default,
|
1230 |
+
)
|
1231 |
+
out = [
|
1232 |
+
_GroupTuple(key, list(values))
|
1233 |
+
for key, values in groupby(sorted(value, key=expr), expr)
|
1234 |
+
]
|
1235 |
+
|
1236 |
+
if not case_sensitive:
|
1237 |
+
# Return the real key from the first value instead of the lowercase key.
|
1238 |
+
output_expr = make_attrgetter(environment, attribute, default=default)
|
1239 |
+
out = [_GroupTuple(output_expr(values[0]), values) for _, values in out]
|
1240 |
+
|
1241 |
+
return out
|
1242 |
+
|
1243 |
+
|
1244 |
+
@async_variant(sync_do_groupby) # type: ignore
|
1245 |
+
async def do_groupby(
|
1246 |
+
environment: "Environment",
|
1247 |
+
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
1248 |
+
attribute: t.Union[str, int],
|
1249 |
+
default: t.Optional[t.Any] = None,
|
1250 |
+
case_sensitive: bool = False,
|
1251 |
+
) -> "t.List[_GroupTuple]":
|
1252 |
+
expr = make_attrgetter(
|
1253 |
+
environment,
|
1254 |
+
attribute,
|
1255 |
+
postprocess=ignore_case if not case_sensitive else None,
|
1256 |
+
default=default,
|
1257 |
+
)
|
1258 |
+
out = [
|
1259 |
+
_GroupTuple(key, await auto_to_list(values))
|
1260 |
+
for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr)
|
1261 |
+
]
|
1262 |
+
|
1263 |
+
if not case_sensitive:
|
1264 |
+
# Return the real key from the first value instead of the lowercase key.
|
1265 |
+
output_expr = make_attrgetter(environment, attribute, default=default)
|
1266 |
+
out = [_GroupTuple(output_expr(values[0]), values) for _, values in out]
|
1267 |
+
|
1268 |
+
return out
|
1269 |
+
|
1270 |
+
|
1271 |
+
@pass_environment
|
1272 |
+
def sync_do_sum(
|
1273 |
+
environment: "Environment",
|
1274 |
+
iterable: "t.Iterable[V]",
|
1275 |
+
attribute: t.Optional[t.Union[str, int]] = None,
|
1276 |
+
start: V = 0, # type: ignore
|
1277 |
+
) -> V:
|
1278 |
+
"""Returns the sum of a sequence of numbers plus the value of parameter
|
1279 |
+
'start' (which defaults to 0). When the sequence is empty it returns
|
1280 |
+
start.
|
1281 |
+
|
1282 |
+
It is also possible to sum up only certain attributes:
|
1283 |
+
|
1284 |
+
.. sourcecode:: jinja
|
1285 |
+
|
1286 |
+
Total: {{ items|sum(attribute='price') }}
|
1287 |
+
|
1288 |
+
.. versionchanged:: 2.6
|
1289 |
+
The ``attribute`` parameter was added to allow summing up over
|
1290 |
+
attributes. Also the ``start`` parameter was moved on to the right.
|
1291 |
+
"""
|
1292 |
+
if attribute is not None:
|
1293 |
+
iterable = map(make_attrgetter(environment, attribute), iterable)
|
1294 |
+
|
1295 |
+
return sum(iterable, start) # type: ignore[no-any-return, call-overload]
|
1296 |
+
|
1297 |
+
|
1298 |
+
@async_variant(sync_do_sum) # type: ignore
|
1299 |
+
async def do_sum(
|
1300 |
+
environment: "Environment",
|
1301 |
+
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
1302 |
+
attribute: t.Optional[t.Union[str, int]] = None,
|
1303 |
+
start: V = 0, # type: ignore
|
1304 |
+
) -> V:
|
1305 |
+
rv = start
|
1306 |
+
|
1307 |
+
if attribute is not None:
|
1308 |
+
func = make_attrgetter(environment, attribute)
|
1309 |
+
else:
|
1310 |
+
|
1311 |
+
def func(x: V) -> V:
|
1312 |
+
return x
|
1313 |
+
|
1314 |
+
async for item in auto_aiter(iterable):
|
1315 |
+
rv += func(item)
|
1316 |
+
|
1317 |
+
return rv
|
1318 |
+
|
1319 |
+
|
1320 |
+
def sync_do_list(value: "t.Iterable[V]") -> "t.List[V]":
|
1321 |
+
"""Convert the value into a list. If it was a string the returned list
|
1322 |
+
will be a list of characters.
|
1323 |
+
"""
|
1324 |
+
return list(value)
|
1325 |
+
|
1326 |
+
|
1327 |
+
@async_variant(sync_do_list) # type: ignore
|
1328 |
+
async def do_list(value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]") -> "t.List[V]":
|
1329 |
+
return await auto_to_list(value)
|
1330 |
+
|
1331 |
+
|
1332 |
+
def do_mark_safe(value: str) -> Markup:
|
1333 |
+
"""Mark the value as safe which means that in an environment with automatic
|
1334 |
+
escaping enabled this variable will not be escaped.
|
1335 |
+
"""
|
1336 |
+
return Markup(value)
|
1337 |
+
|
1338 |
+
|
1339 |
+
def do_mark_unsafe(value: str) -> str:
|
1340 |
+
"""Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
|
1341 |
+
return str(value)
|
1342 |
+
|
1343 |
+
|
1344 |
+
@typing.overload
|
1345 |
+
def do_reverse(value: str) -> str:
|
1346 |
+
...
|
1347 |
+
|
1348 |
+
|
1349 |
+
@typing.overload
|
1350 |
+
def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]":
|
1351 |
+
...
|
1352 |
+
|
1353 |
+
|
1354 |
+
def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]]:
|
1355 |
+
"""Reverse the object or return an iterator that iterates over it the other
|
1356 |
+
way round.
|
1357 |
+
"""
|
1358 |
+
if isinstance(value, str):
|
1359 |
+
return value[::-1]
|
1360 |
+
|
1361 |
+
try:
|
1362 |
+
return reversed(value) # type: ignore
|
1363 |
+
except TypeError:
|
1364 |
+
try:
|
1365 |
+
rv = list(value)
|
1366 |
+
rv.reverse()
|
1367 |
+
return rv
|
1368 |
+
except TypeError as e:
|
1369 |
+
raise FilterArgumentError("argument must be iterable") from e
|
1370 |
+
|
1371 |
+
|
1372 |
+
@pass_environment
|
1373 |
+
def do_attr(
|
1374 |
+
environment: "Environment", obj: t.Any, name: str
|
1375 |
+
) -> t.Union[Undefined, t.Any]:
|
1376 |
+
"""Get an attribute of an object. ``foo|attr("bar")`` works like
|
1377 |
+
``foo.bar`` just that always an attribute is returned and items are not
|
1378 |
+
looked up.
|
1379 |
+
|
1380 |
+
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
|
1381 |
+
"""
|
1382 |
+
try:
|
1383 |
+
name = str(name)
|
1384 |
+
except UnicodeError:
|
1385 |
+
pass
|
1386 |
+
else:
|
1387 |
+
try:
|
1388 |
+
value = getattr(obj, name)
|
1389 |
+
except AttributeError:
|
1390 |
+
pass
|
1391 |
+
else:
|
1392 |
+
if environment.sandboxed:
|
1393 |
+
environment = t.cast("SandboxedEnvironment", environment)
|
1394 |
+
|
1395 |
+
if not environment.is_safe_attribute(obj, name, value):
|
1396 |
+
return environment.unsafe_undefined(obj, name)
|
1397 |
+
|
1398 |
+
return value
|
1399 |
+
|
1400 |
+
return environment.undefined(obj=obj, name=name)
|
1401 |
+
|
1402 |
+
|
1403 |
+
@typing.overload
|
1404 |
+
def sync_do_map(
|
1405 |
+
context: "Context", value: t.Iterable, name: str, *args: t.Any, **kwargs: t.Any
|
1406 |
+
) -> t.Iterable:
|
1407 |
+
...
|
1408 |
+
|
1409 |
+
|
1410 |
+
@typing.overload
|
1411 |
+
def sync_do_map(
|
1412 |
+
context: "Context",
|
1413 |
+
value: t.Iterable,
|
1414 |
+
*,
|
1415 |
+
attribute: str = ...,
|
1416 |
+
default: t.Optional[t.Any] = None,
|
1417 |
+
) -> t.Iterable:
|
1418 |
+
...
|
1419 |
+
|
1420 |
+
|
1421 |
+
@pass_context
|
1422 |
+
def sync_do_map(
|
1423 |
+
context: "Context", value: t.Iterable, *args: t.Any, **kwargs: t.Any
|
1424 |
+
) -> t.Iterable:
|
1425 |
+
"""Applies a filter on a sequence of objects or looks up an attribute.
|
1426 |
+
This is useful when dealing with lists of objects but you are really
|
1427 |
+
only interested in a certain value of it.
|
1428 |
+
|
1429 |
+
The basic usage is mapping on an attribute. Imagine you have a list
|
1430 |
+
of users but you are only interested in a list of usernames:
|
1431 |
+
|
1432 |
+
.. sourcecode:: jinja
|
1433 |
+
|
1434 |
+
Users on this page: {{ users|map(attribute='username')|join(', ') }}
|
1435 |
+
|
1436 |
+
You can specify a ``default`` value to use if an object in the list
|
1437 |
+
does not have the given attribute.
|
1438 |
+
|
1439 |
+
.. sourcecode:: jinja
|
1440 |
+
|
1441 |
+
{{ users|map(attribute="username", default="Anonymous")|join(", ") }}
|
1442 |
+
|
1443 |
+
Alternatively you can let it invoke a filter by passing the name of the
|
1444 |
+
filter and the arguments afterwards. A good example would be applying a
|
1445 |
+
text conversion filter on a sequence:
|
1446 |
+
|
1447 |
+
.. sourcecode:: jinja
|
1448 |
+
|
1449 |
+
Users on this page: {{ titles|map('lower')|join(', ') }}
|
1450 |
+
|
1451 |
+
Similar to a generator comprehension such as:
|
1452 |
+
|
1453 |
+
.. code-block:: python
|
1454 |
+
|
1455 |
+
(u.username for u in users)
|
1456 |
+
(getattr(u, "username", "Anonymous") for u in users)
|
1457 |
+
(do_lower(x) for x in titles)
|
1458 |
+
|
1459 |
+
.. versionchanged:: 2.11.0
|
1460 |
+
Added the ``default`` parameter.
|
1461 |
+
|
1462 |
+
.. versionadded:: 2.7
|
1463 |
+
"""
|
1464 |
+
if value:
|
1465 |
+
func = prepare_map(context, args, kwargs)
|
1466 |
+
|
1467 |
+
for item in value:
|
1468 |
+
yield func(item)
|
1469 |
+
|
1470 |
+
|
1471 |
+
@typing.overload
|
1472 |
+
def do_map(
|
1473 |
+
context: "Context",
|
1474 |
+
value: t.Union[t.AsyncIterable, t.Iterable],
|
1475 |
+
name: str,
|
1476 |
+
*args: t.Any,
|
1477 |
+
**kwargs: t.Any,
|
1478 |
+
) -> t.Iterable:
|
1479 |
+
...
|
1480 |
+
|
1481 |
+
|
1482 |
+
@typing.overload
|
1483 |
+
def do_map(
|
1484 |
+
context: "Context",
|
1485 |
+
value: t.Union[t.AsyncIterable, t.Iterable],
|
1486 |
+
*,
|
1487 |
+
attribute: str = ...,
|
1488 |
+
default: t.Optional[t.Any] = None,
|
1489 |
+
) -> t.Iterable:
|
1490 |
+
...
|
1491 |
+
|
1492 |
+
|
1493 |
+
@async_variant(sync_do_map) # type: ignore
|
1494 |
+
async def do_map(
|
1495 |
+
context: "Context",
|
1496 |
+
value: t.Union[t.AsyncIterable, t.Iterable],
|
1497 |
+
*args: t.Any,
|
1498 |
+
**kwargs: t.Any,
|
1499 |
+
) -> t.AsyncIterable:
|
1500 |
+
if value:
|
1501 |
+
func = prepare_map(context, args, kwargs)
|
1502 |
+
|
1503 |
+
async for item in auto_aiter(value):
|
1504 |
+
yield await auto_await(func(item))
|
1505 |
+
|
1506 |
+
|
1507 |
+
@pass_context
|
1508 |
+
def sync_do_select(
|
1509 |
+
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any
|
1510 |
+
) -> "t.Iterator[V]":
|
1511 |
+
"""Filters a sequence of objects by applying a test to each object,
|
1512 |
+
and only selecting the objects with the test succeeding.
|
1513 |
+
|
1514 |
+
If no test is specified, each object will be evaluated as a boolean.
|
1515 |
+
|
1516 |
+
Example usage:
|
1517 |
+
|
1518 |
+
.. sourcecode:: jinja
|
1519 |
+
|
1520 |
+
{{ numbers|select("odd") }}
|
1521 |
+
{{ numbers|select("odd") }}
|
1522 |
+
{{ numbers|select("divisibleby", 3) }}
|
1523 |
+
{{ numbers|select("lessthan", 42) }}
|
1524 |
+
{{ strings|select("equalto", "mystring") }}
|
1525 |
+
|
1526 |
+
Similar to a generator comprehension such as:
|
1527 |
+
|
1528 |
+
.. code-block:: python
|
1529 |
+
|
1530 |
+
(n for n in numbers if test_odd(n))
|
1531 |
+
(n for n in numbers if test_divisibleby(n, 3))
|
1532 |
+
|
1533 |
+
.. versionadded:: 2.7
|
1534 |
+
"""
|
1535 |
+
return select_or_reject(context, value, args, kwargs, lambda x: x, False)
|
1536 |
+
|
1537 |
+
|
1538 |
+
@async_variant(sync_do_select) # type: ignore
|
1539 |
+
async def do_select(
|
1540 |
+
context: "Context",
|
1541 |
+
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
1542 |
+
*args: t.Any,
|
1543 |
+
**kwargs: t.Any,
|
1544 |
+
) -> "t.AsyncIterator[V]":
|
1545 |
+
return async_select_or_reject(context, value, args, kwargs, lambda x: x, False)
|
1546 |
+
|
1547 |
+
|
1548 |
+
@pass_context
|
1549 |
+
def sync_do_reject(
|
1550 |
+
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any
|
1551 |
+
) -> "t.Iterator[V]":
|
1552 |
+
"""Filters a sequence of objects by applying a test to each object,
|
1553 |
+
and rejecting the objects with the test succeeding.
|
1554 |
+
|
1555 |
+
If no test is specified, each object will be evaluated as a boolean.
|
1556 |
+
|
1557 |
+
Example usage:
|
1558 |
+
|
1559 |
+
.. sourcecode:: jinja
|
1560 |
+
|
1561 |
+
{{ numbers|reject("odd") }}
|
1562 |
+
|
1563 |
+
Similar to a generator comprehension such as:
|
1564 |
+
|
1565 |
+
.. code-block:: python
|
1566 |
+
|
1567 |
+
(n for n in numbers if not test_odd(n))
|
1568 |
+
|
1569 |
+
.. versionadded:: 2.7
|
1570 |
+
"""
|
1571 |
+
return select_or_reject(context, value, args, kwargs, lambda x: not x, False)
|
1572 |
+
|
1573 |
+
|
1574 |
+
@async_variant(sync_do_reject) # type: ignore
|
1575 |
+
async def do_reject(
|
1576 |
+
context: "Context",
|
1577 |
+
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
1578 |
+
*args: t.Any,
|
1579 |
+
**kwargs: t.Any,
|
1580 |
+
) -> "t.AsyncIterator[V]":
|
1581 |
+
return async_select_or_reject(context, value, args, kwargs, lambda x: not x, False)
|
1582 |
+
|
1583 |
+
|
1584 |
+
@pass_context
|
1585 |
+
def sync_do_selectattr(
|
1586 |
+
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any
|
1587 |
+
) -> "t.Iterator[V]":
|
1588 |
+
"""Filters a sequence of objects by applying a test to the specified
|
1589 |
+
attribute of each object, and only selecting the objects with the
|
1590 |
+
test succeeding.
|
1591 |
+
|
1592 |
+
If no test is specified, the attribute's value will be evaluated as
|
1593 |
+
a boolean.
|
1594 |
+
|
1595 |
+
Example usage:
|
1596 |
+
|
1597 |
+
.. sourcecode:: jinja
|
1598 |
+
|
1599 |
+
{{ users|selectattr("is_active") }}
|
1600 |
+
{{ users|selectattr("email", "none") }}
|
1601 |
+
|
1602 |
+
Similar to a generator comprehension such as:
|
1603 |
+
|
1604 |
+
.. code-block:: python
|
1605 |
+
|
1606 |
+
(u for user in users if user.is_active)
|
1607 |
+
(u for user in users if test_none(user.email))
|
1608 |
+
|
1609 |
+
.. versionadded:: 2.7
|
1610 |
+
"""
|
1611 |
+
return select_or_reject(context, value, args, kwargs, lambda x: x, True)
|
1612 |
+
|
1613 |
+
|
1614 |
+
@async_variant(sync_do_selectattr) # type: ignore
|
1615 |
+
async def do_selectattr(
|
1616 |
+
context: "Context",
|
1617 |
+
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
1618 |
+
*args: t.Any,
|
1619 |
+
**kwargs: t.Any,
|
1620 |
+
) -> "t.AsyncIterator[V]":
|
1621 |
+
return async_select_or_reject(context, value, args, kwargs, lambda x: x, True)
|
1622 |
+
|
1623 |
+
|
1624 |
+
@pass_context
|
1625 |
+
def sync_do_rejectattr(
|
1626 |
+
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any
|
1627 |
+
) -> "t.Iterator[V]":
|
1628 |
+
"""Filters a sequence of objects by applying a test to the specified
|
1629 |
+
attribute of each object, and rejecting the objects with the test
|
1630 |
+
succeeding.
|
1631 |
+
|
1632 |
+
If no test is specified, the attribute's value will be evaluated as
|
1633 |
+
a boolean.
|
1634 |
+
|
1635 |
+
.. sourcecode:: jinja
|
1636 |
+
|
1637 |
+
{{ users|rejectattr("is_active") }}
|
1638 |
+
{{ users|rejectattr("email", "none") }}
|
1639 |
+
|
1640 |
+
Similar to a generator comprehension such as:
|
1641 |
+
|
1642 |
+
.. code-block:: python
|
1643 |
+
|
1644 |
+
(u for user in users if not user.is_active)
|
1645 |
+
(u for user in users if not test_none(user.email))
|
1646 |
+
|
1647 |
+
.. versionadded:: 2.7
|
1648 |
+
"""
|
1649 |
+
return select_or_reject(context, value, args, kwargs, lambda x: not x, True)
|
1650 |
+
|
1651 |
+
|
1652 |
+
@async_variant(sync_do_rejectattr) # type: ignore
|
1653 |
+
async def do_rejectattr(
|
1654 |
+
context: "Context",
|
1655 |
+
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
1656 |
+
*args: t.Any,
|
1657 |
+
**kwargs: t.Any,
|
1658 |
+
) -> "t.AsyncIterator[V]":
|
1659 |
+
return async_select_or_reject(context, value, args, kwargs, lambda x: not x, True)
|
1660 |
+
|
1661 |
+
|
1662 |
+
@pass_eval_context
|
1663 |
+
def do_tojson(
|
1664 |
+
eval_ctx: "EvalContext", value: t.Any, indent: t.Optional[int] = None
|
1665 |
+
) -> Markup:
|
1666 |
+
"""Serialize an object to a string of JSON, and mark it safe to
|
1667 |
+
render in HTML. This filter is only for use in HTML documents.
|
1668 |
+
|
1669 |
+
The returned string is safe to render in HTML documents and
|
1670 |
+
``<script>`` tags. The exception is in HTML attributes that are
|
1671 |
+
double quoted; either use single quotes or the ``|forceescape``
|
1672 |
+
filter.
|
1673 |
+
|
1674 |
+
:param value: The object to serialize to JSON.
|
1675 |
+
:param indent: The ``indent`` parameter passed to ``dumps``, for
|
1676 |
+
pretty-printing the value.
|
1677 |
+
|
1678 |
+
.. versionadded:: 2.9
|
1679 |
+
"""
|
1680 |
+
policies = eval_ctx.environment.policies
|
1681 |
+
dumps = policies["json.dumps_function"]
|
1682 |
+
kwargs = policies["json.dumps_kwargs"]
|
1683 |
+
|
1684 |
+
if indent is not None:
|
1685 |
+
kwargs = kwargs.copy()
|
1686 |
+
kwargs["indent"] = indent
|
1687 |
+
|
1688 |
+
return htmlsafe_json_dumps(value, dumps=dumps, **kwargs)
|
1689 |
+
|
1690 |
+
|
1691 |
+
def prepare_map(
|
1692 |
+
context: "Context", args: t.Tuple, kwargs: t.Dict[str, t.Any]
|
1693 |
+
) -> t.Callable[[t.Any], t.Any]:
|
1694 |
+
if not args and "attribute" in kwargs:
|
1695 |
+
attribute = kwargs.pop("attribute")
|
1696 |
+
default = kwargs.pop("default", None)
|
1697 |
+
|
1698 |
+
if kwargs:
|
1699 |
+
raise FilterArgumentError(
|
1700 |
+
f"Unexpected keyword argument {next(iter(kwargs))!r}"
|
1701 |
+
)
|
1702 |
+
|
1703 |
+
func = make_attrgetter(context.environment, attribute, default=default)
|
1704 |
+
else:
|
1705 |
+
try:
|
1706 |
+
name = args[0]
|
1707 |
+
args = args[1:]
|
1708 |
+
except LookupError:
|
1709 |
+
raise FilterArgumentError("map requires a filter argument") from None
|
1710 |
+
|
1711 |
+
def func(item: t.Any) -> t.Any:
|
1712 |
+
return context.environment.call_filter(
|
1713 |
+
name, item, args, kwargs, context=context
|
1714 |
+
)
|
1715 |
+
|
1716 |
+
return func
|
1717 |
+
|
1718 |
+
|
1719 |
+
def prepare_select_or_reject(
|
1720 |
+
context: "Context",
|
1721 |
+
args: t.Tuple,
|
1722 |
+
kwargs: t.Dict[str, t.Any],
|
1723 |
+
modfunc: t.Callable[[t.Any], t.Any],
|
1724 |
+
lookup_attr: bool,
|
1725 |
+
) -> t.Callable[[t.Any], t.Any]:
|
1726 |
+
if lookup_attr:
|
1727 |
+
try:
|
1728 |
+
attr = args[0]
|
1729 |
+
except LookupError:
|
1730 |
+
raise FilterArgumentError("Missing parameter for attribute name") from None
|
1731 |
+
|
1732 |
+
transfunc = make_attrgetter(context.environment, attr)
|
1733 |
+
off = 1
|
1734 |
+
else:
|
1735 |
+
off = 0
|
1736 |
+
|
1737 |
+
def transfunc(x: V) -> V:
|
1738 |
+
return x
|
1739 |
+
|
1740 |
+
try:
|
1741 |
+
name = args[off]
|
1742 |
+
args = args[1 + off :]
|
1743 |
+
|
1744 |
+
def func(item: t.Any) -> t.Any:
|
1745 |
+
return context.environment.call_test(name, item, args, kwargs)
|
1746 |
+
|
1747 |
+
except LookupError:
|
1748 |
+
func = bool # type: ignore
|
1749 |
+
|
1750 |
+
return lambda item: modfunc(func(transfunc(item)))
|
1751 |
+
|
1752 |
+
|
1753 |
+
def select_or_reject(
|
1754 |
+
context: "Context",
|
1755 |
+
value: "t.Iterable[V]",
|
1756 |
+
args: t.Tuple,
|
1757 |
+
kwargs: t.Dict[str, t.Any],
|
1758 |
+
modfunc: t.Callable[[t.Any], t.Any],
|
1759 |
+
lookup_attr: bool,
|
1760 |
+
) -> "t.Iterator[V]":
|
1761 |
+
if value:
|
1762 |
+
func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr)
|
1763 |
+
|
1764 |
+
for item in value:
|
1765 |
+
if func(item):
|
1766 |
+
yield item
|
1767 |
+
|
1768 |
+
|
1769 |
+
async def async_select_or_reject(
|
1770 |
+
context: "Context",
|
1771 |
+
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
1772 |
+
args: t.Tuple,
|
1773 |
+
kwargs: t.Dict[str, t.Any],
|
1774 |
+
modfunc: t.Callable[[t.Any], t.Any],
|
1775 |
+
lookup_attr: bool,
|
1776 |
+
) -> "t.AsyncIterator[V]":
|
1777 |
+
if value:
|
1778 |
+
func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr)
|
1779 |
+
|
1780 |
+
async for item in auto_aiter(value):
|
1781 |
+
if func(item):
|
1782 |
+
yield item
|
1783 |
+
|
1784 |
+
|
1785 |
+
FILTERS = {
|
1786 |
+
"abs": abs,
|
1787 |
+
"attr": do_attr,
|
1788 |
+
"batch": do_batch,
|
1789 |
+
"capitalize": do_capitalize,
|
1790 |
+
"center": do_center,
|
1791 |
+
"count": len,
|
1792 |
+
"d": do_default,
|
1793 |
+
"default": do_default,
|
1794 |
+
"dictsort": do_dictsort,
|
1795 |
+
"e": escape,
|
1796 |
+
"escape": escape,
|
1797 |
+
"filesizeformat": do_filesizeformat,
|
1798 |
+
"first": do_first,
|
1799 |
+
"float": do_float,
|
1800 |
+
"forceescape": do_forceescape,
|
1801 |
+
"format": do_format,
|
1802 |
+
"groupby": do_groupby,
|
1803 |
+
"indent": do_indent,
|
1804 |
+
"int": do_int,
|
1805 |
+
"join": do_join,
|
1806 |
+
"last": do_last,
|
1807 |
+
"length": len,
|
1808 |
+
"list": do_list,
|
1809 |
+
"lower": do_lower,
|
1810 |
+
"items": do_items,
|
1811 |
+
"map": do_map,
|
1812 |
+
"min": do_min,
|
1813 |
+
"max": do_max,
|
1814 |
+
"pprint": do_pprint,
|
1815 |
+
"random": do_random,
|
1816 |
+
"reject": do_reject,
|
1817 |
+
"rejectattr": do_rejectattr,
|
1818 |
+
"replace": do_replace,
|
1819 |
+
"reverse": do_reverse,
|
1820 |
+
"round": do_round,
|
1821 |
+
"safe": do_mark_safe,
|
1822 |
+
"select": do_select,
|
1823 |
+
"selectattr": do_selectattr,
|
1824 |
+
"slice": do_slice,
|
1825 |
+
"sort": do_sort,
|
1826 |
+
"string": soft_str,
|
1827 |
+
"striptags": do_striptags,
|
1828 |
+
"sum": do_sum,
|
1829 |
+
"title": do_title,
|
1830 |
+
"trim": do_trim,
|
1831 |
+
"truncate": do_truncate,
|
1832 |
+
"unique": do_unique,
|
1833 |
+
"upper": do_upper,
|
1834 |
+
"urlencode": do_urlencode,
|
1835 |
+
"urlize": do_urlize,
|
1836 |
+
"wordcount": do_wordcount,
|
1837 |
+
"wordwrap": do_wordwrap,
|
1838 |
+
"xmlattr": do_xmlattr,
|
1839 |
+
"tojson": do_tojson,
|
1840 |
+
}
|
lib/python3.11/site-packages/jinja2/idtracking.py
ADDED
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import typing as t
|
2 |
+
|
3 |
+
from . import nodes
|
4 |
+
from .visitor import NodeVisitor
|
5 |
+
|
6 |
+
VAR_LOAD_PARAMETER = "param"
|
7 |
+
VAR_LOAD_RESOLVE = "resolve"
|
8 |
+
VAR_LOAD_ALIAS = "alias"
|
9 |
+
VAR_LOAD_UNDEFINED = "undefined"
|
10 |
+
|
11 |
+
|
12 |
+
def find_symbols(
|
13 |
+
nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None
|
14 |
+
) -> "Symbols":
|
15 |
+
sym = Symbols(parent=parent_symbols)
|
16 |
+
visitor = FrameSymbolVisitor(sym)
|
17 |
+
for node in nodes:
|
18 |
+
visitor.visit(node)
|
19 |
+
return sym
|
20 |
+
|
21 |
+
|
22 |
+
def symbols_for_node(
|
23 |
+
node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None
|
24 |
+
) -> "Symbols":
|
25 |
+
sym = Symbols(parent=parent_symbols)
|
26 |
+
sym.analyze_node(node)
|
27 |
+
return sym
|
28 |
+
|
29 |
+
|
30 |
+
class Symbols:
|
31 |
+
def __init__(
|
32 |
+
self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
|
33 |
+
) -> None:
|
34 |
+
if level is None:
|
35 |
+
if parent is None:
|
36 |
+
level = 0
|
37 |
+
else:
|
38 |
+
level = parent.level + 1
|
39 |
+
|
40 |
+
self.level: int = level
|
41 |
+
self.parent = parent
|
42 |
+
self.refs: t.Dict[str, str] = {}
|
43 |
+
self.loads: t.Dict[str, t.Any] = {}
|
44 |
+
self.stores: t.Set[str] = set()
|
45 |
+
|
46 |
+
def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None:
|
47 |
+
visitor = RootVisitor(self)
|
48 |
+
visitor.visit(node, **kwargs)
|
49 |
+
|
50 |
+
def _define_ref(
|
51 |
+
self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None
|
52 |
+
) -> str:
|
53 |
+
ident = f"l_{self.level}_{name}"
|
54 |
+
self.refs[name] = ident
|
55 |
+
if load is not None:
|
56 |
+
self.loads[ident] = load
|
57 |
+
return ident
|
58 |
+
|
59 |
+
def find_load(self, target: str) -> t.Optional[t.Any]:
|
60 |
+
if target in self.loads:
|
61 |
+
return self.loads[target]
|
62 |
+
|
63 |
+
if self.parent is not None:
|
64 |
+
return self.parent.find_load(target)
|
65 |
+
|
66 |
+
return None
|
67 |
+
|
68 |
+
def find_ref(self, name: str) -> t.Optional[str]:
|
69 |
+
if name in self.refs:
|
70 |
+
return self.refs[name]
|
71 |
+
|
72 |
+
if self.parent is not None:
|
73 |
+
return self.parent.find_ref(name)
|
74 |
+
|
75 |
+
return None
|
76 |
+
|
77 |
+
def ref(self, name: str) -> str:
|
78 |
+
rv = self.find_ref(name)
|
79 |
+
if rv is None:
|
80 |
+
raise AssertionError(
|
81 |
+
"Tried to resolve a name to a reference that was"
|
82 |
+
f" unknown to the frame ({name!r})"
|
83 |
+
)
|
84 |
+
return rv
|
85 |
+
|
86 |
+
def copy(self) -> "Symbols":
|
87 |
+
rv = object.__new__(self.__class__)
|
88 |
+
rv.__dict__.update(self.__dict__)
|
89 |
+
rv.refs = self.refs.copy()
|
90 |
+
rv.loads = self.loads.copy()
|
91 |
+
rv.stores = self.stores.copy()
|
92 |
+
return rv
|
93 |
+
|
94 |
+
def store(self, name: str) -> None:
|
95 |
+
self.stores.add(name)
|
96 |
+
|
97 |
+
# If we have not see the name referenced yet, we need to figure
|
98 |
+
# out what to set it to.
|
99 |
+
if name not in self.refs:
|
100 |
+
# If there is a parent scope we check if the name has a
|
101 |
+
# reference there. If it does it means we might have to alias
|
102 |
+
# to a variable there.
|
103 |
+
if self.parent is not None:
|
104 |
+
outer_ref = self.parent.find_ref(name)
|
105 |
+
if outer_ref is not None:
|
106 |
+
self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
|
107 |
+
return
|
108 |
+
|
109 |
+
# Otherwise we can just set it to undefined.
|
110 |
+
self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
|
111 |
+
|
112 |
+
def declare_parameter(self, name: str) -> str:
|
113 |
+
self.stores.add(name)
|
114 |
+
return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
|
115 |
+
|
116 |
+
def load(self, name: str) -> None:
|
117 |
+
if self.find_ref(name) is None:
|
118 |
+
self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
|
119 |
+
|
120 |
+
def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
|
121 |
+
stores: t.Dict[str, int] = {}
|
122 |
+
for branch in branch_symbols:
|
123 |
+
for target in branch.stores:
|
124 |
+
if target in self.stores:
|
125 |
+
continue
|
126 |
+
stores[target] = stores.get(target, 0) + 1
|
127 |
+
|
128 |
+
for sym in branch_symbols:
|
129 |
+
self.refs.update(sym.refs)
|
130 |
+
self.loads.update(sym.loads)
|
131 |
+
self.stores.update(sym.stores)
|
132 |
+
|
133 |
+
for name, branch_count in stores.items():
|
134 |
+
if branch_count == len(branch_symbols):
|
135 |
+
continue
|
136 |
+
|
137 |
+
target = self.find_ref(name) # type: ignore
|
138 |
+
assert target is not None, "should not happen"
|
139 |
+
|
140 |
+
if self.parent is not None:
|
141 |
+
outer_target = self.parent.find_ref(name)
|
142 |
+
if outer_target is not None:
|
143 |
+
self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
|
144 |
+
continue
|
145 |
+
self.loads[target] = (VAR_LOAD_RESOLVE, name)
|
146 |
+
|
147 |
+
def dump_stores(self) -> t.Dict[str, str]:
|
148 |
+
rv: t.Dict[str, str] = {}
|
149 |
+
node: t.Optional["Symbols"] = self
|
150 |
+
|
151 |
+
while node is not None:
|
152 |
+
for name in sorted(node.stores):
|
153 |
+
if name not in rv:
|
154 |
+
rv[name] = self.find_ref(name) # type: ignore
|
155 |
+
|
156 |
+
node = node.parent
|
157 |
+
|
158 |
+
return rv
|
159 |
+
|
160 |
+
def dump_param_targets(self) -> t.Set[str]:
|
161 |
+
rv = set()
|
162 |
+
node: t.Optional["Symbols"] = self
|
163 |
+
|
164 |
+
while node is not None:
|
165 |
+
for target, (instr, _) in self.loads.items():
|
166 |
+
if instr == VAR_LOAD_PARAMETER:
|
167 |
+
rv.add(target)
|
168 |
+
|
169 |
+
node = node.parent
|
170 |
+
|
171 |
+
return rv
|
172 |
+
|
173 |
+
|
174 |
+
class RootVisitor(NodeVisitor):
|
175 |
+
def __init__(self, symbols: "Symbols") -> None:
|
176 |
+
self.sym_visitor = FrameSymbolVisitor(symbols)
|
177 |
+
|
178 |
+
def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
|
179 |
+
for child in node.iter_child_nodes():
|
180 |
+
self.sym_visitor.visit(child)
|
181 |
+
|
182 |
+
visit_Template = _simple_visit
|
183 |
+
visit_Block = _simple_visit
|
184 |
+
visit_Macro = _simple_visit
|
185 |
+
visit_FilterBlock = _simple_visit
|
186 |
+
visit_Scope = _simple_visit
|
187 |
+
visit_If = _simple_visit
|
188 |
+
visit_ScopedEvalContextModifier = _simple_visit
|
189 |
+
|
190 |
+
def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
|
191 |
+
for child in node.body:
|
192 |
+
self.sym_visitor.visit(child)
|
193 |
+
|
194 |
+
def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
|
195 |
+
for child in node.iter_child_nodes(exclude=("call",)):
|
196 |
+
self.sym_visitor.visit(child)
|
197 |
+
|
198 |
+
def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
|
199 |
+
for child in node.body:
|
200 |
+
self.sym_visitor.visit(child)
|
201 |
+
|
202 |
+
def visit_For(
|
203 |
+
self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any
|
204 |
+
) -> None:
|
205 |
+
if for_branch == "body":
|
206 |
+
self.sym_visitor.visit(node.target, store_as_param=True)
|
207 |
+
branch = node.body
|
208 |
+
elif for_branch == "else":
|
209 |
+
branch = node.else_
|
210 |
+
elif for_branch == "test":
|
211 |
+
self.sym_visitor.visit(node.target, store_as_param=True)
|
212 |
+
if node.test is not None:
|
213 |
+
self.sym_visitor.visit(node.test)
|
214 |
+
return
|
215 |
+
else:
|
216 |
+
raise RuntimeError("Unknown for branch")
|
217 |
+
|
218 |
+
if branch:
|
219 |
+
for item in branch:
|
220 |
+
self.sym_visitor.visit(item)
|
221 |
+
|
222 |
+
def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
|
223 |
+
for target in node.targets:
|
224 |
+
self.sym_visitor.visit(target)
|
225 |
+
for child in node.body:
|
226 |
+
self.sym_visitor.visit(child)
|
227 |
+
|
228 |
+
def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None:
|
229 |
+
raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}")
|
230 |
+
|
231 |
+
|
232 |
+
class FrameSymbolVisitor(NodeVisitor):
|
233 |
+
"""A visitor for `Frame.inspect`."""
|
234 |
+
|
235 |
+
def __init__(self, symbols: "Symbols") -> None:
|
236 |
+
self.symbols = symbols
|
237 |
+
|
238 |
+
def visit_Name(
|
239 |
+
self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any
|
240 |
+
) -> None:
|
241 |
+
"""All assignments to names go through this function."""
|
242 |
+
if store_as_param or node.ctx == "param":
|
243 |
+
self.symbols.declare_parameter(node.name)
|
244 |
+
elif node.ctx == "store":
|
245 |
+
self.symbols.store(node.name)
|
246 |
+
elif node.ctx == "load":
|
247 |
+
self.symbols.load(node.name)
|
248 |
+
|
249 |
+
def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None:
|
250 |
+
self.symbols.load(node.name)
|
251 |
+
|
252 |
+
def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None:
|
253 |
+
self.visit(node.test, **kwargs)
|
254 |
+
original_symbols = self.symbols
|
255 |
+
|
256 |
+
def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols":
|
257 |
+
self.symbols = rv = original_symbols.copy()
|
258 |
+
|
259 |
+
for subnode in nodes:
|
260 |
+
self.visit(subnode, **kwargs)
|
261 |
+
|
262 |
+
self.symbols = original_symbols
|
263 |
+
return rv
|
264 |
+
|
265 |
+
body_symbols = inner_visit(node.body)
|
266 |
+
elif_symbols = inner_visit(node.elif_)
|
267 |
+
else_symbols = inner_visit(node.else_ or ())
|
268 |
+
self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
|
269 |
+
|
270 |
+
def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None:
|
271 |
+
self.symbols.store(node.name)
|
272 |
+
|
273 |
+
def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None:
|
274 |
+
self.generic_visit(node, **kwargs)
|
275 |
+
self.symbols.store(node.target)
|
276 |
+
|
277 |
+
def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None:
|
278 |
+
self.generic_visit(node, **kwargs)
|
279 |
+
|
280 |
+
for name in node.names:
|
281 |
+
if isinstance(name, tuple):
|
282 |
+
self.symbols.store(name[1])
|
283 |
+
else:
|
284 |
+
self.symbols.store(name)
|
285 |
+
|
286 |
+
def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None:
|
287 |
+
"""Visit assignments in the correct order."""
|
288 |
+
self.visit(node.node, **kwargs)
|
289 |
+
self.visit(node.target, **kwargs)
|
290 |
+
|
291 |
+
def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None:
|
292 |
+
"""Visiting stops at for blocks. However the block sequence
|
293 |
+
is visited as part of the outer scope.
|
294 |
+
"""
|
295 |
+
self.visit(node.iter, **kwargs)
|
296 |
+
|
297 |
+
def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
|
298 |
+
self.visit(node.call, **kwargs)
|
299 |
+
|
300 |
+
def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None:
|
301 |
+
self.visit(node.filter, **kwargs)
|
302 |
+
|
303 |
+
def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
|
304 |
+
for target in node.values:
|
305 |
+
self.visit(target)
|
306 |
+
|
307 |
+
def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
|
308 |
+
"""Stop visiting at block assigns."""
|
309 |
+
self.visit(node.target, **kwargs)
|
310 |
+
|
311 |
+
def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None:
|
312 |
+
"""Stop visiting at scopes."""
|
313 |
+
|
314 |
+
def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None:
|
315 |
+
"""Stop visiting at blocks."""
|
316 |
+
|
317 |
+
def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
|
318 |
+
"""Do not visit into overlay scopes."""
|
lib/python3.11/site-packages/jinja2/lexer.py
ADDED
@@ -0,0 +1,866 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Implements a Jinja / Python combination lexer. The ``Lexer`` class
|
2 |
+
is used to do some preprocessing. It filters out invalid operators like
|
3 |
+
the bitshift operators we don't allow in templates. It separates
|
4 |
+
template code and python code in expressions.
|
5 |
+
"""
|
6 |
+
import re
|
7 |
+
import typing as t
|
8 |
+
from ast import literal_eval
|
9 |
+
from collections import deque
|
10 |
+
from sys import intern
|
11 |
+
|
12 |
+
from ._identifier import pattern as name_re
|
13 |
+
from .exceptions import TemplateSyntaxError
|
14 |
+
from .utils import LRUCache
|
15 |
+
|
16 |
+
if t.TYPE_CHECKING:
|
17 |
+
import typing_extensions as te
|
18 |
+
from .environment import Environment
|
19 |
+
|
20 |
+
# cache for the lexers. Exists in order to be able to have multiple
|
21 |
+
# environments with the same lexer
|
22 |
+
_lexer_cache: t.MutableMapping[t.Tuple, "Lexer"] = LRUCache(50) # type: ignore
|
23 |
+
|
24 |
+
# static regular expressions
|
25 |
+
whitespace_re = re.compile(r"\s+")
|
26 |
+
newline_re = re.compile(r"(\r\n|\r|\n)")
|
27 |
+
string_re = re.compile(
|
28 |
+
r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S
|
29 |
+
)
|
30 |
+
integer_re = re.compile(
|
31 |
+
r"""
|
32 |
+
(
|
33 |
+
0b(_?[0-1])+ # binary
|
34 |
+
|
|
35 |
+
0o(_?[0-7])+ # octal
|
36 |
+
|
|
37 |
+
0x(_?[\da-f])+ # hex
|
38 |
+
|
|
39 |
+
[1-9](_?\d)* # decimal
|
40 |
+
|
|
41 |
+
0(_?0)* # decimal zero
|
42 |
+
)
|
43 |
+
""",
|
44 |
+
re.IGNORECASE | re.VERBOSE,
|
45 |
+
)
|
46 |
+
float_re = re.compile(
|
47 |
+
r"""
|
48 |
+
(?<!\.) # doesn't start with a .
|
49 |
+
(\d+_)*\d+ # digits, possibly _ separated
|
50 |
+
(
|
51 |
+
(\.(\d+_)*\d+)? # optional fractional part
|
52 |
+
e[+\-]?(\d+_)*\d+ # exponent part
|
53 |
+
|
|
54 |
+
\.(\d+_)*\d+ # required fractional part
|
55 |
+
)
|
56 |
+
""",
|
57 |
+
re.IGNORECASE | re.VERBOSE,
|
58 |
+
)
|
59 |
+
|
60 |
+
# internal the tokens and keep references to them
|
61 |
+
TOKEN_ADD = intern("add")
|
62 |
+
TOKEN_ASSIGN = intern("assign")
|
63 |
+
TOKEN_COLON = intern("colon")
|
64 |
+
TOKEN_COMMA = intern("comma")
|
65 |
+
TOKEN_DIV = intern("div")
|
66 |
+
TOKEN_DOT = intern("dot")
|
67 |
+
TOKEN_EQ = intern("eq")
|
68 |
+
TOKEN_FLOORDIV = intern("floordiv")
|
69 |
+
TOKEN_GT = intern("gt")
|
70 |
+
TOKEN_GTEQ = intern("gteq")
|
71 |
+
TOKEN_LBRACE = intern("lbrace")
|
72 |
+
TOKEN_LBRACKET = intern("lbracket")
|
73 |
+
TOKEN_LPAREN = intern("lparen")
|
74 |
+
TOKEN_LT = intern("lt")
|
75 |
+
TOKEN_LTEQ = intern("lteq")
|
76 |
+
TOKEN_MOD = intern("mod")
|
77 |
+
TOKEN_MUL = intern("mul")
|
78 |
+
TOKEN_NE = intern("ne")
|
79 |
+
TOKEN_PIPE = intern("pipe")
|
80 |
+
TOKEN_POW = intern("pow")
|
81 |
+
TOKEN_RBRACE = intern("rbrace")
|
82 |
+
TOKEN_RBRACKET = intern("rbracket")
|
83 |
+
TOKEN_RPAREN = intern("rparen")
|
84 |
+
TOKEN_SEMICOLON = intern("semicolon")
|
85 |
+
TOKEN_SUB = intern("sub")
|
86 |
+
TOKEN_TILDE = intern("tilde")
|
87 |
+
TOKEN_WHITESPACE = intern("whitespace")
|
88 |
+
TOKEN_FLOAT = intern("float")
|
89 |
+
TOKEN_INTEGER = intern("integer")
|
90 |
+
TOKEN_NAME = intern("name")
|
91 |
+
TOKEN_STRING = intern("string")
|
92 |
+
TOKEN_OPERATOR = intern("operator")
|
93 |
+
TOKEN_BLOCK_BEGIN = intern("block_begin")
|
94 |
+
TOKEN_BLOCK_END = intern("block_end")
|
95 |
+
TOKEN_VARIABLE_BEGIN = intern("variable_begin")
|
96 |
+
TOKEN_VARIABLE_END = intern("variable_end")
|
97 |
+
TOKEN_RAW_BEGIN = intern("raw_begin")
|
98 |
+
TOKEN_RAW_END = intern("raw_end")
|
99 |
+
TOKEN_COMMENT_BEGIN = intern("comment_begin")
|
100 |
+
TOKEN_COMMENT_END = intern("comment_end")
|
101 |
+
TOKEN_COMMENT = intern("comment")
|
102 |
+
TOKEN_LINESTATEMENT_BEGIN = intern("linestatement_begin")
|
103 |
+
TOKEN_LINESTATEMENT_END = intern("linestatement_end")
|
104 |
+
TOKEN_LINECOMMENT_BEGIN = intern("linecomment_begin")
|
105 |
+
TOKEN_LINECOMMENT_END = intern("linecomment_end")
|
106 |
+
TOKEN_LINECOMMENT = intern("linecomment")
|
107 |
+
TOKEN_DATA = intern("data")
|
108 |
+
TOKEN_INITIAL = intern("initial")
|
109 |
+
TOKEN_EOF = intern("eof")
|
110 |
+
|
111 |
+
# bind operators to token types
|
112 |
+
operators = {
|
113 |
+
"+": TOKEN_ADD,
|
114 |
+
"-": TOKEN_SUB,
|
115 |
+
"/": TOKEN_DIV,
|
116 |
+
"//": TOKEN_FLOORDIV,
|
117 |
+
"*": TOKEN_MUL,
|
118 |
+
"%": TOKEN_MOD,
|
119 |
+
"**": TOKEN_POW,
|
120 |
+
"~": TOKEN_TILDE,
|
121 |
+
"[": TOKEN_LBRACKET,
|
122 |
+
"]": TOKEN_RBRACKET,
|
123 |
+
"(": TOKEN_LPAREN,
|
124 |
+
")": TOKEN_RPAREN,
|
125 |
+
"{": TOKEN_LBRACE,
|
126 |
+
"}": TOKEN_RBRACE,
|
127 |
+
"==": TOKEN_EQ,
|
128 |
+
"!=": TOKEN_NE,
|
129 |
+
">": TOKEN_GT,
|
130 |
+
">=": TOKEN_GTEQ,
|
131 |
+
"<": TOKEN_LT,
|
132 |
+
"<=": TOKEN_LTEQ,
|
133 |
+
"=": TOKEN_ASSIGN,
|
134 |
+
".": TOKEN_DOT,
|
135 |
+
":": TOKEN_COLON,
|
136 |
+
"|": TOKEN_PIPE,
|
137 |
+
",": TOKEN_COMMA,
|
138 |
+
";": TOKEN_SEMICOLON,
|
139 |
+
}
|
140 |
+
|
141 |
+
reverse_operators = {v: k for k, v in operators.items()}
|
142 |
+
assert len(operators) == len(reverse_operators), "operators dropped"
|
143 |
+
operator_re = re.compile(
|
144 |
+
f"({'|'.join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))})"
|
145 |
+
)
|
146 |
+
|
147 |
+
ignored_tokens = frozenset(
|
148 |
+
[
|
149 |
+
TOKEN_COMMENT_BEGIN,
|
150 |
+
TOKEN_COMMENT,
|
151 |
+
TOKEN_COMMENT_END,
|
152 |
+
TOKEN_WHITESPACE,
|
153 |
+
TOKEN_LINECOMMENT_BEGIN,
|
154 |
+
TOKEN_LINECOMMENT_END,
|
155 |
+
TOKEN_LINECOMMENT,
|
156 |
+
]
|
157 |
+
)
|
158 |
+
ignore_if_empty = frozenset(
|
159 |
+
[TOKEN_WHITESPACE, TOKEN_DATA, TOKEN_COMMENT, TOKEN_LINECOMMENT]
|
160 |
+
)
|
161 |
+
|
162 |
+
|
163 |
+
def _describe_token_type(token_type: str) -> str:
|
164 |
+
if token_type in reverse_operators:
|
165 |
+
return reverse_operators[token_type]
|
166 |
+
|
167 |
+
return {
|
168 |
+
TOKEN_COMMENT_BEGIN: "begin of comment",
|
169 |
+
TOKEN_COMMENT_END: "end of comment",
|
170 |
+
TOKEN_COMMENT: "comment",
|
171 |
+
TOKEN_LINECOMMENT: "comment",
|
172 |
+
TOKEN_BLOCK_BEGIN: "begin of statement block",
|
173 |
+
TOKEN_BLOCK_END: "end of statement block",
|
174 |
+
TOKEN_VARIABLE_BEGIN: "begin of print statement",
|
175 |
+
TOKEN_VARIABLE_END: "end of print statement",
|
176 |
+
TOKEN_LINESTATEMENT_BEGIN: "begin of line statement",
|
177 |
+
TOKEN_LINESTATEMENT_END: "end of line statement",
|
178 |
+
TOKEN_DATA: "template data / text",
|
179 |
+
TOKEN_EOF: "end of template",
|
180 |
+
}.get(token_type, token_type)
|
181 |
+
|
182 |
+
|
183 |
+
def describe_token(token: "Token") -> str:
|
184 |
+
"""Returns a description of the token."""
|
185 |
+
if token.type == TOKEN_NAME:
|
186 |
+
return token.value
|
187 |
+
|
188 |
+
return _describe_token_type(token.type)
|
189 |
+
|
190 |
+
|
191 |
+
def describe_token_expr(expr: str) -> str:
|
192 |
+
"""Like `describe_token` but for token expressions."""
|
193 |
+
if ":" in expr:
|
194 |
+
type, value = expr.split(":", 1)
|
195 |
+
|
196 |
+
if type == TOKEN_NAME:
|
197 |
+
return value
|
198 |
+
else:
|
199 |
+
type = expr
|
200 |
+
|
201 |
+
return _describe_token_type(type)
|
202 |
+
|
203 |
+
|
204 |
+
def count_newlines(value: str) -> int:
|
205 |
+
"""Count the number of newline characters in the string. This is
|
206 |
+
useful for extensions that filter a stream.
|
207 |
+
"""
|
208 |
+
return len(newline_re.findall(value))
|
209 |
+
|
210 |
+
|
211 |
+
def compile_rules(environment: "Environment") -> t.List[t.Tuple[str, str]]:
|
212 |
+
"""Compiles all the rules from the environment into a list of rules."""
|
213 |
+
e = re.escape
|
214 |
+
rules = [
|
215 |
+
(
|
216 |
+
len(environment.comment_start_string),
|
217 |
+
TOKEN_COMMENT_BEGIN,
|
218 |
+
e(environment.comment_start_string),
|
219 |
+
),
|
220 |
+
(
|
221 |
+
len(environment.block_start_string),
|
222 |
+
TOKEN_BLOCK_BEGIN,
|
223 |
+
e(environment.block_start_string),
|
224 |
+
),
|
225 |
+
(
|
226 |
+
len(environment.variable_start_string),
|
227 |
+
TOKEN_VARIABLE_BEGIN,
|
228 |
+
e(environment.variable_start_string),
|
229 |
+
),
|
230 |
+
]
|
231 |
+
|
232 |
+
if environment.line_statement_prefix is not None:
|
233 |
+
rules.append(
|
234 |
+
(
|
235 |
+
len(environment.line_statement_prefix),
|
236 |
+
TOKEN_LINESTATEMENT_BEGIN,
|
237 |
+
r"^[ \t\v]*" + e(environment.line_statement_prefix),
|
238 |
+
)
|
239 |
+
)
|
240 |
+
if environment.line_comment_prefix is not None:
|
241 |
+
rules.append(
|
242 |
+
(
|
243 |
+
len(environment.line_comment_prefix),
|
244 |
+
TOKEN_LINECOMMENT_BEGIN,
|
245 |
+
r"(?:^|(?<=\S))[^\S\r\n]*" + e(environment.line_comment_prefix),
|
246 |
+
)
|
247 |
+
)
|
248 |
+
|
249 |
+
return [x[1:] for x in sorted(rules, reverse=True)]
|
250 |
+
|
251 |
+
|
252 |
+
class Failure:
|
253 |
+
"""Class that raises a `TemplateSyntaxError` if called.
|
254 |
+
Used by the `Lexer` to specify known errors.
|
255 |
+
"""
|
256 |
+
|
257 |
+
def __init__(
|
258 |
+
self, message: str, cls: t.Type[TemplateSyntaxError] = TemplateSyntaxError
|
259 |
+
) -> None:
|
260 |
+
self.message = message
|
261 |
+
self.error_class = cls
|
262 |
+
|
263 |
+
def __call__(self, lineno: int, filename: str) -> "te.NoReturn":
|
264 |
+
raise self.error_class(self.message, lineno, filename)
|
265 |
+
|
266 |
+
|
267 |
+
class Token(t.NamedTuple):
|
268 |
+
lineno: int
|
269 |
+
type: str
|
270 |
+
value: str
|
271 |
+
|
272 |
+
def __str__(self) -> str:
|
273 |
+
return describe_token(self)
|
274 |
+
|
275 |
+
def test(self, expr: str) -> bool:
|
276 |
+
"""Test a token against a token expression. This can either be a
|
277 |
+
token type or ``'token_type:token_value'``. This can only test
|
278 |
+
against string values and types.
|
279 |
+
"""
|
280 |
+
# here we do a regular string equality check as test_any is usually
|
281 |
+
# passed an iterable of not interned strings.
|
282 |
+
if self.type == expr:
|
283 |
+
return True
|
284 |
+
|
285 |
+
if ":" in expr:
|
286 |
+
return expr.split(":", 1) == [self.type, self.value]
|
287 |
+
|
288 |
+
return False
|
289 |
+
|
290 |
+
def test_any(self, *iterable: str) -> bool:
|
291 |
+
"""Test against multiple token expressions."""
|
292 |
+
return any(self.test(expr) for expr in iterable)
|
293 |
+
|
294 |
+
|
295 |
+
class TokenStreamIterator:
|
296 |
+
"""The iterator for tokenstreams. Iterate over the stream
|
297 |
+
until the eof token is reached.
|
298 |
+
"""
|
299 |
+
|
300 |
+
def __init__(self, stream: "TokenStream") -> None:
|
301 |
+
self.stream = stream
|
302 |
+
|
303 |
+
def __iter__(self) -> "TokenStreamIterator":
|
304 |
+
return self
|
305 |
+
|
306 |
+
def __next__(self) -> Token:
|
307 |
+
token = self.stream.current
|
308 |
+
|
309 |
+
if token.type is TOKEN_EOF:
|
310 |
+
self.stream.close()
|
311 |
+
raise StopIteration
|
312 |
+
|
313 |
+
next(self.stream)
|
314 |
+
return token
|
315 |
+
|
316 |
+
|
317 |
+
class TokenStream:
|
318 |
+
"""A token stream is an iterable that yields :class:`Token`\\s. The
|
319 |
+
parser however does not iterate over it but calls :meth:`next` to go
|
320 |
+
one token ahead. The current active token is stored as :attr:`current`.
|
321 |
+
"""
|
322 |
+
|
323 |
+
def __init__(
|
324 |
+
self,
|
325 |
+
generator: t.Iterable[Token],
|
326 |
+
name: t.Optional[str],
|
327 |
+
filename: t.Optional[str],
|
328 |
+
):
|
329 |
+
self._iter = iter(generator)
|
330 |
+
self._pushed: "te.Deque[Token]" = deque()
|
331 |
+
self.name = name
|
332 |
+
self.filename = filename
|
333 |
+
self.closed = False
|
334 |
+
self.current = Token(1, TOKEN_INITIAL, "")
|
335 |
+
next(self)
|
336 |
+
|
337 |
+
def __iter__(self) -> TokenStreamIterator:
|
338 |
+
return TokenStreamIterator(self)
|
339 |
+
|
340 |
+
def __bool__(self) -> bool:
|
341 |
+
return bool(self._pushed) or self.current.type is not TOKEN_EOF
|
342 |
+
|
343 |
+
@property
|
344 |
+
def eos(self) -> bool:
|
345 |
+
"""Are we at the end of the stream?"""
|
346 |
+
return not self
|
347 |
+
|
348 |
+
def push(self, token: Token) -> None:
|
349 |
+
"""Push a token back to the stream."""
|
350 |
+
self._pushed.append(token)
|
351 |
+
|
352 |
+
def look(self) -> Token:
|
353 |
+
"""Look at the next token."""
|
354 |
+
old_token = next(self)
|
355 |
+
result = self.current
|
356 |
+
self.push(result)
|
357 |
+
self.current = old_token
|
358 |
+
return result
|
359 |
+
|
360 |
+
def skip(self, n: int = 1) -> None:
|
361 |
+
"""Got n tokens ahead."""
|
362 |
+
for _ in range(n):
|
363 |
+
next(self)
|
364 |
+
|
365 |
+
def next_if(self, expr: str) -> t.Optional[Token]:
|
366 |
+
"""Perform the token test and return the token if it matched.
|
367 |
+
Otherwise the return value is `None`.
|
368 |
+
"""
|
369 |
+
if self.current.test(expr):
|
370 |
+
return next(self)
|
371 |
+
|
372 |
+
return None
|
373 |
+
|
374 |
+
def skip_if(self, expr: str) -> bool:
|
375 |
+
"""Like :meth:`next_if` but only returns `True` or `False`."""
|
376 |
+
return self.next_if(expr) is not None
|
377 |
+
|
378 |
+
def __next__(self) -> Token:
|
379 |
+
"""Go one token ahead and return the old one.
|
380 |
+
|
381 |
+
Use the built-in :func:`next` instead of calling this directly.
|
382 |
+
"""
|
383 |
+
rv = self.current
|
384 |
+
|
385 |
+
if self._pushed:
|
386 |
+
self.current = self._pushed.popleft()
|
387 |
+
elif self.current.type is not TOKEN_EOF:
|
388 |
+
try:
|
389 |
+
self.current = next(self._iter)
|
390 |
+
except StopIteration:
|
391 |
+
self.close()
|
392 |
+
|
393 |
+
return rv
|
394 |
+
|
395 |
+
def close(self) -> None:
|
396 |
+
"""Close the stream."""
|
397 |
+
self.current = Token(self.current.lineno, TOKEN_EOF, "")
|
398 |
+
self._iter = iter(())
|
399 |
+
self.closed = True
|
400 |
+
|
401 |
+
def expect(self, expr: str) -> Token:
|
402 |
+
"""Expect a given token type and return it. This accepts the same
|
403 |
+
argument as :meth:`jinja2.lexer.Token.test`.
|
404 |
+
"""
|
405 |
+
if not self.current.test(expr):
|
406 |
+
expr = describe_token_expr(expr)
|
407 |
+
|
408 |
+
if self.current.type is TOKEN_EOF:
|
409 |
+
raise TemplateSyntaxError(
|
410 |
+
f"unexpected end of template, expected {expr!r}.",
|
411 |
+
self.current.lineno,
|
412 |
+
self.name,
|
413 |
+
self.filename,
|
414 |
+
)
|
415 |
+
|
416 |
+
raise TemplateSyntaxError(
|
417 |
+
f"expected token {expr!r}, got {describe_token(self.current)!r}",
|
418 |
+
self.current.lineno,
|
419 |
+
self.name,
|
420 |
+
self.filename,
|
421 |
+
)
|
422 |
+
|
423 |
+
return next(self)
|
424 |
+
|
425 |
+
|
426 |
+
def get_lexer(environment: "Environment") -> "Lexer":
|
427 |
+
"""Return a lexer which is probably cached."""
|
428 |
+
key = (
|
429 |
+
environment.block_start_string,
|
430 |
+
environment.block_end_string,
|
431 |
+
environment.variable_start_string,
|
432 |
+
environment.variable_end_string,
|
433 |
+
environment.comment_start_string,
|
434 |
+
environment.comment_end_string,
|
435 |
+
environment.line_statement_prefix,
|
436 |
+
environment.line_comment_prefix,
|
437 |
+
environment.trim_blocks,
|
438 |
+
environment.lstrip_blocks,
|
439 |
+
environment.newline_sequence,
|
440 |
+
environment.keep_trailing_newline,
|
441 |
+
)
|
442 |
+
lexer = _lexer_cache.get(key)
|
443 |
+
|
444 |
+
if lexer is None:
|
445 |
+
_lexer_cache[key] = lexer = Lexer(environment)
|
446 |
+
|
447 |
+
return lexer
|
448 |
+
|
449 |
+
|
450 |
+
class OptionalLStrip(tuple):
|
451 |
+
"""A special tuple for marking a point in the state that can have
|
452 |
+
lstrip applied.
|
453 |
+
"""
|
454 |
+
|
455 |
+
__slots__ = ()
|
456 |
+
|
457 |
+
# Even though it looks like a no-op, creating instances fails
|
458 |
+
# without this.
|
459 |
+
def __new__(cls, *members, **kwargs): # type: ignore
|
460 |
+
return super().__new__(cls, members)
|
461 |
+
|
462 |
+
|
463 |
+
class _Rule(t.NamedTuple):
|
464 |
+
pattern: t.Pattern[str]
|
465 |
+
tokens: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]]
|
466 |
+
command: t.Optional[str]
|
467 |
+
|
468 |
+
|
469 |
+
class Lexer:
|
470 |
+
"""Class that implements a lexer for a given environment. Automatically
|
471 |
+
created by the environment class, usually you don't have to do that.
|
472 |
+
|
473 |
+
Note that the lexer is not automatically bound to an environment.
|
474 |
+
Multiple environments can share the same lexer.
|
475 |
+
"""
|
476 |
+
|
477 |
+
def __init__(self, environment: "Environment") -> None:
|
478 |
+
# shortcuts
|
479 |
+
e = re.escape
|
480 |
+
|
481 |
+
def c(x: str) -> t.Pattern[str]:
|
482 |
+
return re.compile(x, re.M | re.S)
|
483 |
+
|
484 |
+
# lexing rules for tags
|
485 |
+
tag_rules: t.List[_Rule] = [
|
486 |
+
_Rule(whitespace_re, TOKEN_WHITESPACE, None),
|
487 |
+
_Rule(float_re, TOKEN_FLOAT, None),
|
488 |
+
_Rule(integer_re, TOKEN_INTEGER, None),
|
489 |
+
_Rule(name_re, TOKEN_NAME, None),
|
490 |
+
_Rule(string_re, TOKEN_STRING, None),
|
491 |
+
_Rule(operator_re, TOKEN_OPERATOR, None),
|
492 |
+
]
|
493 |
+
|
494 |
+
# assemble the root lexing rule. because "|" is ungreedy
|
495 |
+
# we have to sort by length so that the lexer continues working
|
496 |
+
# as expected when we have parsing rules like <% for block and
|
497 |
+
# <%= for variables. (if someone wants asp like syntax)
|
498 |
+
# variables are just part of the rules if variable processing
|
499 |
+
# is required.
|
500 |
+
root_tag_rules = compile_rules(environment)
|
501 |
+
|
502 |
+
block_start_re = e(environment.block_start_string)
|
503 |
+
block_end_re = e(environment.block_end_string)
|
504 |
+
comment_end_re = e(environment.comment_end_string)
|
505 |
+
variable_end_re = e(environment.variable_end_string)
|
506 |
+
|
507 |
+
# block suffix if trimming is enabled
|
508 |
+
block_suffix_re = "\\n?" if environment.trim_blocks else ""
|
509 |
+
|
510 |
+
self.lstrip_blocks = environment.lstrip_blocks
|
511 |
+
|
512 |
+
self.newline_sequence = environment.newline_sequence
|
513 |
+
self.keep_trailing_newline = environment.keep_trailing_newline
|
514 |
+
|
515 |
+
root_raw_re = (
|
516 |
+
rf"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
|
517 |
+
rf"(?:\-{block_end_re}\s*|{block_end_re}))"
|
518 |
+
)
|
519 |
+
root_parts_re = "|".join(
|
520 |
+
[root_raw_re] + [rf"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules]
|
521 |
+
)
|
522 |
+
|
523 |
+
# global lexing rules
|
524 |
+
self.rules: t.Dict[str, t.List[_Rule]] = {
|
525 |
+
"root": [
|
526 |
+
# directives
|
527 |
+
_Rule(
|
528 |
+
c(rf"(.*?)(?:{root_parts_re})"),
|
529 |
+
OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore
|
530 |
+
"#bygroup",
|
531 |
+
),
|
532 |
+
# data
|
533 |
+
_Rule(c(".+"), TOKEN_DATA, None),
|
534 |
+
],
|
535 |
+
# comments
|
536 |
+
TOKEN_COMMENT_BEGIN: [
|
537 |
+
_Rule(
|
538 |
+
c(
|
539 |
+
rf"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*"
|
540 |
+
rf"|{comment_end_re}{block_suffix_re}))"
|
541 |
+
),
|
542 |
+
(TOKEN_COMMENT, TOKEN_COMMENT_END),
|
543 |
+
"#pop",
|
544 |
+
),
|
545 |
+
_Rule(c(r"(.)"), (Failure("Missing end of comment tag"),), None),
|
546 |
+
],
|
547 |
+
# blocks
|
548 |
+
TOKEN_BLOCK_BEGIN: [
|
549 |
+
_Rule(
|
550 |
+
c(
|
551 |
+
rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
552 |
+
rf"|{block_end_re}{block_suffix_re})"
|
553 |
+
),
|
554 |
+
TOKEN_BLOCK_END,
|
555 |
+
"#pop",
|
556 |
+
),
|
557 |
+
]
|
558 |
+
+ tag_rules,
|
559 |
+
# variables
|
560 |
+
TOKEN_VARIABLE_BEGIN: [
|
561 |
+
_Rule(
|
562 |
+
c(rf"\-{variable_end_re}\s*|{variable_end_re}"),
|
563 |
+
TOKEN_VARIABLE_END,
|
564 |
+
"#pop",
|
565 |
+
)
|
566 |
+
]
|
567 |
+
+ tag_rules,
|
568 |
+
# raw block
|
569 |
+
TOKEN_RAW_BEGIN: [
|
570 |
+
_Rule(
|
571 |
+
c(
|
572 |
+
rf"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
|
573 |
+
rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
574 |
+
rf"|{block_end_re}{block_suffix_re}))"
|
575 |
+
),
|
576 |
+
OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore
|
577 |
+
"#pop",
|
578 |
+
),
|
579 |
+
_Rule(c(r"(.)"), (Failure("Missing end of raw directive"),), None),
|
580 |
+
],
|
581 |
+
# line statements
|
582 |
+
TOKEN_LINESTATEMENT_BEGIN: [
|
583 |
+
_Rule(c(r"\s*(\n|$)"), TOKEN_LINESTATEMENT_END, "#pop")
|
584 |
+
]
|
585 |
+
+ tag_rules,
|
586 |
+
# line comments
|
587 |
+
TOKEN_LINECOMMENT_BEGIN: [
|
588 |
+
_Rule(
|
589 |
+
c(r"(.*?)()(?=\n|$)"),
|
590 |
+
(TOKEN_LINECOMMENT, TOKEN_LINECOMMENT_END),
|
591 |
+
"#pop",
|
592 |
+
)
|
593 |
+
],
|
594 |
+
}
|
595 |
+
|
596 |
+
def _normalize_newlines(self, value: str) -> str:
|
597 |
+
"""Replace all newlines with the configured sequence in strings
|
598 |
+
and template data.
|
599 |
+
"""
|
600 |
+
return newline_re.sub(self.newline_sequence, value)
|
601 |
+
|
602 |
+
def tokenize(
|
603 |
+
self,
|
604 |
+
source: str,
|
605 |
+
name: t.Optional[str] = None,
|
606 |
+
filename: t.Optional[str] = None,
|
607 |
+
state: t.Optional[str] = None,
|
608 |
+
) -> TokenStream:
|
609 |
+
"""Calls tokeniter + tokenize and wraps it in a token stream."""
|
610 |
+
stream = self.tokeniter(source, name, filename, state)
|
611 |
+
return TokenStream(self.wrap(stream, name, filename), name, filename)
|
612 |
+
|
613 |
+
def wrap(
|
614 |
+
self,
|
615 |
+
stream: t.Iterable[t.Tuple[int, str, str]],
|
616 |
+
name: t.Optional[str] = None,
|
617 |
+
filename: t.Optional[str] = None,
|
618 |
+
) -> t.Iterator[Token]:
|
619 |
+
"""This is called with the stream as returned by `tokenize` and wraps
|
620 |
+
every token in a :class:`Token` and converts the value.
|
621 |
+
"""
|
622 |
+
for lineno, token, value_str in stream:
|
623 |
+
if token in ignored_tokens:
|
624 |
+
continue
|
625 |
+
|
626 |
+
value: t.Any = value_str
|
627 |
+
|
628 |
+
if token == TOKEN_LINESTATEMENT_BEGIN:
|
629 |
+
token = TOKEN_BLOCK_BEGIN
|
630 |
+
elif token == TOKEN_LINESTATEMENT_END:
|
631 |
+
token = TOKEN_BLOCK_END
|
632 |
+
# we are not interested in those tokens in the parser
|
633 |
+
elif token in (TOKEN_RAW_BEGIN, TOKEN_RAW_END):
|
634 |
+
continue
|
635 |
+
elif token == TOKEN_DATA:
|
636 |
+
value = self._normalize_newlines(value_str)
|
637 |
+
elif token == "keyword":
|
638 |
+
token = value_str
|
639 |
+
elif token == TOKEN_NAME:
|
640 |
+
value = value_str
|
641 |
+
|
642 |
+
if not value.isidentifier():
|
643 |
+
raise TemplateSyntaxError(
|
644 |
+
"Invalid character in identifier", lineno, name, filename
|
645 |
+
)
|
646 |
+
elif token == TOKEN_STRING:
|
647 |
+
# try to unescape string
|
648 |
+
try:
|
649 |
+
value = (
|
650 |
+
self._normalize_newlines(value_str[1:-1])
|
651 |
+
.encode("ascii", "backslashreplace")
|
652 |
+
.decode("unicode-escape")
|
653 |
+
)
|
654 |
+
except Exception as e:
|
655 |
+
msg = str(e).split(":")[-1].strip()
|
656 |
+
raise TemplateSyntaxError(msg, lineno, name, filename) from e
|
657 |
+
elif token == TOKEN_INTEGER:
|
658 |
+
value = int(value_str.replace("_", ""), 0)
|
659 |
+
elif token == TOKEN_FLOAT:
|
660 |
+
# remove all "_" first to support more Python versions
|
661 |
+
value = literal_eval(value_str.replace("_", ""))
|
662 |
+
elif token == TOKEN_OPERATOR:
|
663 |
+
token = operators[value_str]
|
664 |
+
|
665 |
+
yield Token(lineno, token, value)
|
666 |
+
|
667 |
+
def tokeniter(
|
668 |
+
self,
|
669 |
+
source: str,
|
670 |
+
name: t.Optional[str],
|
671 |
+
filename: t.Optional[str] = None,
|
672 |
+
state: t.Optional[str] = None,
|
673 |
+
) -> t.Iterator[t.Tuple[int, str, str]]:
|
674 |
+
"""This method tokenizes the text and returns the tokens in a
|
675 |
+
generator. Use this method if you just want to tokenize a template.
|
676 |
+
|
677 |
+
.. versionchanged:: 3.0
|
678 |
+
Only ``\\n``, ``\\r\\n`` and ``\\r`` are treated as line
|
679 |
+
breaks.
|
680 |
+
"""
|
681 |
+
lines = newline_re.split(source)[::2]
|
682 |
+
|
683 |
+
if not self.keep_trailing_newline and lines[-1] == "":
|
684 |
+
del lines[-1]
|
685 |
+
|
686 |
+
source = "\n".join(lines)
|
687 |
+
pos = 0
|
688 |
+
lineno = 1
|
689 |
+
stack = ["root"]
|
690 |
+
|
691 |
+
if state is not None and state != "root":
|
692 |
+
assert state in ("variable", "block"), "invalid state"
|
693 |
+
stack.append(state + "_begin")
|
694 |
+
|
695 |
+
statetokens = self.rules[stack[-1]]
|
696 |
+
source_length = len(source)
|
697 |
+
balancing_stack: t.List[str] = []
|
698 |
+
newlines_stripped = 0
|
699 |
+
line_starting = True
|
700 |
+
|
701 |
+
while True:
|
702 |
+
# tokenizer loop
|
703 |
+
for regex, tokens, new_state in statetokens:
|
704 |
+
m = regex.match(source, pos)
|
705 |
+
|
706 |
+
# if no match we try again with the next rule
|
707 |
+
if m is None:
|
708 |
+
continue
|
709 |
+
|
710 |
+
# we only match blocks and variables if braces / parentheses
|
711 |
+
# are balanced. continue parsing with the lower rule which
|
712 |
+
# is the operator rule. do this only if the end tags look
|
713 |
+
# like operators
|
714 |
+
if balancing_stack and tokens in (
|
715 |
+
TOKEN_VARIABLE_END,
|
716 |
+
TOKEN_BLOCK_END,
|
717 |
+
TOKEN_LINESTATEMENT_END,
|
718 |
+
):
|
719 |
+
continue
|
720 |
+
|
721 |
+
# tuples support more options
|
722 |
+
if isinstance(tokens, tuple):
|
723 |
+
groups: t.Sequence[str] = m.groups()
|
724 |
+
|
725 |
+
if isinstance(tokens, OptionalLStrip):
|
726 |
+
# Rule supports lstrip. Match will look like
|
727 |
+
# text, block type, whitespace control, type, control, ...
|
728 |
+
text = groups[0]
|
729 |
+
# Skipping the text and first type, every other group is the
|
730 |
+
# whitespace control for each type. One of the groups will be
|
731 |
+
# -, +, or empty string instead of None.
|
732 |
+
strip_sign = next(g for g in groups[2::2] if g is not None)
|
733 |
+
|
734 |
+
if strip_sign == "-":
|
735 |
+
# Strip all whitespace between the text and the tag.
|
736 |
+
stripped = text.rstrip()
|
737 |
+
newlines_stripped = text[len(stripped) :].count("\n")
|
738 |
+
groups = [stripped, *groups[1:]]
|
739 |
+
elif (
|
740 |
+
# Not marked for preserving whitespace.
|
741 |
+
strip_sign != "+"
|
742 |
+
# lstrip is enabled.
|
743 |
+
and self.lstrip_blocks
|
744 |
+
# Not a variable expression.
|
745 |
+
and not m.groupdict().get(TOKEN_VARIABLE_BEGIN)
|
746 |
+
):
|
747 |
+
# The start of text between the last newline and the tag.
|
748 |
+
l_pos = text.rfind("\n") + 1
|
749 |
+
|
750 |
+
if l_pos > 0 or line_starting:
|
751 |
+
# If there's only whitespace between the newline and the
|
752 |
+
# tag, strip it.
|
753 |
+
if whitespace_re.fullmatch(text, l_pos):
|
754 |
+
groups = [text[:l_pos], *groups[1:]]
|
755 |
+
|
756 |
+
for idx, token in enumerate(tokens):
|
757 |
+
# failure group
|
758 |
+
if token.__class__ is Failure:
|
759 |
+
raise token(lineno, filename)
|
760 |
+
# bygroup is a bit more complex, in that case we
|
761 |
+
# yield for the current token the first named
|
762 |
+
# group that matched
|
763 |
+
elif token == "#bygroup":
|
764 |
+
for key, value in m.groupdict().items():
|
765 |
+
if value is not None:
|
766 |
+
yield lineno, key, value
|
767 |
+
lineno += value.count("\n")
|
768 |
+
break
|
769 |
+
else:
|
770 |
+
raise RuntimeError(
|
771 |
+
f"{regex!r} wanted to resolve the token dynamically"
|
772 |
+
" but no group matched"
|
773 |
+
)
|
774 |
+
# normal group
|
775 |
+
else:
|
776 |
+
data = groups[idx]
|
777 |
+
|
778 |
+
if data or token not in ignore_if_empty:
|
779 |
+
yield lineno, token, data
|
780 |
+
|
781 |
+
lineno += data.count("\n") + newlines_stripped
|
782 |
+
newlines_stripped = 0
|
783 |
+
|
784 |
+
# strings as token just are yielded as it.
|
785 |
+
else:
|
786 |
+
data = m.group()
|
787 |
+
|
788 |
+
# update brace/parentheses balance
|
789 |
+
if tokens == TOKEN_OPERATOR:
|
790 |
+
if data == "{":
|
791 |
+
balancing_stack.append("}")
|
792 |
+
elif data == "(":
|
793 |
+
balancing_stack.append(")")
|
794 |
+
elif data == "[":
|
795 |
+
balancing_stack.append("]")
|
796 |
+
elif data in ("}", ")", "]"):
|
797 |
+
if not balancing_stack:
|
798 |
+
raise TemplateSyntaxError(
|
799 |
+
f"unexpected '{data}'", lineno, name, filename
|
800 |
+
)
|
801 |
+
|
802 |
+
expected_op = balancing_stack.pop()
|
803 |
+
|
804 |
+
if expected_op != data:
|
805 |
+
raise TemplateSyntaxError(
|
806 |
+
f"unexpected '{data}', expected '{expected_op}'",
|
807 |
+
lineno,
|
808 |
+
name,
|
809 |
+
filename,
|
810 |
+
)
|
811 |
+
|
812 |
+
# yield items
|
813 |
+
if data or tokens not in ignore_if_empty:
|
814 |
+
yield lineno, tokens, data
|
815 |
+
|
816 |
+
lineno += data.count("\n")
|
817 |
+
|
818 |
+
line_starting = m.group()[-1:] == "\n"
|
819 |
+
# fetch new position into new variable so that we can check
|
820 |
+
# if there is a internal parsing error which would result
|
821 |
+
# in an infinite loop
|
822 |
+
pos2 = m.end()
|
823 |
+
|
824 |
+
# handle state changes
|
825 |
+
if new_state is not None:
|
826 |
+
# remove the uppermost state
|
827 |
+
if new_state == "#pop":
|
828 |
+
stack.pop()
|
829 |
+
# resolve the new state by group checking
|
830 |
+
elif new_state == "#bygroup":
|
831 |
+
for key, value in m.groupdict().items():
|
832 |
+
if value is not None:
|
833 |
+
stack.append(key)
|
834 |
+
break
|
835 |
+
else:
|
836 |
+
raise RuntimeError(
|
837 |
+
f"{regex!r} wanted to resolve the new state dynamically"
|
838 |
+
f" but no group matched"
|
839 |
+
)
|
840 |
+
# direct state name given
|
841 |
+
else:
|
842 |
+
stack.append(new_state)
|
843 |
+
|
844 |
+
statetokens = self.rules[stack[-1]]
|
845 |
+
# we are still at the same position and no stack change.
|
846 |
+
# this means a loop without break condition, avoid that and
|
847 |
+
# raise error
|
848 |
+
elif pos2 == pos:
|
849 |
+
raise RuntimeError(
|
850 |
+
f"{regex!r} yielded empty string without stack change"
|
851 |
+
)
|
852 |
+
|
853 |
+
# publish new function and start again
|
854 |
+
pos = pos2
|
855 |
+
break
|
856 |
+
# if loop terminated without break we haven't found a single match
|
857 |
+
# either we are at the end of the file or we have a problem
|
858 |
+
else:
|
859 |
+
# end of text
|
860 |
+
if pos >= source_length:
|
861 |
+
return
|
862 |
+
|
863 |
+
# something went wrong
|
864 |
+
raise TemplateSyntaxError(
|
865 |
+
f"unexpected char {source[pos]!r} at {pos}", lineno, name, filename
|
866 |
+
)
|
lib/python3.11/site-packages/jinja2/loaders.py
ADDED
@@ -0,0 +1,661 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""API and implementations for loading templates from different data
|
2 |
+
sources.
|
3 |
+
"""
|
4 |
+
import importlib.util
|
5 |
+
import os
|
6 |
+
import posixpath
|
7 |
+
import sys
|
8 |
+
import typing as t
|
9 |
+
import weakref
|
10 |
+
import zipimport
|
11 |
+
from collections import abc
|
12 |
+
from hashlib import sha1
|
13 |
+
from importlib import import_module
|
14 |
+
from types import ModuleType
|
15 |
+
|
16 |
+
from .exceptions import TemplateNotFound
|
17 |
+
from .utils import internalcode
|
18 |
+
from .utils import open_if_exists
|
19 |
+
|
20 |
+
if t.TYPE_CHECKING:
|
21 |
+
from .environment import Environment
|
22 |
+
from .environment import Template
|
23 |
+
|
24 |
+
|
25 |
+
def split_template_path(template: str) -> t.List[str]:
|
26 |
+
"""Split a path into segments and perform a sanity check. If it detects
|
27 |
+
'..' in the path it will raise a `TemplateNotFound` error.
|
28 |
+
"""
|
29 |
+
pieces = []
|
30 |
+
for piece in template.split("/"):
|
31 |
+
if (
|
32 |
+
os.path.sep in piece
|
33 |
+
or (os.path.altsep and os.path.altsep in piece)
|
34 |
+
or piece == os.path.pardir
|
35 |
+
):
|
36 |
+
raise TemplateNotFound(template)
|
37 |
+
elif piece and piece != ".":
|
38 |
+
pieces.append(piece)
|
39 |
+
return pieces
|
40 |
+
|
41 |
+
|
42 |
+
class BaseLoader:
|
43 |
+
"""Baseclass for all loaders. Subclass this and override `get_source` to
|
44 |
+
implement a custom loading mechanism. The environment provides a
|
45 |
+
`get_template` method that calls the loader's `load` method to get the
|
46 |
+
:class:`Template` object.
|
47 |
+
|
48 |
+
A very basic example for a loader that looks up templates on the file
|
49 |
+
system could look like this::
|
50 |
+
|
51 |
+
from jinja2 import BaseLoader, TemplateNotFound
|
52 |
+
from os.path import join, exists, getmtime
|
53 |
+
|
54 |
+
class MyLoader(BaseLoader):
|
55 |
+
|
56 |
+
def __init__(self, path):
|
57 |
+
self.path = path
|
58 |
+
|
59 |
+
def get_source(self, environment, template):
|
60 |
+
path = join(self.path, template)
|
61 |
+
if not exists(path):
|
62 |
+
raise TemplateNotFound(template)
|
63 |
+
mtime = getmtime(path)
|
64 |
+
with open(path) as f:
|
65 |
+
source = f.read()
|
66 |
+
return source, path, lambda: mtime == getmtime(path)
|
67 |
+
"""
|
68 |
+
|
69 |
+
#: if set to `False` it indicates that the loader cannot provide access
|
70 |
+
#: to the source of templates.
|
71 |
+
#:
|
72 |
+
#: .. versionadded:: 2.4
|
73 |
+
has_source_access = True
|
74 |
+
|
75 |
+
def get_source(
|
76 |
+
self, environment: "Environment", template: str
|
77 |
+
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
78 |
+
"""Get the template source, filename and reload helper for a template.
|
79 |
+
It's passed the environment and template name and has to return a
|
80 |
+
tuple in the form ``(source, filename, uptodate)`` or raise a
|
81 |
+
`TemplateNotFound` error if it can't locate the template.
|
82 |
+
|
83 |
+
The source part of the returned tuple must be the source of the
|
84 |
+
template as a string. The filename should be the name of the
|
85 |
+
file on the filesystem if it was loaded from there, otherwise
|
86 |
+
``None``. The filename is used by Python for the tracebacks
|
87 |
+
if no loader extension is used.
|
88 |
+
|
89 |
+
The last item in the tuple is the `uptodate` function. If auto
|
90 |
+
reloading is enabled it's always called to check if the template
|
91 |
+
changed. No arguments are passed so the function must store the
|
92 |
+
old state somewhere (for example in a closure). If it returns `False`
|
93 |
+
the template will be reloaded.
|
94 |
+
"""
|
95 |
+
if not self.has_source_access:
|
96 |
+
raise RuntimeError(
|
97 |
+
f"{type(self).__name__} cannot provide access to the source"
|
98 |
+
)
|
99 |
+
raise TemplateNotFound(template)
|
100 |
+
|
101 |
+
def list_templates(self) -> t.List[str]:
|
102 |
+
"""Iterates over all templates. If the loader does not support that
|
103 |
+
it should raise a :exc:`TypeError` which is the default behavior.
|
104 |
+
"""
|
105 |
+
raise TypeError("this loader cannot iterate over all templates")
|
106 |
+
|
107 |
+
@internalcode
|
108 |
+
def load(
|
109 |
+
self,
|
110 |
+
environment: "Environment",
|
111 |
+
name: str,
|
112 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
113 |
+
) -> "Template":
|
114 |
+
"""Loads a template. This method looks up the template in the cache
|
115 |
+
or loads one by calling :meth:`get_source`. Subclasses should not
|
116 |
+
override this method as loaders working on collections of other
|
117 |
+
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
|
118 |
+
will not call this method but `get_source` directly.
|
119 |
+
"""
|
120 |
+
code = None
|
121 |
+
if globals is None:
|
122 |
+
globals = {}
|
123 |
+
|
124 |
+
# first we try to get the source for this template together
|
125 |
+
# with the filename and the uptodate function.
|
126 |
+
source, filename, uptodate = self.get_source(environment, name)
|
127 |
+
|
128 |
+
# try to load the code from the bytecode cache if there is a
|
129 |
+
# bytecode cache configured.
|
130 |
+
bcc = environment.bytecode_cache
|
131 |
+
if bcc is not None:
|
132 |
+
bucket = bcc.get_bucket(environment, name, filename, source)
|
133 |
+
code = bucket.code
|
134 |
+
|
135 |
+
# if we don't have code so far (not cached, no longer up to
|
136 |
+
# date) etc. we compile the template
|
137 |
+
if code is None:
|
138 |
+
code = environment.compile(source, name, filename)
|
139 |
+
|
140 |
+
# if the bytecode cache is available and the bucket doesn't
|
141 |
+
# have a code so far, we give the bucket the new code and put
|
142 |
+
# it back to the bytecode cache.
|
143 |
+
if bcc is not None and bucket.code is None:
|
144 |
+
bucket.code = code
|
145 |
+
bcc.set_bucket(bucket)
|
146 |
+
|
147 |
+
return environment.template_class.from_code(
|
148 |
+
environment, code, globals, uptodate
|
149 |
+
)
|
150 |
+
|
151 |
+
|
152 |
+
class FileSystemLoader(BaseLoader):
|
153 |
+
"""Load templates from a directory in the file system.
|
154 |
+
|
155 |
+
The path can be relative or absolute. Relative paths are relative to
|
156 |
+
the current working directory.
|
157 |
+
|
158 |
+
.. code-block:: python
|
159 |
+
|
160 |
+
loader = FileSystemLoader("templates")
|
161 |
+
|
162 |
+
A list of paths can be given. The directories will be searched in
|
163 |
+
order, stopping at the first matching template.
|
164 |
+
|
165 |
+
.. code-block:: python
|
166 |
+
|
167 |
+
loader = FileSystemLoader(["/override/templates", "/default/templates"])
|
168 |
+
|
169 |
+
:param searchpath: A path, or list of paths, to the directory that
|
170 |
+
contains the templates.
|
171 |
+
:param encoding: Use this encoding to read the text from template
|
172 |
+
files.
|
173 |
+
:param followlinks: Follow symbolic links in the path.
|
174 |
+
|
175 |
+
.. versionchanged:: 2.8
|
176 |
+
Added the ``followlinks`` parameter.
|
177 |
+
"""
|
178 |
+
|
179 |
+
def __init__(
|
180 |
+
self,
|
181 |
+
searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]],
|
182 |
+
encoding: str = "utf-8",
|
183 |
+
followlinks: bool = False,
|
184 |
+
) -> None:
|
185 |
+
if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
|
186 |
+
searchpath = [searchpath]
|
187 |
+
|
188 |
+
self.searchpath = [os.fspath(p) for p in searchpath]
|
189 |
+
self.encoding = encoding
|
190 |
+
self.followlinks = followlinks
|
191 |
+
|
192 |
+
def get_source(
|
193 |
+
self, environment: "Environment", template: str
|
194 |
+
) -> t.Tuple[str, str, t.Callable[[], bool]]:
|
195 |
+
pieces = split_template_path(template)
|
196 |
+
for searchpath in self.searchpath:
|
197 |
+
# Use posixpath even on Windows to avoid "drive:" or UNC
|
198 |
+
# segments breaking out of the search directory.
|
199 |
+
filename = posixpath.join(searchpath, *pieces)
|
200 |
+
f = open_if_exists(filename)
|
201 |
+
if f is None:
|
202 |
+
continue
|
203 |
+
try:
|
204 |
+
contents = f.read().decode(self.encoding)
|
205 |
+
finally:
|
206 |
+
f.close()
|
207 |
+
|
208 |
+
mtime = os.path.getmtime(filename)
|
209 |
+
|
210 |
+
def uptodate() -> bool:
|
211 |
+
try:
|
212 |
+
return os.path.getmtime(filename) == mtime
|
213 |
+
except OSError:
|
214 |
+
return False
|
215 |
+
|
216 |
+
# Use normpath to convert Windows altsep to sep.
|
217 |
+
return contents, os.path.normpath(filename), uptodate
|
218 |
+
raise TemplateNotFound(template)
|
219 |
+
|
220 |
+
def list_templates(self) -> t.List[str]:
|
221 |
+
found = set()
|
222 |
+
for searchpath in self.searchpath:
|
223 |
+
walk_dir = os.walk(searchpath, followlinks=self.followlinks)
|
224 |
+
for dirpath, _, filenames in walk_dir:
|
225 |
+
for filename in filenames:
|
226 |
+
template = (
|
227 |
+
os.path.join(dirpath, filename)[len(searchpath) :]
|
228 |
+
.strip(os.path.sep)
|
229 |
+
.replace(os.path.sep, "/")
|
230 |
+
)
|
231 |
+
if template[:2] == "./":
|
232 |
+
template = template[2:]
|
233 |
+
if template not in found:
|
234 |
+
found.add(template)
|
235 |
+
return sorted(found)
|
236 |
+
|
237 |
+
|
238 |
+
class PackageLoader(BaseLoader):
|
239 |
+
"""Load templates from a directory in a Python package.
|
240 |
+
|
241 |
+
:param package_name: Import name of the package that contains the
|
242 |
+
template directory.
|
243 |
+
:param package_path: Directory within the imported package that
|
244 |
+
contains the templates.
|
245 |
+
:param encoding: Encoding of template files.
|
246 |
+
|
247 |
+
The following example looks up templates in the ``pages`` directory
|
248 |
+
within the ``project.ui`` package.
|
249 |
+
|
250 |
+
.. code-block:: python
|
251 |
+
|
252 |
+
loader = PackageLoader("project.ui", "pages")
|
253 |
+
|
254 |
+
Only packages installed as directories (standard pip behavior) or
|
255 |
+
zip/egg files (less common) are supported. The Python API for
|
256 |
+
introspecting data in packages is too limited to support other
|
257 |
+
installation methods the way this loader requires.
|
258 |
+
|
259 |
+
There is limited support for :pep:`420` namespace packages. The
|
260 |
+
template directory is assumed to only be in one namespace
|
261 |
+
contributor. Zip files contributing to a namespace are not
|
262 |
+
supported.
|
263 |
+
|
264 |
+
.. versionchanged:: 3.0
|
265 |
+
No longer uses ``setuptools`` as a dependency.
|
266 |
+
|
267 |
+
.. versionchanged:: 3.0
|
268 |
+
Limited PEP 420 namespace package support.
|
269 |
+
"""
|
270 |
+
|
271 |
+
def __init__(
|
272 |
+
self,
|
273 |
+
package_name: str,
|
274 |
+
package_path: "str" = "templates",
|
275 |
+
encoding: str = "utf-8",
|
276 |
+
) -> None:
|
277 |
+
package_path = os.path.normpath(package_path).rstrip(os.path.sep)
|
278 |
+
|
279 |
+
# normpath preserves ".", which isn't valid in zip paths.
|
280 |
+
if package_path == os.path.curdir:
|
281 |
+
package_path = ""
|
282 |
+
elif package_path[:2] == os.path.curdir + os.path.sep:
|
283 |
+
package_path = package_path[2:]
|
284 |
+
|
285 |
+
self.package_path = package_path
|
286 |
+
self.package_name = package_name
|
287 |
+
self.encoding = encoding
|
288 |
+
|
289 |
+
# Make sure the package exists. This also makes namespace
|
290 |
+
# packages work, otherwise get_loader returns None.
|
291 |
+
import_module(package_name)
|
292 |
+
spec = importlib.util.find_spec(package_name)
|
293 |
+
assert spec is not None, "An import spec was not found for the package."
|
294 |
+
loader = spec.loader
|
295 |
+
assert loader is not None, "A loader was not found for the package."
|
296 |
+
self._loader = loader
|
297 |
+
self._archive = None
|
298 |
+
template_root = None
|
299 |
+
|
300 |
+
if isinstance(loader, zipimport.zipimporter):
|
301 |
+
self._archive = loader.archive
|
302 |
+
pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore
|
303 |
+
template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
|
304 |
+
else:
|
305 |
+
roots: t.List[str] = []
|
306 |
+
|
307 |
+
# One element for regular packages, multiple for namespace
|
308 |
+
# packages, or None for single module file.
|
309 |
+
if spec.submodule_search_locations:
|
310 |
+
roots.extend(spec.submodule_search_locations)
|
311 |
+
# A single module file, use the parent directory instead.
|
312 |
+
elif spec.origin is not None:
|
313 |
+
roots.append(os.path.dirname(spec.origin))
|
314 |
+
|
315 |
+
for root in roots:
|
316 |
+
root = os.path.join(root, package_path)
|
317 |
+
|
318 |
+
if os.path.isdir(root):
|
319 |
+
template_root = root
|
320 |
+
break
|
321 |
+
|
322 |
+
if template_root is None:
|
323 |
+
raise ValueError(
|
324 |
+
f"The {package_name!r} package was not installed in a"
|
325 |
+
" way that PackageLoader understands."
|
326 |
+
)
|
327 |
+
|
328 |
+
self._template_root = template_root
|
329 |
+
|
330 |
+
def get_source(
|
331 |
+
self, environment: "Environment", template: str
|
332 |
+
) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
|
333 |
+
# Use posixpath even on Windows to avoid "drive:" or UNC
|
334 |
+
# segments breaking out of the search directory. Use normpath to
|
335 |
+
# convert Windows altsep to sep.
|
336 |
+
p = os.path.normpath(
|
337 |
+
posixpath.join(self._template_root, *split_template_path(template))
|
338 |
+
)
|
339 |
+
up_to_date: t.Optional[t.Callable[[], bool]]
|
340 |
+
|
341 |
+
if self._archive is None:
|
342 |
+
# Package is a directory.
|
343 |
+
if not os.path.isfile(p):
|
344 |
+
raise TemplateNotFound(template)
|
345 |
+
|
346 |
+
with open(p, "rb") as f:
|
347 |
+
source = f.read()
|
348 |
+
|
349 |
+
mtime = os.path.getmtime(p)
|
350 |
+
|
351 |
+
def up_to_date() -> bool:
|
352 |
+
return os.path.isfile(p) and os.path.getmtime(p) == mtime
|
353 |
+
|
354 |
+
else:
|
355 |
+
# Package is a zip file.
|
356 |
+
try:
|
357 |
+
source = self._loader.get_data(p) # type: ignore
|
358 |
+
except OSError as e:
|
359 |
+
raise TemplateNotFound(template) from e
|
360 |
+
|
361 |
+
# Could use the zip's mtime for all template mtimes, but
|
362 |
+
# would need to safely reload the module if it's out of
|
363 |
+
# date, so just report it as always current.
|
364 |
+
up_to_date = None
|
365 |
+
|
366 |
+
return source.decode(self.encoding), p, up_to_date
|
367 |
+
|
368 |
+
def list_templates(self) -> t.List[str]:
|
369 |
+
results: t.List[str] = []
|
370 |
+
|
371 |
+
if self._archive is None:
|
372 |
+
# Package is a directory.
|
373 |
+
offset = len(self._template_root)
|
374 |
+
|
375 |
+
for dirpath, _, filenames in os.walk(self._template_root):
|
376 |
+
dirpath = dirpath[offset:].lstrip(os.path.sep)
|
377 |
+
results.extend(
|
378 |
+
os.path.join(dirpath, name).replace(os.path.sep, "/")
|
379 |
+
for name in filenames
|
380 |
+
)
|
381 |
+
else:
|
382 |
+
if not hasattr(self._loader, "_files"):
|
383 |
+
raise TypeError(
|
384 |
+
"This zip import does not have the required"
|
385 |
+
" metadata to list templates."
|
386 |
+
)
|
387 |
+
|
388 |
+
# Package is a zip file.
|
389 |
+
prefix = (
|
390 |
+
self._template_root[len(self._archive) :].lstrip(os.path.sep)
|
391 |
+
+ os.path.sep
|
392 |
+
)
|
393 |
+
offset = len(prefix)
|
394 |
+
|
395 |
+
for name in self._loader._files.keys(): # type: ignore
|
396 |
+
# Find names under the templates directory that aren't directories.
|
397 |
+
if name.startswith(prefix) and name[-1] != os.path.sep:
|
398 |
+
results.append(name[offset:].replace(os.path.sep, "/"))
|
399 |
+
|
400 |
+
results.sort()
|
401 |
+
return results
|
402 |
+
|
403 |
+
|
404 |
+
class DictLoader(BaseLoader):
|
405 |
+
"""Loads a template from a Python dict mapping template names to
|
406 |
+
template source. This loader is useful for unittesting:
|
407 |
+
|
408 |
+
>>> loader = DictLoader({'index.html': 'source here'})
|
409 |
+
|
410 |
+
Because auto reloading is rarely useful this is disabled per default.
|
411 |
+
"""
|
412 |
+
|
413 |
+
def __init__(self, mapping: t.Mapping[str, str]) -> None:
|
414 |
+
self.mapping = mapping
|
415 |
+
|
416 |
+
def get_source(
|
417 |
+
self, environment: "Environment", template: str
|
418 |
+
) -> t.Tuple[str, None, t.Callable[[], bool]]:
|
419 |
+
if template in self.mapping:
|
420 |
+
source = self.mapping[template]
|
421 |
+
return source, None, lambda: source == self.mapping.get(template)
|
422 |
+
raise TemplateNotFound(template)
|
423 |
+
|
424 |
+
def list_templates(self) -> t.List[str]:
|
425 |
+
return sorted(self.mapping)
|
426 |
+
|
427 |
+
|
428 |
+
class FunctionLoader(BaseLoader):
|
429 |
+
"""A loader that is passed a function which does the loading. The
|
430 |
+
function receives the name of the template and has to return either
|
431 |
+
a string with the template source, a tuple in the form ``(source,
|
432 |
+
filename, uptodatefunc)`` or `None` if the template does not exist.
|
433 |
+
|
434 |
+
>>> def load_template(name):
|
435 |
+
... if name == 'index.html':
|
436 |
+
... return '...'
|
437 |
+
...
|
438 |
+
>>> loader = FunctionLoader(load_template)
|
439 |
+
|
440 |
+
The `uptodatefunc` is a function that is called if autoreload is enabled
|
441 |
+
and has to return `True` if the template is still up to date. For more
|
442 |
+
details have a look at :meth:`BaseLoader.get_source` which has the same
|
443 |
+
return value.
|
444 |
+
"""
|
445 |
+
|
446 |
+
def __init__(
|
447 |
+
self,
|
448 |
+
load_func: t.Callable[
|
449 |
+
[str],
|
450 |
+
t.Optional[
|
451 |
+
t.Union[
|
452 |
+
str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
|
453 |
+
]
|
454 |
+
],
|
455 |
+
],
|
456 |
+
) -> None:
|
457 |
+
self.load_func = load_func
|
458 |
+
|
459 |
+
def get_source(
|
460 |
+
self, environment: "Environment", template: str
|
461 |
+
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
462 |
+
rv = self.load_func(template)
|
463 |
+
|
464 |
+
if rv is None:
|
465 |
+
raise TemplateNotFound(template)
|
466 |
+
|
467 |
+
if isinstance(rv, str):
|
468 |
+
return rv, None, None
|
469 |
+
|
470 |
+
return rv
|
471 |
+
|
472 |
+
|
473 |
+
class PrefixLoader(BaseLoader):
|
474 |
+
"""A loader that is passed a dict of loaders where each loader is bound
|
475 |
+
to a prefix. The prefix is delimited from the template by a slash per
|
476 |
+
default, which can be changed by setting the `delimiter` argument to
|
477 |
+
something else::
|
478 |
+
|
479 |
+
loader = PrefixLoader({
|
480 |
+
'app1': PackageLoader('mypackage.app1'),
|
481 |
+
'app2': PackageLoader('mypackage.app2')
|
482 |
+
})
|
483 |
+
|
484 |
+
By loading ``'app1/index.html'`` the file from the app1 package is loaded,
|
485 |
+
by loading ``'app2/index.html'`` the file from the second.
|
486 |
+
"""
|
487 |
+
|
488 |
+
def __init__(
|
489 |
+
self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
|
490 |
+
) -> None:
|
491 |
+
self.mapping = mapping
|
492 |
+
self.delimiter = delimiter
|
493 |
+
|
494 |
+
def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
|
495 |
+
try:
|
496 |
+
prefix, name = template.split(self.delimiter, 1)
|
497 |
+
loader = self.mapping[prefix]
|
498 |
+
except (ValueError, KeyError) as e:
|
499 |
+
raise TemplateNotFound(template) from e
|
500 |
+
return loader, name
|
501 |
+
|
502 |
+
def get_source(
|
503 |
+
self, environment: "Environment", template: str
|
504 |
+
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
505 |
+
loader, name = self.get_loader(template)
|
506 |
+
try:
|
507 |
+
return loader.get_source(environment, name)
|
508 |
+
except TemplateNotFound as e:
|
509 |
+
# re-raise the exception with the correct filename here.
|
510 |
+
# (the one that includes the prefix)
|
511 |
+
raise TemplateNotFound(template) from e
|
512 |
+
|
513 |
+
@internalcode
|
514 |
+
def load(
|
515 |
+
self,
|
516 |
+
environment: "Environment",
|
517 |
+
name: str,
|
518 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
519 |
+
) -> "Template":
|
520 |
+
loader, local_name = self.get_loader(name)
|
521 |
+
try:
|
522 |
+
return loader.load(environment, local_name, globals)
|
523 |
+
except TemplateNotFound as e:
|
524 |
+
# re-raise the exception with the correct filename here.
|
525 |
+
# (the one that includes the prefix)
|
526 |
+
raise TemplateNotFound(name) from e
|
527 |
+
|
528 |
+
def list_templates(self) -> t.List[str]:
|
529 |
+
result = []
|
530 |
+
for prefix, loader in self.mapping.items():
|
531 |
+
for template in loader.list_templates():
|
532 |
+
result.append(prefix + self.delimiter + template)
|
533 |
+
return result
|
534 |
+
|
535 |
+
|
536 |
+
class ChoiceLoader(BaseLoader):
|
537 |
+
"""This loader works like the `PrefixLoader` just that no prefix is
|
538 |
+
specified. If a template could not be found by one loader the next one
|
539 |
+
is tried.
|
540 |
+
|
541 |
+
>>> loader = ChoiceLoader([
|
542 |
+
... FileSystemLoader('/path/to/user/templates'),
|
543 |
+
... FileSystemLoader('/path/to/system/templates')
|
544 |
+
... ])
|
545 |
+
|
546 |
+
This is useful if you want to allow users to override builtin templates
|
547 |
+
from a different location.
|
548 |
+
"""
|
549 |
+
|
550 |
+
def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
|
551 |
+
self.loaders = loaders
|
552 |
+
|
553 |
+
def get_source(
|
554 |
+
self, environment: "Environment", template: str
|
555 |
+
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
556 |
+
for loader in self.loaders:
|
557 |
+
try:
|
558 |
+
return loader.get_source(environment, template)
|
559 |
+
except TemplateNotFound:
|
560 |
+
pass
|
561 |
+
raise TemplateNotFound(template)
|
562 |
+
|
563 |
+
@internalcode
|
564 |
+
def load(
|
565 |
+
self,
|
566 |
+
environment: "Environment",
|
567 |
+
name: str,
|
568 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
569 |
+
) -> "Template":
|
570 |
+
for loader in self.loaders:
|
571 |
+
try:
|
572 |
+
return loader.load(environment, name, globals)
|
573 |
+
except TemplateNotFound:
|
574 |
+
pass
|
575 |
+
raise TemplateNotFound(name)
|
576 |
+
|
577 |
+
def list_templates(self) -> t.List[str]:
|
578 |
+
found = set()
|
579 |
+
for loader in self.loaders:
|
580 |
+
found.update(loader.list_templates())
|
581 |
+
return sorted(found)
|
582 |
+
|
583 |
+
|
584 |
+
class _TemplateModule(ModuleType):
|
585 |
+
"""Like a normal module but with support for weak references"""
|
586 |
+
|
587 |
+
|
588 |
+
class ModuleLoader(BaseLoader):
|
589 |
+
"""This loader loads templates from precompiled templates.
|
590 |
+
|
591 |
+
Example usage:
|
592 |
+
|
593 |
+
>>> loader = ChoiceLoader([
|
594 |
+
... ModuleLoader('/path/to/compiled/templates'),
|
595 |
+
... FileSystemLoader('/path/to/templates')
|
596 |
+
... ])
|
597 |
+
|
598 |
+
Templates can be precompiled with :meth:`Environment.compile_templates`.
|
599 |
+
"""
|
600 |
+
|
601 |
+
has_source_access = False
|
602 |
+
|
603 |
+
def __init__(
|
604 |
+
self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]]
|
605 |
+
) -> None:
|
606 |
+
package_name = f"_jinja2_module_templates_{id(self):x}"
|
607 |
+
|
608 |
+
# create a fake module that looks for the templates in the
|
609 |
+
# path given.
|
610 |
+
mod = _TemplateModule(package_name)
|
611 |
+
|
612 |
+
if not isinstance(path, abc.Iterable) or isinstance(path, str):
|
613 |
+
path = [path]
|
614 |
+
|
615 |
+
mod.__path__ = [os.fspath(p) for p in path]
|
616 |
+
|
617 |
+
sys.modules[package_name] = weakref.proxy(
|
618 |
+
mod, lambda x: sys.modules.pop(package_name, None)
|
619 |
+
)
|
620 |
+
|
621 |
+
# the only strong reference, the sys.modules entry is weak
|
622 |
+
# so that the garbage collector can remove it once the
|
623 |
+
# loader that created it goes out of business.
|
624 |
+
self.module = mod
|
625 |
+
self.package_name = package_name
|
626 |
+
|
627 |
+
@staticmethod
|
628 |
+
def get_template_key(name: str) -> str:
|
629 |
+
return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
|
630 |
+
|
631 |
+
@staticmethod
|
632 |
+
def get_module_filename(name: str) -> str:
|
633 |
+
return ModuleLoader.get_template_key(name) + ".py"
|
634 |
+
|
635 |
+
@internalcode
|
636 |
+
def load(
|
637 |
+
self,
|
638 |
+
environment: "Environment",
|
639 |
+
name: str,
|
640 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
641 |
+
) -> "Template":
|
642 |
+
key = self.get_template_key(name)
|
643 |
+
module = f"{self.package_name}.{key}"
|
644 |
+
mod = getattr(self.module, module, None)
|
645 |
+
|
646 |
+
if mod is None:
|
647 |
+
try:
|
648 |
+
mod = __import__(module, None, None, ["root"])
|
649 |
+
except ImportError as e:
|
650 |
+
raise TemplateNotFound(name) from e
|
651 |
+
|
652 |
+
# remove the entry from sys.modules, we only want the attribute
|
653 |
+
# on the module object we have stored on the loader.
|
654 |
+
sys.modules.pop(module, None)
|
655 |
+
|
656 |
+
if globals is None:
|
657 |
+
globals = {}
|
658 |
+
|
659 |
+
return environment.template_class.from_module_dict(
|
660 |
+
environment, mod.__dict__, globals
|
661 |
+
)
|
lib/python3.11/site-packages/jinja2/meta.py
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Functions that expose information about templates that might be
|
2 |
+
interesting for introspection.
|
3 |
+
"""
|
4 |
+
import typing as t
|
5 |
+
|
6 |
+
from . import nodes
|
7 |
+
from .compiler import CodeGenerator
|
8 |
+
from .compiler import Frame
|
9 |
+
|
10 |
+
if t.TYPE_CHECKING:
|
11 |
+
from .environment import Environment
|
12 |
+
|
13 |
+
|
14 |
+
class TrackingCodeGenerator(CodeGenerator):
|
15 |
+
"""We abuse the code generator for introspection."""
|
16 |
+
|
17 |
+
def __init__(self, environment: "Environment") -> None:
|
18 |
+
super().__init__(environment, "<introspection>", "<introspection>")
|
19 |
+
self.undeclared_identifiers: t.Set[str] = set()
|
20 |
+
|
21 |
+
def write(self, x: str) -> None:
|
22 |
+
"""Don't write."""
|
23 |
+
|
24 |
+
def enter_frame(self, frame: Frame) -> None:
|
25 |
+
"""Remember all undeclared identifiers."""
|
26 |
+
super().enter_frame(frame)
|
27 |
+
|
28 |
+
for _, (action, param) in frame.symbols.loads.items():
|
29 |
+
if action == "resolve" and param not in self.environment.globals:
|
30 |
+
self.undeclared_identifiers.add(param)
|
31 |
+
|
32 |
+
|
33 |
+
def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]:
|
34 |
+
"""Returns a set of all variables in the AST that will be looked up from
|
35 |
+
the context at runtime. Because at compile time it's not known which
|
36 |
+
variables will be used depending on the path the execution takes at
|
37 |
+
runtime, all variables are returned.
|
38 |
+
|
39 |
+
>>> from jinja2 import Environment, meta
|
40 |
+
>>> env = Environment()
|
41 |
+
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
42 |
+
>>> meta.find_undeclared_variables(ast) == {'bar'}
|
43 |
+
True
|
44 |
+
|
45 |
+
.. admonition:: Implementation
|
46 |
+
|
47 |
+
Internally the code generator is used for finding undeclared variables.
|
48 |
+
This is good to know because the code generator might raise a
|
49 |
+
:exc:`TemplateAssertionError` during compilation and as a matter of
|
50 |
+
fact this function can currently raise that exception as well.
|
51 |
+
"""
|
52 |
+
codegen = TrackingCodeGenerator(ast.environment) # type: ignore
|
53 |
+
codegen.visit(ast)
|
54 |
+
return codegen.undeclared_identifiers
|
55 |
+
|
56 |
+
|
57 |
+
_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)
|
58 |
+
_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]
|
59 |
+
|
60 |
+
|
61 |
+
def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:
|
62 |
+
"""Finds all the referenced templates from the AST. This will return an
|
63 |
+
iterator over all the hardcoded template extensions, inclusions and
|
64 |
+
imports. If dynamic inheritance or inclusion is used, `None` will be
|
65 |
+
yielded.
|
66 |
+
|
67 |
+
>>> from jinja2 import Environment, meta
|
68 |
+
>>> env = Environment()
|
69 |
+
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
|
70 |
+
>>> list(meta.find_referenced_templates(ast))
|
71 |
+
['layout.html', None]
|
72 |
+
|
73 |
+
This function is useful for dependency tracking. For example if you want
|
74 |
+
to rebuild parts of the website after a layout template has changed.
|
75 |
+
"""
|
76 |
+
template_name: t.Any
|
77 |
+
|
78 |
+
for node in ast.find_all(_ref_types):
|
79 |
+
template: nodes.Expr = node.template # type: ignore
|
80 |
+
|
81 |
+
if not isinstance(template, nodes.Const):
|
82 |
+
# a tuple with some non consts in there
|
83 |
+
if isinstance(template, (nodes.Tuple, nodes.List)):
|
84 |
+
for template_name in template.items:
|
85 |
+
# something const, only yield the strings and ignore
|
86 |
+
# non-string consts that really just make no sense
|
87 |
+
if isinstance(template_name, nodes.Const):
|
88 |
+
if isinstance(template_name.value, str):
|
89 |
+
yield template_name.value
|
90 |
+
# something dynamic in there
|
91 |
+
else:
|
92 |
+
yield None
|
93 |
+
# something dynamic we don't know about here
|
94 |
+
else:
|
95 |
+
yield None
|
96 |
+
continue
|
97 |
+
# constant is a basestring, direct template name
|
98 |
+
if isinstance(template.value, str):
|
99 |
+
yield template.value
|
100 |
+
# a tuple or list (latter *should* not happen) made of consts,
|
101 |
+
# yield the consts that are strings. We could warn here for
|
102 |
+
# non string values
|
103 |
+
elif isinstance(node, nodes.Include) and isinstance(
|
104 |
+
template.value, (tuple, list)
|
105 |
+
):
|
106 |
+
for template_name in template.value:
|
107 |
+
if isinstance(template_name, str):
|
108 |
+
yield template_name
|
109 |
+
# something else we don't care about, we could warn here
|
110 |
+
else:
|
111 |
+
yield None
|
lib/python3.11/site-packages/jinja2/nativetypes.py
ADDED
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import typing as t
|
2 |
+
from ast import literal_eval
|
3 |
+
from ast import parse
|
4 |
+
from itertools import chain
|
5 |
+
from itertools import islice
|
6 |
+
from types import GeneratorType
|
7 |
+
|
8 |
+
from . import nodes
|
9 |
+
from .compiler import CodeGenerator
|
10 |
+
from .compiler import Frame
|
11 |
+
from .compiler import has_safe_repr
|
12 |
+
from .environment import Environment
|
13 |
+
from .environment import Template
|
14 |
+
|
15 |
+
|
16 |
+
def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
|
17 |
+
"""Return a native Python type from the list of compiled nodes. If
|
18 |
+
the result is a single node, its value is returned. Otherwise, the
|
19 |
+
nodes are concatenated as strings. If the result can be parsed with
|
20 |
+
:func:`ast.literal_eval`, the parsed value is returned. Otherwise,
|
21 |
+
the string is returned.
|
22 |
+
|
23 |
+
:param values: Iterable of outputs to concatenate.
|
24 |
+
"""
|
25 |
+
head = list(islice(values, 2))
|
26 |
+
|
27 |
+
if not head:
|
28 |
+
return None
|
29 |
+
|
30 |
+
if len(head) == 1:
|
31 |
+
raw = head[0]
|
32 |
+
if not isinstance(raw, str):
|
33 |
+
return raw
|
34 |
+
else:
|
35 |
+
if isinstance(values, GeneratorType):
|
36 |
+
values = chain(head, values)
|
37 |
+
raw = "".join([str(v) for v in values])
|
38 |
+
|
39 |
+
try:
|
40 |
+
return literal_eval(
|
41 |
+
# In Python 3.10+ ast.literal_eval removes leading spaces/tabs
|
42 |
+
# from the given string. For backwards compatibility we need to
|
43 |
+
# parse the string ourselves without removing leading spaces/tabs.
|
44 |
+
parse(raw, mode="eval")
|
45 |
+
)
|
46 |
+
except (ValueError, SyntaxError, MemoryError):
|
47 |
+
return raw
|
48 |
+
|
49 |
+
|
50 |
+
class NativeCodeGenerator(CodeGenerator):
|
51 |
+
"""A code generator which renders Python types by not adding
|
52 |
+
``str()`` around output nodes.
|
53 |
+
"""
|
54 |
+
|
55 |
+
@staticmethod
|
56 |
+
def _default_finalize(value: t.Any) -> t.Any:
|
57 |
+
return value
|
58 |
+
|
59 |
+
def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
|
60 |
+
return repr("".join([str(v) for v in group]))
|
61 |
+
|
62 |
+
def _output_child_to_const(
|
63 |
+
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
64 |
+
) -> t.Any:
|
65 |
+
const = node.as_const(frame.eval_ctx)
|
66 |
+
|
67 |
+
if not has_safe_repr(const):
|
68 |
+
raise nodes.Impossible()
|
69 |
+
|
70 |
+
if isinstance(node, nodes.TemplateData):
|
71 |
+
return const
|
72 |
+
|
73 |
+
return finalize.const(const) # type: ignore
|
74 |
+
|
75 |
+
def _output_child_pre(
|
76 |
+
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
77 |
+
) -> None:
|
78 |
+
if finalize.src is not None:
|
79 |
+
self.write(finalize.src)
|
80 |
+
|
81 |
+
def _output_child_post(
|
82 |
+
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
83 |
+
) -> None:
|
84 |
+
if finalize.src is not None:
|
85 |
+
self.write(")")
|
86 |
+
|
87 |
+
|
88 |
+
class NativeEnvironment(Environment):
|
89 |
+
"""An environment that renders templates to native Python types."""
|
90 |
+
|
91 |
+
code_generator_class = NativeCodeGenerator
|
92 |
+
concat = staticmethod(native_concat) # type: ignore
|
93 |
+
|
94 |
+
|
95 |
+
class NativeTemplate(Template):
|
96 |
+
environment_class = NativeEnvironment
|
97 |
+
|
98 |
+
def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
99 |
+
"""Render the template to produce a native Python type. If the
|
100 |
+
result is a single node, its value is returned. Otherwise, the
|
101 |
+
nodes are concatenated as strings. If the result can be parsed
|
102 |
+
with :func:`ast.literal_eval`, the parsed value is returned.
|
103 |
+
Otherwise, the string is returned.
|
104 |
+
"""
|
105 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
106 |
+
|
107 |
+
try:
|
108 |
+
return self.environment_class.concat( # type: ignore
|
109 |
+
self.root_render_func(ctx) # type: ignore
|
110 |
+
)
|
111 |
+
except Exception:
|
112 |
+
return self.environment.handle_exception()
|
113 |
+
|
114 |
+
async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
115 |
+
if not self.environment.is_async:
|
116 |
+
raise RuntimeError(
|
117 |
+
"The environment was not created with async mode enabled."
|
118 |
+
)
|
119 |
+
|
120 |
+
ctx = self.new_context(dict(*args, **kwargs))
|
121 |
+
|
122 |
+
try:
|
123 |
+
return self.environment_class.concat( # type: ignore
|
124 |
+
[n async for n in self.root_render_func(ctx)] # type: ignore
|
125 |
+
)
|
126 |
+
except Exception:
|
127 |
+
return self.environment.handle_exception()
|
128 |
+
|
129 |
+
|
130 |
+
NativeEnvironment.template_class = NativeTemplate
|
lib/python3.11/site-packages/jinja2/nodes.py
ADDED
@@ -0,0 +1,1204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""AST nodes generated by the parser for the compiler. Also provides
|
2 |
+
some node tree helper functions used by the parser and compiler in order
|
3 |
+
to normalize nodes.
|
4 |
+
"""
|
5 |
+
import inspect
|
6 |
+
import operator
|
7 |
+
import typing as t
|
8 |
+
from collections import deque
|
9 |
+
|
10 |
+
from markupsafe import Markup
|
11 |
+
|
12 |
+
from .utils import _PassArg
|
13 |
+
|
14 |
+
if t.TYPE_CHECKING:
|
15 |
+
import typing_extensions as te
|
16 |
+
from .environment import Environment
|
17 |
+
|
18 |
+
_NodeBound = t.TypeVar("_NodeBound", bound="Node")
|
19 |
+
|
20 |
+
_binop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
21 |
+
"*": operator.mul,
|
22 |
+
"/": operator.truediv,
|
23 |
+
"//": operator.floordiv,
|
24 |
+
"**": operator.pow,
|
25 |
+
"%": operator.mod,
|
26 |
+
"+": operator.add,
|
27 |
+
"-": operator.sub,
|
28 |
+
}
|
29 |
+
|
30 |
+
_uaop_to_func: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
|
31 |
+
"not": operator.not_,
|
32 |
+
"+": operator.pos,
|
33 |
+
"-": operator.neg,
|
34 |
+
}
|
35 |
+
|
36 |
+
_cmpop_to_func: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
37 |
+
"eq": operator.eq,
|
38 |
+
"ne": operator.ne,
|
39 |
+
"gt": operator.gt,
|
40 |
+
"gteq": operator.ge,
|
41 |
+
"lt": operator.lt,
|
42 |
+
"lteq": operator.le,
|
43 |
+
"in": lambda a, b: a in b,
|
44 |
+
"notin": lambda a, b: a not in b,
|
45 |
+
}
|
46 |
+
|
47 |
+
|
48 |
+
class Impossible(Exception):
|
49 |
+
"""Raised if the node could not perform a requested action."""
|
50 |
+
|
51 |
+
|
52 |
+
class NodeType(type):
|
53 |
+
"""A metaclass for nodes that handles the field and attribute
|
54 |
+
inheritance. fields and attributes from the parent class are
|
55 |
+
automatically forwarded to the child."""
|
56 |
+
|
57 |
+
def __new__(mcs, name, bases, d): # type: ignore
|
58 |
+
for attr in "fields", "attributes":
|
59 |
+
storage = []
|
60 |
+
storage.extend(getattr(bases[0] if bases else object, attr, ()))
|
61 |
+
storage.extend(d.get(attr, ()))
|
62 |
+
assert len(bases) <= 1, "multiple inheritance not allowed"
|
63 |
+
assert len(storage) == len(set(storage)), "layout conflict"
|
64 |
+
d[attr] = tuple(storage)
|
65 |
+
d.setdefault("abstract", False)
|
66 |
+
return type.__new__(mcs, name, bases, d)
|
67 |
+
|
68 |
+
|
69 |
+
class EvalContext:
|
70 |
+
"""Holds evaluation time information. Custom attributes can be attached
|
71 |
+
to it in extensions.
|
72 |
+
"""
|
73 |
+
|
74 |
+
def __init__(
|
75 |
+
self, environment: "Environment", template_name: t.Optional[str] = None
|
76 |
+
) -> None:
|
77 |
+
self.environment = environment
|
78 |
+
if callable(environment.autoescape):
|
79 |
+
self.autoescape = environment.autoescape(template_name)
|
80 |
+
else:
|
81 |
+
self.autoescape = environment.autoescape
|
82 |
+
self.volatile = False
|
83 |
+
|
84 |
+
def save(self) -> t.Mapping[str, t.Any]:
|
85 |
+
return self.__dict__.copy()
|
86 |
+
|
87 |
+
def revert(self, old: t.Mapping[str, t.Any]) -> None:
|
88 |
+
self.__dict__.clear()
|
89 |
+
self.__dict__.update(old)
|
90 |
+
|
91 |
+
|
92 |
+
def get_eval_context(node: "Node", ctx: t.Optional[EvalContext]) -> EvalContext:
|
93 |
+
if ctx is None:
|
94 |
+
if node.environment is None:
|
95 |
+
raise RuntimeError(
|
96 |
+
"if no eval context is passed, the node must have an"
|
97 |
+
" attached environment."
|
98 |
+
)
|
99 |
+
return EvalContext(node.environment)
|
100 |
+
return ctx
|
101 |
+
|
102 |
+
|
103 |
+
class Node(metaclass=NodeType):
|
104 |
+
"""Baseclass for all Jinja nodes. There are a number of nodes available
|
105 |
+
of different types. There are four major types:
|
106 |
+
|
107 |
+
- :class:`Stmt`: statements
|
108 |
+
- :class:`Expr`: expressions
|
109 |
+
- :class:`Helper`: helper nodes
|
110 |
+
- :class:`Template`: the outermost wrapper node
|
111 |
+
|
112 |
+
All nodes have fields and attributes. Fields may be other nodes, lists,
|
113 |
+
or arbitrary values. Fields are passed to the constructor as regular
|
114 |
+
positional arguments, attributes as keyword arguments. Each node has
|
115 |
+
two attributes: `lineno` (the line number of the node) and `environment`.
|
116 |
+
The `environment` attribute is set at the end of the parsing process for
|
117 |
+
all nodes automatically.
|
118 |
+
"""
|
119 |
+
|
120 |
+
fields: t.Tuple[str, ...] = ()
|
121 |
+
attributes: t.Tuple[str, ...] = ("lineno", "environment")
|
122 |
+
abstract = True
|
123 |
+
|
124 |
+
lineno: int
|
125 |
+
environment: t.Optional["Environment"]
|
126 |
+
|
127 |
+
def __init__(self, *fields: t.Any, **attributes: t.Any) -> None:
|
128 |
+
if self.abstract:
|
129 |
+
raise TypeError("abstract nodes are not instantiable")
|
130 |
+
if fields:
|
131 |
+
if len(fields) != len(self.fields):
|
132 |
+
if not self.fields:
|
133 |
+
raise TypeError(f"{type(self).__name__!r} takes 0 arguments")
|
134 |
+
raise TypeError(
|
135 |
+
f"{type(self).__name__!r} takes 0 or {len(self.fields)}"
|
136 |
+
f" argument{'s' if len(self.fields) != 1 else ''}"
|
137 |
+
)
|
138 |
+
for name, arg in zip(self.fields, fields):
|
139 |
+
setattr(self, name, arg)
|
140 |
+
for attr in self.attributes:
|
141 |
+
setattr(self, attr, attributes.pop(attr, None))
|
142 |
+
if attributes:
|
143 |
+
raise TypeError(f"unknown attribute {next(iter(attributes))!r}")
|
144 |
+
|
145 |
+
def iter_fields(
|
146 |
+
self,
|
147 |
+
exclude: t.Optional[t.Container[str]] = None,
|
148 |
+
only: t.Optional[t.Container[str]] = None,
|
149 |
+
) -> t.Iterator[t.Tuple[str, t.Any]]:
|
150 |
+
"""This method iterates over all fields that are defined and yields
|
151 |
+
``(key, value)`` tuples. Per default all fields are returned, but
|
152 |
+
it's possible to limit that to some fields by providing the `only`
|
153 |
+
parameter or to exclude some using the `exclude` parameter. Both
|
154 |
+
should be sets or tuples of field names.
|
155 |
+
"""
|
156 |
+
for name in self.fields:
|
157 |
+
if (
|
158 |
+
(exclude is None and only is None)
|
159 |
+
or (exclude is not None and name not in exclude)
|
160 |
+
or (only is not None and name in only)
|
161 |
+
):
|
162 |
+
try:
|
163 |
+
yield name, getattr(self, name)
|
164 |
+
except AttributeError:
|
165 |
+
pass
|
166 |
+
|
167 |
+
def iter_child_nodes(
|
168 |
+
self,
|
169 |
+
exclude: t.Optional[t.Container[str]] = None,
|
170 |
+
only: t.Optional[t.Container[str]] = None,
|
171 |
+
) -> t.Iterator["Node"]:
|
172 |
+
"""Iterates over all direct child nodes of the node. This iterates
|
173 |
+
over all fields and yields the values of they are nodes. If the value
|
174 |
+
of a field is a list all the nodes in that list are returned.
|
175 |
+
"""
|
176 |
+
for _, item in self.iter_fields(exclude, only):
|
177 |
+
if isinstance(item, list):
|
178 |
+
for n in item:
|
179 |
+
if isinstance(n, Node):
|
180 |
+
yield n
|
181 |
+
elif isinstance(item, Node):
|
182 |
+
yield item
|
183 |
+
|
184 |
+
def find(self, node_type: t.Type[_NodeBound]) -> t.Optional[_NodeBound]:
|
185 |
+
"""Find the first node of a given type. If no such node exists the
|
186 |
+
return value is `None`.
|
187 |
+
"""
|
188 |
+
for result in self.find_all(node_type):
|
189 |
+
return result
|
190 |
+
|
191 |
+
return None
|
192 |
+
|
193 |
+
def find_all(
|
194 |
+
self, node_type: t.Union[t.Type[_NodeBound], t.Tuple[t.Type[_NodeBound], ...]]
|
195 |
+
) -> t.Iterator[_NodeBound]:
|
196 |
+
"""Find all the nodes of a given type. If the type is a tuple,
|
197 |
+
the check is performed for any of the tuple items.
|
198 |
+
"""
|
199 |
+
for child in self.iter_child_nodes():
|
200 |
+
if isinstance(child, node_type):
|
201 |
+
yield child # type: ignore
|
202 |
+
yield from child.find_all(node_type)
|
203 |
+
|
204 |
+
def set_ctx(self, ctx: str) -> "Node":
|
205 |
+
"""Reset the context of a node and all child nodes. Per default the
|
206 |
+
parser will all generate nodes that have a 'load' context as it's the
|
207 |
+
most common one. This method is used in the parser to set assignment
|
208 |
+
targets and other nodes to a store context.
|
209 |
+
"""
|
210 |
+
todo = deque([self])
|
211 |
+
while todo:
|
212 |
+
node = todo.popleft()
|
213 |
+
if "ctx" in node.fields:
|
214 |
+
node.ctx = ctx # type: ignore
|
215 |
+
todo.extend(node.iter_child_nodes())
|
216 |
+
return self
|
217 |
+
|
218 |
+
def set_lineno(self, lineno: int, override: bool = False) -> "Node":
|
219 |
+
"""Set the line numbers of the node and children."""
|
220 |
+
todo = deque([self])
|
221 |
+
while todo:
|
222 |
+
node = todo.popleft()
|
223 |
+
if "lineno" in node.attributes:
|
224 |
+
if node.lineno is None or override:
|
225 |
+
node.lineno = lineno
|
226 |
+
todo.extend(node.iter_child_nodes())
|
227 |
+
return self
|
228 |
+
|
229 |
+
def set_environment(self, environment: "Environment") -> "Node":
|
230 |
+
"""Set the environment for all nodes."""
|
231 |
+
todo = deque([self])
|
232 |
+
while todo:
|
233 |
+
node = todo.popleft()
|
234 |
+
node.environment = environment
|
235 |
+
todo.extend(node.iter_child_nodes())
|
236 |
+
return self
|
237 |
+
|
238 |
+
def __eq__(self, other: t.Any) -> bool:
|
239 |
+
if type(self) is not type(other):
|
240 |
+
return NotImplemented
|
241 |
+
|
242 |
+
return tuple(self.iter_fields()) == tuple(other.iter_fields())
|
243 |
+
|
244 |
+
__hash__ = object.__hash__
|
245 |
+
|
246 |
+
def __repr__(self) -> str:
|
247 |
+
args_str = ", ".join(f"{a}={getattr(self, a, None)!r}" for a in self.fields)
|
248 |
+
return f"{type(self).__name__}({args_str})"
|
249 |
+
|
250 |
+
def dump(self) -> str:
|
251 |
+
def _dump(node: t.Union[Node, t.Any]) -> None:
|
252 |
+
if not isinstance(node, Node):
|
253 |
+
buf.append(repr(node))
|
254 |
+
return
|
255 |
+
|
256 |
+
buf.append(f"nodes.{type(node).__name__}(")
|
257 |
+
if not node.fields:
|
258 |
+
buf.append(")")
|
259 |
+
return
|
260 |
+
for idx, field in enumerate(node.fields):
|
261 |
+
if idx:
|
262 |
+
buf.append(", ")
|
263 |
+
value = getattr(node, field)
|
264 |
+
if isinstance(value, list):
|
265 |
+
buf.append("[")
|
266 |
+
for idx, item in enumerate(value):
|
267 |
+
if idx:
|
268 |
+
buf.append(", ")
|
269 |
+
_dump(item)
|
270 |
+
buf.append("]")
|
271 |
+
else:
|
272 |
+
_dump(value)
|
273 |
+
buf.append(")")
|
274 |
+
|
275 |
+
buf: t.List[str] = []
|
276 |
+
_dump(self)
|
277 |
+
return "".join(buf)
|
278 |
+
|
279 |
+
|
280 |
+
class Stmt(Node):
|
281 |
+
"""Base node for all statements."""
|
282 |
+
|
283 |
+
abstract = True
|
284 |
+
|
285 |
+
|
286 |
+
class Helper(Node):
|
287 |
+
"""Nodes that exist in a specific context only."""
|
288 |
+
|
289 |
+
abstract = True
|
290 |
+
|
291 |
+
|
292 |
+
class Template(Node):
|
293 |
+
"""Node that represents a template. This must be the outermost node that
|
294 |
+
is passed to the compiler.
|
295 |
+
"""
|
296 |
+
|
297 |
+
fields = ("body",)
|
298 |
+
body: t.List[Node]
|
299 |
+
|
300 |
+
|
301 |
+
class Output(Stmt):
|
302 |
+
"""A node that holds multiple expressions which are then printed out.
|
303 |
+
This is used both for the `print` statement and the regular template data.
|
304 |
+
"""
|
305 |
+
|
306 |
+
fields = ("nodes",)
|
307 |
+
nodes: t.List["Expr"]
|
308 |
+
|
309 |
+
|
310 |
+
class Extends(Stmt):
|
311 |
+
"""Represents an extends statement."""
|
312 |
+
|
313 |
+
fields = ("template",)
|
314 |
+
template: "Expr"
|
315 |
+
|
316 |
+
|
317 |
+
class For(Stmt):
|
318 |
+
"""The for loop. `target` is the target for the iteration (usually a
|
319 |
+
:class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list
|
320 |
+
of nodes that are used as loop-body, and `else_` a list of nodes for the
|
321 |
+
`else` block. If no else node exists it has to be an empty list.
|
322 |
+
|
323 |
+
For filtered nodes an expression can be stored as `test`, otherwise `None`.
|
324 |
+
"""
|
325 |
+
|
326 |
+
fields = ("target", "iter", "body", "else_", "test", "recursive")
|
327 |
+
target: Node
|
328 |
+
iter: Node
|
329 |
+
body: t.List[Node]
|
330 |
+
else_: t.List[Node]
|
331 |
+
test: t.Optional[Node]
|
332 |
+
recursive: bool
|
333 |
+
|
334 |
+
|
335 |
+
class If(Stmt):
|
336 |
+
"""If `test` is true, `body` is rendered, else `else_`."""
|
337 |
+
|
338 |
+
fields = ("test", "body", "elif_", "else_")
|
339 |
+
test: Node
|
340 |
+
body: t.List[Node]
|
341 |
+
elif_: t.List["If"]
|
342 |
+
else_: t.List[Node]
|
343 |
+
|
344 |
+
|
345 |
+
class Macro(Stmt):
|
346 |
+
"""A macro definition. `name` is the name of the macro, `args` a list of
|
347 |
+
arguments and `defaults` a list of defaults if there are any. `body` is
|
348 |
+
a list of nodes for the macro body.
|
349 |
+
"""
|
350 |
+
|
351 |
+
fields = ("name", "args", "defaults", "body")
|
352 |
+
name: str
|
353 |
+
args: t.List["Name"]
|
354 |
+
defaults: t.List["Expr"]
|
355 |
+
body: t.List[Node]
|
356 |
+
|
357 |
+
|
358 |
+
class CallBlock(Stmt):
|
359 |
+
"""Like a macro without a name but a call instead. `call` is called with
|
360 |
+
the unnamed macro as `caller` argument this node holds.
|
361 |
+
"""
|
362 |
+
|
363 |
+
fields = ("call", "args", "defaults", "body")
|
364 |
+
call: "Call"
|
365 |
+
args: t.List["Name"]
|
366 |
+
defaults: t.List["Expr"]
|
367 |
+
body: t.List[Node]
|
368 |
+
|
369 |
+
|
370 |
+
class FilterBlock(Stmt):
|
371 |
+
"""Node for filter sections."""
|
372 |
+
|
373 |
+
fields = ("body", "filter")
|
374 |
+
body: t.List[Node]
|
375 |
+
filter: "Filter"
|
376 |
+
|
377 |
+
|
378 |
+
class With(Stmt):
|
379 |
+
"""Specific node for with statements. In older versions of Jinja the
|
380 |
+
with statement was implemented on the base of the `Scope` node instead.
|
381 |
+
|
382 |
+
.. versionadded:: 2.9.3
|
383 |
+
"""
|
384 |
+
|
385 |
+
fields = ("targets", "values", "body")
|
386 |
+
targets: t.List["Expr"]
|
387 |
+
values: t.List["Expr"]
|
388 |
+
body: t.List[Node]
|
389 |
+
|
390 |
+
|
391 |
+
class Block(Stmt):
|
392 |
+
"""A node that represents a block.
|
393 |
+
|
394 |
+
.. versionchanged:: 3.0.0
|
395 |
+
the `required` field was added.
|
396 |
+
"""
|
397 |
+
|
398 |
+
fields = ("name", "body", "scoped", "required")
|
399 |
+
name: str
|
400 |
+
body: t.List[Node]
|
401 |
+
scoped: bool
|
402 |
+
required: bool
|
403 |
+
|
404 |
+
|
405 |
+
class Include(Stmt):
|
406 |
+
"""A node that represents the include tag."""
|
407 |
+
|
408 |
+
fields = ("template", "with_context", "ignore_missing")
|
409 |
+
template: "Expr"
|
410 |
+
with_context: bool
|
411 |
+
ignore_missing: bool
|
412 |
+
|
413 |
+
|
414 |
+
class Import(Stmt):
|
415 |
+
"""A node that represents the import tag."""
|
416 |
+
|
417 |
+
fields = ("template", "target", "with_context")
|
418 |
+
template: "Expr"
|
419 |
+
target: str
|
420 |
+
with_context: bool
|
421 |
+
|
422 |
+
|
423 |
+
class FromImport(Stmt):
|
424 |
+
"""A node that represents the from import tag. It's important to not
|
425 |
+
pass unsafe names to the name attribute. The compiler translates the
|
426 |
+
attribute lookups directly into getattr calls and does *not* use the
|
427 |
+
subscript callback of the interface. As exported variables may not
|
428 |
+
start with double underscores (which the parser asserts) this is not a
|
429 |
+
problem for regular Jinja code, but if this node is used in an extension
|
430 |
+
extra care must be taken.
|
431 |
+
|
432 |
+
The list of names may contain tuples if aliases are wanted.
|
433 |
+
"""
|
434 |
+
|
435 |
+
fields = ("template", "names", "with_context")
|
436 |
+
template: "Expr"
|
437 |
+
names: t.List[t.Union[str, t.Tuple[str, str]]]
|
438 |
+
with_context: bool
|
439 |
+
|
440 |
+
|
441 |
+
class ExprStmt(Stmt):
|
442 |
+
"""A statement that evaluates an expression and discards the result."""
|
443 |
+
|
444 |
+
fields = ("node",)
|
445 |
+
node: Node
|
446 |
+
|
447 |
+
|
448 |
+
class Assign(Stmt):
|
449 |
+
"""Assigns an expression to a target."""
|
450 |
+
|
451 |
+
fields = ("target", "node")
|
452 |
+
target: "Expr"
|
453 |
+
node: Node
|
454 |
+
|
455 |
+
|
456 |
+
class AssignBlock(Stmt):
|
457 |
+
"""Assigns a block to a target."""
|
458 |
+
|
459 |
+
fields = ("target", "filter", "body")
|
460 |
+
target: "Expr"
|
461 |
+
filter: t.Optional["Filter"]
|
462 |
+
body: t.List[Node]
|
463 |
+
|
464 |
+
|
465 |
+
class Expr(Node):
|
466 |
+
"""Baseclass for all expressions."""
|
467 |
+
|
468 |
+
abstract = True
|
469 |
+
|
470 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
471 |
+
"""Return the value of the expression as constant or raise
|
472 |
+
:exc:`Impossible` if this was not possible.
|
473 |
+
|
474 |
+
An :class:`EvalContext` can be provided, if none is given
|
475 |
+
a default context is created which requires the nodes to have
|
476 |
+
an attached environment.
|
477 |
+
|
478 |
+
.. versionchanged:: 2.4
|
479 |
+
the `eval_ctx` parameter was added.
|
480 |
+
"""
|
481 |
+
raise Impossible()
|
482 |
+
|
483 |
+
def can_assign(self) -> bool:
|
484 |
+
"""Check if it's possible to assign something to this node."""
|
485 |
+
return False
|
486 |
+
|
487 |
+
|
488 |
+
class BinExpr(Expr):
|
489 |
+
"""Baseclass for all binary expressions."""
|
490 |
+
|
491 |
+
fields = ("left", "right")
|
492 |
+
left: Expr
|
493 |
+
right: Expr
|
494 |
+
operator: str
|
495 |
+
abstract = True
|
496 |
+
|
497 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
498 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
499 |
+
|
500 |
+
# intercepted operators cannot be folded at compile time
|
501 |
+
if (
|
502 |
+
eval_ctx.environment.sandboxed
|
503 |
+
and self.operator in eval_ctx.environment.intercepted_binops # type: ignore
|
504 |
+
):
|
505 |
+
raise Impossible()
|
506 |
+
f = _binop_to_func[self.operator]
|
507 |
+
try:
|
508 |
+
return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
|
509 |
+
except Exception as e:
|
510 |
+
raise Impossible() from e
|
511 |
+
|
512 |
+
|
513 |
+
class UnaryExpr(Expr):
|
514 |
+
"""Baseclass for all unary expressions."""
|
515 |
+
|
516 |
+
fields = ("node",)
|
517 |
+
node: Expr
|
518 |
+
operator: str
|
519 |
+
abstract = True
|
520 |
+
|
521 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
522 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
523 |
+
|
524 |
+
# intercepted operators cannot be folded at compile time
|
525 |
+
if (
|
526 |
+
eval_ctx.environment.sandboxed
|
527 |
+
and self.operator in eval_ctx.environment.intercepted_unops # type: ignore
|
528 |
+
):
|
529 |
+
raise Impossible()
|
530 |
+
f = _uaop_to_func[self.operator]
|
531 |
+
try:
|
532 |
+
return f(self.node.as_const(eval_ctx))
|
533 |
+
except Exception as e:
|
534 |
+
raise Impossible() from e
|
535 |
+
|
536 |
+
|
537 |
+
class Name(Expr):
|
538 |
+
"""Looks up a name or stores a value in a name.
|
539 |
+
The `ctx` of the node can be one of the following values:
|
540 |
+
|
541 |
+
- `store`: store a value in the name
|
542 |
+
- `load`: load that name
|
543 |
+
- `param`: like `store` but if the name was defined as function parameter.
|
544 |
+
"""
|
545 |
+
|
546 |
+
fields = ("name", "ctx")
|
547 |
+
name: str
|
548 |
+
ctx: str
|
549 |
+
|
550 |
+
def can_assign(self) -> bool:
|
551 |
+
return self.name not in {"true", "false", "none", "True", "False", "None"}
|
552 |
+
|
553 |
+
|
554 |
+
class NSRef(Expr):
|
555 |
+
"""Reference to a namespace value assignment"""
|
556 |
+
|
557 |
+
fields = ("name", "attr")
|
558 |
+
name: str
|
559 |
+
attr: str
|
560 |
+
|
561 |
+
def can_assign(self) -> bool:
|
562 |
+
# We don't need any special checks here; NSRef assignments have a
|
563 |
+
# runtime check to ensure the target is a namespace object which will
|
564 |
+
# have been checked already as it is created using a normal assignment
|
565 |
+
# which goes through a `Name` node.
|
566 |
+
return True
|
567 |
+
|
568 |
+
|
569 |
+
class Literal(Expr):
|
570 |
+
"""Baseclass for literals."""
|
571 |
+
|
572 |
+
abstract = True
|
573 |
+
|
574 |
+
|
575 |
+
class Const(Literal):
|
576 |
+
"""All constant values. The parser will return this node for simple
|
577 |
+
constants such as ``42`` or ``"foo"`` but it can be used to store more
|
578 |
+
complex values such as lists too. Only constants with a safe
|
579 |
+
representation (objects where ``eval(repr(x)) == x`` is true).
|
580 |
+
"""
|
581 |
+
|
582 |
+
fields = ("value",)
|
583 |
+
value: t.Any
|
584 |
+
|
585 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
586 |
+
return self.value
|
587 |
+
|
588 |
+
@classmethod
|
589 |
+
def from_untrusted(
|
590 |
+
cls,
|
591 |
+
value: t.Any,
|
592 |
+
lineno: t.Optional[int] = None,
|
593 |
+
environment: "t.Optional[Environment]" = None,
|
594 |
+
) -> "Const":
|
595 |
+
"""Return a const object if the value is representable as
|
596 |
+
constant value in the generated code, otherwise it will raise
|
597 |
+
an `Impossible` exception.
|
598 |
+
"""
|
599 |
+
from .compiler import has_safe_repr
|
600 |
+
|
601 |
+
if not has_safe_repr(value):
|
602 |
+
raise Impossible()
|
603 |
+
return cls(value, lineno=lineno, environment=environment)
|
604 |
+
|
605 |
+
|
606 |
+
class TemplateData(Literal):
|
607 |
+
"""A constant template string."""
|
608 |
+
|
609 |
+
fields = ("data",)
|
610 |
+
data: str
|
611 |
+
|
612 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
|
613 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
614 |
+
if eval_ctx.volatile:
|
615 |
+
raise Impossible()
|
616 |
+
if eval_ctx.autoescape:
|
617 |
+
return Markup(self.data)
|
618 |
+
return self.data
|
619 |
+
|
620 |
+
|
621 |
+
class Tuple(Literal):
|
622 |
+
"""For loop unpacking and some other things like multiple arguments
|
623 |
+
for subscripts. Like for :class:`Name` `ctx` specifies if the tuple
|
624 |
+
is used for loading the names or storing.
|
625 |
+
"""
|
626 |
+
|
627 |
+
fields = ("items", "ctx")
|
628 |
+
items: t.List[Expr]
|
629 |
+
ctx: str
|
630 |
+
|
631 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[t.Any, ...]:
|
632 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
633 |
+
return tuple(x.as_const(eval_ctx) for x in self.items)
|
634 |
+
|
635 |
+
def can_assign(self) -> bool:
|
636 |
+
for item in self.items:
|
637 |
+
if not item.can_assign():
|
638 |
+
return False
|
639 |
+
return True
|
640 |
+
|
641 |
+
|
642 |
+
class List(Literal):
|
643 |
+
"""Any list literal such as ``[1, 2, 3]``"""
|
644 |
+
|
645 |
+
fields = ("items",)
|
646 |
+
items: t.List[Expr]
|
647 |
+
|
648 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.List[t.Any]:
|
649 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
650 |
+
return [x.as_const(eval_ctx) for x in self.items]
|
651 |
+
|
652 |
+
|
653 |
+
class Dict(Literal):
|
654 |
+
"""Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of
|
655 |
+
:class:`Pair` nodes.
|
656 |
+
"""
|
657 |
+
|
658 |
+
fields = ("items",)
|
659 |
+
items: t.List["Pair"]
|
660 |
+
|
661 |
+
def as_const(
|
662 |
+
self, eval_ctx: t.Optional[EvalContext] = None
|
663 |
+
) -> t.Dict[t.Any, t.Any]:
|
664 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
665 |
+
return dict(x.as_const(eval_ctx) for x in self.items)
|
666 |
+
|
667 |
+
|
668 |
+
class Pair(Helper):
|
669 |
+
"""A key, value pair for dicts."""
|
670 |
+
|
671 |
+
fields = ("key", "value")
|
672 |
+
key: Expr
|
673 |
+
value: Expr
|
674 |
+
|
675 |
+
def as_const(
|
676 |
+
self, eval_ctx: t.Optional[EvalContext] = None
|
677 |
+
) -> t.Tuple[t.Any, t.Any]:
|
678 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
679 |
+
return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
|
680 |
+
|
681 |
+
|
682 |
+
class Keyword(Helper):
|
683 |
+
"""A key, value pair for keyword arguments where key is a string."""
|
684 |
+
|
685 |
+
fields = ("key", "value")
|
686 |
+
key: str
|
687 |
+
value: Expr
|
688 |
+
|
689 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Tuple[str, t.Any]:
|
690 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
691 |
+
return self.key, self.value.as_const(eval_ctx)
|
692 |
+
|
693 |
+
|
694 |
+
class CondExpr(Expr):
|
695 |
+
"""A conditional expression (inline if expression). (``{{
|
696 |
+
foo if bar else baz }}``)
|
697 |
+
"""
|
698 |
+
|
699 |
+
fields = ("test", "expr1", "expr2")
|
700 |
+
test: Expr
|
701 |
+
expr1: Expr
|
702 |
+
expr2: t.Optional[Expr]
|
703 |
+
|
704 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
705 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
706 |
+
if self.test.as_const(eval_ctx):
|
707 |
+
return self.expr1.as_const(eval_ctx)
|
708 |
+
|
709 |
+
# if we evaluate to an undefined object, we better do that at runtime
|
710 |
+
if self.expr2 is None:
|
711 |
+
raise Impossible()
|
712 |
+
|
713 |
+
return self.expr2.as_const(eval_ctx)
|
714 |
+
|
715 |
+
|
716 |
+
def args_as_const(
|
717 |
+
node: t.Union["_FilterTestCommon", "Call"], eval_ctx: t.Optional[EvalContext]
|
718 |
+
) -> t.Tuple[t.List[t.Any], t.Dict[t.Any, t.Any]]:
|
719 |
+
args = [x.as_const(eval_ctx) for x in node.args]
|
720 |
+
kwargs = dict(x.as_const(eval_ctx) for x in node.kwargs)
|
721 |
+
|
722 |
+
if node.dyn_args is not None:
|
723 |
+
try:
|
724 |
+
args.extend(node.dyn_args.as_const(eval_ctx))
|
725 |
+
except Exception as e:
|
726 |
+
raise Impossible() from e
|
727 |
+
|
728 |
+
if node.dyn_kwargs is not None:
|
729 |
+
try:
|
730 |
+
kwargs.update(node.dyn_kwargs.as_const(eval_ctx))
|
731 |
+
except Exception as e:
|
732 |
+
raise Impossible() from e
|
733 |
+
|
734 |
+
return args, kwargs
|
735 |
+
|
736 |
+
|
737 |
+
class _FilterTestCommon(Expr):
|
738 |
+
fields = ("node", "name", "args", "kwargs", "dyn_args", "dyn_kwargs")
|
739 |
+
node: Expr
|
740 |
+
name: str
|
741 |
+
args: t.List[Expr]
|
742 |
+
kwargs: t.List[Pair]
|
743 |
+
dyn_args: t.Optional[Expr]
|
744 |
+
dyn_kwargs: t.Optional[Expr]
|
745 |
+
abstract = True
|
746 |
+
_is_filter = True
|
747 |
+
|
748 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
749 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
750 |
+
|
751 |
+
if eval_ctx.volatile:
|
752 |
+
raise Impossible()
|
753 |
+
|
754 |
+
if self._is_filter:
|
755 |
+
env_map = eval_ctx.environment.filters
|
756 |
+
else:
|
757 |
+
env_map = eval_ctx.environment.tests
|
758 |
+
|
759 |
+
func = env_map.get(self.name)
|
760 |
+
pass_arg = _PassArg.from_obj(func) # type: ignore
|
761 |
+
|
762 |
+
if func is None or pass_arg is _PassArg.context:
|
763 |
+
raise Impossible()
|
764 |
+
|
765 |
+
if eval_ctx.environment.is_async and (
|
766 |
+
getattr(func, "jinja_async_variant", False) is True
|
767 |
+
or inspect.iscoroutinefunction(func)
|
768 |
+
):
|
769 |
+
raise Impossible()
|
770 |
+
|
771 |
+
args, kwargs = args_as_const(self, eval_ctx)
|
772 |
+
args.insert(0, self.node.as_const(eval_ctx))
|
773 |
+
|
774 |
+
if pass_arg is _PassArg.eval_context:
|
775 |
+
args.insert(0, eval_ctx)
|
776 |
+
elif pass_arg is _PassArg.environment:
|
777 |
+
args.insert(0, eval_ctx.environment)
|
778 |
+
|
779 |
+
try:
|
780 |
+
return func(*args, **kwargs)
|
781 |
+
except Exception as e:
|
782 |
+
raise Impossible() from e
|
783 |
+
|
784 |
+
|
785 |
+
class Filter(_FilterTestCommon):
|
786 |
+
"""Apply a filter to an expression. ``name`` is the name of the
|
787 |
+
filter, the other fields are the same as :class:`Call`.
|
788 |
+
|
789 |
+
If ``node`` is ``None``, the filter is being used in a filter block
|
790 |
+
and is applied to the content of the block.
|
791 |
+
"""
|
792 |
+
|
793 |
+
node: t.Optional[Expr] # type: ignore
|
794 |
+
|
795 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
796 |
+
if self.node is None:
|
797 |
+
raise Impossible()
|
798 |
+
|
799 |
+
return super().as_const(eval_ctx=eval_ctx)
|
800 |
+
|
801 |
+
|
802 |
+
class Test(_FilterTestCommon):
|
803 |
+
"""Apply a test to an expression. ``name`` is the name of the test,
|
804 |
+
the other field are the same as :class:`Call`.
|
805 |
+
|
806 |
+
.. versionchanged:: 3.0
|
807 |
+
``as_const`` shares the same logic for filters and tests. Tests
|
808 |
+
check for volatile, async, and ``@pass_context`` etc.
|
809 |
+
decorators.
|
810 |
+
"""
|
811 |
+
|
812 |
+
_is_filter = False
|
813 |
+
|
814 |
+
|
815 |
+
class Call(Expr):
|
816 |
+
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
|
817 |
+
of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
|
818 |
+
and `dyn_kwargs` has to be either `None` or a node that is used as
|
819 |
+
node for dynamic positional (``*args``) or keyword (``**kwargs``)
|
820 |
+
arguments.
|
821 |
+
"""
|
822 |
+
|
823 |
+
fields = ("node", "args", "kwargs", "dyn_args", "dyn_kwargs")
|
824 |
+
node: Expr
|
825 |
+
args: t.List[Expr]
|
826 |
+
kwargs: t.List[Keyword]
|
827 |
+
dyn_args: t.Optional[Expr]
|
828 |
+
dyn_kwargs: t.Optional[Expr]
|
829 |
+
|
830 |
+
|
831 |
+
class Getitem(Expr):
|
832 |
+
"""Get an attribute or item from an expression and prefer the item."""
|
833 |
+
|
834 |
+
fields = ("node", "arg", "ctx")
|
835 |
+
node: Expr
|
836 |
+
arg: Expr
|
837 |
+
ctx: str
|
838 |
+
|
839 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
840 |
+
if self.ctx != "load":
|
841 |
+
raise Impossible()
|
842 |
+
|
843 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
844 |
+
|
845 |
+
try:
|
846 |
+
return eval_ctx.environment.getitem(
|
847 |
+
self.node.as_const(eval_ctx), self.arg.as_const(eval_ctx)
|
848 |
+
)
|
849 |
+
except Exception as e:
|
850 |
+
raise Impossible() from e
|
851 |
+
|
852 |
+
|
853 |
+
class Getattr(Expr):
|
854 |
+
"""Get an attribute or item from an expression that is a ascii-only
|
855 |
+
bytestring and prefer the attribute.
|
856 |
+
"""
|
857 |
+
|
858 |
+
fields = ("node", "attr", "ctx")
|
859 |
+
node: Expr
|
860 |
+
attr: str
|
861 |
+
ctx: str
|
862 |
+
|
863 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
864 |
+
if self.ctx != "load":
|
865 |
+
raise Impossible()
|
866 |
+
|
867 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
868 |
+
|
869 |
+
try:
|
870 |
+
return eval_ctx.environment.getattr(self.node.as_const(eval_ctx), self.attr)
|
871 |
+
except Exception as e:
|
872 |
+
raise Impossible() from e
|
873 |
+
|
874 |
+
|
875 |
+
class Slice(Expr):
|
876 |
+
"""Represents a slice object. This must only be used as argument for
|
877 |
+
:class:`Subscript`.
|
878 |
+
"""
|
879 |
+
|
880 |
+
fields = ("start", "stop", "step")
|
881 |
+
start: t.Optional[Expr]
|
882 |
+
stop: t.Optional[Expr]
|
883 |
+
step: t.Optional[Expr]
|
884 |
+
|
885 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> slice:
|
886 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
887 |
+
|
888 |
+
def const(obj: t.Optional[Expr]) -> t.Optional[t.Any]:
|
889 |
+
if obj is None:
|
890 |
+
return None
|
891 |
+
return obj.as_const(eval_ctx)
|
892 |
+
|
893 |
+
return slice(const(self.start), const(self.stop), const(self.step))
|
894 |
+
|
895 |
+
|
896 |
+
class Concat(Expr):
|
897 |
+
"""Concatenates the list of expressions provided after converting
|
898 |
+
them to strings.
|
899 |
+
"""
|
900 |
+
|
901 |
+
fields = ("nodes",)
|
902 |
+
nodes: t.List[Expr]
|
903 |
+
|
904 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> str:
|
905 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
906 |
+
return "".join(str(x.as_const(eval_ctx)) for x in self.nodes)
|
907 |
+
|
908 |
+
|
909 |
+
class Compare(Expr):
|
910 |
+
"""Compares an expression with some other expressions. `ops` must be a
|
911 |
+
list of :class:`Operand`\\s.
|
912 |
+
"""
|
913 |
+
|
914 |
+
fields = ("expr", "ops")
|
915 |
+
expr: Expr
|
916 |
+
ops: t.List["Operand"]
|
917 |
+
|
918 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
919 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
920 |
+
result = value = self.expr.as_const(eval_ctx)
|
921 |
+
|
922 |
+
try:
|
923 |
+
for op in self.ops:
|
924 |
+
new_value = op.expr.as_const(eval_ctx)
|
925 |
+
result = _cmpop_to_func[op.op](value, new_value)
|
926 |
+
|
927 |
+
if not result:
|
928 |
+
return False
|
929 |
+
|
930 |
+
value = new_value
|
931 |
+
except Exception as e:
|
932 |
+
raise Impossible() from e
|
933 |
+
|
934 |
+
return result
|
935 |
+
|
936 |
+
|
937 |
+
class Operand(Helper):
|
938 |
+
"""Holds an operator and an expression."""
|
939 |
+
|
940 |
+
fields = ("op", "expr")
|
941 |
+
op: str
|
942 |
+
expr: Expr
|
943 |
+
|
944 |
+
|
945 |
+
class Mul(BinExpr):
|
946 |
+
"""Multiplies the left with the right node."""
|
947 |
+
|
948 |
+
operator = "*"
|
949 |
+
|
950 |
+
|
951 |
+
class Div(BinExpr):
|
952 |
+
"""Divides the left by the right node."""
|
953 |
+
|
954 |
+
operator = "/"
|
955 |
+
|
956 |
+
|
957 |
+
class FloorDiv(BinExpr):
|
958 |
+
"""Divides the left by the right node and converts the
|
959 |
+
result into an integer by truncating.
|
960 |
+
"""
|
961 |
+
|
962 |
+
operator = "//"
|
963 |
+
|
964 |
+
|
965 |
+
class Add(BinExpr):
|
966 |
+
"""Add the left to the right node."""
|
967 |
+
|
968 |
+
operator = "+"
|
969 |
+
|
970 |
+
|
971 |
+
class Sub(BinExpr):
|
972 |
+
"""Subtract the right from the left node."""
|
973 |
+
|
974 |
+
operator = "-"
|
975 |
+
|
976 |
+
|
977 |
+
class Mod(BinExpr):
|
978 |
+
"""Left modulo right."""
|
979 |
+
|
980 |
+
operator = "%"
|
981 |
+
|
982 |
+
|
983 |
+
class Pow(BinExpr):
|
984 |
+
"""Left to the power of right."""
|
985 |
+
|
986 |
+
operator = "**"
|
987 |
+
|
988 |
+
|
989 |
+
class And(BinExpr):
|
990 |
+
"""Short circuited AND."""
|
991 |
+
|
992 |
+
operator = "and"
|
993 |
+
|
994 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
995 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
996 |
+
return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
|
997 |
+
|
998 |
+
|
999 |
+
class Or(BinExpr):
|
1000 |
+
"""Short circuited OR."""
|
1001 |
+
|
1002 |
+
operator = "or"
|
1003 |
+
|
1004 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> t.Any:
|
1005 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
1006 |
+
return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
|
1007 |
+
|
1008 |
+
|
1009 |
+
class Not(UnaryExpr):
|
1010 |
+
"""Negate the expression."""
|
1011 |
+
|
1012 |
+
operator = "not"
|
1013 |
+
|
1014 |
+
|
1015 |
+
class Neg(UnaryExpr):
|
1016 |
+
"""Make the expression negative."""
|
1017 |
+
|
1018 |
+
operator = "-"
|
1019 |
+
|
1020 |
+
|
1021 |
+
class Pos(UnaryExpr):
|
1022 |
+
"""Make the expression positive (noop for most expressions)"""
|
1023 |
+
|
1024 |
+
operator = "+"
|
1025 |
+
|
1026 |
+
|
1027 |
+
# Helpers for extensions
|
1028 |
+
|
1029 |
+
|
1030 |
+
class EnvironmentAttribute(Expr):
|
1031 |
+
"""Loads an attribute from the environment object. This is useful for
|
1032 |
+
extensions that want to call a callback stored on the environment.
|
1033 |
+
"""
|
1034 |
+
|
1035 |
+
fields = ("name",)
|
1036 |
+
name: str
|
1037 |
+
|
1038 |
+
|
1039 |
+
class ExtensionAttribute(Expr):
|
1040 |
+
"""Returns the attribute of an extension bound to the environment.
|
1041 |
+
The identifier is the identifier of the :class:`Extension`.
|
1042 |
+
|
1043 |
+
This node is usually constructed by calling the
|
1044 |
+
:meth:`~jinja2.ext.Extension.attr` method on an extension.
|
1045 |
+
"""
|
1046 |
+
|
1047 |
+
fields = ("identifier", "name")
|
1048 |
+
identifier: str
|
1049 |
+
name: str
|
1050 |
+
|
1051 |
+
|
1052 |
+
class ImportedName(Expr):
|
1053 |
+
"""If created with an import name the import name is returned on node
|
1054 |
+
access. For example ``ImportedName('cgi.escape')`` returns the `escape`
|
1055 |
+
function from the cgi module on evaluation. Imports are optimized by the
|
1056 |
+
compiler so there is no need to assign them to local variables.
|
1057 |
+
"""
|
1058 |
+
|
1059 |
+
fields = ("importname",)
|
1060 |
+
importname: str
|
1061 |
+
|
1062 |
+
|
1063 |
+
class InternalName(Expr):
|
1064 |
+
"""An internal name in the compiler. You cannot create these nodes
|
1065 |
+
yourself but the parser provides a
|
1066 |
+
:meth:`~jinja2.parser.Parser.free_identifier` method that creates
|
1067 |
+
a new identifier for you. This identifier is not available from the
|
1068 |
+
template and is not treated specially by the compiler.
|
1069 |
+
"""
|
1070 |
+
|
1071 |
+
fields = ("name",)
|
1072 |
+
name: str
|
1073 |
+
|
1074 |
+
def __init__(self) -> None:
|
1075 |
+
raise TypeError(
|
1076 |
+
"Can't create internal names. Use the "
|
1077 |
+
"`free_identifier` method on a parser."
|
1078 |
+
)
|
1079 |
+
|
1080 |
+
|
1081 |
+
class MarkSafe(Expr):
|
1082 |
+
"""Mark the wrapped expression as safe (wrap it as `Markup`)."""
|
1083 |
+
|
1084 |
+
fields = ("expr",)
|
1085 |
+
expr: Expr
|
1086 |
+
|
1087 |
+
def as_const(self, eval_ctx: t.Optional[EvalContext] = None) -> Markup:
|
1088 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
1089 |
+
return Markup(self.expr.as_const(eval_ctx))
|
1090 |
+
|
1091 |
+
|
1092 |
+
class MarkSafeIfAutoescape(Expr):
|
1093 |
+
"""Mark the wrapped expression as safe (wrap it as `Markup`) but
|
1094 |
+
only if autoescaping is active.
|
1095 |
+
|
1096 |
+
.. versionadded:: 2.5
|
1097 |
+
"""
|
1098 |
+
|
1099 |
+
fields = ("expr",)
|
1100 |
+
expr: Expr
|
1101 |
+
|
1102 |
+
def as_const(
|
1103 |
+
self, eval_ctx: t.Optional[EvalContext] = None
|
1104 |
+
) -> t.Union[Markup, t.Any]:
|
1105 |
+
eval_ctx = get_eval_context(self, eval_ctx)
|
1106 |
+
if eval_ctx.volatile:
|
1107 |
+
raise Impossible()
|
1108 |
+
expr = self.expr.as_const(eval_ctx)
|
1109 |
+
if eval_ctx.autoescape:
|
1110 |
+
return Markup(expr)
|
1111 |
+
return expr
|
1112 |
+
|
1113 |
+
|
1114 |
+
class ContextReference(Expr):
|
1115 |
+
"""Returns the current template context. It can be used like a
|
1116 |
+
:class:`Name` node, with a ``'load'`` ctx and will return the
|
1117 |
+
current :class:`~jinja2.runtime.Context` object.
|
1118 |
+
|
1119 |
+
Here an example that assigns the current template name to a
|
1120 |
+
variable named `foo`::
|
1121 |
+
|
1122 |
+
Assign(Name('foo', ctx='store'),
|
1123 |
+
Getattr(ContextReference(), 'name'))
|
1124 |
+
|
1125 |
+
This is basically equivalent to using the
|
1126 |
+
:func:`~jinja2.pass_context` decorator when using the high-level
|
1127 |
+
API, which causes a reference to the context to be passed as the
|
1128 |
+
first argument to a function.
|
1129 |
+
"""
|
1130 |
+
|
1131 |
+
|
1132 |
+
class DerivedContextReference(Expr):
|
1133 |
+
"""Return the current template context including locals. Behaves
|
1134 |
+
exactly like :class:`ContextReference`, but includes local
|
1135 |
+
variables, such as from a ``for`` loop.
|
1136 |
+
|
1137 |
+
.. versionadded:: 2.11
|
1138 |
+
"""
|
1139 |
+
|
1140 |
+
|
1141 |
+
class Continue(Stmt):
|
1142 |
+
"""Continue a loop."""
|
1143 |
+
|
1144 |
+
|
1145 |
+
class Break(Stmt):
|
1146 |
+
"""Break a loop."""
|
1147 |
+
|
1148 |
+
|
1149 |
+
class Scope(Stmt):
|
1150 |
+
"""An artificial scope."""
|
1151 |
+
|
1152 |
+
fields = ("body",)
|
1153 |
+
body: t.List[Node]
|
1154 |
+
|
1155 |
+
|
1156 |
+
class OverlayScope(Stmt):
|
1157 |
+
"""An overlay scope for extensions. This is a largely unoptimized scope
|
1158 |
+
that however can be used to introduce completely arbitrary variables into
|
1159 |
+
a sub scope from a dictionary or dictionary like object. The `context`
|
1160 |
+
field has to evaluate to a dictionary object.
|
1161 |
+
|
1162 |
+
Example usage::
|
1163 |
+
|
1164 |
+
OverlayScope(context=self.call_method('get_context'),
|
1165 |
+
body=[...])
|
1166 |
+
|
1167 |
+
.. versionadded:: 2.10
|
1168 |
+
"""
|
1169 |
+
|
1170 |
+
fields = ("context", "body")
|
1171 |
+
context: Expr
|
1172 |
+
body: t.List[Node]
|
1173 |
+
|
1174 |
+
|
1175 |
+
class EvalContextModifier(Stmt):
|
1176 |
+
"""Modifies the eval context. For each option that should be modified,
|
1177 |
+
a :class:`Keyword` has to be added to the :attr:`options` list.
|
1178 |
+
|
1179 |
+
Example to change the `autoescape` setting::
|
1180 |
+
|
1181 |
+
EvalContextModifier(options=[Keyword('autoescape', Const(True))])
|
1182 |
+
"""
|
1183 |
+
|
1184 |
+
fields = ("options",)
|
1185 |
+
options: t.List[Keyword]
|
1186 |
+
|
1187 |
+
|
1188 |
+
class ScopedEvalContextModifier(EvalContextModifier):
|
1189 |
+
"""Modifies the eval context and reverts it later. Works exactly like
|
1190 |
+
:class:`EvalContextModifier` but will only modify the
|
1191 |
+
:class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
|
1192 |
+
"""
|
1193 |
+
|
1194 |
+
fields = ("body",)
|
1195 |
+
body: t.List[Node]
|
1196 |
+
|
1197 |
+
|
1198 |
+
# make sure nobody creates custom nodes
|
1199 |
+
def _failing_new(*args: t.Any, **kwargs: t.Any) -> "te.NoReturn":
|
1200 |
+
raise TypeError("can't create custom node types")
|
1201 |
+
|
1202 |
+
|
1203 |
+
NodeType.__new__ = staticmethod(_failing_new) # type: ignore
|
1204 |
+
del _failing_new
|
lib/python3.11/site-packages/jinja2/optimizer.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""The optimizer tries to constant fold expressions and modify the AST
|
2 |
+
in place so that it should be faster to evaluate.
|
3 |
+
|
4 |
+
Because the AST does not contain all the scoping information and the
|
5 |
+
compiler has to find that out, we cannot do all the optimizations we
|
6 |
+
want. For example, loop unrolling doesn't work because unrolled loops
|
7 |
+
would have a different scope. The solution would be a second syntax tree
|
8 |
+
that stored the scoping rules.
|
9 |
+
"""
|
10 |
+
import typing as t
|
11 |
+
|
12 |
+
from . import nodes
|
13 |
+
from .visitor import NodeTransformer
|
14 |
+
|
15 |
+
if t.TYPE_CHECKING:
|
16 |
+
from .environment import Environment
|
17 |
+
|
18 |
+
|
19 |
+
def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node:
|
20 |
+
"""The context hint can be used to perform an static optimization
|
21 |
+
based on the context given."""
|
22 |
+
optimizer = Optimizer(environment)
|
23 |
+
return t.cast(nodes.Node, optimizer.visit(node))
|
24 |
+
|
25 |
+
|
26 |
+
class Optimizer(NodeTransformer):
|
27 |
+
def __init__(self, environment: "t.Optional[Environment]") -> None:
|
28 |
+
self.environment = environment
|
29 |
+
|
30 |
+
def generic_visit(
|
31 |
+
self, node: nodes.Node, *args: t.Any, **kwargs: t.Any
|
32 |
+
) -> nodes.Node:
|
33 |
+
node = super().generic_visit(node, *args, **kwargs)
|
34 |
+
|
35 |
+
# Do constant folding. Some other nodes besides Expr have
|
36 |
+
# as_const, but folding them causes errors later on.
|
37 |
+
if isinstance(node, nodes.Expr):
|
38 |
+
try:
|
39 |
+
return nodes.Const.from_untrusted(
|
40 |
+
node.as_const(args[0] if args else None),
|
41 |
+
lineno=node.lineno,
|
42 |
+
environment=self.environment,
|
43 |
+
)
|
44 |
+
except nodes.Impossible:
|
45 |
+
pass
|
46 |
+
|
47 |
+
return node
|
lib/python3.11/site-packages/jinja2/parser.py
ADDED
@@ -0,0 +1,1032 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Parse tokens from the lexer into nodes for the compiler."""
|
2 |
+
import typing
|
3 |
+
import typing as t
|
4 |
+
|
5 |
+
from . import nodes
|
6 |
+
from .exceptions import TemplateAssertionError
|
7 |
+
from .exceptions import TemplateSyntaxError
|
8 |
+
from .lexer import describe_token
|
9 |
+
from .lexer import describe_token_expr
|
10 |
+
|
11 |
+
if t.TYPE_CHECKING:
|
12 |
+
import typing_extensions as te
|
13 |
+
from .environment import Environment
|
14 |
+
|
15 |
+
_ImportInclude = t.TypeVar("_ImportInclude", nodes.Import, nodes.Include)
|
16 |
+
_MacroCall = t.TypeVar("_MacroCall", nodes.Macro, nodes.CallBlock)
|
17 |
+
|
18 |
+
_statement_keywords = frozenset(
|
19 |
+
[
|
20 |
+
"for",
|
21 |
+
"if",
|
22 |
+
"block",
|
23 |
+
"extends",
|
24 |
+
"print",
|
25 |
+
"macro",
|
26 |
+
"include",
|
27 |
+
"from",
|
28 |
+
"import",
|
29 |
+
"set",
|
30 |
+
"with",
|
31 |
+
"autoescape",
|
32 |
+
]
|
33 |
+
)
|
34 |
+
_compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"])
|
35 |
+
|
36 |
+
_math_nodes: t.Dict[str, t.Type[nodes.Expr]] = {
|
37 |
+
"add": nodes.Add,
|
38 |
+
"sub": nodes.Sub,
|
39 |
+
"mul": nodes.Mul,
|
40 |
+
"div": nodes.Div,
|
41 |
+
"floordiv": nodes.FloorDiv,
|
42 |
+
"mod": nodes.Mod,
|
43 |
+
}
|
44 |
+
|
45 |
+
|
46 |
+
class Parser:
|
47 |
+
"""This is the central parsing class Jinja uses. It's passed to
|
48 |
+
extensions and can be used to parse expressions or statements.
|
49 |
+
"""
|
50 |
+
|
51 |
+
def __init__(
|
52 |
+
self,
|
53 |
+
environment: "Environment",
|
54 |
+
source: str,
|
55 |
+
name: t.Optional[str] = None,
|
56 |
+
filename: t.Optional[str] = None,
|
57 |
+
state: t.Optional[str] = None,
|
58 |
+
) -> None:
|
59 |
+
self.environment = environment
|
60 |
+
self.stream = environment._tokenize(source, name, filename, state)
|
61 |
+
self.name = name
|
62 |
+
self.filename = filename
|
63 |
+
self.closed = False
|
64 |
+
self.extensions: t.Dict[
|
65 |
+
str, t.Callable[["Parser"], t.Union[nodes.Node, t.List[nodes.Node]]]
|
66 |
+
] = {}
|
67 |
+
for extension in environment.iter_extensions():
|
68 |
+
for tag in extension.tags:
|
69 |
+
self.extensions[tag] = extension.parse
|
70 |
+
self._last_identifier = 0
|
71 |
+
self._tag_stack: t.List[str] = []
|
72 |
+
self._end_token_stack: t.List[t.Tuple[str, ...]] = []
|
73 |
+
|
74 |
+
def fail(
|
75 |
+
self,
|
76 |
+
msg: str,
|
77 |
+
lineno: t.Optional[int] = None,
|
78 |
+
exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError,
|
79 |
+
) -> "te.NoReturn":
|
80 |
+
"""Convenience method that raises `exc` with the message, passed
|
81 |
+
line number or last line number as well as the current name and
|
82 |
+
filename.
|
83 |
+
"""
|
84 |
+
if lineno is None:
|
85 |
+
lineno = self.stream.current.lineno
|
86 |
+
raise exc(msg, lineno, self.name, self.filename)
|
87 |
+
|
88 |
+
def _fail_ut_eof(
|
89 |
+
self,
|
90 |
+
name: t.Optional[str],
|
91 |
+
end_token_stack: t.List[t.Tuple[str, ...]],
|
92 |
+
lineno: t.Optional[int],
|
93 |
+
) -> "te.NoReturn":
|
94 |
+
expected: t.Set[str] = set()
|
95 |
+
for exprs in end_token_stack:
|
96 |
+
expected.update(map(describe_token_expr, exprs))
|
97 |
+
if end_token_stack:
|
98 |
+
currently_looking: t.Optional[str] = " or ".join(
|
99 |
+
map(repr, map(describe_token_expr, end_token_stack[-1]))
|
100 |
+
)
|
101 |
+
else:
|
102 |
+
currently_looking = None
|
103 |
+
|
104 |
+
if name is None:
|
105 |
+
message = ["Unexpected end of template."]
|
106 |
+
else:
|
107 |
+
message = [f"Encountered unknown tag {name!r}."]
|
108 |
+
|
109 |
+
if currently_looking:
|
110 |
+
if name is not None and name in expected:
|
111 |
+
message.append(
|
112 |
+
"You probably made a nesting mistake. Jinja is expecting this tag,"
|
113 |
+
f" but currently looking for {currently_looking}."
|
114 |
+
)
|
115 |
+
else:
|
116 |
+
message.append(
|
117 |
+
f"Jinja was looking for the following tags: {currently_looking}."
|
118 |
+
)
|
119 |
+
|
120 |
+
if self._tag_stack:
|
121 |
+
message.append(
|
122 |
+
"The innermost block that needs to be closed is"
|
123 |
+
f" {self._tag_stack[-1]!r}."
|
124 |
+
)
|
125 |
+
|
126 |
+
self.fail(" ".join(message), lineno)
|
127 |
+
|
128 |
+
def fail_unknown_tag(
|
129 |
+
self, name: str, lineno: t.Optional[int] = None
|
130 |
+
) -> "te.NoReturn":
|
131 |
+
"""Called if the parser encounters an unknown tag. Tries to fail
|
132 |
+
with a human readable error message that could help to identify
|
133 |
+
the problem.
|
134 |
+
"""
|
135 |
+
self._fail_ut_eof(name, self._end_token_stack, lineno)
|
136 |
+
|
137 |
+
def fail_eof(
|
138 |
+
self,
|
139 |
+
end_tokens: t.Optional[t.Tuple[str, ...]] = None,
|
140 |
+
lineno: t.Optional[int] = None,
|
141 |
+
) -> "te.NoReturn":
|
142 |
+
"""Like fail_unknown_tag but for end of template situations."""
|
143 |
+
stack = list(self._end_token_stack)
|
144 |
+
if end_tokens is not None:
|
145 |
+
stack.append(end_tokens)
|
146 |
+
self._fail_ut_eof(None, stack, lineno)
|
147 |
+
|
148 |
+
def is_tuple_end(
|
149 |
+
self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None
|
150 |
+
) -> bool:
|
151 |
+
"""Are we at the end of a tuple?"""
|
152 |
+
if self.stream.current.type in ("variable_end", "block_end", "rparen"):
|
153 |
+
return True
|
154 |
+
elif extra_end_rules is not None:
|
155 |
+
return self.stream.current.test_any(extra_end_rules) # type: ignore
|
156 |
+
return False
|
157 |
+
|
158 |
+
def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName:
|
159 |
+
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
|
160 |
+
self._last_identifier += 1
|
161 |
+
rv = object.__new__(nodes.InternalName)
|
162 |
+
nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno)
|
163 |
+
return rv
|
164 |
+
|
165 |
+
def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
166 |
+
"""Parse a single statement."""
|
167 |
+
token = self.stream.current
|
168 |
+
if token.type != "name":
|
169 |
+
self.fail("tag name expected", token.lineno)
|
170 |
+
self._tag_stack.append(token.value)
|
171 |
+
pop_tag = True
|
172 |
+
try:
|
173 |
+
if token.value in _statement_keywords:
|
174 |
+
f = getattr(self, f"parse_{self.stream.current.value}")
|
175 |
+
return f() # type: ignore
|
176 |
+
if token.value == "call":
|
177 |
+
return self.parse_call_block()
|
178 |
+
if token.value == "filter":
|
179 |
+
return self.parse_filter_block()
|
180 |
+
ext = self.extensions.get(token.value)
|
181 |
+
if ext is not None:
|
182 |
+
return ext(self)
|
183 |
+
|
184 |
+
# did not work out, remove the token we pushed by accident
|
185 |
+
# from the stack so that the unknown tag fail function can
|
186 |
+
# produce a proper error message.
|
187 |
+
self._tag_stack.pop()
|
188 |
+
pop_tag = False
|
189 |
+
self.fail_unknown_tag(token.value, token.lineno)
|
190 |
+
finally:
|
191 |
+
if pop_tag:
|
192 |
+
self._tag_stack.pop()
|
193 |
+
|
194 |
+
def parse_statements(
|
195 |
+
self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False
|
196 |
+
) -> t.List[nodes.Node]:
|
197 |
+
"""Parse multiple statements into a list until one of the end tokens
|
198 |
+
is reached. This is used to parse the body of statements as it also
|
199 |
+
parses template data if appropriate. The parser checks first if the
|
200 |
+
current token is a colon and skips it if there is one. Then it checks
|
201 |
+
for the block end and parses until if one of the `end_tokens` is
|
202 |
+
reached. Per default the active token in the stream at the end of
|
203 |
+
the call is the matched end token. If this is not wanted `drop_needle`
|
204 |
+
can be set to `True` and the end token is removed.
|
205 |
+
"""
|
206 |
+
# the first token may be a colon for python compatibility
|
207 |
+
self.stream.skip_if("colon")
|
208 |
+
|
209 |
+
# in the future it would be possible to add whole code sections
|
210 |
+
# by adding some sort of end of statement token and parsing those here.
|
211 |
+
self.stream.expect("block_end")
|
212 |
+
result = self.subparse(end_tokens)
|
213 |
+
|
214 |
+
# we reached the end of the template too early, the subparser
|
215 |
+
# does not check for this, so we do that now
|
216 |
+
if self.stream.current.type == "eof":
|
217 |
+
self.fail_eof(end_tokens)
|
218 |
+
|
219 |
+
if drop_needle:
|
220 |
+
next(self.stream)
|
221 |
+
return result
|
222 |
+
|
223 |
+
def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]:
|
224 |
+
"""Parse an assign statement."""
|
225 |
+
lineno = next(self.stream).lineno
|
226 |
+
target = self.parse_assign_target(with_namespace=True)
|
227 |
+
if self.stream.skip_if("assign"):
|
228 |
+
expr = self.parse_tuple()
|
229 |
+
return nodes.Assign(target, expr, lineno=lineno)
|
230 |
+
filter_node = self.parse_filter(None)
|
231 |
+
body = self.parse_statements(("name:endset",), drop_needle=True)
|
232 |
+
return nodes.AssignBlock(target, filter_node, body, lineno=lineno)
|
233 |
+
|
234 |
+
def parse_for(self) -> nodes.For:
|
235 |
+
"""Parse a for loop."""
|
236 |
+
lineno = self.stream.expect("name:for").lineno
|
237 |
+
target = self.parse_assign_target(extra_end_rules=("name:in",))
|
238 |
+
self.stream.expect("name:in")
|
239 |
+
iter = self.parse_tuple(
|
240 |
+
with_condexpr=False, extra_end_rules=("name:recursive",)
|
241 |
+
)
|
242 |
+
test = None
|
243 |
+
if self.stream.skip_if("name:if"):
|
244 |
+
test = self.parse_expression()
|
245 |
+
recursive = self.stream.skip_if("name:recursive")
|
246 |
+
body = self.parse_statements(("name:endfor", "name:else"))
|
247 |
+
if next(self.stream).value == "endfor":
|
248 |
+
else_ = []
|
249 |
+
else:
|
250 |
+
else_ = self.parse_statements(("name:endfor",), drop_needle=True)
|
251 |
+
return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno)
|
252 |
+
|
253 |
+
def parse_if(self) -> nodes.If:
|
254 |
+
"""Parse an if construct."""
|
255 |
+
node = result = nodes.If(lineno=self.stream.expect("name:if").lineno)
|
256 |
+
while True:
|
257 |
+
node.test = self.parse_tuple(with_condexpr=False)
|
258 |
+
node.body = self.parse_statements(("name:elif", "name:else", "name:endif"))
|
259 |
+
node.elif_ = []
|
260 |
+
node.else_ = []
|
261 |
+
token = next(self.stream)
|
262 |
+
if token.test("name:elif"):
|
263 |
+
node = nodes.If(lineno=self.stream.current.lineno)
|
264 |
+
result.elif_.append(node)
|
265 |
+
continue
|
266 |
+
elif token.test("name:else"):
|
267 |
+
result.else_ = self.parse_statements(("name:endif",), drop_needle=True)
|
268 |
+
break
|
269 |
+
return result
|
270 |
+
|
271 |
+
def parse_with(self) -> nodes.With:
|
272 |
+
node = nodes.With(lineno=next(self.stream).lineno)
|
273 |
+
targets: t.List[nodes.Expr] = []
|
274 |
+
values: t.List[nodes.Expr] = []
|
275 |
+
while self.stream.current.type != "block_end":
|
276 |
+
if targets:
|
277 |
+
self.stream.expect("comma")
|
278 |
+
target = self.parse_assign_target()
|
279 |
+
target.set_ctx("param")
|
280 |
+
targets.append(target)
|
281 |
+
self.stream.expect("assign")
|
282 |
+
values.append(self.parse_expression())
|
283 |
+
node.targets = targets
|
284 |
+
node.values = values
|
285 |
+
node.body = self.parse_statements(("name:endwith",), drop_needle=True)
|
286 |
+
return node
|
287 |
+
|
288 |
+
def parse_autoescape(self) -> nodes.Scope:
|
289 |
+
node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno)
|
290 |
+
node.options = [nodes.Keyword("autoescape", self.parse_expression())]
|
291 |
+
node.body = self.parse_statements(("name:endautoescape",), drop_needle=True)
|
292 |
+
return nodes.Scope([node])
|
293 |
+
|
294 |
+
def parse_block(self) -> nodes.Block:
|
295 |
+
node = nodes.Block(lineno=next(self.stream).lineno)
|
296 |
+
node.name = self.stream.expect("name").value
|
297 |
+
node.scoped = self.stream.skip_if("name:scoped")
|
298 |
+
node.required = self.stream.skip_if("name:required")
|
299 |
+
|
300 |
+
# common problem people encounter when switching from django
|
301 |
+
# to jinja. we do not support hyphens in block names, so let's
|
302 |
+
# raise a nicer error message in that case.
|
303 |
+
if self.stream.current.type == "sub":
|
304 |
+
self.fail(
|
305 |
+
"Block names in Jinja have to be valid Python identifiers and may not"
|
306 |
+
" contain hyphens, use an underscore instead."
|
307 |
+
)
|
308 |
+
|
309 |
+
node.body = self.parse_statements(("name:endblock",), drop_needle=True)
|
310 |
+
|
311 |
+
# enforce that required blocks only contain whitespace or comments
|
312 |
+
# by asserting that the body, if not empty, is just TemplateData nodes
|
313 |
+
# with whitespace data
|
314 |
+
if node.required and not all(
|
315 |
+
isinstance(child, nodes.TemplateData) and child.data.isspace()
|
316 |
+
for body in node.body
|
317 |
+
for child in body.nodes # type: ignore
|
318 |
+
):
|
319 |
+
self.fail("Required blocks can only contain comments or whitespace")
|
320 |
+
|
321 |
+
self.stream.skip_if("name:" + node.name)
|
322 |
+
return node
|
323 |
+
|
324 |
+
def parse_extends(self) -> nodes.Extends:
|
325 |
+
node = nodes.Extends(lineno=next(self.stream).lineno)
|
326 |
+
node.template = self.parse_expression()
|
327 |
+
return node
|
328 |
+
|
329 |
+
def parse_import_context(
|
330 |
+
self, node: _ImportInclude, default: bool
|
331 |
+
) -> _ImportInclude:
|
332 |
+
if self.stream.current.test_any(
|
333 |
+
"name:with", "name:without"
|
334 |
+
) and self.stream.look().test("name:context"):
|
335 |
+
node.with_context = next(self.stream).value == "with"
|
336 |
+
self.stream.skip()
|
337 |
+
else:
|
338 |
+
node.with_context = default
|
339 |
+
return node
|
340 |
+
|
341 |
+
def parse_include(self) -> nodes.Include:
|
342 |
+
node = nodes.Include(lineno=next(self.stream).lineno)
|
343 |
+
node.template = self.parse_expression()
|
344 |
+
if self.stream.current.test("name:ignore") and self.stream.look().test(
|
345 |
+
"name:missing"
|
346 |
+
):
|
347 |
+
node.ignore_missing = True
|
348 |
+
self.stream.skip(2)
|
349 |
+
else:
|
350 |
+
node.ignore_missing = False
|
351 |
+
return self.parse_import_context(node, True)
|
352 |
+
|
353 |
+
def parse_import(self) -> nodes.Import:
|
354 |
+
node = nodes.Import(lineno=next(self.stream).lineno)
|
355 |
+
node.template = self.parse_expression()
|
356 |
+
self.stream.expect("name:as")
|
357 |
+
node.target = self.parse_assign_target(name_only=True).name
|
358 |
+
return self.parse_import_context(node, False)
|
359 |
+
|
360 |
+
def parse_from(self) -> nodes.FromImport:
|
361 |
+
node = nodes.FromImport(lineno=next(self.stream).lineno)
|
362 |
+
node.template = self.parse_expression()
|
363 |
+
self.stream.expect("name:import")
|
364 |
+
node.names = []
|
365 |
+
|
366 |
+
def parse_context() -> bool:
|
367 |
+
if self.stream.current.value in {
|
368 |
+
"with",
|
369 |
+
"without",
|
370 |
+
} and self.stream.look().test("name:context"):
|
371 |
+
node.with_context = next(self.stream).value == "with"
|
372 |
+
self.stream.skip()
|
373 |
+
return True
|
374 |
+
return False
|
375 |
+
|
376 |
+
while True:
|
377 |
+
if node.names:
|
378 |
+
self.stream.expect("comma")
|
379 |
+
if self.stream.current.type == "name":
|
380 |
+
if parse_context():
|
381 |
+
break
|
382 |
+
target = self.parse_assign_target(name_only=True)
|
383 |
+
if target.name.startswith("_"):
|
384 |
+
self.fail(
|
385 |
+
"names starting with an underline can not be imported",
|
386 |
+
target.lineno,
|
387 |
+
exc=TemplateAssertionError,
|
388 |
+
)
|
389 |
+
if self.stream.skip_if("name:as"):
|
390 |
+
alias = self.parse_assign_target(name_only=True)
|
391 |
+
node.names.append((target.name, alias.name))
|
392 |
+
else:
|
393 |
+
node.names.append(target.name)
|
394 |
+
if parse_context() or self.stream.current.type != "comma":
|
395 |
+
break
|
396 |
+
else:
|
397 |
+
self.stream.expect("name")
|
398 |
+
if not hasattr(node, "with_context"):
|
399 |
+
node.with_context = False
|
400 |
+
return node
|
401 |
+
|
402 |
+
def parse_signature(self, node: _MacroCall) -> None:
|
403 |
+
args = node.args = []
|
404 |
+
defaults = node.defaults = []
|
405 |
+
self.stream.expect("lparen")
|
406 |
+
while self.stream.current.type != "rparen":
|
407 |
+
if args:
|
408 |
+
self.stream.expect("comma")
|
409 |
+
arg = self.parse_assign_target(name_only=True)
|
410 |
+
arg.set_ctx("param")
|
411 |
+
if self.stream.skip_if("assign"):
|
412 |
+
defaults.append(self.parse_expression())
|
413 |
+
elif defaults:
|
414 |
+
self.fail("non-default argument follows default argument")
|
415 |
+
args.append(arg)
|
416 |
+
self.stream.expect("rparen")
|
417 |
+
|
418 |
+
def parse_call_block(self) -> nodes.CallBlock:
|
419 |
+
node = nodes.CallBlock(lineno=next(self.stream).lineno)
|
420 |
+
if self.stream.current.type == "lparen":
|
421 |
+
self.parse_signature(node)
|
422 |
+
else:
|
423 |
+
node.args = []
|
424 |
+
node.defaults = []
|
425 |
+
|
426 |
+
call_node = self.parse_expression()
|
427 |
+
if not isinstance(call_node, nodes.Call):
|
428 |
+
self.fail("expected call", node.lineno)
|
429 |
+
node.call = call_node
|
430 |
+
node.body = self.parse_statements(("name:endcall",), drop_needle=True)
|
431 |
+
return node
|
432 |
+
|
433 |
+
def parse_filter_block(self) -> nodes.FilterBlock:
|
434 |
+
node = nodes.FilterBlock(lineno=next(self.stream).lineno)
|
435 |
+
node.filter = self.parse_filter(None, start_inline=True) # type: ignore
|
436 |
+
node.body = self.parse_statements(("name:endfilter",), drop_needle=True)
|
437 |
+
return node
|
438 |
+
|
439 |
+
def parse_macro(self) -> nodes.Macro:
|
440 |
+
node = nodes.Macro(lineno=next(self.stream).lineno)
|
441 |
+
node.name = self.parse_assign_target(name_only=True).name
|
442 |
+
self.parse_signature(node)
|
443 |
+
node.body = self.parse_statements(("name:endmacro",), drop_needle=True)
|
444 |
+
return node
|
445 |
+
|
446 |
+
def parse_print(self) -> nodes.Output:
|
447 |
+
node = nodes.Output(lineno=next(self.stream).lineno)
|
448 |
+
node.nodes = []
|
449 |
+
while self.stream.current.type != "block_end":
|
450 |
+
if node.nodes:
|
451 |
+
self.stream.expect("comma")
|
452 |
+
node.nodes.append(self.parse_expression())
|
453 |
+
return node
|
454 |
+
|
455 |
+
@typing.overload
|
456 |
+
def parse_assign_target(
|
457 |
+
self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ...
|
458 |
+
) -> nodes.Name:
|
459 |
+
...
|
460 |
+
|
461 |
+
@typing.overload
|
462 |
+
def parse_assign_target(
|
463 |
+
self,
|
464 |
+
with_tuple: bool = True,
|
465 |
+
name_only: bool = False,
|
466 |
+
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
467 |
+
with_namespace: bool = False,
|
468 |
+
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
|
469 |
+
...
|
470 |
+
|
471 |
+
def parse_assign_target(
|
472 |
+
self,
|
473 |
+
with_tuple: bool = True,
|
474 |
+
name_only: bool = False,
|
475 |
+
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
476 |
+
with_namespace: bool = False,
|
477 |
+
) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
|
478 |
+
"""Parse an assignment target. As Jinja allows assignments to
|
479 |
+
tuples, this function can parse all allowed assignment targets. Per
|
480 |
+
default assignments to tuples are parsed, that can be disable however
|
481 |
+
by setting `with_tuple` to `False`. If only assignments to names are
|
482 |
+
wanted `name_only` can be set to `True`. The `extra_end_rules`
|
483 |
+
parameter is forwarded to the tuple parsing function. If
|
484 |
+
`with_namespace` is enabled, a namespace assignment may be parsed.
|
485 |
+
"""
|
486 |
+
target: nodes.Expr
|
487 |
+
|
488 |
+
if with_namespace and self.stream.look().type == "dot":
|
489 |
+
token = self.stream.expect("name")
|
490 |
+
next(self.stream) # dot
|
491 |
+
attr = self.stream.expect("name")
|
492 |
+
target = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
|
493 |
+
elif name_only:
|
494 |
+
token = self.stream.expect("name")
|
495 |
+
target = nodes.Name(token.value, "store", lineno=token.lineno)
|
496 |
+
else:
|
497 |
+
if with_tuple:
|
498 |
+
target = self.parse_tuple(
|
499 |
+
simplified=True, extra_end_rules=extra_end_rules
|
500 |
+
)
|
501 |
+
else:
|
502 |
+
target = self.parse_primary()
|
503 |
+
|
504 |
+
target.set_ctx("store")
|
505 |
+
|
506 |
+
if not target.can_assign():
|
507 |
+
self.fail(
|
508 |
+
f"can't assign to {type(target).__name__.lower()!r}", target.lineno
|
509 |
+
)
|
510 |
+
|
511 |
+
return target # type: ignore
|
512 |
+
|
513 |
+
def parse_expression(self, with_condexpr: bool = True) -> nodes.Expr:
|
514 |
+
"""Parse an expression. Per default all expressions are parsed, if
|
515 |
+
the optional `with_condexpr` parameter is set to `False` conditional
|
516 |
+
expressions are not parsed.
|
517 |
+
"""
|
518 |
+
if with_condexpr:
|
519 |
+
return self.parse_condexpr()
|
520 |
+
return self.parse_or()
|
521 |
+
|
522 |
+
def parse_condexpr(self) -> nodes.Expr:
|
523 |
+
lineno = self.stream.current.lineno
|
524 |
+
expr1 = self.parse_or()
|
525 |
+
expr3: t.Optional[nodes.Expr]
|
526 |
+
|
527 |
+
while self.stream.skip_if("name:if"):
|
528 |
+
expr2 = self.parse_or()
|
529 |
+
if self.stream.skip_if("name:else"):
|
530 |
+
expr3 = self.parse_condexpr()
|
531 |
+
else:
|
532 |
+
expr3 = None
|
533 |
+
expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
|
534 |
+
lineno = self.stream.current.lineno
|
535 |
+
return expr1
|
536 |
+
|
537 |
+
def parse_or(self) -> nodes.Expr:
|
538 |
+
lineno = self.stream.current.lineno
|
539 |
+
left = self.parse_and()
|
540 |
+
while self.stream.skip_if("name:or"):
|
541 |
+
right = self.parse_and()
|
542 |
+
left = nodes.Or(left, right, lineno=lineno)
|
543 |
+
lineno = self.stream.current.lineno
|
544 |
+
return left
|
545 |
+
|
546 |
+
def parse_and(self) -> nodes.Expr:
|
547 |
+
lineno = self.stream.current.lineno
|
548 |
+
left = self.parse_not()
|
549 |
+
while self.stream.skip_if("name:and"):
|
550 |
+
right = self.parse_not()
|
551 |
+
left = nodes.And(left, right, lineno=lineno)
|
552 |
+
lineno = self.stream.current.lineno
|
553 |
+
return left
|
554 |
+
|
555 |
+
def parse_not(self) -> nodes.Expr:
|
556 |
+
if self.stream.current.test("name:not"):
|
557 |
+
lineno = next(self.stream).lineno
|
558 |
+
return nodes.Not(self.parse_not(), lineno=lineno)
|
559 |
+
return self.parse_compare()
|
560 |
+
|
561 |
+
def parse_compare(self) -> nodes.Expr:
|
562 |
+
lineno = self.stream.current.lineno
|
563 |
+
expr = self.parse_math1()
|
564 |
+
ops = []
|
565 |
+
while True:
|
566 |
+
token_type = self.stream.current.type
|
567 |
+
if token_type in _compare_operators:
|
568 |
+
next(self.stream)
|
569 |
+
ops.append(nodes.Operand(token_type, self.parse_math1()))
|
570 |
+
elif self.stream.skip_if("name:in"):
|
571 |
+
ops.append(nodes.Operand("in", self.parse_math1()))
|
572 |
+
elif self.stream.current.test("name:not") and self.stream.look().test(
|
573 |
+
"name:in"
|
574 |
+
):
|
575 |
+
self.stream.skip(2)
|
576 |
+
ops.append(nodes.Operand("notin", self.parse_math1()))
|
577 |
+
else:
|
578 |
+
break
|
579 |
+
lineno = self.stream.current.lineno
|
580 |
+
if not ops:
|
581 |
+
return expr
|
582 |
+
return nodes.Compare(expr, ops, lineno=lineno)
|
583 |
+
|
584 |
+
def parse_math1(self) -> nodes.Expr:
|
585 |
+
lineno = self.stream.current.lineno
|
586 |
+
left = self.parse_concat()
|
587 |
+
while self.stream.current.type in ("add", "sub"):
|
588 |
+
cls = _math_nodes[self.stream.current.type]
|
589 |
+
next(self.stream)
|
590 |
+
right = self.parse_concat()
|
591 |
+
left = cls(left, right, lineno=lineno)
|
592 |
+
lineno = self.stream.current.lineno
|
593 |
+
return left
|
594 |
+
|
595 |
+
def parse_concat(self) -> nodes.Expr:
|
596 |
+
lineno = self.stream.current.lineno
|
597 |
+
args = [self.parse_math2()]
|
598 |
+
while self.stream.current.type == "tilde":
|
599 |
+
next(self.stream)
|
600 |
+
args.append(self.parse_math2())
|
601 |
+
if len(args) == 1:
|
602 |
+
return args[0]
|
603 |
+
return nodes.Concat(args, lineno=lineno)
|
604 |
+
|
605 |
+
def parse_math2(self) -> nodes.Expr:
|
606 |
+
lineno = self.stream.current.lineno
|
607 |
+
left = self.parse_pow()
|
608 |
+
while self.stream.current.type in ("mul", "div", "floordiv", "mod"):
|
609 |
+
cls = _math_nodes[self.stream.current.type]
|
610 |
+
next(self.stream)
|
611 |
+
right = self.parse_pow()
|
612 |
+
left = cls(left, right, lineno=lineno)
|
613 |
+
lineno = self.stream.current.lineno
|
614 |
+
return left
|
615 |
+
|
616 |
+
def parse_pow(self) -> nodes.Expr:
|
617 |
+
lineno = self.stream.current.lineno
|
618 |
+
left = self.parse_unary()
|
619 |
+
while self.stream.current.type == "pow":
|
620 |
+
next(self.stream)
|
621 |
+
right = self.parse_unary()
|
622 |
+
left = nodes.Pow(left, right, lineno=lineno)
|
623 |
+
lineno = self.stream.current.lineno
|
624 |
+
return left
|
625 |
+
|
626 |
+
def parse_unary(self, with_filter: bool = True) -> nodes.Expr:
|
627 |
+
token_type = self.stream.current.type
|
628 |
+
lineno = self.stream.current.lineno
|
629 |
+
node: nodes.Expr
|
630 |
+
|
631 |
+
if token_type == "sub":
|
632 |
+
next(self.stream)
|
633 |
+
node = nodes.Neg(self.parse_unary(False), lineno=lineno)
|
634 |
+
elif token_type == "add":
|
635 |
+
next(self.stream)
|
636 |
+
node = nodes.Pos(self.parse_unary(False), lineno=lineno)
|
637 |
+
else:
|
638 |
+
node = self.parse_primary()
|
639 |
+
node = self.parse_postfix(node)
|
640 |
+
if with_filter:
|
641 |
+
node = self.parse_filter_expr(node)
|
642 |
+
return node
|
643 |
+
|
644 |
+
def parse_primary(self) -> nodes.Expr:
|
645 |
+
token = self.stream.current
|
646 |
+
node: nodes.Expr
|
647 |
+
if token.type == "name":
|
648 |
+
if token.value in ("true", "false", "True", "False"):
|
649 |
+
node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)
|
650 |
+
elif token.value in ("none", "None"):
|
651 |
+
node = nodes.Const(None, lineno=token.lineno)
|
652 |
+
else:
|
653 |
+
node = nodes.Name(token.value, "load", lineno=token.lineno)
|
654 |
+
next(self.stream)
|
655 |
+
elif token.type == "string":
|
656 |
+
next(self.stream)
|
657 |
+
buf = [token.value]
|
658 |
+
lineno = token.lineno
|
659 |
+
while self.stream.current.type == "string":
|
660 |
+
buf.append(self.stream.current.value)
|
661 |
+
next(self.stream)
|
662 |
+
node = nodes.Const("".join(buf), lineno=lineno)
|
663 |
+
elif token.type in ("integer", "float"):
|
664 |
+
next(self.stream)
|
665 |
+
node = nodes.Const(token.value, lineno=token.lineno)
|
666 |
+
elif token.type == "lparen":
|
667 |
+
next(self.stream)
|
668 |
+
node = self.parse_tuple(explicit_parentheses=True)
|
669 |
+
self.stream.expect("rparen")
|
670 |
+
elif token.type == "lbracket":
|
671 |
+
node = self.parse_list()
|
672 |
+
elif token.type == "lbrace":
|
673 |
+
node = self.parse_dict()
|
674 |
+
else:
|
675 |
+
self.fail(f"unexpected {describe_token(token)!r}", token.lineno)
|
676 |
+
return node
|
677 |
+
|
678 |
+
def parse_tuple(
|
679 |
+
self,
|
680 |
+
simplified: bool = False,
|
681 |
+
with_condexpr: bool = True,
|
682 |
+
extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
|
683 |
+
explicit_parentheses: bool = False,
|
684 |
+
) -> t.Union[nodes.Tuple, nodes.Expr]:
|
685 |
+
"""Works like `parse_expression` but if multiple expressions are
|
686 |
+
delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
|
687 |
+
This method could also return a regular expression instead of a tuple
|
688 |
+
if no commas where found.
|
689 |
+
|
690 |
+
The default parsing mode is a full tuple. If `simplified` is `True`
|
691 |
+
only names and literals are parsed. The `no_condexpr` parameter is
|
692 |
+
forwarded to :meth:`parse_expression`.
|
693 |
+
|
694 |
+
Because tuples do not require delimiters and may end in a bogus comma
|
695 |
+
an extra hint is needed that marks the end of a tuple. For example
|
696 |
+
for loops support tuples between `for` and `in`. In that case the
|
697 |
+
`extra_end_rules` is set to ``['name:in']``.
|
698 |
+
|
699 |
+
`explicit_parentheses` is true if the parsing was triggered by an
|
700 |
+
expression in parentheses. This is used to figure out if an empty
|
701 |
+
tuple is a valid expression or not.
|
702 |
+
"""
|
703 |
+
lineno = self.stream.current.lineno
|
704 |
+
if simplified:
|
705 |
+
parse = self.parse_primary
|
706 |
+
elif with_condexpr:
|
707 |
+
parse = self.parse_expression
|
708 |
+
else:
|
709 |
+
|
710 |
+
def parse() -> nodes.Expr:
|
711 |
+
return self.parse_expression(with_condexpr=False)
|
712 |
+
|
713 |
+
args: t.List[nodes.Expr] = []
|
714 |
+
is_tuple = False
|
715 |
+
|
716 |
+
while True:
|
717 |
+
if args:
|
718 |
+
self.stream.expect("comma")
|
719 |
+
if self.is_tuple_end(extra_end_rules):
|
720 |
+
break
|
721 |
+
args.append(parse())
|
722 |
+
if self.stream.current.type == "comma":
|
723 |
+
is_tuple = True
|
724 |
+
else:
|
725 |
+
break
|
726 |
+
lineno = self.stream.current.lineno
|
727 |
+
|
728 |
+
if not is_tuple:
|
729 |
+
if args:
|
730 |
+
return args[0]
|
731 |
+
|
732 |
+
# if we don't have explicit parentheses, an empty tuple is
|
733 |
+
# not a valid expression. This would mean nothing (literally
|
734 |
+
# nothing) in the spot of an expression would be an empty
|
735 |
+
# tuple.
|
736 |
+
if not explicit_parentheses:
|
737 |
+
self.fail(
|
738 |
+
"Expected an expression,"
|
739 |
+
f" got {describe_token(self.stream.current)!r}"
|
740 |
+
)
|
741 |
+
|
742 |
+
return nodes.Tuple(args, "load", lineno=lineno)
|
743 |
+
|
744 |
+
def parse_list(self) -> nodes.List:
|
745 |
+
token = self.stream.expect("lbracket")
|
746 |
+
items: t.List[nodes.Expr] = []
|
747 |
+
while self.stream.current.type != "rbracket":
|
748 |
+
if items:
|
749 |
+
self.stream.expect("comma")
|
750 |
+
if self.stream.current.type == "rbracket":
|
751 |
+
break
|
752 |
+
items.append(self.parse_expression())
|
753 |
+
self.stream.expect("rbracket")
|
754 |
+
return nodes.List(items, lineno=token.lineno)
|
755 |
+
|
756 |
+
def parse_dict(self) -> nodes.Dict:
|
757 |
+
token = self.stream.expect("lbrace")
|
758 |
+
items: t.List[nodes.Pair] = []
|
759 |
+
while self.stream.current.type != "rbrace":
|
760 |
+
if items:
|
761 |
+
self.stream.expect("comma")
|
762 |
+
if self.stream.current.type == "rbrace":
|
763 |
+
break
|
764 |
+
key = self.parse_expression()
|
765 |
+
self.stream.expect("colon")
|
766 |
+
value = self.parse_expression()
|
767 |
+
items.append(nodes.Pair(key, value, lineno=key.lineno))
|
768 |
+
self.stream.expect("rbrace")
|
769 |
+
return nodes.Dict(items, lineno=token.lineno)
|
770 |
+
|
771 |
+
def parse_postfix(self, node: nodes.Expr) -> nodes.Expr:
|
772 |
+
while True:
|
773 |
+
token_type = self.stream.current.type
|
774 |
+
if token_type == "dot" or token_type == "lbracket":
|
775 |
+
node = self.parse_subscript(node)
|
776 |
+
# calls are valid both after postfix expressions (getattr
|
777 |
+
# and getitem) as well as filters and tests
|
778 |
+
elif token_type == "lparen":
|
779 |
+
node = self.parse_call(node)
|
780 |
+
else:
|
781 |
+
break
|
782 |
+
return node
|
783 |
+
|
784 |
+
def parse_filter_expr(self, node: nodes.Expr) -> nodes.Expr:
|
785 |
+
while True:
|
786 |
+
token_type = self.stream.current.type
|
787 |
+
if token_type == "pipe":
|
788 |
+
node = self.parse_filter(node) # type: ignore
|
789 |
+
elif token_type == "name" and self.stream.current.value == "is":
|
790 |
+
node = self.parse_test(node)
|
791 |
+
# calls are valid both after postfix expressions (getattr
|
792 |
+
# and getitem) as well as filters and tests
|
793 |
+
elif token_type == "lparen":
|
794 |
+
node = self.parse_call(node)
|
795 |
+
else:
|
796 |
+
break
|
797 |
+
return node
|
798 |
+
|
799 |
+
def parse_subscript(
|
800 |
+
self, node: nodes.Expr
|
801 |
+
) -> t.Union[nodes.Getattr, nodes.Getitem]:
|
802 |
+
token = next(self.stream)
|
803 |
+
arg: nodes.Expr
|
804 |
+
|
805 |
+
if token.type == "dot":
|
806 |
+
attr_token = self.stream.current
|
807 |
+
next(self.stream)
|
808 |
+
if attr_token.type == "name":
|
809 |
+
return nodes.Getattr(
|
810 |
+
node, attr_token.value, "load", lineno=token.lineno
|
811 |
+
)
|
812 |
+
elif attr_token.type != "integer":
|
813 |
+
self.fail("expected name or number", attr_token.lineno)
|
814 |
+
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
|
815 |
+
return nodes.Getitem(node, arg, "load", lineno=token.lineno)
|
816 |
+
if token.type == "lbracket":
|
817 |
+
args: t.List[nodes.Expr] = []
|
818 |
+
while self.stream.current.type != "rbracket":
|
819 |
+
if args:
|
820 |
+
self.stream.expect("comma")
|
821 |
+
args.append(self.parse_subscribed())
|
822 |
+
self.stream.expect("rbracket")
|
823 |
+
if len(args) == 1:
|
824 |
+
arg = args[0]
|
825 |
+
else:
|
826 |
+
arg = nodes.Tuple(args, "load", lineno=token.lineno)
|
827 |
+
return nodes.Getitem(node, arg, "load", lineno=token.lineno)
|
828 |
+
self.fail("expected subscript expression", token.lineno)
|
829 |
+
|
830 |
+
def parse_subscribed(self) -> nodes.Expr:
|
831 |
+
lineno = self.stream.current.lineno
|
832 |
+
args: t.List[t.Optional[nodes.Expr]]
|
833 |
+
|
834 |
+
if self.stream.current.type == "colon":
|
835 |
+
next(self.stream)
|
836 |
+
args = [None]
|
837 |
+
else:
|
838 |
+
node = self.parse_expression()
|
839 |
+
if self.stream.current.type != "colon":
|
840 |
+
return node
|
841 |
+
next(self.stream)
|
842 |
+
args = [node]
|
843 |
+
|
844 |
+
if self.stream.current.type == "colon":
|
845 |
+
args.append(None)
|
846 |
+
elif self.stream.current.type not in ("rbracket", "comma"):
|
847 |
+
args.append(self.parse_expression())
|
848 |
+
else:
|
849 |
+
args.append(None)
|
850 |
+
|
851 |
+
if self.stream.current.type == "colon":
|
852 |
+
next(self.stream)
|
853 |
+
if self.stream.current.type not in ("rbracket", "comma"):
|
854 |
+
args.append(self.parse_expression())
|
855 |
+
else:
|
856 |
+
args.append(None)
|
857 |
+
else:
|
858 |
+
args.append(None)
|
859 |
+
|
860 |
+
return nodes.Slice(lineno=lineno, *args)
|
861 |
+
|
862 |
+
def parse_call_args(self) -> t.Tuple:
|
863 |
+
token = self.stream.expect("lparen")
|
864 |
+
args = []
|
865 |
+
kwargs = []
|
866 |
+
dyn_args = None
|
867 |
+
dyn_kwargs = None
|
868 |
+
require_comma = False
|
869 |
+
|
870 |
+
def ensure(expr: bool) -> None:
|
871 |
+
if not expr:
|
872 |
+
self.fail("invalid syntax for function call expression", token.lineno)
|
873 |
+
|
874 |
+
while self.stream.current.type != "rparen":
|
875 |
+
if require_comma:
|
876 |
+
self.stream.expect("comma")
|
877 |
+
|
878 |
+
# support for trailing comma
|
879 |
+
if self.stream.current.type == "rparen":
|
880 |
+
break
|
881 |
+
|
882 |
+
if self.stream.current.type == "mul":
|
883 |
+
ensure(dyn_args is None and dyn_kwargs is None)
|
884 |
+
next(self.stream)
|
885 |
+
dyn_args = self.parse_expression()
|
886 |
+
elif self.stream.current.type == "pow":
|
887 |
+
ensure(dyn_kwargs is None)
|
888 |
+
next(self.stream)
|
889 |
+
dyn_kwargs = self.parse_expression()
|
890 |
+
else:
|
891 |
+
if (
|
892 |
+
self.stream.current.type == "name"
|
893 |
+
and self.stream.look().type == "assign"
|
894 |
+
):
|
895 |
+
# Parsing a kwarg
|
896 |
+
ensure(dyn_kwargs is None)
|
897 |
+
key = self.stream.current.value
|
898 |
+
self.stream.skip(2)
|
899 |
+
value = self.parse_expression()
|
900 |
+
kwargs.append(nodes.Keyword(key, value, lineno=value.lineno))
|
901 |
+
else:
|
902 |
+
# Parsing an arg
|
903 |
+
ensure(dyn_args is None and dyn_kwargs is None and not kwargs)
|
904 |
+
args.append(self.parse_expression())
|
905 |
+
|
906 |
+
require_comma = True
|
907 |
+
|
908 |
+
self.stream.expect("rparen")
|
909 |
+
return args, kwargs, dyn_args, dyn_kwargs
|
910 |
+
|
911 |
+
def parse_call(self, node: nodes.Expr) -> nodes.Call:
|
912 |
+
# The lparen will be expected in parse_call_args, but the lineno
|
913 |
+
# needs to be recorded before the stream is advanced.
|
914 |
+
token = self.stream.current
|
915 |
+
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
916 |
+
return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)
|
917 |
+
|
918 |
+
def parse_filter(
|
919 |
+
self, node: t.Optional[nodes.Expr], start_inline: bool = False
|
920 |
+
) -> t.Optional[nodes.Expr]:
|
921 |
+
while self.stream.current.type == "pipe" or start_inline:
|
922 |
+
if not start_inline:
|
923 |
+
next(self.stream)
|
924 |
+
token = self.stream.expect("name")
|
925 |
+
name = token.value
|
926 |
+
while self.stream.current.type == "dot":
|
927 |
+
next(self.stream)
|
928 |
+
name += "." + self.stream.expect("name").value
|
929 |
+
if self.stream.current.type == "lparen":
|
930 |
+
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
931 |
+
else:
|
932 |
+
args = []
|
933 |
+
kwargs = []
|
934 |
+
dyn_args = dyn_kwargs = None
|
935 |
+
node = nodes.Filter(
|
936 |
+
node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno
|
937 |
+
)
|
938 |
+
start_inline = False
|
939 |
+
return node
|
940 |
+
|
941 |
+
def parse_test(self, node: nodes.Expr) -> nodes.Expr:
|
942 |
+
token = next(self.stream)
|
943 |
+
if self.stream.current.test("name:not"):
|
944 |
+
next(self.stream)
|
945 |
+
negated = True
|
946 |
+
else:
|
947 |
+
negated = False
|
948 |
+
name = self.stream.expect("name").value
|
949 |
+
while self.stream.current.type == "dot":
|
950 |
+
next(self.stream)
|
951 |
+
name += "." + self.stream.expect("name").value
|
952 |
+
dyn_args = dyn_kwargs = None
|
953 |
+
kwargs = []
|
954 |
+
if self.stream.current.type == "lparen":
|
955 |
+
args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
|
956 |
+
elif self.stream.current.type in {
|
957 |
+
"name",
|
958 |
+
"string",
|
959 |
+
"integer",
|
960 |
+
"float",
|
961 |
+
"lparen",
|
962 |
+
"lbracket",
|
963 |
+
"lbrace",
|
964 |
+
} and not self.stream.current.test_any("name:else", "name:or", "name:and"):
|
965 |
+
if self.stream.current.test("name:is"):
|
966 |
+
self.fail("You cannot chain multiple tests with is")
|
967 |
+
arg_node = self.parse_primary()
|
968 |
+
arg_node = self.parse_postfix(arg_node)
|
969 |
+
args = [arg_node]
|
970 |
+
else:
|
971 |
+
args = []
|
972 |
+
node = nodes.Test(
|
973 |
+
node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno
|
974 |
+
)
|
975 |
+
if negated:
|
976 |
+
node = nodes.Not(node, lineno=token.lineno)
|
977 |
+
return node
|
978 |
+
|
979 |
+
def subparse(
|
980 |
+
self, end_tokens: t.Optional[t.Tuple[str, ...]] = None
|
981 |
+
) -> t.List[nodes.Node]:
|
982 |
+
body: t.List[nodes.Node] = []
|
983 |
+
data_buffer: t.List[nodes.Node] = []
|
984 |
+
add_data = data_buffer.append
|
985 |
+
|
986 |
+
if end_tokens is not None:
|
987 |
+
self._end_token_stack.append(end_tokens)
|
988 |
+
|
989 |
+
def flush_data() -> None:
|
990 |
+
if data_buffer:
|
991 |
+
lineno = data_buffer[0].lineno
|
992 |
+
body.append(nodes.Output(data_buffer[:], lineno=lineno))
|
993 |
+
del data_buffer[:]
|
994 |
+
|
995 |
+
try:
|
996 |
+
while self.stream:
|
997 |
+
token = self.stream.current
|
998 |
+
if token.type == "data":
|
999 |
+
if token.value:
|
1000 |
+
add_data(nodes.TemplateData(token.value, lineno=token.lineno))
|
1001 |
+
next(self.stream)
|
1002 |
+
elif token.type == "variable_begin":
|
1003 |
+
next(self.stream)
|
1004 |
+
add_data(self.parse_tuple(with_condexpr=True))
|
1005 |
+
self.stream.expect("variable_end")
|
1006 |
+
elif token.type == "block_begin":
|
1007 |
+
flush_data()
|
1008 |
+
next(self.stream)
|
1009 |
+
if end_tokens is not None and self.stream.current.test_any(
|
1010 |
+
*end_tokens
|
1011 |
+
):
|
1012 |
+
return body
|
1013 |
+
rv = self.parse_statement()
|
1014 |
+
if isinstance(rv, list):
|
1015 |
+
body.extend(rv)
|
1016 |
+
else:
|
1017 |
+
body.append(rv)
|
1018 |
+
self.stream.expect("block_end")
|
1019 |
+
else:
|
1020 |
+
raise AssertionError("internal parsing error")
|
1021 |
+
|
1022 |
+
flush_data()
|
1023 |
+
finally:
|
1024 |
+
if end_tokens is not None:
|
1025 |
+
self._end_token_stack.pop()
|
1026 |
+
return body
|
1027 |
+
|
1028 |
+
def parse(self) -> nodes.Template:
|
1029 |
+
"""Parse the whole template into a `Template` node."""
|
1030 |
+
result = nodes.Template(self.subparse(), lineno=1)
|
1031 |
+
result.set_environment(self.environment)
|
1032 |
+
return result
|
lib/python3.11/site-packages/jinja2/py.typed
ADDED
File without changes
|
lib/python3.11/site-packages/jinja2/runtime.py
ADDED
@@ -0,0 +1,1053 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""The runtime functions and state used by compiled templates."""
|
2 |
+
import functools
|
3 |
+
import sys
|
4 |
+
import typing as t
|
5 |
+
from collections import abc
|
6 |
+
from itertools import chain
|
7 |
+
|
8 |
+
from markupsafe import escape # noqa: F401
|
9 |
+
from markupsafe import Markup
|
10 |
+
from markupsafe import soft_str
|
11 |
+
|
12 |
+
from .async_utils import auto_aiter
|
13 |
+
from .async_utils import auto_await # noqa: F401
|
14 |
+
from .exceptions import TemplateNotFound # noqa: F401
|
15 |
+
from .exceptions import TemplateRuntimeError # noqa: F401
|
16 |
+
from .exceptions import UndefinedError
|
17 |
+
from .nodes import EvalContext
|
18 |
+
from .utils import _PassArg
|
19 |
+
from .utils import concat
|
20 |
+
from .utils import internalcode
|
21 |
+
from .utils import missing
|
22 |
+
from .utils import Namespace # noqa: F401
|
23 |
+
from .utils import object_type_repr
|
24 |
+
from .utils import pass_eval_context
|
25 |
+
|
26 |
+
V = t.TypeVar("V")
|
27 |
+
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
28 |
+
|
29 |
+
if t.TYPE_CHECKING:
|
30 |
+
import logging
|
31 |
+
import typing_extensions as te
|
32 |
+
from .environment import Environment
|
33 |
+
|
34 |
+
class LoopRenderFunc(te.Protocol):
|
35 |
+
def __call__(
|
36 |
+
self,
|
37 |
+
reciter: t.Iterable[V],
|
38 |
+
loop_render_func: "LoopRenderFunc",
|
39 |
+
depth: int = 0,
|
40 |
+
) -> str:
|
41 |
+
...
|
42 |
+
|
43 |
+
|
44 |
+
# these variables are exported to the template runtime
|
45 |
+
exported = [
|
46 |
+
"LoopContext",
|
47 |
+
"TemplateReference",
|
48 |
+
"Macro",
|
49 |
+
"Markup",
|
50 |
+
"TemplateRuntimeError",
|
51 |
+
"missing",
|
52 |
+
"escape",
|
53 |
+
"markup_join",
|
54 |
+
"str_join",
|
55 |
+
"identity",
|
56 |
+
"TemplateNotFound",
|
57 |
+
"Namespace",
|
58 |
+
"Undefined",
|
59 |
+
"internalcode",
|
60 |
+
]
|
61 |
+
async_exported = [
|
62 |
+
"AsyncLoopContext",
|
63 |
+
"auto_aiter",
|
64 |
+
"auto_await",
|
65 |
+
]
|
66 |
+
|
67 |
+
|
68 |
+
def identity(x: V) -> V:
|
69 |
+
"""Returns its argument. Useful for certain things in the
|
70 |
+
environment.
|
71 |
+
"""
|
72 |
+
return x
|
73 |
+
|
74 |
+
|
75 |
+
def markup_join(seq: t.Iterable[t.Any]) -> str:
|
76 |
+
"""Concatenation that escapes if necessary and converts to string."""
|
77 |
+
buf = []
|
78 |
+
iterator = map(soft_str, seq)
|
79 |
+
for arg in iterator:
|
80 |
+
buf.append(arg)
|
81 |
+
if hasattr(arg, "__html__"):
|
82 |
+
return Markup("").join(chain(buf, iterator))
|
83 |
+
return concat(buf)
|
84 |
+
|
85 |
+
|
86 |
+
def str_join(seq: t.Iterable[t.Any]) -> str:
|
87 |
+
"""Simple args to string conversion and concatenation."""
|
88 |
+
return concat(map(str, seq))
|
89 |
+
|
90 |
+
|
91 |
+
def new_context(
|
92 |
+
environment: "Environment",
|
93 |
+
template_name: t.Optional[str],
|
94 |
+
blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],
|
95 |
+
vars: t.Optional[t.Dict[str, t.Any]] = None,
|
96 |
+
shared: bool = False,
|
97 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
98 |
+
locals: t.Optional[t.Mapping[str, t.Any]] = None,
|
99 |
+
) -> "Context":
|
100 |
+
"""Internal helper for context creation."""
|
101 |
+
if vars is None:
|
102 |
+
vars = {}
|
103 |
+
if shared:
|
104 |
+
parent = vars
|
105 |
+
else:
|
106 |
+
parent = dict(globals or (), **vars)
|
107 |
+
if locals:
|
108 |
+
# if the parent is shared a copy should be created because
|
109 |
+
# we don't want to modify the dict passed
|
110 |
+
if shared:
|
111 |
+
parent = dict(parent)
|
112 |
+
for key, value in locals.items():
|
113 |
+
if value is not missing:
|
114 |
+
parent[key] = value
|
115 |
+
return environment.context_class(
|
116 |
+
environment, parent, template_name, blocks, globals=globals
|
117 |
+
)
|
118 |
+
|
119 |
+
|
120 |
+
class TemplateReference:
|
121 |
+
"""The `self` in templates."""
|
122 |
+
|
123 |
+
def __init__(self, context: "Context") -> None:
|
124 |
+
self.__context = context
|
125 |
+
|
126 |
+
def __getitem__(self, name: str) -> t.Any:
|
127 |
+
blocks = self.__context.blocks[name]
|
128 |
+
return BlockReference(name, self.__context, blocks, 0)
|
129 |
+
|
130 |
+
def __repr__(self) -> str:
|
131 |
+
return f"<{type(self).__name__} {self.__context.name!r}>"
|
132 |
+
|
133 |
+
|
134 |
+
def _dict_method_all(dict_method: F) -> F:
|
135 |
+
@functools.wraps(dict_method)
|
136 |
+
def f_all(self: "Context") -> t.Any:
|
137 |
+
return dict_method(self.get_all())
|
138 |
+
|
139 |
+
return t.cast(F, f_all)
|
140 |
+
|
141 |
+
|
142 |
+
@abc.Mapping.register
|
143 |
+
class Context:
|
144 |
+
"""The template context holds the variables of a template. It stores the
|
145 |
+
values passed to the template and also the names the template exports.
|
146 |
+
Creating instances is neither supported nor useful as it's created
|
147 |
+
automatically at various stages of the template evaluation and should not
|
148 |
+
be created by hand.
|
149 |
+
|
150 |
+
The context is immutable. Modifications on :attr:`parent` **must not**
|
151 |
+
happen and modifications on :attr:`vars` are allowed from generated
|
152 |
+
template code only. Template filters and global functions marked as
|
153 |
+
:func:`pass_context` get the active context passed as first argument
|
154 |
+
and are allowed to access the context read-only.
|
155 |
+
|
156 |
+
The template context supports read only dict operations (`get`,
|
157 |
+
`keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
|
158 |
+
`__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`
|
159 |
+
method that doesn't fail with a `KeyError` but returns an
|
160 |
+
:class:`Undefined` object for missing variables.
|
161 |
+
"""
|
162 |
+
|
163 |
+
def __init__(
|
164 |
+
self,
|
165 |
+
environment: "Environment",
|
166 |
+
parent: t.Dict[str, t.Any],
|
167 |
+
name: t.Optional[str],
|
168 |
+
blocks: t.Dict[str, t.Callable[["Context"], t.Iterator[str]]],
|
169 |
+
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
170 |
+
):
|
171 |
+
self.parent = parent
|
172 |
+
self.vars: t.Dict[str, t.Any] = {}
|
173 |
+
self.environment: "Environment" = environment
|
174 |
+
self.eval_ctx = EvalContext(self.environment, name)
|
175 |
+
self.exported_vars: t.Set[str] = set()
|
176 |
+
self.name = name
|
177 |
+
self.globals_keys = set() if globals is None else set(globals)
|
178 |
+
|
179 |
+
# create the initial mapping of blocks. Whenever template inheritance
|
180 |
+
# takes place the runtime will update this mapping with the new blocks
|
181 |
+
# from the template.
|
182 |
+
self.blocks = {k: [v] for k, v in blocks.items()}
|
183 |
+
|
184 |
+
def super(
|
185 |
+
self, name: str, current: t.Callable[["Context"], t.Iterator[str]]
|
186 |
+
) -> t.Union["BlockReference", "Undefined"]:
|
187 |
+
"""Render a parent block."""
|
188 |
+
try:
|
189 |
+
blocks = self.blocks[name]
|
190 |
+
index = blocks.index(current) + 1
|
191 |
+
blocks[index]
|
192 |
+
except LookupError:
|
193 |
+
return self.environment.undefined(
|
194 |
+
f"there is no parent block called {name!r}.", name="super"
|
195 |
+
)
|
196 |
+
return BlockReference(name, self, blocks, index)
|
197 |
+
|
198 |
+
def get(self, key: str, default: t.Any = None) -> t.Any:
|
199 |
+
"""Look up a variable by name, or return a default if the key is
|
200 |
+
not found.
|
201 |
+
|
202 |
+
:param key: The variable name to look up.
|
203 |
+
:param default: The value to return if the key is not found.
|
204 |
+
"""
|
205 |
+
try:
|
206 |
+
return self[key]
|
207 |
+
except KeyError:
|
208 |
+
return default
|
209 |
+
|
210 |
+
def resolve(self, key: str) -> t.Union[t.Any, "Undefined"]:
|
211 |
+
"""Look up a variable by name, or return an :class:`Undefined`
|
212 |
+
object if the key is not found.
|
213 |
+
|
214 |
+
If you need to add custom behavior, override
|
215 |
+
:meth:`resolve_or_missing`, not this method. The various lookup
|
216 |
+
functions use that method, not this one.
|
217 |
+
|
218 |
+
:param key: The variable name to look up.
|
219 |
+
"""
|
220 |
+
rv = self.resolve_or_missing(key)
|
221 |
+
|
222 |
+
if rv is missing:
|
223 |
+
return self.environment.undefined(name=key)
|
224 |
+
|
225 |
+
return rv
|
226 |
+
|
227 |
+
def resolve_or_missing(self, key: str) -> t.Any:
|
228 |
+
"""Look up a variable by name, or return a ``missing`` sentinel
|
229 |
+
if the key is not found.
|
230 |
+
|
231 |
+
Override this method to add custom lookup behavior.
|
232 |
+
:meth:`resolve`, :meth:`get`, and :meth:`__getitem__` use this
|
233 |
+
method. Don't call this method directly.
|
234 |
+
|
235 |
+
:param key: The variable name to look up.
|
236 |
+
"""
|
237 |
+
if key in self.vars:
|
238 |
+
return self.vars[key]
|
239 |
+
|
240 |
+
if key in self.parent:
|
241 |
+
return self.parent[key]
|
242 |
+
|
243 |
+
return missing
|
244 |
+
|
245 |
+
def get_exported(self) -> t.Dict[str, t.Any]:
|
246 |
+
"""Get a new dict with the exported variables."""
|
247 |
+
return {k: self.vars[k] for k in self.exported_vars}
|
248 |
+
|
249 |
+
def get_all(self) -> t.Dict[str, t.Any]:
|
250 |
+
"""Return the complete context as dict including the exported
|
251 |
+
variables. For optimizations reasons this might not return an
|
252 |
+
actual copy so be careful with using it.
|
253 |
+
"""
|
254 |
+
if not self.vars:
|
255 |
+
return self.parent
|
256 |
+
if not self.parent:
|
257 |
+
return self.vars
|
258 |
+
return dict(self.parent, **self.vars)
|
259 |
+
|
260 |
+
@internalcode
|
261 |
+
def call(
|
262 |
+
__self, __obj: t.Callable, *args: t.Any, **kwargs: t.Any # noqa: B902
|
263 |
+
) -> t.Union[t.Any, "Undefined"]:
|
264 |
+
"""Call the callable with the arguments and keyword arguments
|
265 |
+
provided but inject the active context or environment as first
|
266 |
+
argument if the callable has :func:`pass_context` or
|
267 |
+
:func:`pass_environment`.
|
268 |
+
"""
|
269 |
+
if __debug__:
|
270 |
+
__traceback_hide__ = True # noqa
|
271 |
+
|
272 |
+
# Allow callable classes to take a context
|
273 |
+
if (
|
274 |
+
hasattr(__obj, "__call__") # noqa: B004
|
275 |
+
and _PassArg.from_obj(__obj.__call__) is not None # type: ignore
|
276 |
+
):
|
277 |
+
__obj = __obj.__call__ # type: ignore
|
278 |
+
|
279 |
+
pass_arg = _PassArg.from_obj(__obj)
|
280 |
+
|
281 |
+
if pass_arg is _PassArg.context:
|
282 |
+
# the active context should have access to variables set in
|
283 |
+
# loops and blocks without mutating the context itself
|
284 |
+
if kwargs.get("_loop_vars"):
|
285 |
+
__self = __self.derived(kwargs["_loop_vars"])
|
286 |
+
if kwargs.get("_block_vars"):
|
287 |
+
__self = __self.derived(kwargs["_block_vars"])
|
288 |
+
args = (__self,) + args
|
289 |
+
elif pass_arg is _PassArg.eval_context:
|
290 |
+
args = (__self.eval_ctx,) + args
|
291 |
+
elif pass_arg is _PassArg.environment:
|
292 |
+
args = (__self.environment,) + args
|
293 |
+
|
294 |
+
kwargs.pop("_block_vars", None)
|
295 |
+
kwargs.pop("_loop_vars", None)
|
296 |
+
|
297 |
+
try:
|
298 |
+
return __obj(*args, **kwargs)
|
299 |
+
except StopIteration:
|
300 |
+
return __self.environment.undefined(
|
301 |
+
"value was undefined because a callable raised a"
|
302 |
+
" StopIteration exception"
|
303 |
+
)
|
304 |
+
|
305 |
+
def derived(self, locals: t.Optional[t.Dict[str, t.Any]] = None) -> "Context":
|
306 |
+
"""Internal helper function to create a derived context. This is
|
307 |
+
used in situations where the system needs a new context in the same
|
308 |
+
template that is independent.
|
309 |
+
"""
|
310 |
+
context = new_context(
|
311 |
+
self.environment, self.name, {}, self.get_all(), True, None, locals
|
312 |
+
)
|
313 |
+
context.eval_ctx = self.eval_ctx
|
314 |
+
context.blocks.update((k, list(v)) for k, v in self.blocks.items())
|
315 |
+
return context
|
316 |
+
|
317 |
+
keys = _dict_method_all(dict.keys)
|
318 |
+
values = _dict_method_all(dict.values)
|
319 |
+
items = _dict_method_all(dict.items)
|
320 |
+
|
321 |
+
def __contains__(self, name: str) -> bool:
|
322 |
+
return name in self.vars or name in self.parent
|
323 |
+
|
324 |
+
def __getitem__(self, key: str) -> t.Any:
|
325 |
+
"""Look up a variable by name with ``[]`` syntax, or raise a
|
326 |
+
``KeyError`` if the key is not found.
|
327 |
+
"""
|
328 |
+
item = self.resolve_or_missing(key)
|
329 |
+
|
330 |
+
if item is missing:
|
331 |
+
raise KeyError(key)
|
332 |
+
|
333 |
+
return item
|
334 |
+
|
335 |
+
def __repr__(self) -> str:
|
336 |
+
return f"<{type(self).__name__} {self.get_all()!r} of {self.name!r}>"
|
337 |
+
|
338 |
+
|
339 |
+
class BlockReference:
|
340 |
+
"""One block on a template reference."""
|
341 |
+
|
342 |
+
def __init__(
|
343 |
+
self,
|
344 |
+
name: str,
|
345 |
+
context: "Context",
|
346 |
+
stack: t.List[t.Callable[["Context"], t.Iterator[str]]],
|
347 |
+
depth: int,
|
348 |
+
) -> None:
|
349 |
+
self.name = name
|
350 |
+
self._context = context
|
351 |
+
self._stack = stack
|
352 |
+
self._depth = depth
|
353 |
+
|
354 |
+
@property
|
355 |
+
def super(self) -> t.Union["BlockReference", "Undefined"]:
|
356 |
+
"""Super the block."""
|
357 |
+
if self._depth + 1 >= len(self._stack):
|
358 |
+
return self._context.environment.undefined(
|
359 |
+
f"there is no parent block called {self.name!r}.", name="super"
|
360 |
+
)
|
361 |
+
return BlockReference(self.name, self._context, self._stack, self._depth + 1)
|
362 |
+
|
363 |
+
@internalcode
|
364 |
+
async def _async_call(self) -> str:
|
365 |
+
rv = concat(
|
366 |
+
[x async for x in self._stack[self._depth](self._context)] # type: ignore
|
367 |
+
)
|
368 |
+
|
369 |
+
if self._context.eval_ctx.autoescape:
|
370 |
+
return Markup(rv)
|
371 |
+
|
372 |
+
return rv
|
373 |
+
|
374 |
+
@internalcode
|
375 |
+
def __call__(self) -> str:
|
376 |
+
if self._context.environment.is_async:
|
377 |
+
return self._async_call() # type: ignore
|
378 |
+
|
379 |
+
rv = concat(self._stack[self._depth](self._context))
|
380 |
+
|
381 |
+
if self._context.eval_ctx.autoescape:
|
382 |
+
return Markup(rv)
|
383 |
+
|
384 |
+
return rv
|
385 |
+
|
386 |
+
|
387 |
+
class LoopContext:
|
388 |
+
"""A wrapper iterable for dynamic ``for`` loops, with information
|
389 |
+
about the loop and iteration.
|
390 |
+
"""
|
391 |
+
|
392 |
+
#: Current iteration of the loop, starting at 0.
|
393 |
+
index0 = -1
|
394 |
+
|
395 |
+
_length: t.Optional[int] = None
|
396 |
+
_after: t.Any = missing
|
397 |
+
_current: t.Any = missing
|
398 |
+
_before: t.Any = missing
|
399 |
+
_last_changed_value: t.Any = missing
|
400 |
+
|
401 |
+
def __init__(
|
402 |
+
self,
|
403 |
+
iterable: t.Iterable[V],
|
404 |
+
undefined: t.Type["Undefined"],
|
405 |
+
recurse: t.Optional["LoopRenderFunc"] = None,
|
406 |
+
depth0: int = 0,
|
407 |
+
) -> None:
|
408 |
+
"""
|
409 |
+
:param iterable: Iterable to wrap.
|
410 |
+
:param undefined: :class:`Undefined` class to use for next and
|
411 |
+
previous items.
|
412 |
+
:param recurse: The function to render the loop body when the
|
413 |
+
loop is marked recursive.
|
414 |
+
:param depth0: Incremented when looping recursively.
|
415 |
+
"""
|
416 |
+
self._iterable = iterable
|
417 |
+
self._iterator = self._to_iterator(iterable)
|
418 |
+
self._undefined = undefined
|
419 |
+
self._recurse = recurse
|
420 |
+
#: How many levels deep a recursive loop currently is, starting at 0.
|
421 |
+
self.depth0 = depth0
|
422 |
+
|
423 |
+
@staticmethod
|
424 |
+
def _to_iterator(iterable: t.Iterable[V]) -> t.Iterator[V]:
|
425 |
+
return iter(iterable)
|
426 |
+
|
427 |
+
@property
|
428 |
+
def length(self) -> int:
|
429 |
+
"""Length of the iterable.
|
430 |
+
|
431 |
+
If the iterable is a generator or otherwise does not have a
|
432 |
+
size, it is eagerly evaluated to get a size.
|
433 |
+
"""
|
434 |
+
if self._length is not None:
|
435 |
+
return self._length
|
436 |
+
|
437 |
+
try:
|
438 |
+
self._length = len(self._iterable) # type: ignore
|
439 |
+
except TypeError:
|
440 |
+
iterable = list(self._iterator)
|
441 |
+
self._iterator = self._to_iterator(iterable)
|
442 |
+
self._length = len(iterable) + self.index + (self._after is not missing)
|
443 |
+
|
444 |
+
return self._length
|
445 |
+
|
446 |
+
def __len__(self) -> int:
|
447 |
+
return self.length
|
448 |
+
|
449 |
+
@property
|
450 |
+
def depth(self) -> int:
|
451 |
+
"""How many levels deep a recursive loop currently is, starting at 1."""
|
452 |
+
return self.depth0 + 1
|
453 |
+
|
454 |
+
@property
|
455 |
+
def index(self) -> int:
|
456 |
+
"""Current iteration of the loop, starting at 1."""
|
457 |
+
return self.index0 + 1
|
458 |
+
|
459 |
+
@property
|
460 |
+
def revindex0(self) -> int:
|
461 |
+
"""Number of iterations from the end of the loop, ending at 0.
|
462 |
+
|
463 |
+
Requires calculating :attr:`length`.
|
464 |
+
"""
|
465 |
+
return self.length - self.index
|
466 |
+
|
467 |
+
@property
|
468 |
+
def revindex(self) -> int:
|
469 |
+
"""Number of iterations from the end of the loop, ending at 1.
|
470 |
+
|
471 |
+
Requires calculating :attr:`length`.
|
472 |
+
"""
|
473 |
+
return self.length - self.index0
|
474 |
+
|
475 |
+
@property
|
476 |
+
def first(self) -> bool:
|
477 |
+
"""Whether this is the first iteration of the loop."""
|
478 |
+
return self.index0 == 0
|
479 |
+
|
480 |
+
def _peek_next(self) -> t.Any:
|
481 |
+
"""Return the next element in the iterable, or :data:`missing`
|
482 |
+
if the iterable is exhausted. Only peeks one item ahead, caching
|
483 |
+
the result in :attr:`_last` for use in subsequent checks. The
|
484 |
+
cache is reset when :meth:`__next__` is called.
|
485 |
+
"""
|
486 |
+
if self._after is not missing:
|
487 |
+
return self._after
|
488 |
+
|
489 |
+
self._after = next(self._iterator, missing)
|
490 |
+
return self._after
|
491 |
+
|
492 |
+
@property
|
493 |
+
def last(self) -> bool:
|
494 |
+
"""Whether this is the last iteration of the loop.
|
495 |
+
|
496 |
+
Causes the iterable to advance early. See
|
497 |
+
:func:`itertools.groupby` for issues this can cause.
|
498 |
+
The :func:`groupby` filter avoids that issue.
|
499 |
+
"""
|
500 |
+
return self._peek_next() is missing
|
501 |
+
|
502 |
+
@property
|
503 |
+
def previtem(self) -> t.Union[t.Any, "Undefined"]:
|
504 |
+
"""The item in the previous iteration. Undefined during the
|
505 |
+
first iteration.
|
506 |
+
"""
|
507 |
+
if self.first:
|
508 |
+
return self._undefined("there is no previous item")
|
509 |
+
|
510 |
+
return self._before
|
511 |
+
|
512 |
+
@property
|
513 |
+
def nextitem(self) -> t.Union[t.Any, "Undefined"]:
|
514 |
+
"""The item in the next iteration. Undefined during the last
|
515 |
+
iteration.
|
516 |
+
|
517 |
+
Causes the iterable to advance early. See
|
518 |
+
:func:`itertools.groupby` for issues this can cause.
|
519 |
+
The :func:`jinja-filters.groupby` filter avoids that issue.
|
520 |
+
"""
|
521 |
+
rv = self._peek_next()
|
522 |
+
|
523 |
+
if rv is missing:
|
524 |
+
return self._undefined("there is no next item")
|
525 |
+
|
526 |
+
return rv
|
527 |
+
|
528 |
+
def cycle(self, *args: V) -> V:
|
529 |
+
"""Return a value from the given args, cycling through based on
|
530 |
+
the current :attr:`index0`.
|
531 |
+
|
532 |
+
:param args: One or more values to cycle through.
|
533 |
+
"""
|
534 |
+
if not args:
|
535 |
+
raise TypeError("no items for cycling given")
|
536 |
+
|
537 |
+
return args[self.index0 % len(args)]
|
538 |
+
|
539 |
+
def changed(self, *value: t.Any) -> bool:
|
540 |
+
"""Return ``True`` if previously called with a different value
|
541 |
+
(including when called for the first time).
|
542 |
+
|
543 |
+
:param value: One or more values to compare to the last call.
|
544 |
+
"""
|
545 |
+
if self._last_changed_value != value:
|
546 |
+
self._last_changed_value = value
|
547 |
+
return True
|
548 |
+
|
549 |
+
return False
|
550 |
+
|
551 |
+
def __iter__(self) -> "LoopContext":
|
552 |
+
return self
|
553 |
+
|
554 |
+
def __next__(self) -> t.Tuple[t.Any, "LoopContext"]:
|
555 |
+
if self._after is not missing:
|
556 |
+
rv = self._after
|
557 |
+
self._after = missing
|
558 |
+
else:
|
559 |
+
rv = next(self._iterator)
|
560 |
+
|
561 |
+
self.index0 += 1
|
562 |
+
self._before = self._current
|
563 |
+
self._current = rv
|
564 |
+
return rv, self
|
565 |
+
|
566 |
+
@internalcode
|
567 |
+
def __call__(self, iterable: t.Iterable[V]) -> str:
|
568 |
+
"""When iterating over nested data, render the body of the loop
|
569 |
+
recursively with the given inner iterable data.
|
570 |
+
|
571 |
+
The loop must have the ``recursive`` marker for this to work.
|
572 |
+
"""
|
573 |
+
if self._recurse is None:
|
574 |
+
raise TypeError(
|
575 |
+
"The loop must have the 'recursive' marker to be called recursively."
|
576 |
+
)
|
577 |
+
|
578 |
+
return self._recurse(iterable, self._recurse, depth=self.depth)
|
579 |
+
|
580 |
+
def __repr__(self) -> str:
|
581 |
+
return f"<{type(self).__name__} {self.index}/{self.length}>"
|
582 |
+
|
583 |
+
|
584 |
+
class AsyncLoopContext(LoopContext):
|
585 |
+
_iterator: t.AsyncIterator[t.Any] # type: ignore
|
586 |
+
|
587 |
+
@staticmethod
|
588 |
+
def _to_iterator( # type: ignore
|
589 |
+
iterable: t.Union[t.Iterable[V], t.AsyncIterable[V]]
|
590 |
+
) -> t.AsyncIterator[V]:
|
591 |
+
return auto_aiter(iterable)
|
592 |
+
|
593 |
+
@property
|
594 |
+
async def length(self) -> int: # type: ignore
|
595 |
+
if self._length is not None:
|
596 |
+
return self._length
|
597 |
+
|
598 |
+
try:
|
599 |
+
self._length = len(self._iterable) # type: ignore
|
600 |
+
except TypeError:
|
601 |
+
iterable = [x async for x in self._iterator]
|
602 |
+
self._iterator = self._to_iterator(iterable)
|
603 |
+
self._length = len(iterable) + self.index + (self._after is not missing)
|
604 |
+
|
605 |
+
return self._length
|
606 |
+
|
607 |
+
@property
|
608 |
+
async def revindex0(self) -> int: # type: ignore
|
609 |
+
return await self.length - self.index
|
610 |
+
|
611 |
+
@property
|
612 |
+
async def revindex(self) -> int: # type: ignore
|
613 |
+
return await self.length - self.index0
|
614 |
+
|
615 |
+
async def _peek_next(self) -> t.Any:
|
616 |
+
if self._after is not missing:
|
617 |
+
return self._after
|
618 |
+
|
619 |
+
try:
|
620 |
+
self._after = await self._iterator.__anext__()
|
621 |
+
except StopAsyncIteration:
|
622 |
+
self._after = missing
|
623 |
+
|
624 |
+
return self._after
|
625 |
+
|
626 |
+
@property
|
627 |
+
async def last(self) -> bool: # type: ignore
|
628 |
+
return await self._peek_next() is missing
|
629 |
+
|
630 |
+
@property
|
631 |
+
async def nextitem(self) -> t.Union[t.Any, "Undefined"]:
|
632 |
+
rv = await self._peek_next()
|
633 |
+
|
634 |
+
if rv is missing:
|
635 |
+
return self._undefined("there is no next item")
|
636 |
+
|
637 |
+
return rv
|
638 |
+
|
639 |
+
def __aiter__(self) -> "AsyncLoopContext":
|
640 |
+
return self
|
641 |
+
|
642 |
+
async def __anext__(self) -> t.Tuple[t.Any, "AsyncLoopContext"]:
|
643 |
+
if self._after is not missing:
|
644 |
+
rv = self._after
|
645 |
+
self._after = missing
|
646 |
+
else:
|
647 |
+
rv = await self._iterator.__anext__()
|
648 |
+
|
649 |
+
self.index0 += 1
|
650 |
+
self._before = self._current
|
651 |
+
self._current = rv
|
652 |
+
return rv, self
|
653 |
+
|
654 |
+
|
655 |
+
class Macro:
|
656 |
+
"""Wraps a macro function."""
|
657 |
+
|
658 |
+
def __init__(
|
659 |
+
self,
|
660 |
+
environment: "Environment",
|
661 |
+
func: t.Callable[..., str],
|
662 |
+
name: str,
|
663 |
+
arguments: t.List[str],
|
664 |
+
catch_kwargs: bool,
|
665 |
+
catch_varargs: bool,
|
666 |
+
caller: bool,
|
667 |
+
default_autoescape: t.Optional[bool] = None,
|
668 |
+
):
|
669 |
+
self._environment = environment
|
670 |
+
self._func = func
|
671 |
+
self._argument_count = len(arguments)
|
672 |
+
self.name = name
|
673 |
+
self.arguments = arguments
|
674 |
+
self.catch_kwargs = catch_kwargs
|
675 |
+
self.catch_varargs = catch_varargs
|
676 |
+
self.caller = caller
|
677 |
+
self.explicit_caller = "caller" in arguments
|
678 |
+
|
679 |
+
if default_autoescape is None:
|
680 |
+
if callable(environment.autoescape):
|
681 |
+
default_autoescape = environment.autoescape(None)
|
682 |
+
else:
|
683 |
+
default_autoescape = environment.autoescape
|
684 |
+
|
685 |
+
self._default_autoescape = default_autoescape
|
686 |
+
|
687 |
+
@internalcode
|
688 |
+
@pass_eval_context
|
689 |
+
def __call__(self, *args: t.Any, **kwargs: t.Any) -> str:
|
690 |
+
# This requires a bit of explanation, In the past we used to
|
691 |
+
# decide largely based on compile-time information if a macro is
|
692 |
+
# safe or unsafe. While there was a volatile mode it was largely
|
693 |
+
# unused for deciding on escaping. This turns out to be
|
694 |
+
# problematic for macros because whether a macro is safe depends not
|
695 |
+
# on the escape mode when it was defined, but rather when it was used.
|
696 |
+
#
|
697 |
+
# Because however we export macros from the module system and
|
698 |
+
# there are historic callers that do not pass an eval context (and
|
699 |
+
# will continue to not pass one), we need to perform an instance
|
700 |
+
# check here.
|
701 |
+
#
|
702 |
+
# This is considered safe because an eval context is not a valid
|
703 |
+
# argument to callables otherwise anyway. Worst case here is
|
704 |
+
# that if no eval context is passed we fall back to the compile
|
705 |
+
# time autoescape flag.
|
706 |
+
if args and isinstance(args[0], EvalContext):
|
707 |
+
autoescape = args[0].autoescape
|
708 |
+
args = args[1:]
|
709 |
+
else:
|
710 |
+
autoescape = self._default_autoescape
|
711 |
+
|
712 |
+
# try to consume the positional arguments
|
713 |
+
arguments = list(args[: self._argument_count])
|
714 |
+
off = len(arguments)
|
715 |
+
|
716 |
+
# For information why this is necessary refer to the handling
|
717 |
+
# of caller in the `macro_body` handler in the compiler.
|
718 |
+
found_caller = False
|
719 |
+
|
720 |
+
# if the number of arguments consumed is not the number of
|
721 |
+
# arguments expected we start filling in keyword arguments
|
722 |
+
# and defaults.
|
723 |
+
if off != self._argument_count:
|
724 |
+
for name in self.arguments[len(arguments) :]:
|
725 |
+
try:
|
726 |
+
value = kwargs.pop(name)
|
727 |
+
except KeyError:
|
728 |
+
value = missing
|
729 |
+
if name == "caller":
|
730 |
+
found_caller = True
|
731 |
+
arguments.append(value)
|
732 |
+
else:
|
733 |
+
found_caller = self.explicit_caller
|
734 |
+
|
735 |
+
# it's important that the order of these arguments does not change
|
736 |
+
# if not also changed in the compiler's `function_scoping` method.
|
737 |
+
# the order is caller, keyword arguments, positional arguments!
|
738 |
+
if self.caller and not found_caller:
|
739 |
+
caller = kwargs.pop("caller", None)
|
740 |
+
if caller is None:
|
741 |
+
caller = self._environment.undefined("No caller defined", name="caller")
|
742 |
+
arguments.append(caller)
|
743 |
+
|
744 |
+
if self.catch_kwargs:
|
745 |
+
arguments.append(kwargs)
|
746 |
+
elif kwargs:
|
747 |
+
if "caller" in kwargs:
|
748 |
+
raise TypeError(
|
749 |
+
f"macro {self.name!r} was invoked with two values for the special"
|
750 |
+
" caller argument. This is most likely a bug."
|
751 |
+
)
|
752 |
+
raise TypeError(
|
753 |
+
f"macro {self.name!r} takes no keyword argument {next(iter(kwargs))!r}"
|
754 |
+
)
|
755 |
+
if self.catch_varargs:
|
756 |
+
arguments.append(args[self._argument_count :])
|
757 |
+
elif len(args) > self._argument_count:
|
758 |
+
raise TypeError(
|
759 |
+
f"macro {self.name!r} takes not more than"
|
760 |
+
f" {len(self.arguments)} argument(s)"
|
761 |
+
)
|
762 |
+
|
763 |
+
return self._invoke(arguments, autoescape)
|
764 |
+
|
765 |
+
async def _async_invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:
|
766 |
+
rv = await self._func(*arguments) # type: ignore
|
767 |
+
|
768 |
+
if autoescape:
|
769 |
+
return Markup(rv)
|
770 |
+
|
771 |
+
return rv # type: ignore
|
772 |
+
|
773 |
+
def _invoke(self, arguments: t.List[t.Any], autoescape: bool) -> str:
|
774 |
+
if self._environment.is_async:
|
775 |
+
return self._async_invoke(arguments, autoescape) # type: ignore
|
776 |
+
|
777 |
+
rv = self._func(*arguments)
|
778 |
+
|
779 |
+
if autoescape:
|
780 |
+
rv = Markup(rv)
|
781 |
+
|
782 |
+
return rv
|
783 |
+
|
784 |
+
def __repr__(self) -> str:
|
785 |
+
name = "anonymous" if self.name is None else repr(self.name)
|
786 |
+
return f"<{type(self).__name__} {name}>"
|
787 |
+
|
788 |
+
|
789 |
+
class Undefined:
|
790 |
+
"""The default undefined type. This undefined type can be printed and
|
791 |
+
iterated over, but every other access will raise an :exc:`UndefinedError`:
|
792 |
+
|
793 |
+
>>> foo = Undefined(name='foo')
|
794 |
+
>>> str(foo)
|
795 |
+
''
|
796 |
+
>>> not foo
|
797 |
+
True
|
798 |
+
>>> foo + 42
|
799 |
+
Traceback (most recent call last):
|
800 |
+
...
|
801 |
+
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
802 |
+
"""
|
803 |
+
|
804 |
+
__slots__ = (
|
805 |
+
"_undefined_hint",
|
806 |
+
"_undefined_obj",
|
807 |
+
"_undefined_name",
|
808 |
+
"_undefined_exception",
|
809 |
+
)
|
810 |
+
|
811 |
+
def __init__(
|
812 |
+
self,
|
813 |
+
hint: t.Optional[str] = None,
|
814 |
+
obj: t.Any = missing,
|
815 |
+
name: t.Optional[str] = None,
|
816 |
+
exc: t.Type[TemplateRuntimeError] = UndefinedError,
|
817 |
+
) -> None:
|
818 |
+
self._undefined_hint = hint
|
819 |
+
self._undefined_obj = obj
|
820 |
+
self._undefined_name = name
|
821 |
+
self._undefined_exception = exc
|
822 |
+
|
823 |
+
@property
|
824 |
+
def _undefined_message(self) -> str:
|
825 |
+
"""Build a message about the undefined value based on how it was
|
826 |
+
accessed.
|
827 |
+
"""
|
828 |
+
if self._undefined_hint:
|
829 |
+
return self._undefined_hint
|
830 |
+
|
831 |
+
if self._undefined_obj is missing:
|
832 |
+
return f"{self._undefined_name!r} is undefined"
|
833 |
+
|
834 |
+
if not isinstance(self._undefined_name, str):
|
835 |
+
return (
|
836 |
+
f"{object_type_repr(self._undefined_obj)} has no"
|
837 |
+
f" element {self._undefined_name!r}"
|
838 |
+
)
|
839 |
+
|
840 |
+
return (
|
841 |
+
f"{object_type_repr(self._undefined_obj)!r} has no"
|
842 |
+
f" attribute {self._undefined_name!r}"
|
843 |
+
)
|
844 |
+
|
845 |
+
@internalcode
|
846 |
+
def _fail_with_undefined_error(
|
847 |
+
self, *args: t.Any, **kwargs: t.Any
|
848 |
+
) -> "te.NoReturn":
|
849 |
+
"""Raise an :exc:`UndefinedError` when operations are performed
|
850 |
+
on the undefined value.
|
851 |
+
"""
|
852 |
+
raise self._undefined_exception(self._undefined_message)
|
853 |
+
|
854 |
+
@internalcode
|
855 |
+
def __getattr__(self, name: str) -> t.Any:
|
856 |
+
if name[:2] == "__":
|
857 |
+
raise AttributeError(name)
|
858 |
+
|
859 |
+
return self._fail_with_undefined_error()
|
860 |
+
|
861 |
+
__add__ = __radd__ = __sub__ = __rsub__ = _fail_with_undefined_error
|
862 |
+
__mul__ = __rmul__ = __div__ = __rdiv__ = _fail_with_undefined_error
|
863 |
+
__truediv__ = __rtruediv__ = _fail_with_undefined_error
|
864 |
+
__floordiv__ = __rfloordiv__ = _fail_with_undefined_error
|
865 |
+
__mod__ = __rmod__ = _fail_with_undefined_error
|
866 |
+
__pos__ = __neg__ = _fail_with_undefined_error
|
867 |
+
__call__ = __getitem__ = _fail_with_undefined_error
|
868 |
+
__lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error
|
869 |
+
__int__ = __float__ = __complex__ = _fail_with_undefined_error
|
870 |
+
__pow__ = __rpow__ = _fail_with_undefined_error
|
871 |
+
|
872 |
+
def __eq__(self, other: t.Any) -> bool:
|
873 |
+
return type(self) is type(other)
|
874 |
+
|
875 |
+
def __ne__(self, other: t.Any) -> bool:
|
876 |
+
return not self.__eq__(other)
|
877 |
+
|
878 |
+
def __hash__(self) -> int:
|
879 |
+
return id(type(self))
|
880 |
+
|
881 |
+
def __str__(self) -> str:
|
882 |
+
return ""
|
883 |
+
|
884 |
+
def __len__(self) -> int:
|
885 |
+
return 0
|
886 |
+
|
887 |
+
def __iter__(self) -> t.Iterator[t.Any]:
|
888 |
+
yield from ()
|
889 |
+
|
890 |
+
async def __aiter__(self) -> t.AsyncIterator[t.Any]:
|
891 |
+
for _ in ():
|
892 |
+
yield
|
893 |
+
|
894 |
+
def __bool__(self) -> bool:
|
895 |
+
return False
|
896 |
+
|
897 |
+
def __repr__(self) -> str:
|
898 |
+
return "Undefined"
|
899 |
+
|
900 |
+
|
901 |
+
def make_logging_undefined(
|
902 |
+
logger: t.Optional["logging.Logger"] = None, base: t.Type[Undefined] = Undefined
|
903 |
+
) -> t.Type[Undefined]:
|
904 |
+
"""Given a logger object this returns a new undefined class that will
|
905 |
+
log certain failures. It will log iterations and printing. If no
|
906 |
+
logger is given a default logger is created.
|
907 |
+
|
908 |
+
Example::
|
909 |
+
|
910 |
+
logger = logging.getLogger(__name__)
|
911 |
+
LoggingUndefined = make_logging_undefined(
|
912 |
+
logger=logger,
|
913 |
+
base=Undefined
|
914 |
+
)
|
915 |
+
|
916 |
+
.. versionadded:: 2.8
|
917 |
+
|
918 |
+
:param logger: the logger to use. If not provided, a default logger
|
919 |
+
is created.
|
920 |
+
:param base: the base class to add logging functionality to. This
|
921 |
+
defaults to :class:`Undefined`.
|
922 |
+
"""
|
923 |
+
if logger is None:
|
924 |
+
import logging
|
925 |
+
|
926 |
+
logger = logging.getLogger(__name__)
|
927 |
+
logger.addHandler(logging.StreamHandler(sys.stderr))
|
928 |
+
|
929 |
+
def _log_message(undef: Undefined) -> None:
|
930 |
+
logger.warning( # type: ignore
|
931 |
+
"Template variable warning: %s", undef._undefined_message
|
932 |
+
)
|
933 |
+
|
934 |
+
class LoggingUndefined(base): # type: ignore
|
935 |
+
__slots__ = ()
|
936 |
+
|
937 |
+
def _fail_with_undefined_error( # type: ignore
|
938 |
+
self, *args: t.Any, **kwargs: t.Any
|
939 |
+
) -> "te.NoReturn":
|
940 |
+
try:
|
941 |
+
super()._fail_with_undefined_error(*args, **kwargs)
|
942 |
+
except self._undefined_exception as e:
|
943 |
+
logger.error("Template variable error: %s", e) # type: ignore
|
944 |
+
raise e
|
945 |
+
|
946 |
+
def __str__(self) -> str:
|
947 |
+
_log_message(self)
|
948 |
+
return super().__str__() # type: ignore
|
949 |
+
|
950 |
+
def __iter__(self) -> t.Iterator[t.Any]:
|
951 |
+
_log_message(self)
|
952 |
+
return super().__iter__() # type: ignore
|
953 |
+
|
954 |
+
def __bool__(self) -> bool:
|
955 |
+
_log_message(self)
|
956 |
+
return super().__bool__() # type: ignore
|
957 |
+
|
958 |
+
return LoggingUndefined
|
959 |
+
|
960 |
+
|
961 |
+
class ChainableUndefined(Undefined):
|
962 |
+
"""An undefined that is chainable, where both ``__getattr__`` and
|
963 |
+
``__getitem__`` return itself rather than raising an
|
964 |
+
:exc:`UndefinedError`.
|
965 |
+
|
966 |
+
>>> foo = ChainableUndefined(name='foo')
|
967 |
+
>>> str(foo.bar['baz'])
|
968 |
+
''
|
969 |
+
>>> foo.bar['baz'] + 42
|
970 |
+
Traceback (most recent call last):
|
971 |
+
...
|
972 |
+
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
973 |
+
|
974 |
+
.. versionadded:: 2.11.0
|
975 |
+
"""
|
976 |
+
|
977 |
+
__slots__ = ()
|
978 |
+
|
979 |
+
def __html__(self) -> str:
|
980 |
+
return str(self)
|
981 |
+
|
982 |
+
def __getattr__(self, _: str) -> "ChainableUndefined":
|
983 |
+
return self
|
984 |
+
|
985 |
+
__getitem__ = __getattr__ # type: ignore
|
986 |
+
|
987 |
+
|
988 |
+
class DebugUndefined(Undefined):
|
989 |
+
"""An undefined that returns the debug info when printed.
|
990 |
+
|
991 |
+
>>> foo = DebugUndefined(name='foo')
|
992 |
+
>>> str(foo)
|
993 |
+
'{{ foo }}'
|
994 |
+
>>> not foo
|
995 |
+
True
|
996 |
+
>>> foo + 42
|
997 |
+
Traceback (most recent call last):
|
998 |
+
...
|
999 |
+
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
1000 |
+
"""
|
1001 |
+
|
1002 |
+
__slots__ = ()
|
1003 |
+
|
1004 |
+
def __str__(self) -> str:
|
1005 |
+
if self._undefined_hint:
|
1006 |
+
message = f"undefined value printed: {self._undefined_hint}"
|
1007 |
+
|
1008 |
+
elif self._undefined_obj is missing:
|
1009 |
+
message = self._undefined_name # type: ignore
|
1010 |
+
|
1011 |
+
else:
|
1012 |
+
message = (
|
1013 |
+
f"no such element: {object_type_repr(self._undefined_obj)}"
|
1014 |
+
f"[{self._undefined_name!r}]"
|
1015 |
+
)
|
1016 |
+
|
1017 |
+
return f"{{{{ {message} }}}}"
|
1018 |
+
|
1019 |
+
|
1020 |
+
class StrictUndefined(Undefined):
|
1021 |
+
"""An undefined that barks on print and iteration as well as boolean
|
1022 |
+
tests and all kinds of comparisons. In other words: you can do nothing
|
1023 |
+
with it except checking if it's defined using the `defined` test.
|
1024 |
+
|
1025 |
+
>>> foo = StrictUndefined(name='foo')
|
1026 |
+
>>> str(foo)
|
1027 |
+
Traceback (most recent call last):
|
1028 |
+
...
|
1029 |
+
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
1030 |
+
>>> not foo
|
1031 |
+
Traceback (most recent call last):
|
1032 |
+
...
|
1033 |
+
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
1034 |
+
>>> foo + 42
|
1035 |
+
Traceback (most recent call last):
|
1036 |
+
...
|
1037 |
+
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
1038 |
+
"""
|
1039 |
+
|
1040 |
+
__slots__ = ()
|
1041 |
+
__iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error
|
1042 |
+
__eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error
|
1043 |
+
__contains__ = Undefined._fail_with_undefined_error
|
1044 |
+
|
1045 |
+
|
1046 |
+
# Remove slots attributes, after the metaclass is applied they are
|
1047 |
+
# unneeded and contain wrong data for subclasses.
|
1048 |
+
del (
|
1049 |
+
Undefined.__slots__,
|
1050 |
+
ChainableUndefined.__slots__,
|
1051 |
+
DebugUndefined.__slots__,
|
1052 |
+
StrictUndefined.__slots__,
|
1053 |
+
)
|
lib/python3.11/site-packages/jinja2/sandbox.py
ADDED
@@ -0,0 +1,428 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""A sandbox layer that ensures unsafe operations cannot be performed.
|
2 |
+
Useful when the template itself comes from an untrusted source.
|
3 |
+
"""
|
4 |
+
import operator
|
5 |
+
import types
|
6 |
+
import typing as t
|
7 |
+
from _string import formatter_field_name_split # type: ignore
|
8 |
+
from collections import abc
|
9 |
+
from collections import deque
|
10 |
+
from string import Formatter
|
11 |
+
|
12 |
+
from markupsafe import EscapeFormatter
|
13 |
+
from markupsafe import Markup
|
14 |
+
|
15 |
+
from .environment import Environment
|
16 |
+
from .exceptions import SecurityError
|
17 |
+
from .runtime import Context
|
18 |
+
from .runtime import Undefined
|
19 |
+
|
20 |
+
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
21 |
+
|
22 |
+
#: maximum number of items a range may produce
|
23 |
+
MAX_RANGE = 100000
|
24 |
+
|
25 |
+
#: Unsafe function attributes.
|
26 |
+
UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
|
27 |
+
|
28 |
+
#: Unsafe method attributes. Function attributes are unsafe for methods too.
|
29 |
+
UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
|
30 |
+
|
31 |
+
#: unsafe generator attributes.
|
32 |
+
UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
|
33 |
+
|
34 |
+
#: unsafe attributes on coroutines
|
35 |
+
UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
|
36 |
+
|
37 |
+
#: unsafe attributes on async generators
|
38 |
+
UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
|
39 |
+
|
40 |
+
_mutable_spec: t.Tuple[t.Tuple[t.Type, t.FrozenSet[str]], ...] = (
|
41 |
+
(
|
42 |
+
abc.MutableSet,
|
43 |
+
frozenset(
|
44 |
+
[
|
45 |
+
"add",
|
46 |
+
"clear",
|
47 |
+
"difference_update",
|
48 |
+
"discard",
|
49 |
+
"pop",
|
50 |
+
"remove",
|
51 |
+
"symmetric_difference_update",
|
52 |
+
"update",
|
53 |
+
]
|
54 |
+
),
|
55 |
+
),
|
56 |
+
(
|
57 |
+
abc.MutableMapping,
|
58 |
+
frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
|
59 |
+
),
|
60 |
+
(
|
61 |
+
abc.MutableSequence,
|
62 |
+
frozenset(["append", "reverse", "insert", "sort", "extend", "remove"]),
|
63 |
+
),
|
64 |
+
(
|
65 |
+
deque,
|
66 |
+
frozenset(
|
67 |
+
[
|
68 |
+
"append",
|
69 |
+
"appendleft",
|
70 |
+
"clear",
|
71 |
+
"extend",
|
72 |
+
"extendleft",
|
73 |
+
"pop",
|
74 |
+
"popleft",
|
75 |
+
"remove",
|
76 |
+
"rotate",
|
77 |
+
]
|
78 |
+
),
|
79 |
+
),
|
80 |
+
)
|
81 |
+
|
82 |
+
|
83 |
+
def inspect_format_method(callable: t.Callable) -> t.Optional[str]:
|
84 |
+
if not isinstance(
|
85 |
+
callable, (types.MethodType, types.BuiltinMethodType)
|
86 |
+
) or callable.__name__ not in ("format", "format_map"):
|
87 |
+
return None
|
88 |
+
|
89 |
+
obj = callable.__self__
|
90 |
+
|
91 |
+
if isinstance(obj, str):
|
92 |
+
return obj
|
93 |
+
|
94 |
+
return None
|
95 |
+
|
96 |
+
|
97 |
+
def safe_range(*args: int) -> range:
|
98 |
+
"""A range that can't generate ranges with a length of more than
|
99 |
+
MAX_RANGE items.
|
100 |
+
"""
|
101 |
+
rng = range(*args)
|
102 |
+
|
103 |
+
if len(rng) > MAX_RANGE:
|
104 |
+
raise OverflowError(
|
105 |
+
"Range too big. The sandbox blocks ranges larger than"
|
106 |
+
f" MAX_RANGE ({MAX_RANGE})."
|
107 |
+
)
|
108 |
+
|
109 |
+
return rng
|
110 |
+
|
111 |
+
|
112 |
+
def unsafe(f: F) -> F:
|
113 |
+
"""Marks a function or method as unsafe.
|
114 |
+
|
115 |
+
.. code-block: python
|
116 |
+
|
117 |
+
@unsafe
|
118 |
+
def delete(self):
|
119 |
+
pass
|
120 |
+
"""
|
121 |
+
f.unsafe_callable = True # type: ignore
|
122 |
+
return f
|
123 |
+
|
124 |
+
|
125 |
+
def is_internal_attribute(obj: t.Any, attr: str) -> bool:
|
126 |
+
"""Test if the attribute given is an internal python attribute. For
|
127 |
+
example this function returns `True` for the `func_code` attribute of
|
128 |
+
python objects. This is useful if the environment method
|
129 |
+
:meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
|
130 |
+
|
131 |
+
>>> from jinja2.sandbox import is_internal_attribute
|
132 |
+
>>> is_internal_attribute(str, "mro")
|
133 |
+
True
|
134 |
+
>>> is_internal_attribute(str, "upper")
|
135 |
+
False
|
136 |
+
"""
|
137 |
+
if isinstance(obj, types.FunctionType):
|
138 |
+
if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
139 |
+
return True
|
140 |
+
elif isinstance(obj, types.MethodType):
|
141 |
+
if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES:
|
142 |
+
return True
|
143 |
+
elif isinstance(obj, type):
|
144 |
+
if attr == "mro":
|
145 |
+
return True
|
146 |
+
elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
|
147 |
+
return True
|
148 |
+
elif isinstance(obj, types.GeneratorType):
|
149 |
+
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
|
150 |
+
return True
|
151 |
+
elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType):
|
152 |
+
if attr in UNSAFE_COROUTINE_ATTRIBUTES:
|
153 |
+
return True
|
154 |
+
elif hasattr(types, "AsyncGeneratorType") and isinstance(
|
155 |
+
obj, types.AsyncGeneratorType
|
156 |
+
):
|
157 |
+
if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
|
158 |
+
return True
|
159 |
+
return attr.startswith("__")
|
160 |
+
|
161 |
+
|
162 |
+
def modifies_known_mutable(obj: t.Any, attr: str) -> bool:
|
163 |
+
"""This function checks if an attribute on a builtin mutable object
|
164 |
+
(list, dict, set or deque) or the corresponding ABCs would modify it
|
165 |
+
if called.
|
166 |
+
|
167 |
+
>>> modifies_known_mutable({}, "clear")
|
168 |
+
True
|
169 |
+
>>> modifies_known_mutable({}, "keys")
|
170 |
+
False
|
171 |
+
>>> modifies_known_mutable([], "append")
|
172 |
+
True
|
173 |
+
>>> modifies_known_mutable([], "index")
|
174 |
+
False
|
175 |
+
|
176 |
+
If called with an unsupported object, ``False`` is returned.
|
177 |
+
|
178 |
+
>>> modifies_known_mutable("foo", "upper")
|
179 |
+
False
|
180 |
+
"""
|
181 |
+
for typespec, unsafe in _mutable_spec:
|
182 |
+
if isinstance(obj, typespec):
|
183 |
+
return attr in unsafe
|
184 |
+
return False
|
185 |
+
|
186 |
+
|
187 |
+
class SandboxedEnvironment(Environment):
|
188 |
+
"""The sandboxed environment. It works like the regular environment but
|
189 |
+
tells the compiler to generate sandboxed code. Additionally subclasses of
|
190 |
+
this environment may override the methods that tell the runtime what
|
191 |
+
attributes or functions are safe to access.
|
192 |
+
|
193 |
+
If the template tries to access insecure code a :exc:`SecurityError` is
|
194 |
+
raised. However also other exceptions may occur during the rendering so
|
195 |
+
the caller has to ensure that all exceptions are caught.
|
196 |
+
"""
|
197 |
+
|
198 |
+
sandboxed = True
|
199 |
+
|
200 |
+
#: default callback table for the binary operators. A copy of this is
|
201 |
+
#: available on each instance of a sandboxed environment as
|
202 |
+
#: :attr:`binop_table`
|
203 |
+
default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
204 |
+
"+": operator.add,
|
205 |
+
"-": operator.sub,
|
206 |
+
"*": operator.mul,
|
207 |
+
"/": operator.truediv,
|
208 |
+
"//": operator.floordiv,
|
209 |
+
"**": operator.pow,
|
210 |
+
"%": operator.mod,
|
211 |
+
}
|
212 |
+
|
213 |
+
#: default callback table for the unary operators. A copy of this is
|
214 |
+
#: available on each instance of a sandboxed environment as
|
215 |
+
#: :attr:`unop_table`
|
216 |
+
default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
|
217 |
+
"+": operator.pos,
|
218 |
+
"-": operator.neg,
|
219 |
+
}
|
220 |
+
|
221 |
+
#: a set of binary operators that should be intercepted. Each operator
|
222 |
+
#: that is added to this set (empty by default) is delegated to the
|
223 |
+
#: :meth:`call_binop` method that will perform the operator. The default
|
224 |
+
#: operator callback is specified by :attr:`binop_table`.
|
225 |
+
#:
|
226 |
+
#: The following binary operators are interceptable:
|
227 |
+
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
|
228 |
+
#:
|
229 |
+
#: The default operation form the operator table corresponds to the
|
230 |
+
#: builtin function. Intercepted calls are always slower than the native
|
231 |
+
#: operator call, so make sure only to intercept the ones you are
|
232 |
+
#: interested in.
|
233 |
+
#:
|
234 |
+
#: .. versionadded:: 2.6
|
235 |
+
intercepted_binops: t.FrozenSet[str] = frozenset()
|
236 |
+
|
237 |
+
#: a set of unary operators that should be intercepted. Each operator
|
238 |
+
#: that is added to this set (empty by default) is delegated to the
|
239 |
+
#: :meth:`call_unop` method that will perform the operator. The default
|
240 |
+
#: operator callback is specified by :attr:`unop_table`.
|
241 |
+
#:
|
242 |
+
#: The following unary operators are interceptable: ``+``, ``-``
|
243 |
+
#:
|
244 |
+
#: The default operation form the operator table corresponds to the
|
245 |
+
#: builtin function. Intercepted calls are always slower than the native
|
246 |
+
#: operator call, so make sure only to intercept the ones you are
|
247 |
+
#: interested in.
|
248 |
+
#:
|
249 |
+
#: .. versionadded:: 2.6
|
250 |
+
intercepted_unops: t.FrozenSet[str] = frozenset()
|
251 |
+
|
252 |
+
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
253 |
+
super().__init__(*args, **kwargs)
|
254 |
+
self.globals["range"] = safe_range
|
255 |
+
self.binop_table = self.default_binop_table.copy()
|
256 |
+
self.unop_table = self.default_unop_table.copy()
|
257 |
+
|
258 |
+
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
|
259 |
+
"""The sandboxed environment will call this method to check if the
|
260 |
+
attribute of an object is safe to access. Per default all attributes
|
261 |
+
starting with an underscore are considered private as well as the
|
262 |
+
special attributes of internal python objects as returned by the
|
263 |
+
:func:`is_internal_attribute` function.
|
264 |
+
"""
|
265 |
+
return not (attr.startswith("_") or is_internal_attribute(obj, attr))
|
266 |
+
|
267 |
+
def is_safe_callable(self, obj: t.Any) -> bool:
|
268 |
+
"""Check if an object is safely callable. By default callables
|
269 |
+
are considered safe unless decorated with :func:`unsafe`.
|
270 |
+
|
271 |
+
This also recognizes the Django convention of setting
|
272 |
+
``func.alters_data = True``.
|
273 |
+
"""
|
274 |
+
return not (
|
275 |
+
getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
|
276 |
+
)
|
277 |
+
|
278 |
+
def call_binop(
|
279 |
+
self, context: Context, operator: str, left: t.Any, right: t.Any
|
280 |
+
) -> t.Any:
|
281 |
+
"""For intercepted binary operator calls (:meth:`intercepted_binops`)
|
282 |
+
this function is executed instead of the builtin operator. This can
|
283 |
+
be used to fine tune the behavior of certain operators.
|
284 |
+
|
285 |
+
.. versionadded:: 2.6
|
286 |
+
"""
|
287 |
+
return self.binop_table[operator](left, right)
|
288 |
+
|
289 |
+
def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any:
|
290 |
+
"""For intercepted unary operator calls (:meth:`intercepted_unops`)
|
291 |
+
this function is executed instead of the builtin operator. This can
|
292 |
+
be used to fine tune the behavior of certain operators.
|
293 |
+
|
294 |
+
.. versionadded:: 2.6
|
295 |
+
"""
|
296 |
+
return self.unop_table[operator](arg)
|
297 |
+
|
298 |
+
def getitem(
|
299 |
+
self, obj: t.Any, argument: t.Union[str, t.Any]
|
300 |
+
) -> t.Union[t.Any, Undefined]:
|
301 |
+
"""Subscribe an object from sandboxed code."""
|
302 |
+
try:
|
303 |
+
return obj[argument]
|
304 |
+
except (TypeError, LookupError):
|
305 |
+
if isinstance(argument, str):
|
306 |
+
try:
|
307 |
+
attr = str(argument)
|
308 |
+
except Exception:
|
309 |
+
pass
|
310 |
+
else:
|
311 |
+
try:
|
312 |
+
value = getattr(obj, attr)
|
313 |
+
except AttributeError:
|
314 |
+
pass
|
315 |
+
else:
|
316 |
+
if self.is_safe_attribute(obj, argument, value):
|
317 |
+
return value
|
318 |
+
return self.unsafe_undefined(obj, argument)
|
319 |
+
return self.undefined(obj=obj, name=argument)
|
320 |
+
|
321 |
+
def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
|
322 |
+
"""Subscribe an object from sandboxed code and prefer the
|
323 |
+
attribute. The attribute passed *must* be a bytestring.
|
324 |
+
"""
|
325 |
+
try:
|
326 |
+
value = getattr(obj, attribute)
|
327 |
+
except AttributeError:
|
328 |
+
try:
|
329 |
+
return obj[attribute]
|
330 |
+
except (TypeError, LookupError):
|
331 |
+
pass
|
332 |
+
else:
|
333 |
+
if self.is_safe_attribute(obj, attribute, value):
|
334 |
+
return value
|
335 |
+
return self.unsafe_undefined(obj, attribute)
|
336 |
+
return self.undefined(obj=obj, name=attribute)
|
337 |
+
|
338 |
+
def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined:
|
339 |
+
"""Return an undefined object for unsafe attributes."""
|
340 |
+
return self.undefined(
|
341 |
+
f"access to attribute {attribute!r} of"
|
342 |
+
f" {type(obj).__name__!r} object is unsafe.",
|
343 |
+
name=attribute,
|
344 |
+
obj=obj,
|
345 |
+
exc=SecurityError,
|
346 |
+
)
|
347 |
+
|
348 |
+
def format_string(
|
349 |
+
self,
|
350 |
+
s: str,
|
351 |
+
args: t.Tuple[t.Any, ...],
|
352 |
+
kwargs: t.Dict[str, t.Any],
|
353 |
+
format_func: t.Optional[t.Callable] = None,
|
354 |
+
) -> str:
|
355 |
+
"""If a format call is detected, then this is routed through this
|
356 |
+
method so that our safety sandbox can be used for it.
|
357 |
+
"""
|
358 |
+
formatter: SandboxedFormatter
|
359 |
+
if isinstance(s, Markup):
|
360 |
+
formatter = SandboxedEscapeFormatter(self, escape=s.escape)
|
361 |
+
else:
|
362 |
+
formatter = SandboxedFormatter(self)
|
363 |
+
|
364 |
+
if format_func is not None and format_func.__name__ == "format_map":
|
365 |
+
if len(args) != 1 or kwargs:
|
366 |
+
raise TypeError(
|
367 |
+
"format_map() takes exactly one argument"
|
368 |
+
f" {len(args) + (kwargs is not None)} given"
|
369 |
+
)
|
370 |
+
|
371 |
+
kwargs = args[0]
|
372 |
+
args = ()
|
373 |
+
|
374 |
+
rv = formatter.vformat(s, args, kwargs)
|
375 |
+
return type(s)(rv)
|
376 |
+
|
377 |
+
def call(
|
378 |
+
__self, # noqa: B902
|
379 |
+
__context: Context,
|
380 |
+
__obj: t.Any,
|
381 |
+
*args: t.Any,
|
382 |
+
**kwargs: t.Any,
|
383 |
+
) -> t.Any:
|
384 |
+
"""Call an object from sandboxed code."""
|
385 |
+
fmt = inspect_format_method(__obj)
|
386 |
+
if fmt is not None:
|
387 |
+
return __self.format_string(fmt, args, kwargs, __obj)
|
388 |
+
|
389 |
+
# the double prefixes are to avoid double keyword argument
|
390 |
+
# errors when proxying the call.
|
391 |
+
if not __self.is_safe_callable(__obj):
|
392 |
+
raise SecurityError(f"{__obj!r} is not safely callable")
|
393 |
+
return __context.call(__obj, *args, **kwargs)
|
394 |
+
|
395 |
+
|
396 |
+
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
397 |
+
"""Works exactly like the regular `SandboxedEnvironment` but does not
|
398 |
+
permit modifications on the builtin mutable objects `list`, `set`, and
|
399 |
+
`dict` by using the :func:`modifies_known_mutable` function.
|
400 |
+
"""
|
401 |
+
|
402 |
+
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
|
403 |
+
if not super().is_safe_attribute(obj, attr, value):
|
404 |
+
return False
|
405 |
+
|
406 |
+
return not modifies_known_mutable(obj, attr)
|
407 |
+
|
408 |
+
|
409 |
+
class SandboxedFormatter(Formatter):
|
410 |
+
def __init__(self, env: Environment, **kwargs: t.Any) -> None:
|
411 |
+
self._env = env
|
412 |
+
super().__init__(**kwargs)
|
413 |
+
|
414 |
+
def get_field(
|
415 |
+
self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
|
416 |
+
) -> t.Tuple[t.Any, str]:
|
417 |
+
first, rest = formatter_field_name_split(field_name)
|
418 |
+
obj = self.get_value(first, args, kwargs)
|
419 |
+
for is_attr, i in rest:
|
420 |
+
if is_attr:
|
421 |
+
obj = self._env.getattr(obj, i)
|
422 |
+
else:
|
423 |
+
obj = self._env.getitem(obj, i)
|
424 |
+
return obj, first
|
425 |
+
|
426 |
+
|
427 |
+
class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter):
|
428 |
+
pass
|
lib/python3.11/site-packages/jinja2/tests.py
ADDED
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Built-in template tests used with the ``is`` operator."""
|
2 |
+
import operator
|
3 |
+
import typing as t
|
4 |
+
from collections import abc
|
5 |
+
from numbers import Number
|
6 |
+
|
7 |
+
from .runtime import Undefined
|
8 |
+
from .utils import pass_environment
|
9 |
+
|
10 |
+
if t.TYPE_CHECKING:
|
11 |
+
from .environment import Environment
|
12 |
+
|
13 |
+
|
14 |
+
def test_odd(value: int) -> bool:
|
15 |
+
"""Return true if the variable is odd."""
|
16 |
+
return value % 2 == 1
|
17 |
+
|
18 |
+
|
19 |
+
def test_even(value: int) -> bool:
|
20 |
+
"""Return true if the variable is even."""
|
21 |
+
return value % 2 == 0
|
22 |
+
|
23 |
+
|
24 |
+
def test_divisibleby(value: int, num: int) -> bool:
|
25 |
+
"""Check if a variable is divisible by a number."""
|
26 |
+
return value % num == 0
|
27 |
+
|
28 |
+
|
29 |
+
def test_defined(value: t.Any) -> bool:
|
30 |
+
"""Return true if the variable is defined:
|
31 |
+
|
32 |
+
.. sourcecode:: jinja
|
33 |
+
|
34 |
+
{% if variable is defined %}
|
35 |
+
value of variable: {{ variable }}
|
36 |
+
{% else %}
|
37 |
+
variable is not defined
|
38 |
+
{% endif %}
|
39 |
+
|
40 |
+
See the :func:`default` filter for a simple way to set undefined
|
41 |
+
variables.
|
42 |
+
"""
|
43 |
+
return not isinstance(value, Undefined)
|
44 |
+
|
45 |
+
|
46 |
+
def test_undefined(value: t.Any) -> bool:
|
47 |
+
"""Like :func:`defined` but the other way round."""
|
48 |
+
return isinstance(value, Undefined)
|
49 |
+
|
50 |
+
|
51 |
+
@pass_environment
|
52 |
+
def test_filter(env: "Environment", value: str) -> bool:
|
53 |
+
"""Check if a filter exists by name. Useful if a filter may be
|
54 |
+
optionally available.
|
55 |
+
|
56 |
+
.. code-block:: jinja
|
57 |
+
|
58 |
+
{% if 'markdown' is filter %}
|
59 |
+
{{ value | markdown }}
|
60 |
+
{% else %}
|
61 |
+
{{ value }}
|
62 |
+
{% endif %}
|
63 |
+
|
64 |
+
.. versionadded:: 3.0
|
65 |
+
"""
|
66 |
+
return value in env.filters
|
67 |
+
|
68 |
+
|
69 |
+
@pass_environment
|
70 |
+
def test_test(env: "Environment", value: str) -> bool:
|
71 |
+
"""Check if a test exists by name. Useful if a test may be
|
72 |
+
optionally available.
|
73 |
+
|
74 |
+
.. code-block:: jinja
|
75 |
+
|
76 |
+
{% if 'loud' is test %}
|
77 |
+
{% if value is loud %}
|
78 |
+
{{ value|upper }}
|
79 |
+
{% else %}
|
80 |
+
{{ value|lower }}
|
81 |
+
{% endif %}
|
82 |
+
{% else %}
|
83 |
+
{{ value }}
|
84 |
+
{% endif %}
|
85 |
+
|
86 |
+
.. versionadded:: 3.0
|
87 |
+
"""
|
88 |
+
return value in env.tests
|
89 |
+
|
90 |
+
|
91 |
+
def test_none(value: t.Any) -> bool:
|
92 |
+
"""Return true if the variable is none."""
|
93 |
+
return value is None
|
94 |
+
|
95 |
+
|
96 |
+
def test_boolean(value: t.Any) -> bool:
|
97 |
+
"""Return true if the object is a boolean value.
|
98 |
+
|
99 |
+
.. versionadded:: 2.11
|
100 |
+
"""
|
101 |
+
return value is True or value is False
|
102 |
+
|
103 |
+
|
104 |
+
def test_false(value: t.Any) -> bool:
|
105 |
+
"""Return true if the object is False.
|
106 |
+
|
107 |
+
.. versionadded:: 2.11
|
108 |
+
"""
|
109 |
+
return value is False
|
110 |
+
|
111 |
+
|
112 |
+
def test_true(value: t.Any) -> bool:
|
113 |
+
"""Return true if the object is True.
|
114 |
+
|
115 |
+
.. versionadded:: 2.11
|
116 |
+
"""
|
117 |
+
return value is True
|
118 |
+
|
119 |
+
|
120 |
+
# NOTE: The existing 'number' test matches booleans and floats
|
121 |
+
def test_integer(value: t.Any) -> bool:
|
122 |
+
"""Return true if the object is an integer.
|
123 |
+
|
124 |
+
.. versionadded:: 2.11
|
125 |
+
"""
|
126 |
+
return isinstance(value, int) and value is not True and value is not False
|
127 |
+
|
128 |
+
|
129 |
+
# NOTE: The existing 'number' test matches booleans and integers
|
130 |
+
def test_float(value: t.Any) -> bool:
|
131 |
+
"""Return true if the object is a float.
|
132 |
+
|
133 |
+
.. versionadded:: 2.11
|
134 |
+
"""
|
135 |
+
return isinstance(value, float)
|
136 |
+
|
137 |
+
|
138 |
+
def test_lower(value: str) -> bool:
|
139 |
+
"""Return true if the variable is lowercased."""
|
140 |
+
return str(value).islower()
|
141 |
+
|
142 |
+
|
143 |
+
def test_upper(value: str) -> bool:
|
144 |
+
"""Return true if the variable is uppercased."""
|
145 |
+
return str(value).isupper()
|
146 |
+
|
147 |
+
|
148 |
+
def test_string(value: t.Any) -> bool:
|
149 |
+
"""Return true if the object is a string."""
|
150 |
+
return isinstance(value, str)
|
151 |
+
|
152 |
+
|
153 |
+
def test_mapping(value: t.Any) -> bool:
|
154 |
+
"""Return true if the object is a mapping (dict etc.).
|
155 |
+
|
156 |
+
.. versionadded:: 2.6
|
157 |
+
"""
|
158 |
+
return isinstance(value, abc.Mapping)
|
159 |
+
|
160 |
+
|
161 |
+
def test_number(value: t.Any) -> bool:
|
162 |
+
"""Return true if the variable is a number."""
|
163 |
+
return isinstance(value, Number)
|
164 |
+
|
165 |
+
|
166 |
+
def test_sequence(value: t.Any) -> bool:
|
167 |
+
"""Return true if the variable is a sequence. Sequences are variables
|
168 |
+
that are iterable.
|
169 |
+
"""
|
170 |
+
try:
|
171 |
+
len(value)
|
172 |
+
value.__getitem__
|
173 |
+
except Exception:
|
174 |
+
return False
|
175 |
+
|
176 |
+
return True
|
177 |
+
|
178 |
+
|
179 |
+
def test_sameas(value: t.Any, other: t.Any) -> bool:
|
180 |
+
"""Check if an object points to the same memory address than another
|
181 |
+
object:
|
182 |
+
|
183 |
+
.. sourcecode:: jinja
|
184 |
+
|
185 |
+
{% if foo.attribute is sameas false %}
|
186 |
+
the foo attribute really is the `False` singleton
|
187 |
+
{% endif %}
|
188 |
+
"""
|
189 |
+
return value is other
|
190 |
+
|
191 |
+
|
192 |
+
def test_iterable(value: t.Any) -> bool:
|
193 |
+
"""Check if it's possible to iterate over an object."""
|
194 |
+
try:
|
195 |
+
iter(value)
|
196 |
+
except TypeError:
|
197 |
+
return False
|
198 |
+
|
199 |
+
return True
|
200 |
+
|
201 |
+
|
202 |
+
def test_escaped(value: t.Any) -> bool:
|
203 |
+
"""Check if the value is escaped."""
|
204 |
+
return hasattr(value, "__html__")
|
205 |
+
|
206 |
+
|
207 |
+
def test_in(value: t.Any, seq: t.Container) -> bool:
|
208 |
+
"""Check if value is in seq.
|
209 |
+
|
210 |
+
.. versionadded:: 2.10
|
211 |
+
"""
|
212 |
+
return value in seq
|
213 |
+
|
214 |
+
|
215 |
+
TESTS = {
|
216 |
+
"odd": test_odd,
|
217 |
+
"even": test_even,
|
218 |
+
"divisibleby": test_divisibleby,
|
219 |
+
"defined": test_defined,
|
220 |
+
"undefined": test_undefined,
|
221 |
+
"filter": test_filter,
|
222 |
+
"test": test_test,
|
223 |
+
"none": test_none,
|
224 |
+
"boolean": test_boolean,
|
225 |
+
"false": test_false,
|
226 |
+
"true": test_true,
|
227 |
+
"integer": test_integer,
|
228 |
+
"float": test_float,
|
229 |
+
"lower": test_lower,
|
230 |
+
"upper": test_upper,
|
231 |
+
"string": test_string,
|
232 |
+
"mapping": test_mapping,
|
233 |
+
"number": test_number,
|
234 |
+
"sequence": test_sequence,
|
235 |
+
"iterable": test_iterable,
|
236 |
+
"callable": callable,
|
237 |
+
"sameas": test_sameas,
|
238 |
+
"escaped": test_escaped,
|
239 |
+
"in": test_in,
|
240 |
+
"==": operator.eq,
|
241 |
+
"eq": operator.eq,
|
242 |
+
"equalto": operator.eq,
|
243 |
+
"!=": operator.ne,
|
244 |
+
"ne": operator.ne,
|
245 |
+
">": operator.gt,
|
246 |
+
"gt": operator.gt,
|
247 |
+
"greaterthan": operator.gt,
|
248 |
+
"ge": operator.ge,
|
249 |
+
">=": operator.ge,
|
250 |
+
"<": operator.lt,
|
251 |
+
"lt": operator.lt,
|
252 |
+
"lessthan": operator.lt,
|
253 |
+
"<=": operator.le,
|
254 |
+
"le": operator.le,
|
255 |
+
}
|
lib/python3.11/site-packages/jinja2/utils.py
ADDED
@@ -0,0 +1,755 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import enum
|
2 |
+
import json
|
3 |
+
import os
|
4 |
+
import re
|
5 |
+
import typing as t
|
6 |
+
from collections import abc
|
7 |
+
from collections import deque
|
8 |
+
from random import choice
|
9 |
+
from random import randrange
|
10 |
+
from threading import Lock
|
11 |
+
from types import CodeType
|
12 |
+
from urllib.parse import quote_from_bytes
|
13 |
+
|
14 |
+
import markupsafe
|
15 |
+
|
16 |
+
if t.TYPE_CHECKING:
|
17 |
+
import typing_extensions as te
|
18 |
+
|
19 |
+
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
20 |
+
|
21 |
+
# special singleton representing missing values for the runtime
|
22 |
+
missing: t.Any = type("MissingType", (), {"__repr__": lambda x: "missing"})()
|
23 |
+
|
24 |
+
internal_code: t.MutableSet[CodeType] = set()
|
25 |
+
|
26 |
+
concat = "".join
|
27 |
+
|
28 |
+
|
29 |
+
def pass_context(f: F) -> F:
|
30 |
+
"""Pass the :class:`~jinja2.runtime.Context` as the first argument
|
31 |
+
to the decorated function when called while rendering a template.
|
32 |
+
|
33 |
+
Can be used on functions, filters, and tests.
|
34 |
+
|
35 |
+
If only ``Context.eval_context`` is needed, use
|
36 |
+
:func:`pass_eval_context`. If only ``Context.environment`` is
|
37 |
+
needed, use :func:`pass_environment`.
|
38 |
+
|
39 |
+
.. versionadded:: 3.0.0
|
40 |
+
Replaces ``contextfunction`` and ``contextfilter``.
|
41 |
+
"""
|
42 |
+
f.jinja_pass_arg = _PassArg.context # type: ignore
|
43 |
+
return f
|
44 |
+
|
45 |
+
|
46 |
+
def pass_eval_context(f: F) -> F:
|
47 |
+
"""Pass the :class:`~jinja2.nodes.EvalContext` as the first argument
|
48 |
+
to the decorated function when called while rendering a template.
|
49 |
+
See :ref:`eval-context`.
|
50 |
+
|
51 |
+
Can be used on functions, filters, and tests.
|
52 |
+
|
53 |
+
If only ``EvalContext.environment`` is needed, use
|
54 |
+
:func:`pass_environment`.
|
55 |
+
|
56 |
+
.. versionadded:: 3.0.0
|
57 |
+
Replaces ``evalcontextfunction`` and ``evalcontextfilter``.
|
58 |
+
"""
|
59 |
+
f.jinja_pass_arg = _PassArg.eval_context # type: ignore
|
60 |
+
return f
|
61 |
+
|
62 |
+
|
63 |
+
def pass_environment(f: F) -> F:
|
64 |
+
"""Pass the :class:`~jinja2.Environment` as the first argument to
|
65 |
+
the decorated function when called while rendering a template.
|
66 |
+
|
67 |
+
Can be used on functions, filters, and tests.
|
68 |
+
|
69 |
+
.. versionadded:: 3.0.0
|
70 |
+
Replaces ``environmentfunction`` and ``environmentfilter``.
|
71 |
+
"""
|
72 |
+
f.jinja_pass_arg = _PassArg.environment # type: ignore
|
73 |
+
return f
|
74 |
+
|
75 |
+
|
76 |
+
class _PassArg(enum.Enum):
|
77 |
+
context = enum.auto()
|
78 |
+
eval_context = enum.auto()
|
79 |
+
environment = enum.auto()
|
80 |
+
|
81 |
+
@classmethod
|
82 |
+
def from_obj(cls, obj: F) -> t.Optional["_PassArg"]:
|
83 |
+
if hasattr(obj, "jinja_pass_arg"):
|
84 |
+
return obj.jinja_pass_arg # type: ignore
|
85 |
+
|
86 |
+
return None
|
87 |
+
|
88 |
+
|
89 |
+
def internalcode(f: F) -> F:
|
90 |
+
"""Marks the function as internally used"""
|
91 |
+
internal_code.add(f.__code__)
|
92 |
+
return f
|
93 |
+
|
94 |
+
|
95 |
+
def is_undefined(obj: t.Any) -> bool:
|
96 |
+
"""Check if the object passed is undefined. This does nothing more than
|
97 |
+
performing an instance check against :class:`Undefined` but looks nicer.
|
98 |
+
This can be used for custom filters or tests that want to react to
|
99 |
+
undefined variables. For example a custom default filter can look like
|
100 |
+
this::
|
101 |
+
|
102 |
+
def default(var, default=''):
|
103 |
+
if is_undefined(var):
|
104 |
+
return default
|
105 |
+
return var
|
106 |
+
"""
|
107 |
+
from .runtime import Undefined
|
108 |
+
|
109 |
+
return isinstance(obj, Undefined)
|
110 |
+
|
111 |
+
|
112 |
+
def consume(iterable: t.Iterable[t.Any]) -> None:
|
113 |
+
"""Consumes an iterable without doing anything with it."""
|
114 |
+
for _ in iterable:
|
115 |
+
pass
|
116 |
+
|
117 |
+
|
118 |
+
def clear_caches() -> None:
|
119 |
+
"""Jinja keeps internal caches for environments and lexers. These are
|
120 |
+
used so that Jinja doesn't have to recreate environments and lexers all
|
121 |
+
the time. Normally you don't have to care about that but if you are
|
122 |
+
measuring memory consumption you may want to clean the caches.
|
123 |
+
"""
|
124 |
+
from .environment import get_spontaneous_environment
|
125 |
+
from .lexer import _lexer_cache
|
126 |
+
|
127 |
+
get_spontaneous_environment.cache_clear()
|
128 |
+
_lexer_cache.clear()
|
129 |
+
|
130 |
+
|
131 |
+
def import_string(import_name: str, silent: bool = False) -> t.Any:
|
132 |
+
"""Imports an object based on a string. This is useful if you want to
|
133 |
+
use import paths as endpoints or something similar. An import path can
|
134 |
+
be specified either in dotted notation (``xml.sax.saxutils.escape``)
|
135 |
+
or with a colon as object delimiter (``xml.sax.saxutils:escape``).
|
136 |
+
|
137 |
+
If the `silent` is True the return value will be `None` if the import
|
138 |
+
fails.
|
139 |
+
|
140 |
+
:return: imported object
|
141 |
+
"""
|
142 |
+
try:
|
143 |
+
if ":" in import_name:
|
144 |
+
module, obj = import_name.split(":", 1)
|
145 |
+
elif "." in import_name:
|
146 |
+
module, _, obj = import_name.rpartition(".")
|
147 |
+
else:
|
148 |
+
return __import__(import_name)
|
149 |
+
return getattr(__import__(module, None, None, [obj]), obj)
|
150 |
+
except (ImportError, AttributeError):
|
151 |
+
if not silent:
|
152 |
+
raise
|
153 |
+
|
154 |
+
|
155 |
+
def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO]:
|
156 |
+
"""Returns a file descriptor for the filename if that file exists,
|
157 |
+
otherwise ``None``.
|
158 |
+
"""
|
159 |
+
if not os.path.isfile(filename):
|
160 |
+
return None
|
161 |
+
|
162 |
+
return open(filename, mode)
|
163 |
+
|
164 |
+
|
165 |
+
def object_type_repr(obj: t.Any) -> str:
|
166 |
+
"""Returns the name of the object's type. For some recognized
|
167 |
+
singletons the name of the object is returned instead. (For
|
168 |
+
example for `None` and `Ellipsis`).
|
169 |
+
"""
|
170 |
+
if obj is None:
|
171 |
+
return "None"
|
172 |
+
elif obj is Ellipsis:
|
173 |
+
return "Ellipsis"
|
174 |
+
|
175 |
+
cls = type(obj)
|
176 |
+
|
177 |
+
if cls.__module__ == "builtins":
|
178 |
+
return f"{cls.__name__} object"
|
179 |
+
|
180 |
+
return f"{cls.__module__}.{cls.__name__} object"
|
181 |
+
|
182 |
+
|
183 |
+
def pformat(obj: t.Any) -> str:
|
184 |
+
"""Format an object using :func:`pprint.pformat`."""
|
185 |
+
from pprint import pformat # type: ignore
|
186 |
+
|
187 |
+
return pformat(obj)
|
188 |
+
|
189 |
+
|
190 |
+
_http_re = re.compile(
|
191 |
+
r"""
|
192 |
+
^
|
193 |
+
(
|
194 |
+
(https?://|www\.) # scheme or www
|
195 |
+
(([\w%-]+\.)+)? # subdomain
|
196 |
+
(
|
197 |
+
[a-z]{2,63} # basic tld
|
198 |
+
|
|
199 |
+
xn--[\w%]{2,59} # idna tld
|
200 |
+
)
|
201 |
+
|
|
202 |
+
([\w%-]{2,63}\.)+ # basic domain
|
203 |
+
(com|net|int|edu|gov|org|info|mil) # basic tld
|
204 |
+
|
|
205 |
+
(https?://) # scheme
|
206 |
+
(
|
207 |
+
(([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4
|
208 |
+
|
|
209 |
+
(\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6
|
210 |
+
)
|
211 |
+
)
|
212 |
+
(?::[\d]{1,5})? # port
|
213 |
+
(?:[/?#]\S*)? # path, query, and fragment
|
214 |
+
$
|
215 |
+
""",
|
216 |
+
re.IGNORECASE | re.VERBOSE,
|
217 |
+
)
|
218 |
+
_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$")
|
219 |
+
|
220 |
+
|
221 |
+
def urlize(
|
222 |
+
text: str,
|
223 |
+
trim_url_limit: t.Optional[int] = None,
|
224 |
+
rel: t.Optional[str] = None,
|
225 |
+
target: t.Optional[str] = None,
|
226 |
+
extra_schemes: t.Optional[t.Iterable[str]] = None,
|
227 |
+
) -> str:
|
228 |
+
"""Convert URLs in text into clickable links.
|
229 |
+
|
230 |
+
This may not recognize links in some situations. Usually, a more
|
231 |
+
comprehensive formatter, such as a Markdown library, is a better
|
232 |
+
choice.
|
233 |
+
|
234 |
+
Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
|
235 |
+
addresses. Links with trailing punctuation (periods, commas, closing
|
236 |
+
parentheses) and leading punctuation (opening parentheses) are
|
237 |
+
recognized excluding the punctuation. Email addresses that include
|
238 |
+
header fields are not recognized (for example,
|
239 |
+
``mailto:[email protected][email protected]``).
|
240 |
+
|
241 |
+
:param text: Original text containing URLs to link.
|
242 |
+
:param trim_url_limit: Shorten displayed URL values to this length.
|
243 |
+
:param target: Add the ``target`` attribute to links.
|
244 |
+
:param rel: Add the ``rel`` attribute to links.
|
245 |
+
:param extra_schemes: Recognize URLs that start with these schemes
|
246 |
+
in addition to the default behavior.
|
247 |
+
|
248 |
+
.. versionchanged:: 3.0
|
249 |
+
The ``extra_schemes`` parameter was added.
|
250 |
+
|
251 |
+
.. versionchanged:: 3.0
|
252 |
+
Generate ``https://`` links for URLs without a scheme.
|
253 |
+
|
254 |
+
.. versionchanged:: 3.0
|
255 |
+
The parsing rules were updated. Recognize email addresses with
|
256 |
+
or without the ``mailto:`` scheme. Validate IP addresses. Ignore
|
257 |
+
parentheses and brackets in more cases.
|
258 |
+
"""
|
259 |
+
if trim_url_limit is not None:
|
260 |
+
|
261 |
+
def trim_url(x: str) -> str:
|
262 |
+
if len(x) > trim_url_limit: # type: ignore
|
263 |
+
return f"{x[:trim_url_limit]}..."
|
264 |
+
|
265 |
+
return x
|
266 |
+
|
267 |
+
else:
|
268 |
+
|
269 |
+
def trim_url(x: str) -> str:
|
270 |
+
return x
|
271 |
+
|
272 |
+
words = re.split(r"(\s+)", str(markupsafe.escape(text)))
|
273 |
+
rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else ""
|
274 |
+
target_attr = f' target="{markupsafe.escape(target)}"' if target else ""
|
275 |
+
|
276 |
+
for i, word in enumerate(words):
|
277 |
+
head, middle, tail = "", word, ""
|
278 |
+
match = re.match(r"^([(<]|<)+", middle)
|
279 |
+
|
280 |
+
if match:
|
281 |
+
head = match.group()
|
282 |
+
middle = middle[match.end() :]
|
283 |
+
|
284 |
+
# Unlike lead, which is anchored to the start of the string,
|
285 |
+
# need to check that the string ends with any of the characters
|
286 |
+
# before trying to match all of them, to avoid backtracking.
|
287 |
+
if middle.endswith((")", ">", ".", ",", "\n", ">")):
|
288 |
+
match = re.search(r"([)>.,\n]|>)+$", middle)
|
289 |
+
|
290 |
+
if match:
|
291 |
+
tail = match.group()
|
292 |
+
middle = middle[: match.start()]
|
293 |
+
|
294 |
+
# Prefer balancing parentheses in URLs instead of ignoring a
|
295 |
+
# trailing character.
|
296 |
+
for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"):
|
297 |
+
start_count = middle.count(start_char)
|
298 |
+
|
299 |
+
if start_count <= middle.count(end_char):
|
300 |
+
# Balanced, or lighter on the left
|
301 |
+
continue
|
302 |
+
|
303 |
+
# Move as many as possible from the tail to balance
|
304 |
+
for _ in range(min(start_count, tail.count(end_char))):
|
305 |
+
end_index = tail.index(end_char) + len(end_char)
|
306 |
+
# Move anything in the tail before the end char too
|
307 |
+
middle += tail[:end_index]
|
308 |
+
tail = tail[end_index:]
|
309 |
+
|
310 |
+
if _http_re.match(middle):
|
311 |
+
if middle.startswith("https://") or middle.startswith("http://"):
|
312 |
+
middle = (
|
313 |
+
f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'
|
314 |
+
)
|
315 |
+
else:
|
316 |
+
middle = (
|
317 |
+
f'<a href="https://{middle}"{rel_attr}{target_attr}>'
|
318 |
+
f"{trim_url(middle)}</a>"
|
319 |
+
)
|
320 |
+
|
321 |
+
elif middle.startswith("mailto:") and _email_re.match(middle[7:]):
|
322 |
+
middle = f'<a href="{middle}">{middle[7:]}</a>'
|
323 |
+
|
324 |
+
elif (
|
325 |
+
"@" in middle
|
326 |
+
and not middle.startswith("www.")
|
327 |
+
and ":" not in middle
|
328 |
+
and _email_re.match(middle)
|
329 |
+
):
|
330 |
+
middle = f'<a href="mailto:{middle}">{middle}</a>'
|
331 |
+
|
332 |
+
elif extra_schemes is not None:
|
333 |
+
for scheme in extra_schemes:
|
334 |
+
if middle != scheme and middle.startswith(scheme):
|
335 |
+
middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>'
|
336 |
+
|
337 |
+
words[i] = f"{head}{middle}{tail}"
|
338 |
+
|
339 |
+
return "".join(words)
|
340 |
+
|
341 |
+
|
342 |
+
def generate_lorem_ipsum(
|
343 |
+
n: int = 5, html: bool = True, min: int = 20, max: int = 100
|
344 |
+
) -> str:
|
345 |
+
"""Generate some lorem ipsum for the template."""
|
346 |
+
from .constants import LOREM_IPSUM_WORDS
|
347 |
+
|
348 |
+
words = LOREM_IPSUM_WORDS.split()
|
349 |
+
result = []
|
350 |
+
|
351 |
+
for _ in range(n):
|
352 |
+
next_capitalized = True
|
353 |
+
last_comma = last_fullstop = 0
|
354 |
+
word = None
|
355 |
+
last = None
|
356 |
+
p = []
|
357 |
+
|
358 |
+
# each paragraph contains out of 20 to 100 words.
|
359 |
+
for idx, _ in enumerate(range(randrange(min, max))):
|
360 |
+
while True:
|
361 |
+
word = choice(words)
|
362 |
+
if word != last:
|
363 |
+
last = word
|
364 |
+
break
|
365 |
+
if next_capitalized:
|
366 |
+
word = word.capitalize()
|
367 |
+
next_capitalized = False
|
368 |
+
# add commas
|
369 |
+
if idx - randrange(3, 8) > last_comma:
|
370 |
+
last_comma = idx
|
371 |
+
last_fullstop += 2
|
372 |
+
word += ","
|
373 |
+
# add end of sentences
|
374 |
+
if idx - randrange(10, 20) > last_fullstop:
|
375 |
+
last_comma = last_fullstop = idx
|
376 |
+
word += "."
|
377 |
+
next_capitalized = True
|
378 |
+
p.append(word)
|
379 |
+
|
380 |
+
# ensure that the paragraph ends with a dot.
|
381 |
+
p_str = " ".join(p)
|
382 |
+
|
383 |
+
if p_str.endswith(","):
|
384 |
+
p_str = p_str[:-1] + "."
|
385 |
+
elif not p_str.endswith("."):
|
386 |
+
p_str += "."
|
387 |
+
|
388 |
+
result.append(p_str)
|
389 |
+
|
390 |
+
if not html:
|
391 |
+
return "\n\n".join(result)
|
392 |
+
return markupsafe.Markup(
|
393 |
+
"\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result)
|
394 |
+
)
|
395 |
+
|
396 |
+
|
397 |
+
def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
|
398 |
+
"""Quote a string for use in a URL using the given charset.
|
399 |
+
|
400 |
+
:param obj: String or bytes to quote. Other types are converted to
|
401 |
+
string then encoded to bytes using the given charset.
|
402 |
+
:param charset: Encode text to bytes using this charset.
|
403 |
+
:param for_qs: Quote "/" and use "+" for spaces.
|
404 |
+
"""
|
405 |
+
if not isinstance(obj, bytes):
|
406 |
+
if not isinstance(obj, str):
|
407 |
+
obj = str(obj)
|
408 |
+
|
409 |
+
obj = obj.encode(charset)
|
410 |
+
|
411 |
+
safe = b"" if for_qs else b"/"
|
412 |
+
rv = quote_from_bytes(obj, safe)
|
413 |
+
|
414 |
+
if for_qs:
|
415 |
+
rv = rv.replace("%20", "+")
|
416 |
+
|
417 |
+
return rv
|
418 |
+
|
419 |
+
|
420 |
+
@abc.MutableMapping.register
|
421 |
+
class LRUCache:
|
422 |
+
"""A simple LRU Cache implementation."""
|
423 |
+
|
424 |
+
# this is fast for small capacities (something below 1000) but doesn't
|
425 |
+
# scale. But as long as it's only used as storage for templates this
|
426 |
+
# won't do any harm.
|
427 |
+
|
428 |
+
def __init__(self, capacity: int) -> None:
|
429 |
+
self.capacity = capacity
|
430 |
+
self._mapping: t.Dict[t.Any, t.Any] = {}
|
431 |
+
self._queue: "te.Deque[t.Any]" = deque()
|
432 |
+
self._postinit()
|
433 |
+
|
434 |
+
def _postinit(self) -> None:
|
435 |
+
# alias all queue methods for faster lookup
|
436 |
+
self._popleft = self._queue.popleft
|
437 |
+
self._pop = self._queue.pop
|
438 |
+
self._remove = self._queue.remove
|
439 |
+
self._wlock = Lock()
|
440 |
+
self._append = self._queue.append
|
441 |
+
|
442 |
+
def __getstate__(self) -> t.Mapping[str, t.Any]:
|
443 |
+
return {
|
444 |
+
"capacity": self.capacity,
|
445 |
+
"_mapping": self._mapping,
|
446 |
+
"_queue": self._queue,
|
447 |
+
}
|
448 |
+
|
449 |
+
def __setstate__(self, d: t.Mapping[str, t.Any]) -> None:
|
450 |
+
self.__dict__.update(d)
|
451 |
+
self._postinit()
|
452 |
+
|
453 |
+
def __getnewargs__(self) -> t.Tuple:
|
454 |
+
return (self.capacity,)
|
455 |
+
|
456 |
+
def copy(self) -> "LRUCache":
|
457 |
+
"""Return a shallow copy of the instance."""
|
458 |
+
rv = self.__class__(self.capacity)
|
459 |
+
rv._mapping.update(self._mapping)
|
460 |
+
rv._queue.extend(self._queue)
|
461 |
+
return rv
|
462 |
+
|
463 |
+
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
|
464 |
+
"""Return an item from the cache dict or `default`"""
|
465 |
+
try:
|
466 |
+
return self[key]
|
467 |
+
except KeyError:
|
468 |
+
return default
|
469 |
+
|
470 |
+
def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any:
|
471 |
+
"""Set `default` if the key is not in the cache otherwise
|
472 |
+
leave unchanged. Return the value of this key.
|
473 |
+
"""
|
474 |
+
try:
|
475 |
+
return self[key]
|
476 |
+
except KeyError:
|
477 |
+
self[key] = default
|
478 |
+
return default
|
479 |
+
|
480 |
+
def clear(self) -> None:
|
481 |
+
"""Clear the cache."""
|
482 |
+
with self._wlock:
|
483 |
+
self._mapping.clear()
|
484 |
+
self._queue.clear()
|
485 |
+
|
486 |
+
def __contains__(self, key: t.Any) -> bool:
|
487 |
+
"""Check if a key exists in this cache."""
|
488 |
+
return key in self._mapping
|
489 |
+
|
490 |
+
def __len__(self) -> int:
|
491 |
+
"""Return the current size of the cache."""
|
492 |
+
return len(self._mapping)
|
493 |
+
|
494 |
+
def __repr__(self) -> str:
|
495 |
+
return f"<{type(self).__name__} {self._mapping!r}>"
|
496 |
+
|
497 |
+
def __getitem__(self, key: t.Any) -> t.Any:
|
498 |
+
"""Get an item from the cache. Moves the item up so that it has the
|
499 |
+
highest priority then.
|
500 |
+
|
501 |
+
Raise a `KeyError` if it does not exist.
|
502 |
+
"""
|
503 |
+
with self._wlock:
|
504 |
+
rv = self._mapping[key]
|
505 |
+
|
506 |
+
if self._queue[-1] != key:
|
507 |
+
try:
|
508 |
+
self._remove(key)
|
509 |
+
except ValueError:
|
510 |
+
# if something removed the key from the container
|
511 |
+
# when we read, ignore the ValueError that we would
|
512 |
+
# get otherwise.
|
513 |
+
pass
|
514 |
+
|
515 |
+
self._append(key)
|
516 |
+
|
517 |
+
return rv
|
518 |
+
|
519 |
+
def __setitem__(self, key: t.Any, value: t.Any) -> None:
|
520 |
+
"""Sets the value for an item. Moves the item up so that it
|
521 |
+
has the highest priority then.
|
522 |
+
"""
|
523 |
+
with self._wlock:
|
524 |
+
if key in self._mapping:
|
525 |
+
self._remove(key)
|
526 |
+
elif len(self._mapping) == self.capacity:
|
527 |
+
del self._mapping[self._popleft()]
|
528 |
+
|
529 |
+
self._append(key)
|
530 |
+
self._mapping[key] = value
|
531 |
+
|
532 |
+
def __delitem__(self, key: t.Any) -> None:
|
533 |
+
"""Remove an item from the cache dict.
|
534 |
+
Raise a `KeyError` if it does not exist.
|
535 |
+
"""
|
536 |
+
with self._wlock:
|
537 |
+
del self._mapping[key]
|
538 |
+
|
539 |
+
try:
|
540 |
+
self._remove(key)
|
541 |
+
except ValueError:
|
542 |
+
pass
|
543 |
+
|
544 |
+
def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]:
|
545 |
+
"""Return a list of items."""
|
546 |
+
result = [(key, self._mapping[key]) for key in list(self._queue)]
|
547 |
+
result.reverse()
|
548 |
+
return result
|
549 |
+
|
550 |
+
def values(self) -> t.Iterable[t.Any]:
|
551 |
+
"""Return a list of all values."""
|
552 |
+
return [x[1] for x in self.items()]
|
553 |
+
|
554 |
+
def keys(self) -> t.Iterable[t.Any]:
|
555 |
+
"""Return a list of all keys ordered by most recent usage."""
|
556 |
+
return list(self)
|
557 |
+
|
558 |
+
def __iter__(self) -> t.Iterator[t.Any]:
|
559 |
+
return reversed(tuple(self._queue))
|
560 |
+
|
561 |
+
def __reversed__(self) -> t.Iterator[t.Any]:
|
562 |
+
"""Iterate over the keys in the cache dict, oldest items
|
563 |
+
coming first.
|
564 |
+
"""
|
565 |
+
return iter(tuple(self._queue))
|
566 |
+
|
567 |
+
__copy__ = copy
|
568 |
+
|
569 |
+
|
570 |
+
def select_autoescape(
|
571 |
+
enabled_extensions: t.Collection[str] = ("html", "htm", "xml"),
|
572 |
+
disabled_extensions: t.Collection[str] = (),
|
573 |
+
default_for_string: bool = True,
|
574 |
+
default: bool = False,
|
575 |
+
) -> t.Callable[[t.Optional[str]], bool]:
|
576 |
+
"""Intelligently sets the initial value of autoescaping based on the
|
577 |
+
filename of the template. This is the recommended way to configure
|
578 |
+
autoescaping if you do not want to write a custom function yourself.
|
579 |
+
|
580 |
+
If you want to enable it for all templates created from strings or
|
581 |
+
for all templates with `.html` and `.xml` extensions::
|
582 |
+
|
583 |
+
from jinja2 import Environment, select_autoescape
|
584 |
+
env = Environment(autoescape=select_autoescape(
|
585 |
+
enabled_extensions=('html', 'xml'),
|
586 |
+
default_for_string=True,
|
587 |
+
))
|
588 |
+
|
589 |
+
Example configuration to turn it on at all times except if the template
|
590 |
+
ends with `.txt`::
|
591 |
+
|
592 |
+
from jinja2 import Environment, select_autoescape
|
593 |
+
env = Environment(autoescape=select_autoescape(
|
594 |
+
disabled_extensions=('txt',),
|
595 |
+
default_for_string=True,
|
596 |
+
default=True,
|
597 |
+
))
|
598 |
+
|
599 |
+
The `enabled_extensions` is an iterable of all the extensions that
|
600 |
+
autoescaping should be enabled for. Likewise `disabled_extensions` is
|
601 |
+
a list of all templates it should be disabled for. If a template is
|
602 |
+
loaded from a string then the default from `default_for_string` is used.
|
603 |
+
If nothing matches then the initial value of autoescaping is set to the
|
604 |
+
value of `default`.
|
605 |
+
|
606 |
+
For security reasons this function operates case insensitive.
|
607 |
+
|
608 |
+
.. versionadded:: 2.9
|
609 |
+
"""
|
610 |
+
enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
|
611 |
+
disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)
|
612 |
+
|
613 |
+
def autoescape(template_name: t.Optional[str]) -> bool:
|
614 |
+
if template_name is None:
|
615 |
+
return default_for_string
|
616 |
+
template_name = template_name.lower()
|
617 |
+
if template_name.endswith(enabled_patterns):
|
618 |
+
return True
|
619 |
+
if template_name.endswith(disabled_patterns):
|
620 |
+
return False
|
621 |
+
return default
|
622 |
+
|
623 |
+
return autoescape
|
624 |
+
|
625 |
+
|
626 |
+
def htmlsafe_json_dumps(
|
627 |
+
obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any
|
628 |
+
) -> markupsafe.Markup:
|
629 |
+
"""Serialize an object to a string of JSON with :func:`json.dumps`,
|
630 |
+
then replace HTML-unsafe characters with Unicode escapes and mark
|
631 |
+
the result safe with :class:`~markupsafe.Markup`.
|
632 |
+
|
633 |
+
This is available in templates as the ``|tojson`` filter.
|
634 |
+
|
635 |
+
The following characters are escaped: ``<``, ``>``, ``&``, ``'``.
|
636 |
+
|
637 |
+
The returned string is safe to render in HTML documents and
|
638 |
+
``<script>`` tags. The exception is in HTML attributes that are
|
639 |
+
double quoted; either use single quotes or the ``|forceescape``
|
640 |
+
filter.
|
641 |
+
|
642 |
+
:param obj: The object to serialize to JSON.
|
643 |
+
:param dumps: The ``dumps`` function to use. Defaults to
|
644 |
+
``env.policies["json.dumps_function"]``, which defaults to
|
645 |
+
:func:`json.dumps`.
|
646 |
+
:param kwargs: Extra arguments to pass to ``dumps``. Merged onto
|
647 |
+
``env.policies["json.dumps_kwargs"]``.
|
648 |
+
|
649 |
+
.. versionchanged:: 3.0
|
650 |
+
The ``dumper`` parameter is renamed to ``dumps``.
|
651 |
+
|
652 |
+
.. versionadded:: 2.9
|
653 |
+
"""
|
654 |
+
if dumps is None:
|
655 |
+
dumps = json.dumps
|
656 |
+
|
657 |
+
return markupsafe.Markup(
|
658 |
+
dumps(obj, **kwargs)
|
659 |
+
.replace("<", "\\u003c")
|
660 |
+
.replace(">", "\\u003e")
|
661 |
+
.replace("&", "\\u0026")
|
662 |
+
.replace("'", "\\u0027")
|
663 |
+
)
|
664 |
+
|
665 |
+
|
666 |
+
class Cycler:
|
667 |
+
"""Cycle through values by yield them one at a time, then restarting
|
668 |
+
once the end is reached. Available as ``cycler`` in templates.
|
669 |
+
|
670 |
+
Similar to ``loop.cycle``, but can be used outside loops or across
|
671 |
+
multiple loops. For example, render a list of folders and files in a
|
672 |
+
list, alternating giving them "odd" and "even" classes.
|
673 |
+
|
674 |
+
.. code-block:: html+jinja
|
675 |
+
|
676 |
+
{% set row_class = cycler("odd", "even") %}
|
677 |
+
<ul class="browser">
|
678 |
+
{% for folder in folders %}
|
679 |
+
<li class="folder {{ row_class.next() }}">{{ folder }}
|
680 |
+
{% endfor %}
|
681 |
+
{% for file in files %}
|
682 |
+
<li class="file {{ row_class.next() }}">{{ file }}
|
683 |
+
{% endfor %}
|
684 |
+
</ul>
|
685 |
+
|
686 |
+
:param items: Each positional argument will be yielded in the order
|
687 |
+
given for each cycle.
|
688 |
+
|
689 |
+
.. versionadded:: 2.1
|
690 |
+
"""
|
691 |
+
|
692 |
+
def __init__(self, *items: t.Any) -> None:
|
693 |
+
if not items:
|
694 |
+
raise RuntimeError("at least one item has to be provided")
|
695 |
+
self.items = items
|
696 |
+
self.pos = 0
|
697 |
+
|
698 |
+
def reset(self) -> None:
|
699 |
+
"""Resets the current item to the first item."""
|
700 |
+
self.pos = 0
|
701 |
+
|
702 |
+
@property
|
703 |
+
def current(self) -> t.Any:
|
704 |
+
"""Return the current item. Equivalent to the item that will be
|
705 |
+
returned next time :meth:`next` is called.
|
706 |
+
"""
|
707 |
+
return self.items[self.pos]
|
708 |
+
|
709 |
+
def next(self) -> t.Any:
|
710 |
+
"""Return the current item, then advance :attr:`current` to the
|
711 |
+
next item.
|
712 |
+
"""
|
713 |
+
rv = self.current
|
714 |
+
self.pos = (self.pos + 1) % len(self.items)
|
715 |
+
return rv
|
716 |
+
|
717 |
+
__next__ = next
|
718 |
+
|
719 |
+
|
720 |
+
class Joiner:
|
721 |
+
"""A joining helper for templates."""
|
722 |
+
|
723 |
+
def __init__(self, sep: str = ", ") -> None:
|
724 |
+
self.sep = sep
|
725 |
+
self.used = False
|
726 |
+
|
727 |
+
def __call__(self) -> str:
|
728 |
+
if not self.used:
|
729 |
+
self.used = True
|
730 |
+
return ""
|
731 |
+
return self.sep
|
732 |
+
|
733 |
+
|
734 |
+
class Namespace:
|
735 |
+
"""A namespace object that can hold arbitrary attributes. It may be
|
736 |
+
initialized from a dictionary or with keyword arguments."""
|
737 |
+
|
738 |
+
def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902
|
739 |
+
self, args = args[0], args[1:]
|
740 |
+
self.__attrs = dict(*args, **kwargs)
|
741 |
+
|
742 |
+
def __getattribute__(self, name: str) -> t.Any:
|
743 |
+
# __class__ is needed for the awaitable check in async mode
|
744 |
+
if name in {"_Namespace__attrs", "__class__"}:
|
745 |
+
return object.__getattribute__(self, name)
|
746 |
+
try:
|
747 |
+
return self.__attrs[name]
|
748 |
+
except KeyError:
|
749 |
+
raise AttributeError(name) from None
|
750 |
+
|
751 |
+
def __setitem__(self, name: str, value: t.Any) -> None:
|
752 |
+
self.__attrs[name] = value
|
753 |
+
|
754 |
+
def __repr__(self) -> str:
|
755 |
+
return f"<Namespace {self.__attrs!r}>"
|
lib/python3.11/site-packages/jinja2/visitor.py
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""API for traversing the AST nodes. Implemented by the compiler and
|
2 |
+
meta introspection.
|
3 |
+
"""
|
4 |
+
import typing as t
|
5 |
+
|
6 |
+
from .nodes import Node
|
7 |
+
|
8 |
+
if t.TYPE_CHECKING:
|
9 |
+
import typing_extensions as te
|
10 |
+
|
11 |
+
class VisitCallable(te.Protocol):
|
12 |
+
def __call__(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
13 |
+
...
|
14 |
+
|
15 |
+
|
16 |
+
class NodeVisitor:
|
17 |
+
"""Walks the abstract syntax tree and call visitor functions for every
|
18 |
+
node found. The visitor functions may return values which will be
|
19 |
+
forwarded by the `visit` method.
|
20 |
+
|
21 |
+
Per default the visitor functions for the nodes are ``'visit_'`` +
|
22 |
+
class name of the node. So a `TryFinally` node visit function would
|
23 |
+
be `visit_TryFinally`. This behavior can be changed by overriding
|
24 |
+
the `get_visitor` function. If no visitor function exists for a node
|
25 |
+
(return value `None`) the `generic_visit` visitor is used instead.
|
26 |
+
"""
|
27 |
+
|
28 |
+
def get_visitor(self, node: Node) -> "t.Optional[VisitCallable]":
|
29 |
+
"""Return the visitor function for this node or `None` if no visitor
|
30 |
+
exists for this node. In that case the generic visit function is
|
31 |
+
used instead.
|
32 |
+
"""
|
33 |
+
return getattr(self, f"visit_{type(node).__name__}", None)
|
34 |
+
|
35 |
+
def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
36 |
+
"""Visit a node."""
|
37 |
+
f = self.get_visitor(node)
|
38 |
+
|
39 |
+
if f is not None:
|
40 |
+
return f(node, *args, **kwargs)
|
41 |
+
|
42 |
+
return self.generic_visit(node, *args, **kwargs)
|
43 |
+
|
44 |
+
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
45 |
+
"""Called if no explicit visitor function exists for a node."""
|
46 |
+
for child_node in node.iter_child_nodes():
|
47 |
+
self.visit(child_node, *args, **kwargs)
|
48 |
+
|
49 |
+
|
50 |
+
class NodeTransformer(NodeVisitor):
|
51 |
+
"""Walks the abstract syntax tree and allows modifications of nodes.
|
52 |
+
|
53 |
+
The `NodeTransformer` will walk the AST and use the return value of the
|
54 |
+
visitor functions to replace or remove the old node. If the return
|
55 |
+
value of the visitor function is `None` the node will be removed
|
56 |
+
from the previous location otherwise it's replaced with the return
|
57 |
+
value. The return value may be the original node in which case no
|
58 |
+
replacement takes place.
|
59 |
+
"""
|
60 |
+
|
61 |
+
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> Node:
|
62 |
+
for field, old_value in node.iter_fields():
|
63 |
+
if isinstance(old_value, list):
|
64 |
+
new_values = []
|
65 |
+
for value in old_value:
|
66 |
+
if isinstance(value, Node):
|
67 |
+
value = self.visit(value, *args, **kwargs)
|
68 |
+
if value is None:
|
69 |
+
continue
|
70 |
+
elif not isinstance(value, Node):
|
71 |
+
new_values.extend(value)
|
72 |
+
continue
|
73 |
+
new_values.append(value)
|
74 |
+
old_value[:] = new_values
|
75 |
+
elif isinstance(old_value, Node):
|
76 |
+
new_node = self.visit(old_value, *args, **kwargs)
|
77 |
+
if new_node is None:
|
78 |
+
delattr(node, field)
|
79 |
+
else:
|
80 |
+
setattr(node, field, new_node)
|
81 |
+
return node
|
82 |
+
|
83 |
+
def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.List[Node]:
|
84 |
+
"""As transformers may return lists in some places this method
|
85 |
+
can be used to enforce a list as return value.
|
86 |
+
"""
|
87 |
+
rv = self.visit(node, *args, **kwargs)
|
88 |
+
|
89 |
+
if not isinstance(rv, list):
|
90 |
+
return [rv]
|
91 |
+
|
92 |
+
return rv
|
lib/python3.11/site-packages/llvmlite/__init__.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
from ._version import get_versions
|
2 |
+
__version__ = get_versions()['version']
|
3 |
+
del get_versions
|
lib/python3.11/site-packages/llvmlite/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (355 Bytes). View file
|
|
lib/python3.11/site-packages/llvmlite/__pycache__/_version.cpython-311.pyc
ADDED
Binary file (535 Bytes). View file
|
|
lib/python3.11/site-packages/llvmlite/__pycache__/utils.cpython-311.pyc
ADDED
Binary file (1.13 kB). View file
|
|
lib/python3.11/site-packages/llvmlite/_version.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
# This file was generated by 'versioneer.py' (0.14) from
|
3 |
+
# revision-control system data, or from the parent directory name of an
|
4 |
+
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
5 |
+
# of this file.
|
6 |
+
|
7 |
+
version_version = '0.41.1'
|
8 |
+
version_full = '42db9b74c8e8ca012cb4394cbf941f2f9e4ae12b'
|
9 |
+
def get_versions(default={}, verbose=False):
|
10 |
+
return {'version': version_version, 'full': version_full}
|
11 |
+
|
lib/python3.11/site-packages/llvmlite/binding/__init__.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Things that rely on the LLVM library
|
3 |
+
"""
|
4 |
+
from .dylib import *
|
5 |
+
from .executionengine import *
|
6 |
+
from .initfini import *
|
7 |
+
from .linker import *
|
8 |
+
from .module import *
|
9 |
+
from .options import *
|
10 |
+
from .passmanagers import *
|
11 |
+
from .targets import *
|
12 |
+
from .transforms import *
|
13 |
+
from .value import *
|
14 |
+
from .analysis import *
|
15 |
+
from .object_file import *
|
16 |
+
from .context import *
|
17 |
+
from .orcjit import *
|
lib/python3.11/site-packages/llvmlite/binding/__pycache__/__init__.cpython-311.pyc
ADDED
Binary file (720 Bytes). View file
|
|
lib/python3.11/site-packages/llvmlite/binding/__pycache__/analysis.cpython-311.pyc
ADDED
Binary file (3.33 kB). View file
|
|
lib/python3.11/site-packages/llvmlite/binding/__pycache__/common.cpython-311.pyc
ADDED
Binary file (1.45 kB). View file
|
|
lib/python3.11/site-packages/llvmlite/binding/__pycache__/context.cpython-311.pyc
ADDED
Binary file (2.27 kB). View file
|
|
lib/python3.11/site-packages/llvmlite/binding/__pycache__/dylib.cpython-311.pyc
ADDED
Binary file (2.39 kB). View file
|
|
lib/python3.11/site-packages/llvmlite/binding/__pycache__/executionengine.cpython-311.pyc
ADDED
Binary file (15.3 kB). View file
|
|