second commit
This commit is contained in:
38
env/lib/python3.11/site-packages/jinja2/__init__.py
vendored
Normal file
38
env/lib/python3.11/site-packages/jinja2/__init__.py
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
"""Jinja is a template engine written in pure Python. It provides a
|
||||
non-XML syntax that supports inline expressions and an optional
|
||||
sandboxed environment.
|
||||
"""
|
||||
|
||||
from .bccache import BytecodeCache as BytecodeCache
|
||||
from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache
|
||||
from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache
|
||||
from .environment import Environment as Environment
|
||||
from .environment import Template as Template
|
||||
from .exceptions import TemplateAssertionError as TemplateAssertionError
|
||||
from .exceptions import TemplateError as TemplateError
|
||||
from .exceptions import TemplateNotFound as TemplateNotFound
|
||||
from .exceptions import TemplateRuntimeError as TemplateRuntimeError
|
||||
from .exceptions import TemplatesNotFound as TemplatesNotFound
|
||||
from .exceptions import TemplateSyntaxError as TemplateSyntaxError
|
||||
from .exceptions import UndefinedError as UndefinedError
|
||||
from .loaders import BaseLoader as BaseLoader
|
||||
from .loaders import ChoiceLoader as ChoiceLoader
|
||||
from .loaders import DictLoader as DictLoader
|
||||
from .loaders import FileSystemLoader as FileSystemLoader
|
||||
from .loaders import FunctionLoader as FunctionLoader
|
||||
from .loaders import ModuleLoader as ModuleLoader
|
||||
from .loaders import PackageLoader as PackageLoader
|
||||
from .loaders import PrefixLoader as PrefixLoader
|
||||
from .runtime import ChainableUndefined as ChainableUndefined
|
||||
from .runtime import DebugUndefined as DebugUndefined
|
||||
from .runtime import make_logging_undefined as make_logging_undefined
|
||||
from .runtime import StrictUndefined as StrictUndefined
|
||||
from .runtime import Undefined as Undefined
|
||||
from .utils import clear_caches as clear_caches
|
||||
from .utils import is_undefined as is_undefined
|
||||
from .utils import pass_context as pass_context
|
||||
from .utils import pass_environment as pass_environment
|
||||
from .utils import pass_eval_context as pass_eval_context
|
||||
from .utils import select_autoescape as select_autoescape
|
||||
|
||||
__version__ = "3.1.5"
|
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/_identifier.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/_identifier.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/async_utils.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/async_utils.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/bccache.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/bccache.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/compiler.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/compiler.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/constants.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/constants.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/debug.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/debug.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/defaults.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/defaults.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/environment.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/environment.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/exceptions.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/exceptions.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/ext.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/ext.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/filters.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/filters.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/idtracking.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/idtracking.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/lexer.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/lexer.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/loaders.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/loaders.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/meta.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/meta.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/nativetypes.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/nativetypes.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/nodes.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/nodes.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/optimizer.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/optimizer.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/parser.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/parser.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/runtime.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/runtime.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/sandbox.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/sandbox.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/tests.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/tests.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/utils.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/utils.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/visitor.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/jinja2/__pycache__/visitor.cpython-311.pyc
vendored
Normal file
Binary file not shown.
6
env/lib/python3.11/site-packages/jinja2/_identifier.py
vendored
Normal file
6
env/lib/python3.11/site-packages/jinja2/_identifier.py
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
import re
|
||||
|
||||
# generated by scripts/generate_identifier_pattern.py
|
||||
pattern = re.compile(
|
||||
r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
|
||||
)
|
99
env/lib/python3.11/site-packages/jinja2/async_utils.py
vendored
Normal file
99
env/lib/python3.11/site-packages/jinja2/async_utils.py
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
import inspect
|
||||
import typing as t
|
||||
from functools import WRAPPER_ASSIGNMENTS
|
||||
from functools import wraps
|
||||
|
||||
from .utils import _PassArg
|
||||
from .utils import pass_eval_context
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
V = t.TypeVar("V")
|
||||
|
||||
|
||||
def async_variant(normal_func): # type: ignore
|
||||
def decorator(async_func): # type: ignore
|
||||
pass_arg = _PassArg.from_obj(normal_func)
|
||||
need_eval_context = pass_arg is None
|
||||
|
||||
if pass_arg is _PassArg.environment:
|
||||
|
||||
def is_async(args: t.Any) -> bool:
|
||||
return t.cast(bool, args[0].is_async)
|
||||
|
||||
else:
|
||||
|
||||
def is_async(args: t.Any) -> bool:
|
||||
return t.cast(bool, args[0].environment.is_async)
|
||||
|
||||
# Take the doc and annotations from the sync function, but the
|
||||
# name from the async function. Pallets-Sphinx-Themes
|
||||
# build_function_directive expects __wrapped__ to point to the
|
||||
# sync function.
|
||||
async_func_attrs = ("__module__", "__name__", "__qualname__")
|
||||
normal_func_attrs = tuple(set(WRAPPER_ASSIGNMENTS).difference(async_func_attrs))
|
||||
|
||||
@wraps(normal_func, assigned=normal_func_attrs)
|
||||
@wraps(async_func, assigned=async_func_attrs, updated=())
|
||||
def wrapper(*args, **kwargs): # type: ignore
|
||||
b = is_async(args)
|
||||
|
||||
if need_eval_context:
|
||||
args = args[1:]
|
||||
|
||||
if b:
|
||||
return async_func(*args, **kwargs)
|
||||
|
||||
return normal_func(*args, **kwargs)
|
||||
|
||||
if need_eval_context:
|
||||
wrapper = pass_eval_context(wrapper)
|
||||
|
||||
wrapper.jinja_async_variant = True # type: ignore[attr-defined]
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
_common_primitives = {int, float, bool, str, list, dict, tuple, type(None)}
|
||||
|
||||
|
||||
async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V":
|
||||
# Avoid a costly call to isawaitable
|
||||
if type(value) in _common_primitives:
|
||||
return t.cast("V", value)
|
||||
|
||||
if inspect.isawaitable(value):
|
||||
return await t.cast("t.Awaitable[V]", value)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class _IteratorToAsyncIterator(t.Generic[V]):
|
||||
def __init__(self, iterator: "t.Iterator[V]"):
|
||||
self._iterator = iterator
|
||||
|
||||
def __aiter__(self) -> "te.Self":
|
||||
return self
|
||||
|
||||
async def __anext__(self) -> V:
|
||||
try:
|
||||
return next(self._iterator)
|
||||
except StopIteration as e:
|
||||
raise StopAsyncIteration(e.value) from e
|
||||
|
||||
|
||||
def auto_aiter(
|
||||
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
) -> "t.AsyncIterator[V]":
|
||||
if hasattr(iterable, "__aiter__"):
|
||||
return iterable.__aiter__()
|
||||
else:
|
||||
return _IteratorToAsyncIterator(iter(iterable))
|
||||
|
||||
|
||||
async def auto_to_list(
|
||||
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
|
||||
) -> t.List["V"]:
|
||||
return [x async for x in auto_aiter(value)]
|
408
env/lib/python3.11/site-packages/jinja2/bccache.py
vendored
Normal file
408
env/lib/python3.11/site-packages/jinja2/bccache.py
vendored
Normal file
@ -0,0 +1,408 @@
|
||||
"""The optional bytecode cache system. This is useful if you have very
|
||||
complex template situations and the compilation of all those templates
|
||||
slows down your application too much.
|
||||
|
||||
Situations where this is useful are often forking web applications that
|
||||
are initialized on the first request.
|
||||
"""
|
||||
|
||||
import errno
|
||||
import fnmatch
|
||||
import marshal
|
||||
import os
|
||||
import pickle
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import typing as t
|
||||
from hashlib import sha1
|
||||
from io import BytesIO
|
||||
from types import CodeType
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
|
||||
class _MemcachedClient(te.Protocol):
|
||||
def get(self, key: str) -> bytes: ...
|
||||
|
||||
def set(
|
||||
self, key: str, value: bytes, timeout: t.Optional[int] = None
|
||||
) -> None: ...
|
||||
|
||||
|
||||
bc_version = 5
|
||||
# Magic bytes to identify Jinja bytecode cache files. Contains the
|
||||
# Python major and minor version to avoid loading incompatible bytecode
|
||||
# if a project upgrades its Python version.
|
||||
bc_magic = (
|
||||
b"j2"
|
||||
+ pickle.dumps(bc_version, 2)
|
||||
+ pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2)
|
||||
)
|
||||
|
||||
|
||||
class Bucket:
|
||||
"""Buckets are used to store the bytecode for one template. It's created
|
||||
and initialized by the bytecode cache and passed to the loading functions.
|
||||
|
||||
The buckets get an internal checksum from the cache assigned and use this
|
||||
to automatically reject outdated cache material. Individual bytecode
|
||||
cache subclasses don't have to care about cache invalidation.
|
||||
"""
|
||||
|
||||
def __init__(self, environment: "Environment", key: str, checksum: str) -> None:
|
||||
self.environment = environment
|
||||
self.key = key
|
||||
self.checksum = checksum
|
||||
self.reset()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Resets the bucket (unloads the bytecode)."""
|
||||
self.code: t.Optional[CodeType] = None
|
||||
|
||||
def load_bytecode(self, f: t.BinaryIO) -> None:
|
||||
"""Loads bytecode from a file or file like object."""
|
||||
# make sure the magic header is correct
|
||||
magic = f.read(len(bc_magic))
|
||||
if magic != bc_magic:
|
||||
self.reset()
|
||||
return
|
||||
# the source code of the file changed, we need to reload
|
||||
checksum = pickle.load(f)
|
||||
if self.checksum != checksum:
|
||||
self.reset()
|
||||
return
|
||||
# if marshal_load fails then we need to reload
|
||||
try:
|
||||
self.code = marshal.load(f)
|
||||
except (EOFError, ValueError, TypeError):
|
||||
self.reset()
|
||||
return
|
||||
|
||||
def write_bytecode(self, f: t.IO[bytes]) -> None:
|
||||
"""Dump the bytecode into the file or file like object passed."""
|
||||
if self.code is None:
|
||||
raise TypeError("can't write empty bucket")
|
||||
f.write(bc_magic)
|
||||
pickle.dump(self.checksum, f, 2)
|
||||
marshal.dump(self.code, f)
|
||||
|
||||
def bytecode_from_string(self, string: bytes) -> None:
|
||||
"""Load bytecode from bytes."""
|
||||
self.load_bytecode(BytesIO(string))
|
||||
|
||||
def bytecode_to_string(self) -> bytes:
|
||||
"""Return the bytecode as bytes."""
|
||||
out = BytesIO()
|
||||
self.write_bytecode(out)
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
class BytecodeCache:
|
||||
"""To implement your own bytecode cache you have to subclass this class
|
||||
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
|
||||
these methods are passed a :class:`~jinja2.bccache.Bucket`.
|
||||
|
||||
A very basic bytecode cache that saves the bytecode on the file system::
|
||||
|
||||
from os import path
|
||||
|
||||
class MyCache(BytecodeCache):
|
||||
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
|
||||
def load_bytecode(self, bucket):
|
||||
filename = path.join(self.directory, bucket.key)
|
||||
if path.exists(filename):
|
||||
with open(filename, 'rb') as f:
|
||||
bucket.load_bytecode(f)
|
||||
|
||||
def dump_bytecode(self, bucket):
|
||||
filename = path.join(self.directory, bucket.key)
|
||||
with open(filename, 'wb') as f:
|
||||
bucket.write_bytecode(f)
|
||||
|
||||
A more advanced version of a filesystem based bytecode cache is part of
|
||||
Jinja.
|
||||
"""
|
||||
|
||||
def load_bytecode(self, bucket: Bucket) -> None:
|
||||
"""Subclasses have to override this method to load bytecode into a
|
||||
bucket. If they are not able to find code in the cache for the
|
||||
bucket, it must not do anything.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def dump_bytecode(self, bucket: Bucket) -> None:
|
||||
"""Subclasses have to override this method to write the bytecode
|
||||
from a bucket back to the cache. If it unable to do so it must not
|
||||
fail silently but raise an exception.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clears the cache. This method is not used by Jinja but should be
|
||||
implemented to allow applications to clear the bytecode cache used
|
||||
by a particular environment.
|
||||
"""
|
||||
|
||||
def get_cache_key(
|
||||
self, name: str, filename: t.Optional[t.Union[str]] = None
|
||||
) -> str:
|
||||
"""Returns the unique hash key for this template name."""
|
||||
hash = sha1(name.encode("utf-8"))
|
||||
|
||||
if filename is not None:
|
||||
hash.update(f"|{filename}".encode())
|
||||
|
||||
return hash.hexdigest()
|
||||
|
||||
def get_source_checksum(self, source: str) -> str:
|
||||
"""Returns a checksum for the source."""
|
||||
return sha1(source.encode("utf-8")).hexdigest()
|
||||
|
||||
def get_bucket(
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
filename: t.Optional[str],
|
||||
source: str,
|
||||
) -> Bucket:
|
||||
"""Return a cache bucket for the given template. All arguments are
|
||||
mandatory but filename may be `None`.
|
||||
"""
|
||||
key = self.get_cache_key(name, filename)
|
||||
checksum = self.get_source_checksum(source)
|
||||
bucket = Bucket(environment, key, checksum)
|
||||
self.load_bytecode(bucket)
|
||||
return bucket
|
||||
|
||||
def set_bucket(self, bucket: Bucket) -> None:
|
||||
"""Put the bucket into the cache."""
|
||||
self.dump_bytecode(bucket)
|
||||
|
||||
|
||||
class FileSystemBytecodeCache(BytecodeCache):
|
||||
"""A bytecode cache that stores bytecode on the filesystem. It accepts
|
||||
two arguments: The directory where the cache items are stored and a
|
||||
pattern string that is used to build the filename.
|
||||
|
||||
If no directory is specified a default cache directory is selected. On
|
||||
Windows the user's temp directory is used, on UNIX systems a directory
|
||||
is created for the user in the system temp directory.
|
||||
|
||||
The pattern can be used to have multiple separate caches operate on the
|
||||
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
|
||||
is replaced with the cache key.
|
||||
|
||||
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
|
||||
|
||||
This bytecode cache supports clearing of the cache using the clear method.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
|
||||
) -> None:
|
||||
if directory is None:
|
||||
directory = self._get_default_cache_dir()
|
||||
self.directory = directory
|
||||
self.pattern = pattern
|
||||
|
||||
def _get_default_cache_dir(self) -> str:
|
||||
def _unsafe_dir() -> "te.NoReturn":
|
||||
raise RuntimeError(
|
||||
"Cannot determine safe temp directory. You "
|
||||
"need to explicitly provide one."
|
||||
)
|
||||
|
||||
tmpdir = tempfile.gettempdir()
|
||||
|
||||
# On windows the temporary directory is used specific unless
|
||||
# explicitly forced otherwise. We can just use that.
|
||||
if os.name == "nt":
|
||||
return tmpdir
|
||||
if not hasattr(os, "getuid"):
|
||||
_unsafe_dir()
|
||||
|
||||
dirname = f"_jinja2-cache-{os.getuid()}"
|
||||
actual_dir = os.path.join(tmpdir, dirname)
|
||||
|
||||
try:
|
||||
os.mkdir(actual_dir, stat.S_IRWXU)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
try:
|
||||
os.chmod(actual_dir, stat.S_IRWXU)
|
||||
actual_dir_stat = os.lstat(actual_dir)
|
||||
if (
|
||||
actual_dir_stat.st_uid != os.getuid()
|
||||
or not stat.S_ISDIR(actual_dir_stat.st_mode)
|
||||
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
|
||||
):
|
||||
_unsafe_dir()
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
actual_dir_stat = os.lstat(actual_dir)
|
||||
if (
|
||||
actual_dir_stat.st_uid != os.getuid()
|
||||
or not stat.S_ISDIR(actual_dir_stat.st_mode)
|
||||
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
|
||||
):
|
||||
_unsafe_dir()
|
||||
|
||||
return actual_dir
|
||||
|
||||
def _get_cache_filename(self, bucket: Bucket) -> str:
|
||||
return os.path.join(self.directory, self.pattern % (bucket.key,))
|
||||
|
||||
def load_bytecode(self, bucket: Bucket) -> None:
|
||||
filename = self._get_cache_filename(bucket)
|
||||
|
||||
# Don't test for existence before opening the file, since the
|
||||
# file could disappear after the test before the open.
|
||||
try:
|
||||
f = open(filename, "rb")
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||
# PermissionError can occur on Windows when an operation is
|
||||
# in progress, such as calling clear().
|
||||
return
|
||||
|
||||
with f:
|
||||
bucket.load_bytecode(f)
|
||||
|
||||
def dump_bytecode(self, bucket: Bucket) -> None:
|
||||
# Write to a temporary file, then rename to the real name after
|
||||
# writing. This avoids another process reading the file before
|
||||
# it is fully written.
|
||||
name = self._get_cache_filename(bucket)
|
||||
f = tempfile.NamedTemporaryFile(
|
||||
mode="wb",
|
||||
dir=os.path.dirname(name),
|
||||
prefix=os.path.basename(name),
|
||||
suffix=".tmp",
|
||||
delete=False,
|
||||
)
|
||||
|
||||
def remove_silent() -> None:
|
||||
try:
|
||||
os.remove(f.name)
|
||||
except OSError:
|
||||
# Another process may have called clear(). On Windows,
|
||||
# another program may be holding the file open.
|
||||
pass
|
||||
|
||||
try:
|
||||
with f:
|
||||
bucket.write_bytecode(f)
|
||||
except BaseException:
|
||||
remove_silent()
|
||||
raise
|
||||
|
||||
try:
|
||||
os.replace(f.name, name)
|
||||
except OSError:
|
||||
# Another process may have called clear(). On Windows,
|
||||
# another program may be holding the file open.
|
||||
remove_silent()
|
||||
except BaseException:
|
||||
remove_silent()
|
||||
raise
|
||||
|
||||
def clear(self) -> None:
|
||||
# imported lazily here because google app-engine doesn't support
|
||||
# write access on the file system and the function does not exist
|
||||
# normally.
|
||||
from os import remove
|
||||
|
||||
files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",))
|
||||
for filename in files:
|
||||
try:
|
||||
remove(os.path.join(self.directory, filename))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class MemcachedBytecodeCache(BytecodeCache):
|
||||
"""This class implements a bytecode cache that uses a memcache cache for
|
||||
storing the information. It does not enforce a specific memcache library
|
||||
(tummy's memcache or cmemcache) but will accept any class that provides
|
||||
the minimal interface required.
|
||||
|
||||
Libraries compatible with this class:
|
||||
|
||||
- `cachelib <https://github.com/pallets/cachelib>`_
|
||||
- `python-memcached <https://pypi.org/project/python-memcached/>`_
|
||||
|
||||
(Unfortunately the django cache interface is not compatible because it
|
||||
does not support storing binary data, only text. You can however pass
|
||||
the underlying cache client to the bytecode cache which is available
|
||||
as `django.core.cache.cache._client`.)
|
||||
|
||||
The minimal interface for the client passed to the constructor is this:
|
||||
|
||||
.. class:: MinimalClientInterface
|
||||
|
||||
.. method:: set(key, value[, timeout])
|
||||
|
||||
Stores the bytecode in the cache. `value` is a string and
|
||||
`timeout` the timeout of the key. If timeout is not provided
|
||||
a default timeout or no timeout should be assumed, if it's
|
||||
provided it's an integer with the number of seconds the cache
|
||||
item should exist.
|
||||
|
||||
.. method:: get(key)
|
||||
|
||||
Returns the value for the cache key. If the item does not
|
||||
exist in the cache the return value must be `None`.
|
||||
|
||||
The other arguments to the constructor are the prefix for all keys that
|
||||
is added before the actual cache key and the timeout for the bytecode in
|
||||
the cache system. We recommend a high (or no) timeout.
|
||||
|
||||
This bytecode cache does not support clearing of used items in the cache.
|
||||
The clear method is a no-operation function.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
Added support for ignoring memcache errors through the
|
||||
`ignore_memcache_errors` parameter.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: "_MemcachedClient",
|
||||
prefix: str = "jinja2/bytecode/",
|
||||
timeout: t.Optional[int] = None,
|
||||
ignore_memcache_errors: bool = True,
|
||||
):
|
||||
self.client = client
|
||||
self.prefix = prefix
|
||||
self.timeout = timeout
|
||||
self.ignore_memcache_errors = ignore_memcache_errors
|
||||
|
||||
def load_bytecode(self, bucket: Bucket) -> None:
|
||||
try:
|
||||
code = self.client.get(self.prefix + bucket.key)
|
||||
except Exception:
|
||||
if not self.ignore_memcache_errors:
|
||||
raise
|
||||
else:
|
||||
bucket.bytecode_from_string(code)
|
||||
|
||||
def dump_bytecode(self, bucket: Bucket) -> None:
|
||||
key = self.prefix + bucket.key
|
||||
value = bucket.bytecode_to_string()
|
||||
|
||||
try:
|
||||
if self.timeout is not None:
|
||||
self.client.set(key, value, self.timeout)
|
||||
else:
|
||||
self.client.set(key, value)
|
||||
except Exception:
|
||||
if not self.ignore_memcache_errors:
|
||||
raise
|
1998
env/lib/python3.11/site-packages/jinja2/compiler.py
vendored
Normal file
1998
env/lib/python3.11/site-packages/jinja2/compiler.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
20
env/lib/python3.11/site-packages/jinja2/constants.py
vendored
Normal file
20
env/lib/python3.11/site-packages/jinja2/constants.py
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
#: list of lorem ipsum words used by the lipsum() helper function
|
||||
LOREM_IPSUM_WORDS = """\
|
||||
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
|
||||
auctor augue bibendum blandit class commodo condimentum congue consectetuer
|
||||
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
|
||||
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
|
||||
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
|
||||
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
|
||||
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
|
||||
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
|
||||
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
|
||||
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
|
||||
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
|
||||
penatibus per pharetra phasellus placerat platea porta porttitor posuere
|
||||
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
|
||||
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
|
||||
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
|
||||
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
|
||||
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
|
||||
viverra volutpat vulputate"""
|
191
env/lib/python3.11/site-packages/jinja2/debug.py
vendored
Normal file
191
env/lib/python3.11/site-packages/jinja2/debug.py
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
import sys
|
||||
import typing as t
|
||||
from types import CodeType
|
||||
from types import TracebackType
|
||||
|
||||
from .exceptions import TemplateSyntaxError
|
||||
from .utils import internal_code
|
||||
from .utils import missing
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .runtime import Context
|
||||
|
||||
|
||||
def rewrite_traceback_stack(source: t.Optional[str] = None) -> BaseException:
|
||||
"""Rewrite the current exception to replace any tracebacks from
|
||||
within compiled template code with tracebacks that look like they
|
||||
came from the template source.
|
||||
|
||||
This must be called within an ``except`` block.
|
||||
|
||||
:param source: For ``TemplateSyntaxError``, the original source if
|
||||
known.
|
||||
:return: The original exception with the rewritten traceback.
|
||||
"""
|
||||
_, exc_value, tb = sys.exc_info()
|
||||
exc_value = t.cast(BaseException, exc_value)
|
||||
tb = t.cast(TracebackType, tb)
|
||||
|
||||
if isinstance(exc_value, TemplateSyntaxError) and not exc_value.translated:
|
||||
exc_value.translated = True
|
||||
exc_value.source = source
|
||||
# Remove the old traceback, otherwise the frames from the
|
||||
# compiler still show up.
|
||||
exc_value.with_traceback(None)
|
||||
# Outside of runtime, so the frame isn't executing template
|
||||
# code, but it still needs to point at the template.
|
||||
tb = fake_traceback(
|
||||
exc_value, None, exc_value.filename or "<unknown>", exc_value.lineno
|
||||
)
|
||||
else:
|
||||
# Skip the frame for the render function.
|
||||
tb = tb.tb_next
|
||||
|
||||
stack = []
|
||||
|
||||
# Build the stack of traceback object, replacing any in template
|
||||
# code with the source file and line information.
|
||||
while tb is not None:
|
||||
# Skip frames decorated with @internalcode. These are internal
|
||||
# calls that aren't useful in template debugging output.
|
||||
if tb.tb_frame.f_code in internal_code:
|
||||
tb = tb.tb_next
|
||||
continue
|
||||
|
||||
template = tb.tb_frame.f_globals.get("__jinja_template__")
|
||||
|
||||
if template is not None:
|
||||
lineno = template.get_corresponding_lineno(tb.tb_lineno)
|
||||
fake_tb = fake_traceback(exc_value, tb, template.filename, lineno)
|
||||
stack.append(fake_tb)
|
||||
else:
|
||||
stack.append(tb)
|
||||
|
||||
tb = tb.tb_next
|
||||
|
||||
tb_next = None
|
||||
|
||||
# Assign tb_next in reverse to avoid circular references.
|
||||
for tb in reversed(stack):
|
||||
tb.tb_next = tb_next
|
||||
tb_next = tb
|
||||
|
||||
return exc_value.with_traceback(tb_next)
|
||||
|
||||
|
||||
def fake_traceback( # type: ignore
|
||||
exc_value: BaseException, tb: t.Optional[TracebackType], filename: str, lineno: int
|
||||
) -> TracebackType:
|
||||
"""Produce a new traceback object that looks like it came from the
|
||||
template source instead of the compiled code. The filename, line
|
||||
number, and location name will point to the template, and the local
|
||||
variables will be the current template context.
|
||||
|
||||
:param exc_value: The original exception to be re-raised to create
|
||||
the new traceback.
|
||||
:param tb: The original traceback to get the local variables and
|
||||
code info from.
|
||||
:param filename: The template filename.
|
||||
:param lineno: The line number in the template source.
|
||||
"""
|
||||
if tb is not None:
|
||||
# Replace the real locals with the context that would be
|
||||
# available at that point in the template.
|
||||
locals = get_template_locals(tb.tb_frame.f_locals)
|
||||
locals.pop("__jinja_exception__", None)
|
||||
else:
|
||||
locals = {}
|
||||
|
||||
globals = {
|
||||
"__name__": filename,
|
||||
"__file__": filename,
|
||||
"__jinja_exception__": exc_value,
|
||||
}
|
||||
# Raise an exception at the correct line number.
|
||||
code: CodeType = compile(
|
||||
"\n" * (lineno - 1) + "raise __jinja_exception__", filename, "exec"
|
||||
)
|
||||
|
||||
# Build a new code object that points to the template file and
|
||||
# replaces the location with a block name.
|
||||
location = "template"
|
||||
|
||||
if tb is not None:
|
||||
function = tb.tb_frame.f_code.co_name
|
||||
|
||||
if function == "root":
|
||||
location = "top-level template code"
|
||||
elif function.startswith("block_"):
|
||||
location = f"block {function[6:]!r}"
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
code = code.replace(co_name=location)
|
||||
else:
|
||||
code = CodeType(
|
||||
code.co_argcount,
|
||||
code.co_kwonlyargcount,
|
||||
code.co_nlocals,
|
||||
code.co_stacksize,
|
||||
code.co_flags,
|
||||
code.co_code,
|
||||
code.co_consts,
|
||||
code.co_names,
|
||||
code.co_varnames,
|
||||
code.co_filename,
|
||||
location,
|
||||
code.co_firstlineno,
|
||||
code.co_lnotab,
|
||||
code.co_freevars,
|
||||
code.co_cellvars,
|
||||
)
|
||||
|
||||
# Execute the new code, which is guaranteed to raise, and return
|
||||
# the new traceback without this frame.
|
||||
try:
|
||||
exec(code, globals, locals)
|
||||
except BaseException:
|
||||
return sys.exc_info()[2].tb_next # type: ignore
|
||||
|
||||
|
||||
def get_template_locals(real_locals: t.Mapping[str, t.Any]) -> t.Dict[str, t.Any]:
|
||||
"""Based on the runtime locals, get the context that would be
|
||||
available at that point in the template.
|
||||
"""
|
||||
# Start with the current template context.
|
||||
ctx: t.Optional[Context] = real_locals.get("context")
|
||||
|
||||
if ctx is not None:
|
||||
data: t.Dict[str, t.Any] = ctx.get_all().copy()
|
||||
else:
|
||||
data = {}
|
||||
|
||||
# Might be in a derived context that only sets local variables
|
||||
# rather than pushing a context. Local variables follow the scheme
|
||||
# l_depth_name. Find the highest-depth local that has a value for
|
||||
# each name.
|
||||
local_overrides: t.Dict[str, t.Tuple[int, t.Any]] = {}
|
||||
|
||||
for name, value in real_locals.items():
|
||||
if not name.startswith("l_") or value is missing:
|
||||
# Not a template variable, or no longer relevant.
|
||||
continue
|
||||
|
||||
try:
|
||||
_, depth_str, name = name.split("_", 2)
|
||||
depth = int(depth_str)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
cur_depth = local_overrides.get(name, (-1,))[0]
|
||||
|
||||
if cur_depth < depth:
|
||||
local_overrides[name] = (depth, value)
|
||||
|
||||
# Modify the context with any derived context.
|
||||
for name, (_, value) in local_overrides.items():
|
||||
if value is missing:
|
||||
data.pop(name, None)
|
||||
else:
|
||||
data[name] = value
|
||||
|
||||
return data
|
48
env/lib/python3.11/site-packages/jinja2/defaults.py
vendored
Normal file
48
env/lib/python3.11/site-packages/jinja2/defaults.py
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
import typing as t
|
||||
|
||||
from .filters import FILTERS as DEFAULT_FILTERS # noqa: F401
|
||||
from .tests import TESTS as DEFAULT_TESTS # noqa: F401
|
||||
from .utils import Cycler
|
||||
from .utils import generate_lorem_ipsum
|
||||
from .utils import Joiner
|
||||
from .utils import Namespace
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
# defaults for the parser / lexer
|
||||
BLOCK_START_STRING = "{%"
|
||||
BLOCK_END_STRING = "%}"
|
||||
VARIABLE_START_STRING = "{{"
|
||||
VARIABLE_END_STRING = "}}"
|
||||
COMMENT_START_STRING = "{#"
|
||||
COMMENT_END_STRING = "#}"
|
||||
LINE_STATEMENT_PREFIX: t.Optional[str] = None
|
||||
LINE_COMMENT_PREFIX: t.Optional[str] = None
|
||||
TRIM_BLOCKS = False
|
||||
LSTRIP_BLOCKS = False
|
||||
NEWLINE_SEQUENCE: "te.Literal['\\n', '\\r\\n', '\\r']" = "\n"
|
||||
KEEP_TRAILING_NEWLINE = False
|
||||
|
||||
# default filters, tests and namespace
|
||||
|
||||
DEFAULT_NAMESPACE = {
|
||||
"range": range,
|
||||
"dict": dict,
|
||||
"lipsum": generate_lorem_ipsum,
|
||||
"cycler": Cycler,
|
||||
"joiner": Joiner,
|
||||
"namespace": Namespace,
|
||||
}
|
||||
|
||||
# default policies
|
||||
DEFAULT_POLICIES: t.Dict[str, t.Any] = {
|
||||
"compiler.ascii_str": True,
|
||||
"urlize.rel": "noopener",
|
||||
"urlize.target": None,
|
||||
"urlize.extra_schemes": None,
|
||||
"truncate.leeway": 5,
|
||||
"json.dumps_function": None,
|
||||
"json.dumps_kwargs": {"sort_keys": True},
|
||||
"ext.i18n.trimmed": False,
|
||||
}
|
1672
env/lib/python3.11/site-packages/jinja2/environment.py
vendored
Normal file
1672
env/lib/python3.11/site-packages/jinja2/environment.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
166
env/lib/python3.11/site-packages/jinja2/exceptions.py
vendored
Normal file
166
env/lib/python3.11/site-packages/jinja2/exceptions.py
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
import typing as t
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .runtime import Undefined
|
||||
|
||||
|
||||
class TemplateError(Exception):
|
||||
"""Baseclass for all template errors."""
|
||||
|
||||
def __init__(self, message: t.Optional[str] = None) -> None:
|
||||
super().__init__(message)
|
||||
|
||||
@property
|
||||
def message(self) -> t.Optional[str]:
|
||||
return self.args[0] if self.args else None
|
||||
|
||||
|
||||
class TemplateNotFound(IOError, LookupError, TemplateError):
|
||||
"""Raised if a template does not exist.
|
||||
|
||||
.. versionchanged:: 2.11
|
||||
If the given name is :class:`Undefined` and no message was
|
||||
provided, an :exc:`UndefinedError` is raised.
|
||||
"""
|
||||
|
||||
# Silence the Python warning about message being deprecated since
|
||||
# it's not valid here.
|
||||
message: t.Optional[str] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: t.Optional[t.Union[str, "Undefined"]],
|
||||
message: t.Optional[str] = None,
|
||||
) -> None:
|
||||
IOError.__init__(self, name)
|
||||
|
||||
if message is None:
|
||||
from .runtime import Undefined
|
||||
|
||||
if isinstance(name, Undefined):
|
||||
name._fail_with_undefined_error()
|
||||
|
||||
message = name
|
||||
|
||||
self.message = message
|
||||
self.name = name
|
||||
self.templates = [name]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.message)
|
||||
|
||||
|
||||
class TemplatesNotFound(TemplateNotFound):
|
||||
"""Like :class:`TemplateNotFound` but raised if multiple templates
|
||||
are selected. This is a subclass of :class:`TemplateNotFound`
|
||||
exception, so just catching the base exception will catch both.
|
||||
|
||||
.. versionchanged:: 2.11
|
||||
If a name in the list of names is :class:`Undefined`, a message
|
||||
about it being undefined is shown rather than the empty string.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
names: t.Sequence[t.Union[str, "Undefined"]] = (),
|
||||
message: t.Optional[str] = None,
|
||||
) -> None:
|
||||
if message is None:
|
||||
from .runtime import Undefined
|
||||
|
||||
parts = []
|
||||
|
||||
for name in names:
|
||||
if isinstance(name, Undefined):
|
||||
parts.append(name._undefined_message)
|
||||
else:
|
||||
parts.append(name)
|
||||
|
||||
parts_str = ", ".join(map(str, parts))
|
||||
message = f"none of the templates given were found: {parts_str}"
|
||||
|
||||
super().__init__(names[-1] if names else None, message)
|
||||
self.templates = list(names)
|
||||
|
||||
|
||||
class TemplateSyntaxError(TemplateError):
|
||||
"""Raised to tell the user that there is a problem with the template."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
lineno: int,
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__(message)
|
||||
self.lineno = lineno
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.source: t.Optional[str] = None
|
||||
|
||||
# this is set to True if the debug.translate_syntax_error
|
||||
# function translated the syntax error into a new traceback
|
||||
self.translated = False
|
||||
|
||||
def __str__(self) -> str:
|
||||
# for translated errors we only return the message
|
||||
if self.translated:
|
||||
return t.cast(str, self.message)
|
||||
|
||||
# otherwise attach some stuff
|
||||
location = f"line {self.lineno}"
|
||||
name = self.filename or self.name
|
||||
if name:
|
||||
location = f'File "{name}", {location}'
|
||||
lines = [t.cast(str, self.message), " " + location]
|
||||
|
||||
# if the source is set, add the line to the output
|
||||
if self.source is not None:
|
||||
try:
|
||||
line = self.source.splitlines()[self.lineno - 1]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
lines.append(" " + line.strip())
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def __reduce__(self): # type: ignore
|
||||
# https://bugs.python.org/issue1692335 Exceptions that take
|
||||
# multiple required arguments have problems with pickling.
|
||||
# Without this, raises TypeError: __init__() missing 1 required
|
||||
# positional argument: 'lineno'
|
||||
return self.__class__, (self.message, self.lineno, self.name, self.filename)
|
||||
|
||||
|
||||
class TemplateAssertionError(TemplateSyntaxError):
|
||||
"""Like a template syntax error, but covers cases where something in the
|
||||
template caused an error at compile time that wasn't necessarily caused
|
||||
by a syntax error. However it's a direct subclass of
|
||||
:exc:`TemplateSyntaxError` and has the same attributes.
|
||||
"""
|
||||
|
||||
|
||||
class TemplateRuntimeError(TemplateError):
|
||||
"""A generic runtime error in the template engine. Under some situations
|
||||
Jinja may raise this exception.
|
||||
"""
|
||||
|
||||
|
||||
class UndefinedError(TemplateRuntimeError):
|
||||
"""Raised if a template tries to operate on :class:`Undefined`."""
|
||||
|
||||
|
||||
class SecurityError(TemplateRuntimeError):
|
||||
"""Raised if a template tries to do something insecure if the
|
||||
sandbox is enabled.
|
||||
"""
|
||||
|
||||
|
||||
class FilterArgumentError(TemplateRuntimeError):
|
||||
"""This error is raised if a filter was called with inappropriate
|
||||
arguments
|
||||
"""
|
870
env/lib/python3.11/site-packages/jinja2/ext.py
vendored
Normal file
870
env/lib/python3.11/site-packages/jinja2/ext.py
vendored
Normal file
@ -0,0 +1,870 @@
|
||||
"""Extension API for adding custom tags and behavior."""
|
||||
|
||||
import pprint
|
||||
import re
|
||||
import typing as t
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
from . import defaults
|
||||
from . import nodes
|
||||
from .environment import Environment
|
||||
from .exceptions import TemplateAssertionError
|
||||
from .exceptions import TemplateSyntaxError
|
||||
from .runtime import concat # type: ignore
|
||||
from .runtime import Context
|
||||
from .runtime import Undefined
|
||||
from .utils import import_string
|
||||
from .utils import pass_context
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .lexer import Token
|
||||
from .lexer import TokenStream
|
||||
from .parser import Parser
|
||||
|
||||
class _TranslationsBasic(te.Protocol):
|
||||
def gettext(self, message: str) -> str: ...
|
||||
|
||||
def ngettext(self, singular: str, plural: str, n: int) -> str:
|
||||
pass
|
||||
|
||||
class _TranslationsContext(_TranslationsBasic):
|
||||
def pgettext(self, context: str, message: str) -> str: ...
|
||||
|
||||
def npgettext(
|
||||
self, context: str, singular: str, plural: str, n: int
|
||||
) -> str: ...
|
||||
|
||||
_SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]
|
||||
|
||||
|
||||
# I18N functions available in Jinja templates. If the I18N library
|
||||
# provides ugettext, it will be assigned to gettext.
|
||||
GETTEXT_FUNCTIONS: t.Tuple[str, ...] = (
|
||||
"_",
|
||||
"gettext",
|
||||
"ngettext",
|
||||
"pgettext",
|
||||
"npgettext",
|
||||
)
|
||||
_ws_re = re.compile(r"\s*\n\s*")
|
||||
|
||||
|
||||
class Extension:
|
||||
"""Extensions can be used to add extra functionality to the Jinja template
|
||||
system at the parser level. Custom extensions are bound to an environment
|
||||
but may not store environment specific data on `self`. The reason for
|
||||
this is that an extension can be bound to another environment (for
|
||||
overlays) by creating a copy and reassigning the `environment` attribute.
|
||||
|
||||
As extensions are created by the environment they cannot accept any
|
||||
arguments for configuration. One may want to work around that by using
|
||||
a factory function, but that is not possible as extensions are identified
|
||||
by their import name. The correct way to configure the extension is
|
||||
storing the configuration values on the environment. Because this way the
|
||||
environment ends up acting as central configuration storage the
|
||||
attributes may clash which is why extensions have to ensure that the names
|
||||
they choose for configuration are not too generic. ``prefix`` for example
|
||||
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
|
||||
name as includes the name of the extension (fragment cache).
|
||||
"""
|
||||
|
||||
identifier: t.ClassVar[str]
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
cls.identifier = f"{cls.__module__}.{cls.__name__}"
|
||||
|
||||
#: if this extension parses this is the list of tags it's listening to.
|
||||
tags: t.Set[str] = set()
|
||||
|
||||
#: the priority of that extension. This is especially useful for
|
||||
#: extensions that preprocess values. A lower value means higher
|
||||
#: priority.
|
||||
#:
|
||||
#: .. versionadded:: 2.4
|
||||
priority = 100
|
||||
|
||||
def __init__(self, environment: Environment) -> None:
|
||||
self.environment = environment
|
||||
|
||||
def bind(self, environment: Environment) -> "te.Self":
|
||||
"""Create a copy of this extension bound to another environment."""
|
||||
rv = object.__new__(self.__class__)
|
||||
rv.__dict__.update(self.__dict__)
|
||||
rv.environment = environment
|
||||
return rv
|
||||
|
||||
def preprocess(
|
||||
self, source: str, name: t.Optional[str], filename: t.Optional[str] = None
|
||||
) -> str:
|
||||
"""This method is called before the actual lexing and can be used to
|
||||
preprocess the source. The `filename` is optional. The return value
|
||||
must be the preprocessed source.
|
||||
"""
|
||||
return source
|
||||
|
||||
def filter_stream(
|
||||
self, stream: "TokenStream"
|
||||
) -> t.Union["TokenStream", t.Iterable["Token"]]:
|
||||
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
|
||||
to filter tokens returned. This method has to return an iterable of
|
||||
:class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
|
||||
:class:`~jinja2.lexer.TokenStream`.
|
||||
"""
|
||||
return stream
|
||||
|
||||
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
||||
"""If any of the :attr:`tags` matched this method is called with the
|
||||
parser as first argument. The token the parser stream is pointing at
|
||||
is the name token that matched. This method has to return one or a
|
||||
list of multiple nodes.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def attr(
|
||||
self, name: str, lineno: t.Optional[int] = None
|
||||
) -> nodes.ExtensionAttribute:
|
||||
"""Return an attribute node for the current extension. This is useful
|
||||
to pass constants on extensions to generated template code.
|
||||
|
||||
::
|
||||
|
||||
self.attr('_my_attribute', lineno=lineno)
|
||||
"""
|
||||
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
|
||||
|
||||
def call_method(
|
||||
self,
|
||||
name: str,
|
||||
args: t.Optional[t.List[nodes.Expr]] = None,
|
||||
kwargs: t.Optional[t.List[nodes.Keyword]] = None,
|
||||
dyn_args: t.Optional[nodes.Expr] = None,
|
||||
dyn_kwargs: t.Optional[nodes.Expr] = None,
|
||||
lineno: t.Optional[int] = None,
|
||||
) -> nodes.Call:
|
||||
"""Call a method of the extension. This is a shortcut for
|
||||
:meth:`attr` + :class:`jinja2.nodes.Call`.
|
||||
"""
|
||||
if args is None:
|
||||
args = []
|
||||
if kwargs is None:
|
||||
kwargs = []
|
||||
return nodes.Call(
|
||||
self.attr(name, lineno=lineno),
|
||||
args,
|
||||
kwargs,
|
||||
dyn_args,
|
||||
dyn_kwargs,
|
||||
lineno=lineno,
|
||||
)
|
||||
|
||||
|
||||
@pass_context
|
||||
def _gettext_alias(
|
||||
__context: Context, *args: t.Any, **kwargs: t.Any
|
||||
) -> t.Union[t.Any, Undefined]:
|
||||
return __context.call(__context.resolve("gettext"), *args, **kwargs)
|
||||
|
||||
|
||||
def _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]:
|
||||
@pass_context
|
||||
def gettext(__context: Context, __string: str, **variables: t.Any) -> str:
|
||||
rv = __context.call(func, __string)
|
||||
if __context.eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
# Always treat as a format string, even if there are no
|
||||
# variables. This makes translation strings more consistent
|
||||
# and predictable. This requires escaping
|
||||
return rv % variables # type: ignore
|
||||
|
||||
return gettext
|
||||
|
||||
|
||||
def _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]:
|
||||
@pass_context
|
||||
def ngettext(
|
||||
__context: Context,
|
||||
__singular: str,
|
||||
__plural: str,
|
||||
__num: int,
|
||||
**variables: t.Any,
|
||||
) -> str:
|
||||
variables.setdefault("num", __num)
|
||||
rv = __context.call(func, __singular, __plural, __num)
|
||||
if __context.eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
# Always treat as a format string, see gettext comment above.
|
||||
return rv % variables # type: ignore
|
||||
|
||||
return ngettext
|
||||
|
||||
|
||||
def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]:
|
||||
@pass_context
|
||||
def pgettext(
|
||||
__context: Context, __string_ctx: str, __string: str, **variables: t.Any
|
||||
) -> str:
|
||||
variables.setdefault("context", __string_ctx)
|
||||
rv = __context.call(func, __string_ctx, __string)
|
||||
|
||||
if __context.eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
|
||||
# Always treat as a format string, see gettext comment above.
|
||||
return rv % variables # type: ignore
|
||||
|
||||
return pgettext
|
||||
|
||||
|
||||
def _make_new_npgettext(
|
||||
func: t.Callable[[str, str, str, int], str],
|
||||
) -> t.Callable[..., str]:
|
||||
@pass_context
|
||||
def npgettext(
|
||||
__context: Context,
|
||||
__string_ctx: str,
|
||||
__singular: str,
|
||||
__plural: str,
|
||||
__num: int,
|
||||
**variables: t.Any,
|
||||
) -> str:
|
||||
variables.setdefault("context", __string_ctx)
|
||||
variables.setdefault("num", __num)
|
||||
rv = __context.call(func, __string_ctx, __singular, __plural, __num)
|
||||
|
||||
if __context.eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
|
||||
# Always treat as a format string, see gettext comment above.
|
||||
return rv % variables # type: ignore
|
||||
|
||||
return npgettext
|
||||
|
||||
|
||||
class InternationalizationExtension(Extension):
|
||||
"""This extension adds gettext support to Jinja."""
|
||||
|
||||
tags = {"trans"}
|
||||
|
||||
# TODO: the i18n extension is currently reevaluating values in a few
|
||||
# situations. Take this example:
|
||||
# {% trans count=something() %}{{ count }} foo{% pluralize
|
||||
# %}{{ count }} fooss{% endtrans %}
|
||||
# something is called twice here. One time for the gettext value and
|
||||
# the other time for the n-parameter of the ngettext function.
|
||||
|
||||
def __init__(self, environment: Environment) -> None:
|
||||
super().__init__(environment)
|
||||
environment.globals["_"] = _gettext_alias
|
||||
environment.extend(
|
||||
install_gettext_translations=self._install,
|
||||
install_null_translations=self._install_null,
|
||||
install_gettext_callables=self._install_callables,
|
||||
uninstall_gettext_translations=self._uninstall,
|
||||
extract_translations=self._extract,
|
||||
newstyle_gettext=False,
|
||||
)
|
||||
|
||||
def _install(
|
||||
self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None
|
||||
) -> None:
|
||||
# ugettext and ungettext are preferred in case the I18N library
|
||||
# is providing compatibility with older Python versions.
|
||||
gettext = getattr(translations, "ugettext", None)
|
||||
if gettext is None:
|
||||
gettext = translations.gettext
|
||||
ngettext = getattr(translations, "ungettext", None)
|
||||
if ngettext is None:
|
||||
ngettext = translations.ngettext
|
||||
|
||||
pgettext = getattr(translations, "pgettext", None)
|
||||
npgettext = getattr(translations, "npgettext", None)
|
||||
self._install_callables(
|
||||
gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
|
||||
)
|
||||
|
||||
def _install_null(self, newstyle: t.Optional[bool] = None) -> None:
|
||||
import gettext
|
||||
|
||||
translations = gettext.NullTranslations()
|
||||
|
||||
if hasattr(translations, "pgettext"):
|
||||
# Python < 3.8
|
||||
pgettext = translations.pgettext
|
||||
else:
|
||||
|
||||
def pgettext(c: str, s: str) -> str: # type: ignore[misc]
|
||||
return s
|
||||
|
||||
if hasattr(translations, "npgettext"):
|
||||
npgettext = translations.npgettext
|
||||
else:
|
||||
|
||||
def npgettext(c: str, s: str, p: str, n: int) -> str: # type: ignore[misc]
|
||||
return s if n == 1 else p
|
||||
|
||||
self._install_callables(
|
||||
gettext=translations.gettext,
|
||||
ngettext=translations.ngettext,
|
||||
newstyle=newstyle,
|
||||
pgettext=pgettext,
|
||||
npgettext=npgettext,
|
||||
)
|
||||
|
||||
def _install_callables(
|
||||
self,
|
||||
gettext: t.Callable[[str], str],
|
||||
ngettext: t.Callable[[str, str, int], str],
|
||||
newstyle: t.Optional[bool] = None,
|
||||
pgettext: t.Optional[t.Callable[[str, str], str]] = None,
|
||||
npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None,
|
||||
) -> None:
|
||||
if newstyle is not None:
|
||||
self.environment.newstyle_gettext = newstyle # type: ignore
|
||||
if self.environment.newstyle_gettext: # type: ignore
|
||||
gettext = _make_new_gettext(gettext)
|
||||
ngettext = _make_new_ngettext(ngettext)
|
||||
|
||||
if pgettext is not None:
|
||||
pgettext = _make_new_pgettext(pgettext)
|
||||
|
||||
if npgettext is not None:
|
||||
npgettext = _make_new_npgettext(npgettext)
|
||||
|
||||
self.environment.globals.update(
|
||||
gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext
|
||||
)
|
||||
|
||||
def _uninstall(self, translations: "_SupportedTranslations") -> None:
|
||||
for key in ("gettext", "ngettext", "pgettext", "npgettext"):
|
||||
self.environment.globals.pop(key, None)
|
||||
|
||||
def _extract(
|
||||
self,
|
||||
source: t.Union[str, nodes.Template],
|
||||
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
|
||||
) -> t.Iterator[
|
||||
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
|
||||
]:
|
||||
if isinstance(source, str):
|
||||
source = self.environment.parse(source)
|
||||
return extract_from_ast(source, gettext_functions)
|
||||
|
||||
def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
|
||||
"""Parse a translatable tag."""
|
||||
lineno = next(parser.stream).lineno
|
||||
|
||||
context = None
|
||||
context_token = parser.stream.next_if("string")
|
||||
|
||||
if context_token is not None:
|
||||
context = context_token.value
|
||||
|
||||
# find all the variables referenced. Additionally a variable can be
|
||||
# defined in the body of the trans block too, but this is checked at
|
||||
# a later state.
|
||||
plural_expr: t.Optional[nodes.Expr] = None
|
||||
plural_expr_assignment: t.Optional[nodes.Assign] = None
|
||||
num_called_num = False
|
||||
variables: t.Dict[str, nodes.Expr] = {}
|
||||
trimmed = None
|
||||
while parser.stream.current.type != "block_end":
|
||||
if variables:
|
||||
parser.stream.expect("comma")
|
||||
|
||||
# skip colon for python compatibility
|
||||
if parser.stream.skip_if("colon"):
|
||||
break
|
||||
|
||||
token = parser.stream.expect("name")
|
||||
if token.value in variables:
|
||||
parser.fail(
|
||||
f"translatable variable {token.value!r} defined twice.",
|
||||
token.lineno,
|
||||
exc=TemplateAssertionError,
|
||||
)
|
||||
|
||||
# expressions
|
||||
if parser.stream.current.type == "assign":
|
||||
next(parser.stream)
|
||||
variables[token.value] = var = parser.parse_expression()
|
||||
elif trimmed is None and token.value in ("trimmed", "notrimmed"):
|
||||
trimmed = token.value == "trimmed"
|
||||
continue
|
||||
else:
|
||||
variables[token.value] = var = nodes.Name(token.value, "load")
|
||||
|
||||
if plural_expr is None:
|
||||
if isinstance(var, nodes.Call):
|
||||
plural_expr = nodes.Name("_trans", "load")
|
||||
variables[token.value] = plural_expr
|
||||
plural_expr_assignment = nodes.Assign(
|
||||
nodes.Name("_trans", "store"), var
|
||||
)
|
||||
else:
|
||||
plural_expr = var
|
||||
num_called_num = token.value == "num"
|
||||
|
||||
parser.stream.expect("block_end")
|
||||
|
||||
plural = None
|
||||
have_plural = False
|
||||
referenced = set()
|
||||
|
||||
# now parse until endtrans or pluralize
|
||||
singular_names, singular = self._parse_block(parser, True)
|
||||
if singular_names:
|
||||
referenced.update(singular_names)
|
||||
if plural_expr is None:
|
||||
plural_expr = nodes.Name(singular_names[0], "load")
|
||||
num_called_num = singular_names[0] == "num"
|
||||
|
||||
# if we have a pluralize block, we parse that too
|
||||
if parser.stream.current.test("name:pluralize"):
|
||||
have_plural = True
|
||||
next(parser.stream)
|
||||
if parser.stream.current.type != "block_end":
|
||||
token = parser.stream.expect("name")
|
||||
if token.value not in variables:
|
||||
parser.fail(
|
||||
f"unknown variable {token.value!r} for pluralization",
|
||||
token.lineno,
|
||||
exc=TemplateAssertionError,
|
||||
)
|
||||
plural_expr = variables[token.value]
|
||||
num_called_num = token.value == "num"
|
||||
parser.stream.expect("block_end")
|
||||
plural_names, plural = self._parse_block(parser, False)
|
||||
next(parser.stream)
|
||||
referenced.update(plural_names)
|
||||
else:
|
||||
next(parser.stream)
|
||||
|
||||
# register free names as simple name expressions
|
||||
for name in referenced:
|
||||
if name not in variables:
|
||||
variables[name] = nodes.Name(name, "load")
|
||||
|
||||
if not have_plural:
|
||||
plural_expr = None
|
||||
elif plural_expr is None:
|
||||
parser.fail("pluralize without variables", lineno)
|
||||
|
||||
if trimmed is None:
|
||||
trimmed = self.environment.policies["ext.i18n.trimmed"]
|
||||
if trimmed:
|
||||
singular = self._trim_whitespace(singular)
|
||||
if plural:
|
||||
plural = self._trim_whitespace(plural)
|
||||
|
||||
node = self._make_node(
|
||||
singular,
|
||||
plural,
|
||||
context,
|
||||
variables,
|
||||
plural_expr,
|
||||
bool(referenced),
|
||||
num_called_num and have_plural,
|
||||
)
|
||||
node.set_lineno(lineno)
|
||||
if plural_expr_assignment is not None:
|
||||
return [plural_expr_assignment, node]
|
||||
else:
|
||||
return node
|
||||
|
||||
def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str:
|
||||
return _ws_re.sub(" ", string.strip())
|
||||
|
||||
def _parse_block(
|
||||
self, parser: "Parser", allow_pluralize: bool
|
||||
) -> t.Tuple[t.List[str], str]:
|
||||
"""Parse until the next block tag with a given name."""
|
||||
referenced = []
|
||||
buf = []
|
||||
|
||||
while True:
|
||||
if parser.stream.current.type == "data":
|
||||
buf.append(parser.stream.current.value.replace("%", "%%"))
|
||||
next(parser.stream)
|
||||
elif parser.stream.current.type == "variable_begin":
|
||||
next(parser.stream)
|
||||
name = parser.stream.expect("name").value
|
||||
referenced.append(name)
|
||||
buf.append(f"%({name})s")
|
||||
parser.stream.expect("variable_end")
|
||||
elif parser.stream.current.type == "block_begin":
|
||||
next(parser.stream)
|
||||
block_name = (
|
||||
parser.stream.current.value
|
||||
if parser.stream.current.type == "name"
|
||||
else None
|
||||
)
|
||||
if block_name == "endtrans":
|
||||
break
|
||||
elif block_name == "pluralize":
|
||||
if allow_pluralize:
|
||||
break
|
||||
parser.fail(
|
||||
"a translatable section can have only one pluralize section"
|
||||
)
|
||||
elif block_name == "trans":
|
||||
parser.fail(
|
||||
"trans blocks can't be nested; did you mean `endtrans`?"
|
||||
)
|
||||
parser.fail(
|
||||
f"control structures in translatable sections are not allowed; "
|
||||
f"saw `{block_name}`"
|
||||
)
|
||||
elif parser.stream.eos:
|
||||
parser.fail("unclosed translation block")
|
||||
else:
|
||||
raise RuntimeError("internal parser error")
|
||||
|
||||
return referenced, concat(buf)
|
||||
|
||||
def _make_node(
|
||||
self,
|
||||
singular: str,
|
||||
plural: t.Optional[str],
|
||||
context: t.Optional[str],
|
||||
variables: t.Dict[str, nodes.Expr],
|
||||
plural_expr: t.Optional[nodes.Expr],
|
||||
vars_referenced: bool,
|
||||
num_called_num: bool,
|
||||
) -> nodes.Output:
|
||||
"""Generates a useful node from the data provided."""
|
||||
newstyle = self.environment.newstyle_gettext # type: ignore
|
||||
node: nodes.Expr
|
||||
|
||||
# no variables referenced? no need to escape for old style
|
||||
# gettext invocations only if there are vars.
|
||||
if not vars_referenced and not newstyle:
|
||||
singular = singular.replace("%%", "%")
|
||||
if plural:
|
||||
plural = plural.replace("%%", "%")
|
||||
|
||||
func_name = "gettext"
|
||||
func_args: t.List[nodes.Expr] = [nodes.Const(singular)]
|
||||
|
||||
if context is not None:
|
||||
func_args.insert(0, nodes.Const(context))
|
||||
func_name = f"p{func_name}"
|
||||
|
||||
if plural_expr is not None:
|
||||
func_name = f"n{func_name}"
|
||||
func_args.extend((nodes.Const(plural), plural_expr))
|
||||
|
||||
node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None)
|
||||
|
||||
# in case newstyle gettext is used, the method is powerful
|
||||
# enough to handle the variable expansion and autoescape
|
||||
# handling itself
|
||||
if newstyle:
|
||||
for key, value in variables.items():
|
||||
# the function adds that later anyways in case num was
|
||||
# called num, so just skip it.
|
||||
if num_called_num and key == "num":
|
||||
continue
|
||||
node.kwargs.append(nodes.Keyword(key, value))
|
||||
|
||||
# otherwise do that here
|
||||
else:
|
||||
# mark the return value as safe if we are in an
|
||||
# environment with autoescaping turned on
|
||||
node = nodes.MarkSafeIfAutoescape(node)
|
||||
if variables:
|
||||
node = nodes.Mod(
|
||||
node,
|
||||
nodes.Dict(
|
||||
[
|
||||
nodes.Pair(nodes.Const(key), value)
|
||||
for key, value in variables.items()
|
||||
]
|
||||
),
|
||||
)
|
||||
return nodes.Output([node])
|
||||
|
||||
|
||||
class ExprStmtExtension(Extension):
|
||||
"""Adds a `do` tag to Jinja that works like the print statement just
|
||||
that it doesn't print the return value.
|
||||
"""
|
||||
|
||||
tags = {"do"}
|
||||
|
||||
def parse(self, parser: "Parser") -> nodes.ExprStmt:
|
||||
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
|
||||
node.node = parser.parse_tuple()
|
||||
return node
|
||||
|
||||
|
||||
class LoopControlExtension(Extension):
|
||||
"""Adds break and continue to the template engine."""
|
||||
|
||||
tags = {"break", "continue"}
|
||||
|
||||
def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]:
|
||||
token = next(parser.stream)
|
||||
if token.value == "break":
|
||||
return nodes.Break(lineno=token.lineno)
|
||||
return nodes.Continue(lineno=token.lineno)
|
||||
|
||||
|
||||
class DebugExtension(Extension):
|
||||
"""A ``{% debug %}`` tag that dumps the available variables,
|
||||
filters, and tests.
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
<pre>{% debug %}</pre>
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
{'context': {'cycler': <class 'jinja2.utils.Cycler'>,
|
||||
...,
|
||||
'namespace': <class 'jinja2.utils.Namespace'>},
|
||||
'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
|
||||
..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
|
||||
'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
|
||||
..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}
|
||||
|
||||
.. versionadded:: 2.11.0
|
||||
"""
|
||||
|
||||
tags = {"debug"}
|
||||
|
||||
def parse(self, parser: "Parser") -> nodes.Output:
|
||||
lineno = parser.stream.expect("name:debug").lineno
|
||||
context = nodes.ContextReference()
|
||||
result = self.call_method("_render", [context], lineno=lineno)
|
||||
return nodes.Output([result], lineno=lineno)
|
||||
|
||||
def _render(self, context: Context) -> str:
|
||||
result = {
|
||||
"context": context.get_all(),
|
||||
"filters": sorted(self.environment.filters.keys()),
|
||||
"tests": sorted(self.environment.tests.keys()),
|
||||
}
|
||||
|
||||
# Set the depth since the intent is to show the top few names.
|
||||
return pprint.pformat(result, depth=3, compact=True)
|
||||
|
||||
|
||||
def extract_from_ast(
|
||||
ast: nodes.Template,
|
||||
gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
|
||||
babel_style: bool = True,
|
||||
) -> t.Iterator[
|
||||
t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
|
||||
]:
|
||||
"""Extract localizable strings from the given template node. Per
|
||||
default this function returns matches in babel style that means non string
|
||||
parameters as well as keyword arguments are returned as `None`. This
|
||||
allows Babel to figure out what you really meant if you are using
|
||||
gettext functions that allow keyword arguments for placeholder expansion.
|
||||
If you don't want that behavior set the `babel_style` parameter to `False`
|
||||
which causes only strings to be returned and parameters are always stored
|
||||
in tuples. As a consequence invalid gettext calls (calls without a single
|
||||
string parameter or string parameters after non-string parameters) are
|
||||
skipped.
|
||||
|
||||
This example explains the behavior:
|
||||
|
||||
>>> from jinja2 import Environment
|
||||
>>> env = Environment()
|
||||
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
|
||||
>>> list(extract_from_ast(node))
|
||||
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
|
||||
>>> list(extract_from_ast(node, babel_style=False))
|
||||
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
|
||||
|
||||
For every string found this function yields a ``(lineno, function,
|
||||
message)`` tuple, where:
|
||||
|
||||
* ``lineno`` is the number of the line on which the string was found,
|
||||
* ``function`` is the name of the ``gettext`` function used (if the
|
||||
string was extracted from embedded Python code), and
|
||||
* ``message`` is the string, or a tuple of strings for functions
|
||||
with multiple string arguments.
|
||||
|
||||
This extraction function operates on the AST and is because of that unable
|
||||
to extract any comments. For comment support you have to use the babel
|
||||
extraction interface or extract comments yourself.
|
||||
"""
|
||||
out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]
|
||||
|
||||
for node in ast.find_all(nodes.Call):
|
||||
if (
|
||||
not isinstance(node.node, nodes.Name)
|
||||
or node.node.name not in gettext_functions
|
||||
):
|
||||
continue
|
||||
|
||||
strings: t.List[t.Optional[str]] = []
|
||||
|
||||
for arg in node.args:
|
||||
if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
|
||||
strings.append(arg.value)
|
||||
else:
|
||||
strings.append(None)
|
||||
|
||||
for _ in node.kwargs:
|
||||
strings.append(None)
|
||||
if node.dyn_args is not None:
|
||||
strings.append(None)
|
||||
if node.dyn_kwargs is not None:
|
||||
strings.append(None)
|
||||
|
||||
if not babel_style:
|
||||
out = tuple(x for x in strings if x is not None)
|
||||
|
||||
if not out:
|
||||
continue
|
||||
else:
|
||||
if len(strings) == 1:
|
||||
out = strings[0]
|
||||
else:
|
||||
out = tuple(strings)
|
||||
|
||||
yield node.lineno, node.node.name, out
|
||||
|
||||
|
||||
class _CommentFinder:
|
||||
"""Helper class to find comments in a token stream. Can only
|
||||
find comments for gettext calls forwards. Once the comment
|
||||
from line 4 is found, a comment for line 1 will not return a
|
||||
usable value.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str]
|
||||
) -> None:
|
||||
self.tokens = tokens
|
||||
self.comment_tags = comment_tags
|
||||
self.offset = 0
|
||||
self.last_lineno = 0
|
||||
|
||||
def find_backwards(self, offset: int) -> t.List[str]:
|
||||
try:
|
||||
for _, token_type, token_value in reversed(
|
||||
self.tokens[self.offset : offset]
|
||||
):
|
||||
if token_type in ("comment", "linecomment"):
|
||||
try:
|
||||
prefix, comment = token_value.split(None, 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if prefix in self.comment_tags:
|
||||
return [comment.rstrip()]
|
||||
return []
|
||||
finally:
|
||||
self.offset = offset
|
||||
|
||||
def find_comments(self, lineno: int) -> t.List[str]:
|
||||
if not self.comment_tags or self.last_lineno > lineno:
|
||||
return []
|
||||
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
|
||||
if token_lineno > lineno:
|
||||
return self.find_backwards(self.offset + idx)
|
||||
return self.find_backwards(len(self.tokens))
|
||||
|
||||
|
||||
def babel_extract(
|
||||
fileobj: t.BinaryIO,
|
||||
keywords: t.Sequence[str],
|
||||
comment_tags: t.Sequence[str],
|
||||
options: t.Dict[str, t.Any],
|
||||
) -> t.Iterator[
|
||||
t.Tuple[
|
||||
int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str]
|
||||
]
|
||||
]:
|
||||
"""Babel extraction method for Jinja templates.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Basic support for translation comments was added. If `comment_tags`
|
||||
is now set to a list of keywords for extraction, the extractor will
|
||||
try to find the best preceding comment that begins with one of the
|
||||
keywords. For best results, make sure to not have more than one
|
||||
gettext call in one line of code and the matching comment in the
|
||||
same line or the line before.
|
||||
|
||||
.. versionchanged:: 2.5.1
|
||||
The `newstyle_gettext` flag can be set to `True` to enable newstyle
|
||||
gettext calls.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
A `silent` option can now be provided. If set to `False` template
|
||||
syntax errors are propagated instead of being ignored.
|
||||
|
||||
:param fileobj: the file-like object the messages should be extracted from
|
||||
:param keywords: a list of keywords (i.e. function names) that should be
|
||||
recognized as translation functions
|
||||
:param comment_tags: a list of translator tags to search for and include
|
||||
in the results.
|
||||
:param options: a dictionary of additional options (optional)
|
||||
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
|
||||
(comments will be empty currently)
|
||||
"""
|
||||
extensions: t.Dict[t.Type[Extension], None] = {}
|
||||
|
||||
for extension_name in options.get("extensions", "").split(","):
|
||||
extension_name = extension_name.strip()
|
||||
|
||||
if not extension_name:
|
||||
continue
|
||||
|
||||
extensions[import_string(extension_name)] = None
|
||||
|
||||
if InternationalizationExtension not in extensions:
|
||||
extensions[InternationalizationExtension] = None
|
||||
|
||||
def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool:
|
||||
return options.get(key, str(default)).lower() in {"1", "on", "yes", "true"}
|
||||
|
||||
silent = getbool(options, "silent", True)
|
||||
environment = Environment(
|
||||
options.get("block_start_string", defaults.BLOCK_START_STRING),
|
||||
options.get("block_end_string", defaults.BLOCK_END_STRING),
|
||||
options.get("variable_start_string", defaults.VARIABLE_START_STRING),
|
||||
options.get("variable_end_string", defaults.VARIABLE_END_STRING),
|
||||
options.get("comment_start_string", defaults.COMMENT_START_STRING),
|
||||
options.get("comment_end_string", defaults.COMMENT_END_STRING),
|
||||
options.get("line_statement_prefix") or defaults.LINE_STATEMENT_PREFIX,
|
||||
options.get("line_comment_prefix") or defaults.LINE_COMMENT_PREFIX,
|
||||
getbool(options, "trim_blocks", defaults.TRIM_BLOCKS),
|
||||
getbool(options, "lstrip_blocks", defaults.LSTRIP_BLOCKS),
|
||||
defaults.NEWLINE_SEQUENCE,
|
||||
getbool(options, "keep_trailing_newline", defaults.KEEP_TRAILING_NEWLINE),
|
||||
tuple(extensions),
|
||||
cache_size=0,
|
||||
auto_reload=False,
|
||||
)
|
||||
|
||||
if getbool(options, "trimmed"):
|
||||
environment.policies["ext.i18n.trimmed"] = True
|
||||
if getbool(options, "newstyle_gettext"):
|
||||
environment.newstyle_gettext = True # type: ignore
|
||||
|
||||
source = fileobj.read().decode(options.get("encoding", "utf-8"))
|
||||
try:
|
||||
node = environment.parse(source)
|
||||
tokens = list(environment.lex(environment.preprocess(source)))
|
||||
except TemplateSyntaxError:
|
||||
if not silent:
|
||||
raise
|
||||
# skip templates with syntax errors
|
||||
return
|
||||
|
||||
finder = _CommentFinder(tokens, comment_tags)
|
||||
for lineno, func, message in extract_from_ast(node, keywords):
|
||||
yield lineno, func, message, finder.find_comments(lineno)
|
||||
|
||||
|
||||
#: nicer import names
|
||||
i18n = InternationalizationExtension
|
||||
do = ExprStmtExtension
|
||||
loopcontrols = LoopControlExtension
|
||||
debug = DebugExtension
|
1878
env/lib/python3.11/site-packages/jinja2/filters.py
vendored
Normal file
1878
env/lib/python3.11/site-packages/jinja2/filters.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
318
env/lib/python3.11/site-packages/jinja2/idtracking.py
vendored
Normal file
318
env/lib/python3.11/site-packages/jinja2/idtracking.py
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
import typing as t
|
||||
|
||||
from . import nodes
|
||||
from .visitor import NodeVisitor
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
VAR_LOAD_PARAMETER = "param"
|
||||
VAR_LOAD_RESOLVE = "resolve"
|
||||
VAR_LOAD_ALIAS = "alias"
|
||||
VAR_LOAD_UNDEFINED = "undefined"
|
||||
|
||||
|
||||
def find_symbols(
|
||||
nodes: t.Iterable[nodes.Node], parent_symbols: t.Optional["Symbols"] = None
|
||||
) -> "Symbols":
|
||||
sym = Symbols(parent=parent_symbols)
|
||||
visitor = FrameSymbolVisitor(sym)
|
||||
for node in nodes:
|
||||
visitor.visit(node)
|
||||
return sym
|
||||
|
||||
|
||||
def symbols_for_node(
|
||||
node: nodes.Node, parent_symbols: t.Optional["Symbols"] = None
|
||||
) -> "Symbols":
|
||||
sym = Symbols(parent=parent_symbols)
|
||||
sym.analyze_node(node)
|
||||
return sym
|
||||
|
||||
|
||||
class Symbols:
|
||||
def __init__(
|
||||
self, parent: t.Optional["Symbols"] = None, level: t.Optional[int] = None
|
||||
) -> None:
|
||||
if level is None:
|
||||
if parent is None:
|
||||
level = 0
|
||||
else:
|
||||
level = parent.level + 1
|
||||
|
||||
self.level: int = level
|
||||
self.parent = parent
|
||||
self.refs: t.Dict[str, str] = {}
|
||||
self.loads: t.Dict[str, t.Any] = {}
|
||||
self.stores: t.Set[str] = set()
|
||||
|
||||
def analyze_node(self, node: nodes.Node, **kwargs: t.Any) -> None:
|
||||
visitor = RootVisitor(self)
|
||||
visitor.visit(node, **kwargs)
|
||||
|
||||
def _define_ref(
|
||||
self, name: str, load: t.Optional[t.Tuple[str, t.Optional[str]]] = None
|
||||
) -> str:
|
||||
ident = f"l_{self.level}_{name}"
|
||||
self.refs[name] = ident
|
||||
if load is not None:
|
||||
self.loads[ident] = load
|
||||
return ident
|
||||
|
||||
def find_load(self, target: str) -> t.Optional[t.Any]:
|
||||
if target in self.loads:
|
||||
return self.loads[target]
|
||||
|
||||
if self.parent is not None:
|
||||
return self.parent.find_load(target)
|
||||
|
||||
return None
|
||||
|
||||
def find_ref(self, name: str) -> t.Optional[str]:
|
||||
if name in self.refs:
|
||||
return self.refs[name]
|
||||
|
||||
if self.parent is not None:
|
||||
return self.parent.find_ref(name)
|
||||
|
||||
return None
|
||||
|
||||
def ref(self, name: str) -> str:
|
||||
rv = self.find_ref(name)
|
||||
if rv is None:
|
||||
raise AssertionError(
|
||||
"Tried to resolve a name to a reference that was"
|
||||
f" unknown to the frame ({name!r})"
|
||||
)
|
||||
return rv
|
||||
|
||||
def copy(self) -> "te.Self":
|
||||
rv = object.__new__(self.__class__)
|
||||
rv.__dict__.update(self.__dict__)
|
||||
rv.refs = self.refs.copy()
|
||||
rv.loads = self.loads.copy()
|
||||
rv.stores = self.stores.copy()
|
||||
return rv
|
||||
|
||||
def store(self, name: str) -> None:
|
||||
self.stores.add(name)
|
||||
|
||||
# If we have not see the name referenced yet, we need to figure
|
||||
# out what to set it to.
|
||||
if name not in self.refs:
|
||||
# If there is a parent scope we check if the name has a
|
||||
# reference there. If it does it means we might have to alias
|
||||
# to a variable there.
|
||||
if self.parent is not None:
|
||||
outer_ref = self.parent.find_ref(name)
|
||||
if outer_ref is not None:
|
||||
self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
|
||||
return
|
||||
|
||||
# Otherwise we can just set it to undefined.
|
||||
self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
|
||||
|
||||
def declare_parameter(self, name: str) -> str:
|
||||
self.stores.add(name)
|
||||
return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
|
||||
|
||||
def load(self, name: str) -> None:
|
||||
if self.find_ref(name) is None:
|
||||
self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
|
||||
|
||||
def branch_update(self, branch_symbols: t.Sequence["Symbols"]) -> None:
|
||||
stores: t.Set[str] = set()
|
||||
|
||||
for branch in branch_symbols:
|
||||
stores.update(branch.stores)
|
||||
|
||||
stores.difference_update(self.stores)
|
||||
|
||||
for sym in branch_symbols:
|
||||
self.refs.update(sym.refs)
|
||||
self.loads.update(sym.loads)
|
||||
self.stores.update(sym.stores)
|
||||
|
||||
for name in stores:
|
||||
target = self.find_ref(name)
|
||||
assert target is not None, "should not happen"
|
||||
|
||||
if self.parent is not None:
|
||||
outer_target = self.parent.find_ref(name)
|
||||
if outer_target is not None:
|
||||
self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
|
||||
continue
|
||||
self.loads[target] = (VAR_LOAD_RESOLVE, name)
|
||||
|
||||
def dump_stores(self) -> t.Dict[str, str]:
|
||||
rv: t.Dict[str, str] = {}
|
||||
node: t.Optional[Symbols] = self
|
||||
|
||||
while node is not None:
|
||||
for name in sorted(node.stores):
|
||||
if name not in rv:
|
||||
rv[name] = self.find_ref(name) # type: ignore
|
||||
|
||||
node = node.parent
|
||||
|
||||
return rv
|
||||
|
||||
def dump_param_targets(self) -> t.Set[str]:
|
||||
rv = set()
|
||||
node: t.Optional[Symbols] = self
|
||||
|
||||
while node is not None:
|
||||
for target, (instr, _) in self.loads.items():
|
||||
if instr == VAR_LOAD_PARAMETER:
|
||||
rv.add(target)
|
||||
|
||||
node = node.parent
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class RootVisitor(NodeVisitor):
|
||||
def __init__(self, symbols: "Symbols") -> None:
|
||||
self.sym_visitor = FrameSymbolVisitor(symbols)
|
||||
|
||||
def _simple_visit(self, node: nodes.Node, **kwargs: t.Any) -> None:
|
||||
for child in node.iter_child_nodes():
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
visit_Template = _simple_visit
|
||||
visit_Block = _simple_visit
|
||||
visit_Macro = _simple_visit
|
||||
visit_FilterBlock = _simple_visit
|
||||
visit_Scope = _simple_visit
|
||||
visit_If = _simple_visit
|
||||
visit_ScopedEvalContextModifier = _simple_visit
|
||||
|
||||
def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
|
||||
for child in node.body:
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
|
||||
for child in node.iter_child_nodes(exclude=("call",)):
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
|
||||
for child in node.body:
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def visit_For(
|
||||
self, node: nodes.For, for_branch: str = "body", **kwargs: t.Any
|
||||
) -> None:
|
||||
if for_branch == "body":
|
||||
self.sym_visitor.visit(node.target, store_as_param=True)
|
||||
branch = node.body
|
||||
elif for_branch == "else":
|
||||
branch = node.else_
|
||||
elif for_branch == "test":
|
||||
self.sym_visitor.visit(node.target, store_as_param=True)
|
||||
if node.test is not None:
|
||||
self.sym_visitor.visit(node.test)
|
||||
return
|
||||
else:
|
||||
raise RuntimeError("Unknown for branch")
|
||||
|
||||
if branch:
|
||||
for item in branch:
|
||||
self.sym_visitor.visit(item)
|
||||
|
||||
def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
|
||||
for target in node.targets:
|
||||
self.sym_visitor.visit(target)
|
||||
for child in node.body:
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def generic_visit(self, node: nodes.Node, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
raise NotImplementedError(f"Cannot find symbols for {type(node).__name__!r}")
|
||||
|
||||
|
||||
class FrameSymbolVisitor(NodeVisitor):
|
||||
"""A visitor for `Frame.inspect`."""
|
||||
|
||||
def __init__(self, symbols: "Symbols") -> None:
|
||||
self.symbols = symbols
|
||||
|
||||
def visit_Name(
|
||||
self, node: nodes.Name, store_as_param: bool = False, **kwargs: t.Any
|
||||
) -> None:
|
||||
"""All assignments to names go through this function."""
|
||||
if store_as_param or node.ctx == "param":
|
||||
self.symbols.declare_parameter(node.name)
|
||||
elif node.ctx == "store":
|
||||
self.symbols.store(node.name)
|
||||
elif node.ctx == "load":
|
||||
self.symbols.load(node.name)
|
||||
|
||||
def visit_NSRef(self, node: nodes.NSRef, **kwargs: t.Any) -> None:
|
||||
self.symbols.load(node.name)
|
||||
|
||||
def visit_If(self, node: nodes.If, **kwargs: t.Any) -> None:
|
||||
self.visit(node.test, **kwargs)
|
||||
original_symbols = self.symbols
|
||||
|
||||
def inner_visit(nodes: t.Iterable[nodes.Node]) -> "Symbols":
|
||||
self.symbols = rv = original_symbols.copy()
|
||||
|
||||
for subnode in nodes:
|
||||
self.visit(subnode, **kwargs)
|
||||
|
||||
self.symbols = original_symbols
|
||||
return rv
|
||||
|
||||
body_symbols = inner_visit(node.body)
|
||||
elif_symbols = inner_visit(node.elif_)
|
||||
else_symbols = inner_visit(node.else_ or ())
|
||||
self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
|
||||
|
||||
def visit_Macro(self, node: nodes.Macro, **kwargs: t.Any) -> None:
|
||||
self.symbols.store(node.name)
|
||||
|
||||
def visit_Import(self, node: nodes.Import, **kwargs: t.Any) -> None:
|
||||
self.generic_visit(node, **kwargs)
|
||||
self.symbols.store(node.target)
|
||||
|
||||
def visit_FromImport(self, node: nodes.FromImport, **kwargs: t.Any) -> None:
|
||||
self.generic_visit(node, **kwargs)
|
||||
|
||||
for name in node.names:
|
||||
if isinstance(name, tuple):
|
||||
self.symbols.store(name[1])
|
||||
else:
|
||||
self.symbols.store(name)
|
||||
|
||||
def visit_Assign(self, node: nodes.Assign, **kwargs: t.Any) -> None:
|
||||
"""Visit assignments in the correct order."""
|
||||
self.visit(node.node, **kwargs)
|
||||
self.visit(node.target, **kwargs)
|
||||
|
||||
def visit_For(self, node: nodes.For, **kwargs: t.Any) -> None:
|
||||
"""Visiting stops at for blocks. However the block sequence
|
||||
is visited as part of the outer scope.
|
||||
"""
|
||||
self.visit(node.iter, **kwargs)
|
||||
|
||||
def visit_CallBlock(self, node: nodes.CallBlock, **kwargs: t.Any) -> None:
|
||||
self.visit(node.call, **kwargs)
|
||||
|
||||
def visit_FilterBlock(self, node: nodes.FilterBlock, **kwargs: t.Any) -> None:
|
||||
self.visit(node.filter, **kwargs)
|
||||
|
||||
def visit_With(self, node: nodes.With, **kwargs: t.Any) -> None:
|
||||
for target in node.values:
|
||||
self.visit(target)
|
||||
|
||||
def visit_AssignBlock(self, node: nodes.AssignBlock, **kwargs: t.Any) -> None:
|
||||
"""Stop visiting at block assigns."""
|
||||
self.visit(node.target, **kwargs)
|
||||
|
||||
def visit_Scope(self, node: nodes.Scope, **kwargs: t.Any) -> None:
|
||||
"""Stop visiting at scopes."""
|
||||
|
||||
def visit_Block(self, node: nodes.Block, **kwargs: t.Any) -> None:
|
||||
"""Stop visiting at blocks."""
|
||||
|
||||
def visit_OverlayScope(self, node: nodes.OverlayScope, **kwargs: t.Any) -> None:
|
||||
"""Do not visit into overlay scopes."""
|
868
env/lib/python3.11/site-packages/jinja2/lexer.py
vendored
Normal file
868
env/lib/python3.11/site-packages/jinja2/lexer.py
vendored
Normal file
@ -0,0 +1,868 @@
|
||||
"""Implements a Jinja / Python combination lexer. The ``Lexer`` class
|
||||
is used to do some preprocessing. It filters out invalid operators like
|
||||
the bitshift operators we don't allow in templates. It separates
|
||||
template code and python code in expressions.
|
||||
"""
|
||||
|
||||
import re
|
||||
import typing as t
|
||||
from ast import literal_eval
|
||||
from collections import deque
|
||||
from sys import intern
|
||||
|
||||
from ._identifier import pattern as name_re
|
||||
from .exceptions import TemplateSyntaxError
|
||||
from .utils import LRUCache
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
from .environment import Environment
|
||||
|
||||
# cache for the lexers. Exists in order to be able to have multiple
|
||||
# environments with the same lexer
|
||||
_lexer_cache: t.MutableMapping[t.Tuple, "Lexer"] = LRUCache(50) # type: ignore
|
||||
|
||||
# static regular expressions
|
||||
whitespace_re = re.compile(r"\s+")
|
||||
newline_re = re.compile(r"(\r\n|\r|\n)")
|
||||
string_re = re.compile(
|
||||
r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S
|
||||
)
|
||||
integer_re = re.compile(
|
||||
r"""
|
||||
(
|
||||
0b(_?[0-1])+ # binary
|
||||
|
|
||||
0o(_?[0-7])+ # octal
|
||||
|
|
||||
0x(_?[\da-f])+ # hex
|
||||
|
|
||||
[1-9](_?\d)* # decimal
|
||||
|
|
||||
0(_?0)* # decimal zero
|
||||
)
|
||||
""",
|
||||
re.IGNORECASE | re.VERBOSE,
|
||||
)
|
||||
float_re = re.compile(
|
||||
r"""
|
||||
(?<!\.) # doesn't start with a .
|
||||
(\d+_)*\d+ # digits, possibly _ separated
|
||||
(
|
||||
(\.(\d+_)*\d+)? # optional fractional part
|
||||
e[+\-]?(\d+_)*\d+ # exponent part
|
||||
|
|
||||
\.(\d+_)*\d+ # required fractional part
|
||||
)
|
||||
""",
|
||||
re.IGNORECASE | re.VERBOSE,
|
||||
)
|
||||
|
||||
# internal the tokens and keep references to them
|
||||
TOKEN_ADD = intern("add")
|
||||
TOKEN_ASSIGN = intern("assign")
|
||||
TOKEN_COLON = intern("colon")
|
||||
TOKEN_COMMA = intern("comma")
|
||||
TOKEN_DIV = intern("div")
|
||||
TOKEN_DOT = intern("dot")
|
||||
TOKEN_EQ = intern("eq")
|
||||
TOKEN_FLOORDIV = intern("floordiv")
|
||||
TOKEN_GT = intern("gt")
|
||||
TOKEN_GTEQ = intern("gteq")
|
||||
TOKEN_LBRACE = intern("lbrace")
|
||||
TOKEN_LBRACKET = intern("lbracket")
|
||||
TOKEN_LPAREN = intern("lparen")
|
||||
TOKEN_LT = intern("lt")
|
||||
TOKEN_LTEQ = intern("lteq")
|
||||
TOKEN_MOD = intern("mod")
|
||||
TOKEN_MUL = intern("mul")
|
||||
TOKEN_NE = intern("ne")
|
||||
TOKEN_PIPE = intern("pipe")
|
||||
TOKEN_POW = intern("pow")
|
||||
TOKEN_RBRACE = intern("rbrace")
|
||||
TOKEN_RBRACKET = intern("rbracket")
|
||||
TOKEN_RPAREN = intern("rparen")
|
||||
TOKEN_SEMICOLON = intern("semicolon")
|
||||
TOKEN_SUB = intern("sub")
|
||||
TOKEN_TILDE = intern("tilde")
|
||||
TOKEN_WHITESPACE = intern("whitespace")
|
||||
TOKEN_FLOAT = intern("float")
|
||||
TOKEN_INTEGER = intern("integer")
|
||||
TOKEN_NAME = intern("name")
|
||||
TOKEN_STRING = intern("string")
|
||||
TOKEN_OPERATOR = intern("operator")
|
||||
TOKEN_BLOCK_BEGIN = intern("block_begin")
|
||||
TOKEN_BLOCK_END = intern("block_end")
|
||||
TOKEN_VARIABLE_BEGIN = intern("variable_begin")
|
||||
TOKEN_VARIABLE_END = intern("variable_end")
|
||||
TOKEN_RAW_BEGIN = intern("raw_begin")
|
||||
TOKEN_RAW_END = intern("raw_end")
|
||||
TOKEN_COMMENT_BEGIN = intern("comment_begin")
|
||||
TOKEN_COMMENT_END = intern("comment_end")
|
||||
TOKEN_COMMENT = intern("comment")
|
||||
TOKEN_LINESTATEMENT_BEGIN = intern("linestatement_begin")
|
||||
TOKEN_LINESTATEMENT_END = intern("linestatement_end")
|
||||
TOKEN_LINECOMMENT_BEGIN = intern("linecomment_begin")
|
||||
TOKEN_LINECOMMENT_END = intern("linecomment_end")
|
||||
TOKEN_LINECOMMENT = intern("linecomment")
|
||||
TOKEN_DATA = intern("data")
|
||||
TOKEN_INITIAL = intern("initial")
|
||||
TOKEN_EOF = intern("eof")
|
||||
|
||||
# bind operators to token types
|
||||
operators = {
|
||||
"+": TOKEN_ADD,
|
||||
"-": TOKEN_SUB,
|
||||
"/": TOKEN_DIV,
|
||||
"//": TOKEN_FLOORDIV,
|
||||
"*": TOKEN_MUL,
|
||||
"%": TOKEN_MOD,
|
||||
"**": TOKEN_POW,
|
||||
"~": TOKEN_TILDE,
|
||||
"[": TOKEN_LBRACKET,
|
||||
"]": TOKEN_RBRACKET,
|
||||
"(": TOKEN_LPAREN,
|
||||
")": TOKEN_RPAREN,
|
||||
"{": TOKEN_LBRACE,
|
||||
"}": TOKEN_RBRACE,
|
||||
"==": TOKEN_EQ,
|
||||
"!=": TOKEN_NE,
|
||||
">": TOKEN_GT,
|
||||
">=": TOKEN_GTEQ,
|
||||
"<": TOKEN_LT,
|
||||
"<=": TOKEN_LTEQ,
|
||||
"=": TOKEN_ASSIGN,
|
||||
".": TOKEN_DOT,
|
||||
":": TOKEN_COLON,
|
||||
"|": TOKEN_PIPE,
|
||||
",": TOKEN_COMMA,
|
||||
";": TOKEN_SEMICOLON,
|
||||
}
|
||||
|
||||
reverse_operators = {v: k for k, v in operators.items()}
|
||||
assert len(operators) == len(reverse_operators), "operators dropped"
|
||||
operator_re = re.compile(
|
||||
f"({'|'.join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))})"
|
||||
)
|
||||
|
||||
ignored_tokens = frozenset(
|
||||
[
|
||||
TOKEN_COMMENT_BEGIN,
|
||||
TOKEN_COMMENT,
|
||||
TOKEN_COMMENT_END,
|
||||
TOKEN_WHITESPACE,
|
||||
TOKEN_LINECOMMENT_BEGIN,
|
||||
TOKEN_LINECOMMENT_END,
|
||||
TOKEN_LINECOMMENT,
|
||||
]
|
||||
)
|
||||
ignore_if_empty = frozenset(
|
||||
[TOKEN_WHITESPACE, TOKEN_DATA, TOKEN_COMMENT, TOKEN_LINECOMMENT]
|
||||
)
|
||||
|
||||
|
||||
def _describe_token_type(token_type: str) -> str:
|
||||
if token_type in reverse_operators:
|
||||
return reverse_operators[token_type]
|
||||
|
||||
return {
|
||||
TOKEN_COMMENT_BEGIN: "begin of comment",
|
||||
TOKEN_COMMENT_END: "end of comment",
|
||||
TOKEN_COMMENT: "comment",
|
||||
TOKEN_LINECOMMENT: "comment",
|
||||
TOKEN_BLOCK_BEGIN: "begin of statement block",
|
||||
TOKEN_BLOCK_END: "end of statement block",
|
||||
TOKEN_VARIABLE_BEGIN: "begin of print statement",
|
||||
TOKEN_VARIABLE_END: "end of print statement",
|
||||
TOKEN_LINESTATEMENT_BEGIN: "begin of line statement",
|
||||
TOKEN_LINESTATEMENT_END: "end of line statement",
|
||||
TOKEN_DATA: "template data / text",
|
||||
TOKEN_EOF: "end of template",
|
||||
}.get(token_type, token_type)
|
||||
|
||||
|
||||
def describe_token(token: "Token") -> str:
|
||||
"""Returns a description of the token."""
|
||||
if token.type == TOKEN_NAME:
|
||||
return token.value
|
||||
|
||||
return _describe_token_type(token.type)
|
||||
|
||||
|
||||
def describe_token_expr(expr: str) -> str:
|
||||
"""Like `describe_token` but for token expressions."""
|
||||
if ":" in expr:
|
||||
type, value = expr.split(":", 1)
|
||||
|
||||
if type == TOKEN_NAME:
|
||||
return value
|
||||
else:
|
||||
type = expr
|
||||
|
||||
return _describe_token_type(type)
|
||||
|
||||
|
||||
def count_newlines(value: str) -> int:
|
||||
"""Count the number of newline characters in the string. This is
|
||||
useful for extensions that filter a stream.
|
||||
"""
|
||||
return len(newline_re.findall(value))
|
||||
|
||||
|
||||
def compile_rules(environment: "Environment") -> t.List[t.Tuple[str, str]]:
|
||||
"""Compiles all the rules from the environment into a list of rules."""
|
||||
e = re.escape
|
||||
rules = [
|
||||
(
|
||||
len(environment.comment_start_string),
|
||||
TOKEN_COMMENT_BEGIN,
|
||||
e(environment.comment_start_string),
|
||||
),
|
||||
(
|
||||
len(environment.block_start_string),
|
||||
TOKEN_BLOCK_BEGIN,
|
||||
e(environment.block_start_string),
|
||||
),
|
||||
(
|
||||
len(environment.variable_start_string),
|
||||
TOKEN_VARIABLE_BEGIN,
|
||||
e(environment.variable_start_string),
|
||||
),
|
||||
]
|
||||
|
||||
if environment.line_statement_prefix is not None:
|
||||
rules.append(
|
||||
(
|
||||
len(environment.line_statement_prefix),
|
||||
TOKEN_LINESTATEMENT_BEGIN,
|
||||
r"^[ \t\v]*" + e(environment.line_statement_prefix),
|
||||
)
|
||||
)
|
||||
if environment.line_comment_prefix is not None:
|
||||
rules.append(
|
||||
(
|
||||
len(environment.line_comment_prefix),
|
||||
TOKEN_LINECOMMENT_BEGIN,
|
||||
r"(?:^|(?<=\S))[^\S\r\n]*" + e(environment.line_comment_prefix),
|
||||
)
|
||||
)
|
||||
|
||||
return [x[1:] for x in sorted(rules, reverse=True)]
|
||||
|
||||
|
||||
class Failure:
|
||||
"""Class that raises a `TemplateSyntaxError` if called.
|
||||
Used by the `Lexer` to specify known errors.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, message: str, cls: t.Type[TemplateSyntaxError] = TemplateSyntaxError
|
||||
) -> None:
|
||||
self.message = message
|
||||
self.error_class = cls
|
||||
|
||||
def __call__(self, lineno: int, filename: t.Optional[str]) -> "te.NoReturn":
|
||||
raise self.error_class(self.message, lineno, filename)
|
||||
|
||||
|
||||
class Token(t.NamedTuple):
|
||||
lineno: int
|
||||
type: str
|
||||
value: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return describe_token(self)
|
||||
|
||||
def test(self, expr: str) -> bool:
|
||||
"""Test a token against a token expression. This can either be a
|
||||
token type or ``'token_type:token_value'``. This can only test
|
||||
against string values and types.
|
||||
"""
|
||||
# here we do a regular string equality check as test_any is usually
|
||||
# passed an iterable of not interned strings.
|
||||
if self.type == expr:
|
||||
return True
|
||||
|
||||
if ":" in expr:
|
||||
return expr.split(":", 1) == [self.type, self.value]
|
||||
|
||||
return False
|
||||
|
||||
def test_any(self, *iterable: str) -> bool:
|
||||
"""Test against multiple token expressions."""
|
||||
return any(self.test(expr) for expr in iterable)
|
||||
|
||||
|
||||
class TokenStreamIterator:
|
||||
"""The iterator for tokenstreams. Iterate over the stream
|
||||
until the eof token is reached.
|
||||
"""
|
||||
|
||||
def __init__(self, stream: "TokenStream") -> None:
|
||||
self.stream = stream
|
||||
|
||||
def __iter__(self) -> "TokenStreamIterator":
|
||||
return self
|
||||
|
||||
def __next__(self) -> Token:
|
||||
token = self.stream.current
|
||||
|
||||
if token.type is TOKEN_EOF:
|
||||
self.stream.close()
|
||||
raise StopIteration
|
||||
|
||||
next(self.stream)
|
||||
return token
|
||||
|
||||
|
||||
class TokenStream:
|
||||
"""A token stream is an iterable that yields :class:`Token`\\s. The
|
||||
parser however does not iterate over it but calls :meth:`next` to go
|
||||
one token ahead. The current active token is stored as :attr:`current`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
generator: t.Iterable[Token],
|
||||
name: t.Optional[str],
|
||||
filename: t.Optional[str],
|
||||
):
|
||||
self._iter = iter(generator)
|
||||
self._pushed: te.Deque[Token] = deque()
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.closed = False
|
||||
self.current = Token(1, TOKEN_INITIAL, "")
|
||||
next(self)
|
||||
|
||||
def __iter__(self) -> TokenStreamIterator:
|
||||
return TokenStreamIterator(self)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self._pushed) or self.current.type is not TOKEN_EOF
|
||||
|
||||
@property
|
||||
def eos(self) -> bool:
|
||||
"""Are we at the end of the stream?"""
|
||||
return not self
|
||||
|
||||
def push(self, token: Token) -> None:
|
||||
"""Push a token back to the stream."""
|
||||
self._pushed.append(token)
|
||||
|
||||
def look(self) -> Token:
|
||||
"""Look at the next token."""
|
||||
old_token = next(self)
|
||||
result = self.current
|
||||
self.push(result)
|
||||
self.current = old_token
|
||||
return result
|
||||
|
||||
def skip(self, n: int = 1) -> None:
|
||||
"""Got n tokens ahead."""
|
||||
for _ in range(n):
|
||||
next(self)
|
||||
|
||||
def next_if(self, expr: str) -> t.Optional[Token]:
|
||||
"""Perform the token test and return the token if it matched.
|
||||
Otherwise the return value is `None`.
|
||||
"""
|
||||
if self.current.test(expr):
|
||||
return next(self)
|
||||
|
||||
return None
|
||||
|
||||
def skip_if(self, expr: str) -> bool:
|
||||
"""Like :meth:`next_if` but only returns `True` or `False`."""
|
||||
return self.next_if(expr) is not None
|
||||
|
||||
def __next__(self) -> Token:
|
||||
"""Go one token ahead and return the old one.
|
||||
|
||||
Use the built-in :func:`next` instead of calling this directly.
|
||||
"""
|
||||
rv = self.current
|
||||
|
||||
if self._pushed:
|
||||
self.current = self._pushed.popleft()
|
||||
elif self.current.type is not TOKEN_EOF:
|
||||
try:
|
||||
self.current = next(self._iter)
|
||||
except StopIteration:
|
||||
self.close()
|
||||
|
||||
return rv
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the stream."""
|
||||
self.current = Token(self.current.lineno, TOKEN_EOF, "")
|
||||
self._iter = iter(())
|
||||
self.closed = True
|
||||
|
||||
def expect(self, expr: str) -> Token:
|
||||
"""Expect a given token type and return it. This accepts the same
|
||||
argument as :meth:`jinja2.lexer.Token.test`.
|
||||
"""
|
||||
if not self.current.test(expr):
|
||||
expr = describe_token_expr(expr)
|
||||
|
||||
if self.current.type is TOKEN_EOF:
|
||||
raise TemplateSyntaxError(
|
||||
f"unexpected end of template, expected {expr!r}.",
|
||||
self.current.lineno,
|
||||
self.name,
|
||||
self.filename,
|
||||
)
|
||||
|
||||
raise TemplateSyntaxError(
|
||||
f"expected token {expr!r}, got {describe_token(self.current)!r}",
|
||||
self.current.lineno,
|
||||
self.name,
|
||||
self.filename,
|
||||
)
|
||||
|
||||
return next(self)
|
||||
|
||||
|
||||
def get_lexer(environment: "Environment") -> "Lexer":
|
||||
"""Return a lexer which is probably cached."""
|
||||
key = (
|
||||
environment.block_start_string,
|
||||
environment.block_end_string,
|
||||
environment.variable_start_string,
|
||||
environment.variable_end_string,
|
||||
environment.comment_start_string,
|
||||
environment.comment_end_string,
|
||||
environment.line_statement_prefix,
|
||||
environment.line_comment_prefix,
|
||||
environment.trim_blocks,
|
||||
environment.lstrip_blocks,
|
||||
environment.newline_sequence,
|
||||
environment.keep_trailing_newline,
|
||||
)
|
||||
lexer = _lexer_cache.get(key)
|
||||
|
||||
if lexer is None:
|
||||
_lexer_cache[key] = lexer = Lexer(environment)
|
||||
|
||||
return lexer
|
||||
|
||||
|
||||
class OptionalLStrip(tuple): # type: ignore[type-arg]
|
||||
"""A special tuple for marking a point in the state that can have
|
||||
lstrip applied.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
# Even though it looks like a no-op, creating instances fails
|
||||
# without this.
|
||||
def __new__(cls, *members, **kwargs): # type: ignore
|
||||
return super().__new__(cls, members)
|
||||
|
||||
|
||||
class _Rule(t.NamedTuple):
|
||||
pattern: t.Pattern[str]
|
||||
tokens: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]]
|
||||
command: t.Optional[str]
|
||||
|
||||
|
||||
class Lexer:
|
||||
"""Class that implements a lexer for a given environment. Automatically
|
||||
created by the environment class, usually you don't have to do that.
|
||||
|
||||
Note that the lexer is not automatically bound to an environment.
|
||||
Multiple environments can share the same lexer.
|
||||
"""
|
||||
|
||||
def __init__(self, environment: "Environment") -> None:
|
||||
# shortcuts
|
||||
e = re.escape
|
||||
|
||||
def c(x: str) -> t.Pattern[str]:
|
||||
return re.compile(x, re.M | re.S)
|
||||
|
||||
# lexing rules for tags
|
||||
tag_rules: t.List[_Rule] = [
|
||||
_Rule(whitespace_re, TOKEN_WHITESPACE, None),
|
||||
_Rule(float_re, TOKEN_FLOAT, None),
|
||||
_Rule(integer_re, TOKEN_INTEGER, None),
|
||||
_Rule(name_re, TOKEN_NAME, None),
|
||||
_Rule(string_re, TOKEN_STRING, None),
|
||||
_Rule(operator_re, TOKEN_OPERATOR, None),
|
||||
]
|
||||
|
||||
# assemble the root lexing rule. because "|" is ungreedy
|
||||
# we have to sort by length so that the lexer continues working
|
||||
# as expected when we have parsing rules like <% for block and
|
||||
# <%= for variables. (if someone wants asp like syntax)
|
||||
# variables are just part of the rules if variable processing
|
||||
# is required.
|
||||
root_tag_rules = compile_rules(environment)
|
||||
|
||||
block_start_re = e(environment.block_start_string)
|
||||
block_end_re = e(environment.block_end_string)
|
||||
comment_end_re = e(environment.comment_end_string)
|
||||
variable_end_re = e(environment.variable_end_string)
|
||||
|
||||
# block suffix if trimming is enabled
|
||||
block_suffix_re = "\\n?" if environment.trim_blocks else ""
|
||||
|
||||
self.lstrip_blocks = environment.lstrip_blocks
|
||||
|
||||
self.newline_sequence = environment.newline_sequence
|
||||
self.keep_trailing_newline = environment.keep_trailing_newline
|
||||
|
||||
root_raw_re = (
|
||||
rf"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
|
||||
rf"(?:\-{block_end_re}\s*|{block_end_re}))"
|
||||
)
|
||||
root_parts_re = "|".join(
|
||||
[root_raw_re] + [rf"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules]
|
||||
)
|
||||
|
||||
# global lexing rules
|
||||
self.rules: t.Dict[str, t.List[_Rule]] = {
|
||||
"root": [
|
||||
# directives
|
||||
_Rule(
|
||||
c(rf"(.*?)(?:{root_parts_re})"),
|
||||
OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore
|
||||
"#bygroup",
|
||||
),
|
||||
# data
|
||||
_Rule(c(".+"), TOKEN_DATA, None),
|
||||
],
|
||||
# comments
|
||||
TOKEN_COMMENT_BEGIN: [
|
||||
_Rule(
|
||||
c(
|
||||
rf"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*"
|
||||
rf"|{comment_end_re}{block_suffix_re}))"
|
||||
),
|
||||
(TOKEN_COMMENT, TOKEN_COMMENT_END),
|
||||
"#pop",
|
||||
),
|
||||
_Rule(c(r"(.)"), (Failure("Missing end of comment tag"),), None),
|
||||
],
|
||||
# blocks
|
||||
TOKEN_BLOCK_BEGIN: [
|
||||
_Rule(
|
||||
c(
|
||||
rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
||||
rf"|{block_end_re}{block_suffix_re})"
|
||||
),
|
||||
TOKEN_BLOCK_END,
|
||||
"#pop",
|
||||
),
|
||||
]
|
||||
+ tag_rules,
|
||||
# variables
|
||||
TOKEN_VARIABLE_BEGIN: [
|
||||
_Rule(
|
||||
c(rf"\-{variable_end_re}\s*|{variable_end_re}"),
|
||||
TOKEN_VARIABLE_END,
|
||||
"#pop",
|
||||
)
|
||||
]
|
||||
+ tag_rules,
|
||||
# raw block
|
||||
TOKEN_RAW_BEGIN: [
|
||||
_Rule(
|
||||
c(
|
||||
rf"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
|
||||
rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
|
||||
rf"|{block_end_re}{block_suffix_re}))"
|
||||
),
|
||||
OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore
|
||||
"#pop",
|
||||
),
|
||||
_Rule(c(r"(.)"), (Failure("Missing end of raw directive"),), None),
|
||||
],
|
||||
# line statements
|
||||
TOKEN_LINESTATEMENT_BEGIN: [
|
||||
_Rule(c(r"\s*(\n|$)"), TOKEN_LINESTATEMENT_END, "#pop")
|
||||
]
|
||||
+ tag_rules,
|
||||
# line comments
|
||||
TOKEN_LINECOMMENT_BEGIN: [
|
||||
_Rule(
|
||||
c(r"(.*?)()(?=\n|$)"),
|
||||
(TOKEN_LINECOMMENT, TOKEN_LINECOMMENT_END),
|
||||
"#pop",
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
def _normalize_newlines(self, value: str) -> str:
|
||||
"""Replace all newlines with the configured sequence in strings
|
||||
and template data.
|
||||
"""
|
||||
return newline_re.sub(self.newline_sequence, value)
|
||||
|
||||
def tokenize(
|
||||
self,
|
||||
source: str,
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
state: t.Optional[str] = None,
|
||||
) -> TokenStream:
|
||||
"""Calls tokeniter + tokenize and wraps it in a token stream."""
|
||||
stream = self.tokeniter(source, name, filename, state)
|
||||
return TokenStream(self.wrap(stream, name, filename), name, filename)
|
||||
|
||||
def wrap(
|
||||
self,
|
||||
stream: t.Iterable[t.Tuple[int, str, str]],
|
||||
name: t.Optional[str] = None,
|
||||
filename: t.Optional[str] = None,
|
||||
) -> t.Iterator[Token]:
|
||||
"""This is called with the stream as returned by `tokenize` and wraps
|
||||
every token in a :class:`Token` and converts the value.
|
||||
"""
|
||||
for lineno, token, value_str in stream:
|
||||
if token in ignored_tokens:
|
||||
continue
|
||||
|
||||
value: t.Any = value_str
|
||||
|
||||
if token == TOKEN_LINESTATEMENT_BEGIN:
|
||||
token = TOKEN_BLOCK_BEGIN
|
||||
elif token == TOKEN_LINESTATEMENT_END:
|
||||
token = TOKEN_BLOCK_END
|
||||
# we are not interested in those tokens in the parser
|
||||
elif token in (TOKEN_RAW_BEGIN, TOKEN_RAW_END):
|
||||
continue
|
||||
elif token == TOKEN_DATA:
|
||||
value = self._normalize_newlines(value_str)
|
||||
elif token == "keyword":
|
||||
token = value_str
|
||||
elif token == TOKEN_NAME:
|
||||
value = value_str
|
||||
|
||||
if not value.isidentifier():
|
||||
raise TemplateSyntaxError(
|
||||
"Invalid character in identifier", lineno, name, filename
|
||||
)
|
||||
elif token == TOKEN_STRING:
|
||||
# try to unescape string
|
||||
try:
|
||||
value = (
|
||||
self._normalize_newlines(value_str[1:-1])
|
||||
.encode("ascii", "backslashreplace")
|
||||
.decode("unicode-escape")
|
||||
)
|
||||
except Exception as e:
|
||||
msg = str(e).split(":")[-1].strip()
|
||||
raise TemplateSyntaxError(msg, lineno, name, filename) from e
|
||||
elif token == TOKEN_INTEGER:
|
||||
value = int(value_str.replace("_", ""), 0)
|
||||
elif token == TOKEN_FLOAT:
|
||||
# remove all "_" first to support more Python versions
|
||||
value = literal_eval(value_str.replace("_", ""))
|
||||
elif token == TOKEN_OPERATOR:
|
||||
token = operators[value_str]
|
||||
|
||||
yield Token(lineno, token, value)
|
||||
|
||||
def tokeniter(
|
||||
self,
|
||||
source: str,
|
||||
name: t.Optional[str],
|
||||
filename: t.Optional[str] = None,
|
||||
state: t.Optional[str] = None,
|
||||
) -> t.Iterator[t.Tuple[int, str, str]]:
|
||||
"""This method tokenizes the text and returns the tokens in a
|
||||
generator. Use this method if you just want to tokenize a template.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Only ``\\n``, ``\\r\\n`` and ``\\r`` are treated as line
|
||||
breaks.
|
||||
"""
|
||||
lines = newline_re.split(source)[::2]
|
||||
|
||||
if not self.keep_trailing_newline and lines[-1] == "":
|
||||
del lines[-1]
|
||||
|
||||
source = "\n".join(lines)
|
||||
pos = 0
|
||||
lineno = 1
|
||||
stack = ["root"]
|
||||
|
||||
if state is not None and state != "root":
|
||||
assert state in ("variable", "block"), "invalid state"
|
||||
stack.append(state + "_begin")
|
||||
|
||||
statetokens = self.rules[stack[-1]]
|
||||
source_length = len(source)
|
||||
balancing_stack: t.List[str] = []
|
||||
newlines_stripped = 0
|
||||
line_starting = True
|
||||
|
||||
while True:
|
||||
# tokenizer loop
|
||||
for regex, tokens, new_state in statetokens:
|
||||
m = regex.match(source, pos)
|
||||
|
||||
# if no match we try again with the next rule
|
||||
if m is None:
|
||||
continue
|
||||
|
||||
# we only match blocks and variables if braces / parentheses
|
||||
# are balanced. continue parsing with the lower rule which
|
||||
# is the operator rule. do this only if the end tags look
|
||||
# like operators
|
||||
if balancing_stack and tokens in (
|
||||
TOKEN_VARIABLE_END,
|
||||
TOKEN_BLOCK_END,
|
||||
TOKEN_LINESTATEMENT_END,
|
||||
):
|
||||
continue
|
||||
|
||||
# tuples support more options
|
||||
if isinstance(tokens, tuple):
|
||||
groups: t.Sequence[str] = m.groups()
|
||||
|
||||
if isinstance(tokens, OptionalLStrip):
|
||||
# Rule supports lstrip. Match will look like
|
||||
# text, block type, whitespace control, type, control, ...
|
||||
text = groups[0]
|
||||
# Skipping the text and first type, every other group is the
|
||||
# whitespace control for each type. One of the groups will be
|
||||
# -, +, or empty string instead of None.
|
||||
strip_sign = next(g for g in groups[2::2] if g is not None)
|
||||
|
||||
if strip_sign == "-":
|
||||
# Strip all whitespace between the text and the tag.
|
||||
stripped = text.rstrip()
|
||||
newlines_stripped = text[len(stripped) :].count("\n")
|
||||
groups = [stripped, *groups[1:]]
|
||||
elif (
|
||||
# Not marked for preserving whitespace.
|
||||
strip_sign != "+"
|
||||
# lstrip is enabled.
|
||||
and self.lstrip_blocks
|
||||
# Not a variable expression.
|
||||
and not m.groupdict().get(TOKEN_VARIABLE_BEGIN)
|
||||
):
|
||||
# The start of text between the last newline and the tag.
|
||||
l_pos = text.rfind("\n") + 1
|
||||
|
||||
if l_pos > 0 or line_starting:
|
||||
# If there's only whitespace between the newline and the
|
||||
# tag, strip it.
|
||||
if whitespace_re.fullmatch(text, l_pos):
|
||||
groups = [text[:l_pos], *groups[1:]]
|
||||
|
||||
for idx, token in enumerate(tokens):
|
||||
# failure group
|
||||
if isinstance(token, Failure):
|
||||
raise token(lineno, filename)
|
||||
# bygroup is a bit more complex, in that case we
|
||||
# yield for the current token the first named
|
||||
# group that matched
|
||||
elif token == "#bygroup":
|
||||
for key, value in m.groupdict().items():
|
||||
if value is not None:
|
||||
yield lineno, key, value
|
||||
lineno += value.count("\n")
|
||||
break
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"{regex!r} wanted to resolve the token dynamically"
|
||||
" but no group matched"
|
||||
)
|
||||
# normal group
|
||||
else:
|
||||
data = groups[idx]
|
||||
|
||||
if data or token not in ignore_if_empty:
|
||||
yield lineno, token, data # type: ignore[misc]
|
||||
|
||||
lineno += data.count("\n") + newlines_stripped
|
||||
newlines_stripped = 0
|
||||
|
||||
# strings as token just are yielded as it.
|
||||
else:
|
||||
data = m.group()
|
||||
|
||||
# update brace/parentheses balance
|
||||
if tokens == TOKEN_OPERATOR:
|
||||
if data == "{":
|
||||
balancing_stack.append("}")
|
||||
elif data == "(":
|
||||
balancing_stack.append(")")
|
||||
elif data == "[":
|
||||
balancing_stack.append("]")
|
||||
elif data in ("}", ")", "]"):
|
||||
if not balancing_stack:
|
||||
raise TemplateSyntaxError(
|
||||
f"unexpected '{data}'", lineno, name, filename
|
||||
)
|
||||
|
||||
expected_op = balancing_stack.pop()
|
||||
|
||||
if expected_op != data:
|
||||
raise TemplateSyntaxError(
|
||||
f"unexpected '{data}', expected '{expected_op}'",
|
||||
lineno,
|
||||
name,
|
||||
filename,
|
||||
)
|
||||
|
||||
# yield items
|
||||
if data or tokens not in ignore_if_empty:
|
||||
yield lineno, tokens, data
|
||||
|
||||
lineno += data.count("\n")
|
||||
|
||||
line_starting = m.group()[-1:] == "\n"
|
||||
# fetch new position into new variable so that we can check
|
||||
# if there is a internal parsing error which would result
|
||||
# in an infinite loop
|
||||
pos2 = m.end()
|
||||
|
||||
# handle state changes
|
||||
if new_state is not None:
|
||||
# remove the uppermost state
|
||||
if new_state == "#pop":
|
||||
stack.pop()
|
||||
# resolve the new state by group checking
|
||||
elif new_state == "#bygroup":
|
||||
for key, value in m.groupdict().items():
|
||||
if value is not None:
|
||||
stack.append(key)
|
||||
break
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"{regex!r} wanted to resolve the new state dynamically"
|
||||
f" but no group matched"
|
||||
)
|
||||
# direct state name given
|
||||
else:
|
||||
stack.append(new_state)
|
||||
|
||||
statetokens = self.rules[stack[-1]]
|
||||
# we are still at the same position and no stack change.
|
||||
# this means a loop without break condition, avoid that and
|
||||
# raise error
|
||||
elif pos2 == pos:
|
||||
raise RuntimeError(
|
||||
f"{regex!r} yielded empty string without stack change"
|
||||
)
|
||||
|
||||
# publish new function and start again
|
||||
pos = pos2
|
||||
break
|
||||
# if loop terminated without break we haven't found a single match
|
||||
# either we are at the end of the file or we have a problem
|
||||
else:
|
||||
# end of text
|
||||
if pos >= source_length:
|
||||
return
|
||||
|
||||
# something went wrong
|
||||
raise TemplateSyntaxError(
|
||||
f"unexpected char {source[pos]!r} at {pos}", lineno, name, filename
|
||||
)
|
693
env/lib/python3.11/site-packages/jinja2/loaders.py
vendored
Normal file
693
env/lib/python3.11/site-packages/jinja2/loaders.py
vendored
Normal file
@ -0,0 +1,693 @@
|
||||
"""API and implementations for loading templates from different data
|
||||
sources.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import posixpath
|
||||
import sys
|
||||
import typing as t
|
||||
import weakref
|
||||
import zipimport
|
||||
from collections import abc
|
||||
from hashlib import sha1
|
||||
from importlib import import_module
|
||||
from types import ModuleType
|
||||
|
||||
from .exceptions import TemplateNotFound
|
||||
from .utils import internalcode
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .environment import Environment
|
||||
from .environment import Template
|
||||
|
||||
|
||||
def split_template_path(template: str) -> t.List[str]:
|
||||
"""Split a path into segments and perform a sanity check. If it detects
|
||||
'..' in the path it will raise a `TemplateNotFound` error.
|
||||
"""
|
||||
pieces = []
|
||||
for piece in template.split("/"):
|
||||
if (
|
||||
os.path.sep in piece
|
||||
or (os.path.altsep and os.path.altsep in piece)
|
||||
or piece == os.path.pardir
|
||||
):
|
||||
raise TemplateNotFound(template)
|
||||
elif piece and piece != ".":
|
||||
pieces.append(piece)
|
||||
return pieces
|
||||
|
||||
|
||||
class BaseLoader:
|
||||
"""Baseclass for all loaders. Subclass this and override `get_source` to
|
||||
implement a custom loading mechanism. The environment provides a
|
||||
`get_template` method that calls the loader's `load` method to get the
|
||||
:class:`Template` object.
|
||||
|
||||
A very basic example for a loader that looks up templates on the file
|
||||
system could look like this::
|
||||
|
||||
from jinja2 import BaseLoader, TemplateNotFound
|
||||
from os.path import join, exists, getmtime
|
||||
|
||||
class MyLoader(BaseLoader):
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def get_source(self, environment, template):
|
||||
path = join(self.path, template)
|
||||
if not exists(path):
|
||||
raise TemplateNotFound(template)
|
||||
mtime = getmtime(path)
|
||||
with open(path) as f:
|
||||
source = f.read()
|
||||
return source, path, lambda: mtime == getmtime(path)
|
||||
"""
|
||||
|
||||
#: if set to `False` it indicates that the loader cannot provide access
|
||||
#: to the source of templates.
|
||||
#:
|
||||
#: .. versionadded:: 2.4
|
||||
has_source_access = True
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||
"""Get the template source, filename and reload helper for a template.
|
||||
It's passed the environment and template name and has to return a
|
||||
tuple in the form ``(source, filename, uptodate)`` or raise a
|
||||
`TemplateNotFound` error if it can't locate the template.
|
||||
|
||||
The source part of the returned tuple must be the source of the
|
||||
template as a string. The filename should be the name of the
|
||||
file on the filesystem if it was loaded from there, otherwise
|
||||
``None``. The filename is used by Python for the tracebacks
|
||||
if no loader extension is used.
|
||||
|
||||
The last item in the tuple is the `uptodate` function. If auto
|
||||
reloading is enabled it's always called to check if the template
|
||||
changed. No arguments are passed so the function must store the
|
||||
old state somewhere (for example in a closure). If it returns `False`
|
||||
the template will be reloaded.
|
||||
"""
|
||||
if not self.has_source_access:
|
||||
raise RuntimeError(
|
||||
f"{type(self).__name__} cannot provide access to the source"
|
||||
)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self) -> t.List[str]:
|
||||
"""Iterates over all templates. If the loader does not support that
|
||||
it should raise a :exc:`TypeError` which is the default behavior.
|
||||
"""
|
||||
raise TypeError("this loader cannot iterate over all templates")
|
||||
|
||||
@internalcode
|
||||
def load(
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
"""Loads a template. This method looks up the template in the cache
|
||||
or loads one by calling :meth:`get_source`. Subclasses should not
|
||||
override this method as loaders working on collections of other
|
||||
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
|
||||
will not call this method but `get_source` directly.
|
||||
"""
|
||||
code = None
|
||||
if globals is None:
|
||||
globals = {}
|
||||
|
||||
# first we try to get the source for this template together
|
||||
# with the filename and the uptodate function.
|
||||
source, filename, uptodate = self.get_source(environment, name)
|
||||
|
||||
# try to load the code from the bytecode cache if there is a
|
||||
# bytecode cache configured.
|
||||
bcc = environment.bytecode_cache
|
||||
if bcc is not None:
|
||||
bucket = bcc.get_bucket(environment, name, filename, source)
|
||||
code = bucket.code
|
||||
|
||||
# if we don't have code so far (not cached, no longer up to
|
||||
# date) etc. we compile the template
|
||||
if code is None:
|
||||
code = environment.compile(source, name, filename)
|
||||
|
||||
# if the bytecode cache is available and the bucket doesn't
|
||||
# have a code so far, we give the bucket the new code and put
|
||||
# it back to the bytecode cache.
|
||||
if bcc is not None and bucket.code is None:
|
||||
bucket.code = code
|
||||
bcc.set_bucket(bucket)
|
||||
|
||||
return environment.template_class.from_code(
|
||||
environment, code, globals, uptodate
|
||||
)
|
||||
|
||||
|
||||
class FileSystemLoader(BaseLoader):
|
||||
"""Load templates from a directory in the file system.
|
||||
|
||||
The path can be relative or absolute. Relative paths are relative to
|
||||
the current working directory.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
loader = FileSystemLoader("templates")
|
||||
|
||||
A list of paths can be given. The directories will be searched in
|
||||
order, stopping at the first matching template.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
loader = FileSystemLoader(["/override/templates", "/default/templates"])
|
||||
|
||||
:param searchpath: A path, or list of paths, to the directory that
|
||||
contains the templates.
|
||||
:param encoding: Use this encoding to read the text from template
|
||||
files.
|
||||
:param followlinks: Follow symbolic links in the path.
|
||||
|
||||
.. versionchanged:: 2.8
|
||||
Added the ``followlinks`` parameter.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
searchpath: t.Union[
|
||||
str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
|
||||
],
|
||||
encoding: str = "utf-8",
|
||||
followlinks: bool = False,
|
||||
) -> None:
|
||||
if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
|
||||
searchpath = [searchpath]
|
||||
|
||||
self.searchpath = [os.fspath(p) for p in searchpath]
|
||||
self.encoding = encoding
|
||||
self.followlinks = followlinks
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> t.Tuple[str, str, t.Callable[[], bool]]:
|
||||
pieces = split_template_path(template)
|
||||
|
||||
for searchpath in self.searchpath:
|
||||
# Use posixpath even on Windows to avoid "drive:" or UNC
|
||||
# segments breaking out of the search directory.
|
||||
filename = posixpath.join(searchpath, *pieces)
|
||||
|
||||
if os.path.isfile(filename):
|
||||
break
|
||||
else:
|
||||
plural = "path" if len(self.searchpath) == 1 else "paths"
|
||||
paths_str = ", ".join(repr(p) for p in self.searchpath)
|
||||
raise TemplateNotFound(
|
||||
template,
|
||||
f"{template!r} not found in search {plural}: {paths_str}",
|
||||
)
|
||||
|
||||
with open(filename, encoding=self.encoding) as f:
|
||||
contents = f.read()
|
||||
|
||||
mtime = os.path.getmtime(filename)
|
||||
|
||||
def uptodate() -> bool:
|
||||
try:
|
||||
return os.path.getmtime(filename) == mtime
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
# Use normpath to convert Windows altsep to sep.
|
||||
return contents, os.path.normpath(filename), uptodate
|
||||
|
||||
def list_templates(self) -> t.List[str]:
|
||||
found = set()
|
||||
for searchpath in self.searchpath:
|
||||
walk_dir = os.walk(searchpath, followlinks=self.followlinks)
|
||||
for dirpath, _, filenames in walk_dir:
|
||||
for filename in filenames:
|
||||
template = (
|
||||
os.path.join(dirpath, filename)[len(searchpath) :]
|
||||
.strip(os.path.sep)
|
||||
.replace(os.path.sep, "/")
|
||||
)
|
||||
if template[:2] == "./":
|
||||
template = template[2:]
|
||||
if template not in found:
|
||||
found.add(template)
|
||||
return sorted(found)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
|
||||
def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
|
||||
try:
|
||||
get_files = z._get_files
|
||||
except AttributeError as e:
|
||||
raise TypeError(
|
||||
"This zip import does not have the required"
|
||||
" metadata to list templates."
|
||||
) from e
|
||||
return get_files()
|
||||
else:
|
||||
|
||||
def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
|
||||
try:
|
||||
files = z._files
|
||||
except AttributeError as e:
|
||||
raise TypeError(
|
||||
"This zip import does not have the required"
|
||||
" metadata to list templates."
|
||||
) from e
|
||||
return files # type: ignore[no-any-return]
|
||||
|
||||
|
||||
class PackageLoader(BaseLoader):
|
||||
"""Load templates from a directory in a Python package.
|
||||
|
||||
:param package_name: Import name of the package that contains the
|
||||
template directory.
|
||||
:param package_path: Directory within the imported package that
|
||||
contains the templates.
|
||||
:param encoding: Encoding of template files.
|
||||
|
||||
The following example looks up templates in the ``pages`` directory
|
||||
within the ``project.ui`` package.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
loader = PackageLoader("project.ui", "pages")
|
||||
|
||||
Only packages installed as directories (standard pip behavior) or
|
||||
zip/egg files (less common) are supported. The Python API for
|
||||
introspecting data in packages is too limited to support other
|
||||
installation methods the way this loader requires.
|
||||
|
||||
There is limited support for :pep:`420` namespace packages. The
|
||||
template directory is assumed to only be in one namespace
|
||||
contributor. Zip files contributing to a namespace are not
|
||||
supported.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
No longer uses ``setuptools`` as a dependency.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Limited PEP 420 namespace package support.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
package_name: str,
|
||||
package_path: "str" = "templates",
|
||||
encoding: str = "utf-8",
|
||||
) -> None:
|
||||
package_path = os.path.normpath(package_path).rstrip(os.path.sep)
|
||||
|
||||
# normpath preserves ".", which isn't valid in zip paths.
|
||||
if package_path == os.path.curdir:
|
||||
package_path = ""
|
||||
elif package_path[:2] == os.path.curdir + os.path.sep:
|
||||
package_path = package_path[2:]
|
||||
|
||||
self.package_path = package_path
|
||||
self.package_name = package_name
|
||||
self.encoding = encoding
|
||||
|
||||
# Make sure the package exists. This also makes namespace
|
||||
# packages work, otherwise get_loader returns None.
|
||||
import_module(package_name)
|
||||
spec = importlib.util.find_spec(package_name)
|
||||
assert spec is not None, "An import spec was not found for the package."
|
||||
loader = spec.loader
|
||||
assert loader is not None, "A loader was not found for the package."
|
||||
self._loader = loader
|
||||
self._archive = None
|
||||
|
||||
if isinstance(loader, zipimport.zipimporter):
|
||||
self._archive = loader.archive
|
||||
pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore
|
||||
template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
|
||||
else:
|
||||
roots: t.List[str] = []
|
||||
|
||||
# One element for regular packages, multiple for namespace
|
||||
# packages, or None for single module file.
|
||||
if spec.submodule_search_locations:
|
||||
roots.extend(spec.submodule_search_locations)
|
||||
# A single module file, use the parent directory instead.
|
||||
elif spec.origin is not None:
|
||||
roots.append(os.path.dirname(spec.origin))
|
||||
|
||||
if not roots:
|
||||
raise ValueError(
|
||||
f"The {package_name!r} package was not installed in a"
|
||||
" way that PackageLoader understands."
|
||||
)
|
||||
|
||||
for root in roots:
|
||||
root = os.path.join(root, package_path)
|
||||
|
||||
if os.path.isdir(root):
|
||||
template_root = root
|
||||
break
|
||||
else:
|
||||
raise ValueError(
|
||||
f"PackageLoader could not find a {package_path!r} directory"
|
||||
f" in the {package_name!r} package."
|
||||
)
|
||||
|
||||
self._template_root = template_root
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
|
||||
# Use posixpath even on Windows to avoid "drive:" or UNC
|
||||
# segments breaking out of the search directory. Use normpath to
|
||||
# convert Windows altsep to sep.
|
||||
p = os.path.normpath(
|
||||
posixpath.join(self._template_root, *split_template_path(template))
|
||||
)
|
||||
up_to_date: t.Optional[t.Callable[[], bool]]
|
||||
|
||||
if self._archive is None:
|
||||
# Package is a directory.
|
||||
if not os.path.isfile(p):
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
with open(p, "rb") as f:
|
||||
source = f.read()
|
||||
|
||||
mtime = os.path.getmtime(p)
|
||||
|
||||
def up_to_date() -> bool:
|
||||
return os.path.isfile(p) and os.path.getmtime(p) == mtime
|
||||
|
||||
else:
|
||||
# Package is a zip file.
|
||||
try:
|
||||
source = self._loader.get_data(p) # type: ignore
|
||||
except OSError as e:
|
||||
raise TemplateNotFound(template) from e
|
||||
|
||||
# Could use the zip's mtime for all template mtimes, but
|
||||
# would need to safely reload the module if it's out of
|
||||
# date, so just report it as always current.
|
||||
up_to_date = None
|
||||
|
||||
return source.decode(self.encoding), p, up_to_date
|
||||
|
||||
def list_templates(self) -> t.List[str]:
|
||||
results: t.List[str] = []
|
||||
|
||||
if self._archive is None:
|
||||
# Package is a directory.
|
||||
offset = len(self._template_root)
|
||||
|
||||
for dirpath, _, filenames in os.walk(self._template_root):
|
||||
dirpath = dirpath[offset:].lstrip(os.path.sep)
|
||||
results.extend(
|
||||
os.path.join(dirpath, name).replace(os.path.sep, "/")
|
||||
for name in filenames
|
||||
)
|
||||
else:
|
||||
files = _get_zipimporter_files(self._loader)
|
||||
|
||||
# Package is a zip file.
|
||||
prefix = (
|
||||
self._template_root[len(self._archive) :].lstrip(os.path.sep)
|
||||
+ os.path.sep
|
||||
)
|
||||
offset = len(prefix)
|
||||
|
||||
for name in files:
|
||||
# Find names under the templates directory that aren't directories.
|
||||
if name.startswith(prefix) and name[-1] != os.path.sep:
|
||||
results.append(name[offset:].replace(os.path.sep, "/"))
|
||||
|
||||
results.sort()
|
||||
return results
|
||||
|
||||
|
||||
class DictLoader(BaseLoader):
|
||||
"""Loads a template from a Python dict mapping template names to
|
||||
template source. This loader is useful for unittesting:
|
||||
|
||||
>>> loader = DictLoader({'index.html': 'source here'})
|
||||
|
||||
Because auto reloading is rarely useful this is disabled by default.
|
||||
"""
|
||||
|
||||
def __init__(self, mapping: t.Mapping[str, str]) -> None:
|
||||
self.mapping = mapping
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> t.Tuple[str, None, t.Callable[[], bool]]:
|
||||
if template in self.mapping:
|
||||
source = self.mapping[template]
|
||||
return source, None, lambda: source == self.mapping.get(template)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self) -> t.List[str]:
|
||||
return sorted(self.mapping)
|
||||
|
||||
|
||||
class FunctionLoader(BaseLoader):
|
||||
"""A loader that is passed a function which does the loading. The
|
||||
function receives the name of the template and has to return either
|
||||
a string with the template source, a tuple in the form ``(source,
|
||||
filename, uptodatefunc)`` or `None` if the template does not exist.
|
||||
|
||||
>>> def load_template(name):
|
||||
... if name == 'index.html':
|
||||
... return '...'
|
||||
...
|
||||
>>> loader = FunctionLoader(load_template)
|
||||
|
||||
The `uptodatefunc` is a function that is called if autoreload is enabled
|
||||
and has to return `True` if the template is still up to date. For more
|
||||
details have a look at :meth:`BaseLoader.get_source` which has the same
|
||||
return value.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
load_func: t.Callable[
|
||||
[str],
|
||||
t.Optional[
|
||||
t.Union[
|
||||
str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
|
||||
]
|
||||
],
|
||||
],
|
||||
) -> None:
|
||||
self.load_func = load_func
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||
rv = self.load_func(template)
|
||||
|
||||
if rv is None:
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
if isinstance(rv, str):
|
||||
return rv, None, None
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class PrefixLoader(BaseLoader):
|
||||
"""A loader that is passed a dict of loaders where each loader is bound
|
||||
to a prefix. The prefix is delimited from the template by a slash per
|
||||
default, which can be changed by setting the `delimiter` argument to
|
||||
something else::
|
||||
|
||||
loader = PrefixLoader({
|
||||
'app1': PackageLoader('mypackage.app1'),
|
||||
'app2': PackageLoader('mypackage.app2')
|
||||
})
|
||||
|
||||
By loading ``'app1/index.html'`` the file from the app1 package is loaded,
|
||||
by loading ``'app2/index.html'`` the file from the second.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
|
||||
) -> None:
|
||||
self.mapping = mapping
|
||||
self.delimiter = delimiter
|
||||
|
||||
def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
|
||||
try:
|
||||
prefix, name = template.split(self.delimiter, 1)
|
||||
loader = self.mapping[prefix]
|
||||
except (ValueError, KeyError) as e:
|
||||
raise TemplateNotFound(template) from e
|
||||
return loader, name
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||
loader, name = self.get_loader(template)
|
||||
try:
|
||||
return loader.get_source(environment, name)
|
||||
except TemplateNotFound as e:
|
||||
# re-raise the exception with the correct filename here.
|
||||
# (the one that includes the prefix)
|
||||
raise TemplateNotFound(template) from e
|
||||
|
||||
@internalcode
|
||||
def load(
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
loader, local_name = self.get_loader(name)
|
||||
try:
|
||||
return loader.load(environment, local_name, globals)
|
||||
except TemplateNotFound as e:
|
||||
# re-raise the exception with the correct filename here.
|
||||
# (the one that includes the prefix)
|
||||
raise TemplateNotFound(name) from e
|
||||
|
||||
def list_templates(self) -> t.List[str]:
|
||||
result = []
|
||||
for prefix, loader in self.mapping.items():
|
||||
for template in loader.list_templates():
|
||||
result.append(prefix + self.delimiter + template)
|
||||
return result
|
||||
|
||||
|
||||
class ChoiceLoader(BaseLoader):
|
||||
"""This loader works like the `PrefixLoader` just that no prefix is
|
||||
specified. If a template could not be found by one loader the next one
|
||||
is tried.
|
||||
|
||||
>>> loader = ChoiceLoader([
|
||||
... FileSystemLoader('/path/to/user/templates'),
|
||||
... FileSystemLoader('/path/to/system/templates')
|
||||
... ])
|
||||
|
||||
This is useful if you want to allow users to override builtin templates
|
||||
from a different location.
|
||||
"""
|
||||
|
||||
def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
|
||||
self.loaders = loaders
|
||||
|
||||
def get_source(
|
||||
self, environment: "Environment", template: str
|
||||
) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
return loader.get_source(environment, template)
|
||||
except TemplateNotFound:
|
||||
pass
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
@internalcode
|
||||
def load(
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
return loader.load(environment, name, globals)
|
||||
except TemplateNotFound:
|
||||
pass
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
def list_templates(self) -> t.List[str]:
|
||||
found = set()
|
||||
for loader in self.loaders:
|
||||
found.update(loader.list_templates())
|
||||
return sorted(found)
|
||||
|
||||
|
||||
class _TemplateModule(ModuleType):
|
||||
"""Like a normal module but with support for weak references"""
|
||||
|
||||
|
||||
class ModuleLoader(BaseLoader):
|
||||
"""This loader loads templates from precompiled templates.
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> loader = ModuleLoader('/path/to/compiled/templates')
|
||||
|
||||
Templates can be precompiled with :meth:`Environment.compile_templates`.
|
||||
"""
|
||||
|
||||
has_source_access = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path: t.Union[
|
||||
str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
|
||||
],
|
||||
) -> None:
|
||||
package_name = f"_jinja2_module_templates_{id(self):x}"
|
||||
|
||||
# create a fake module that looks for the templates in the
|
||||
# path given.
|
||||
mod = _TemplateModule(package_name)
|
||||
|
||||
if not isinstance(path, abc.Iterable) or isinstance(path, str):
|
||||
path = [path]
|
||||
|
||||
mod.__path__ = [os.fspath(p) for p in path]
|
||||
|
||||
sys.modules[package_name] = weakref.proxy(
|
||||
mod, lambda x: sys.modules.pop(package_name, None)
|
||||
)
|
||||
|
||||
# the only strong reference, the sys.modules entry is weak
|
||||
# so that the garbage collector can remove it once the
|
||||
# loader that created it goes out of business.
|
||||
self.module = mod
|
||||
self.package_name = package_name
|
||||
|
||||
@staticmethod
|
||||
def get_template_key(name: str) -> str:
|
||||
return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def get_module_filename(name: str) -> str:
|
||||
return ModuleLoader.get_template_key(name) + ".py"
|
||||
|
||||
@internalcode
|
||||
def load(
|
||||
self,
|
||||
environment: "Environment",
|
||||
name: str,
|
||||
globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
|
||||
) -> "Template":
|
||||
key = self.get_template_key(name)
|
||||
module = f"{self.package_name}.{key}"
|
||||
mod = getattr(self.module, module, None)
|
||||
|
||||
if mod is None:
|
||||
try:
|
||||
mod = __import__(module, None, None, ["root"])
|
||||
except ImportError as e:
|
||||
raise TemplateNotFound(name) from e
|
||||
|
||||
# remove the entry from sys.modules, we only want the attribute
|
||||
# on the module object we have stored on the loader.
|
||||
sys.modules.pop(module, None)
|
||||
|
||||
if globals is None:
|
||||
globals = {}
|
||||
|
||||
return environment.template_class.from_module_dict(
|
||||
environment, mod.__dict__, globals
|
||||
)
|
112
env/lib/python3.11/site-packages/jinja2/meta.py
vendored
Normal file
112
env/lib/python3.11/site-packages/jinja2/meta.py
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
"""Functions that expose information about templates that might be
|
||||
interesting for introspection.
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
|
||||
from . import nodes
|
||||
from .compiler import CodeGenerator
|
||||
from .compiler import Frame
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .environment import Environment
|
||||
|
||||
|
||||
class TrackingCodeGenerator(CodeGenerator):
|
||||
"""We abuse the code generator for introspection."""
|
||||
|
||||
def __init__(self, environment: "Environment") -> None:
|
||||
super().__init__(environment, "<introspection>", "<introspection>")
|
||||
self.undeclared_identifiers: t.Set[str] = set()
|
||||
|
||||
def write(self, x: str) -> None:
|
||||
"""Don't write."""
|
||||
|
||||
def enter_frame(self, frame: Frame) -> None:
|
||||
"""Remember all undeclared identifiers."""
|
||||
super().enter_frame(frame)
|
||||
|
||||
for _, (action, param) in frame.symbols.loads.items():
|
||||
if action == "resolve" and param not in self.environment.globals:
|
||||
self.undeclared_identifiers.add(param)
|
||||
|
||||
|
||||
def find_undeclared_variables(ast: nodes.Template) -> t.Set[str]:
|
||||
"""Returns a set of all variables in the AST that will be looked up from
|
||||
the context at runtime. Because at compile time it's not known which
|
||||
variables will be used depending on the path the execution takes at
|
||||
runtime, all variables are returned.
|
||||
|
||||
>>> from jinja2 import Environment, meta
|
||||
>>> env = Environment()
|
||||
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
||||
>>> meta.find_undeclared_variables(ast) == {'bar'}
|
||||
True
|
||||
|
||||
.. admonition:: Implementation
|
||||
|
||||
Internally the code generator is used for finding undeclared variables.
|
||||
This is good to know because the code generator might raise a
|
||||
:exc:`TemplateAssertionError` during compilation and as a matter of
|
||||
fact this function can currently raise that exception as well.
|
||||
"""
|
||||
codegen = TrackingCodeGenerator(ast.environment) # type: ignore
|
||||
codegen.visit(ast)
|
||||
return codegen.undeclared_identifiers
|
||||
|
||||
|
||||
_ref_types = (nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include)
|
||||
_RefType = t.Union[nodes.Extends, nodes.FromImport, nodes.Import, nodes.Include]
|
||||
|
||||
|
||||
def find_referenced_templates(ast: nodes.Template) -> t.Iterator[t.Optional[str]]:
|
||||
"""Finds all the referenced templates from the AST. This will return an
|
||||
iterator over all the hardcoded template extensions, inclusions and
|
||||
imports. If dynamic inheritance or inclusion is used, `None` will be
|
||||
yielded.
|
||||
|
||||
>>> from jinja2 import Environment, meta
|
||||
>>> env = Environment()
|
||||
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
|
||||
>>> list(meta.find_referenced_templates(ast))
|
||||
['layout.html', None]
|
||||
|
||||
This function is useful for dependency tracking. For example if you want
|
||||
to rebuild parts of the website after a layout template has changed.
|
||||
"""
|
||||
template_name: t.Any
|
||||
|
||||
for node in ast.find_all(_ref_types):
|
||||
template: nodes.Expr = node.template # type: ignore
|
||||
|
||||
if not isinstance(template, nodes.Const):
|
||||
# a tuple with some non consts in there
|
||||
if isinstance(template, (nodes.Tuple, nodes.List)):
|
||||
for template_name in template.items:
|
||||
# something const, only yield the strings and ignore
|
||||
# non-string consts that really just make no sense
|
||||
if isinstance(template_name, nodes.Const):
|
||||
if isinstance(template_name.value, str):
|
||||
yield template_name.value
|
||||
# something dynamic in there
|
||||
else:
|
||||
yield None
|
||||
# something dynamic we don't know about here
|
||||
else:
|
||||
yield None
|
||||
continue
|
||||
# constant is a basestring, direct template name
|
||||
if isinstance(template.value, str):
|
||||
yield template.value
|
||||
# a tuple or list (latter *should* not happen) made of consts,
|
||||
# yield the consts that are strings. We could warn here for
|
||||
# non string values
|
||||
elif isinstance(node, nodes.Include) and isinstance(
|
||||
template.value, (tuple, list)
|
||||
):
|
||||
for template_name in template.value:
|
||||
if isinstance(template_name, str):
|
||||
yield template_name
|
||||
# something else we don't care about, we could warn here
|
||||
else:
|
||||
yield None
|
130
env/lib/python3.11/site-packages/jinja2/nativetypes.py
vendored
Normal file
130
env/lib/python3.11/site-packages/jinja2/nativetypes.py
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
import typing as t
|
||||
from ast import literal_eval
|
||||
from ast import parse
|
||||
from itertools import chain
|
||||
from itertools import islice
|
||||
from types import GeneratorType
|
||||
|
||||
from . import nodes
|
||||
from .compiler import CodeGenerator
|
||||
from .compiler import Frame
|
||||
from .compiler import has_safe_repr
|
||||
from .environment import Environment
|
||||
from .environment import Template
|
||||
|
||||
|
||||
def native_concat(values: t.Iterable[t.Any]) -> t.Optional[t.Any]:
|
||||
"""Return a native Python type from the list of compiled nodes. If
|
||||
the result is a single node, its value is returned. Otherwise, the
|
||||
nodes are concatenated as strings. If the result can be parsed with
|
||||
:func:`ast.literal_eval`, the parsed value is returned. Otherwise,
|
||||
the string is returned.
|
||||
|
||||
:param values: Iterable of outputs to concatenate.
|
||||
"""
|
||||
head = list(islice(values, 2))
|
||||
|
||||
if not head:
|
||||
return None
|
||||
|
||||
if len(head) == 1:
|
||||
raw = head[0]
|
||||
if not isinstance(raw, str):
|
||||
return raw
|
||||
else:
|
||||
if isinstance(values, GeneratorType):
|
||||
values = chain(head, values)
|
||||
raw = "".join([str(v) for v in values])
|
||||
|
||||
try:
|
||||
return literal_eval(
|
||||
# In Python 3.10+ ast.literal_eval removes leading spaces/tabs
|
||||
# from the given string. For backwards compatibility we need to
|
||||
# parse the string ourselves without removing leading spaces/tabs.
|
||||
parse(raw, mode="eval")
|
||||
)
|
||||
except (ValueError, SyntaxError, MemoryError):
|
||||
return raw
|
||||
|
||||
|
||||
class NativeCodeGenerator(CodeGenerator):
|
||||
"""A code generator which renders Python types by not adding
|
||||
``str()`` around output nodes.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _default_finalize(value: t.Any) -> t.Any:
|
||||
return value
|
||||
|
||||
def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
|
||||
return repr("".join([str(v) for v in group]))
|
||||
|
||||
def _output_child_to_const(
|
||||
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
||||
) -> t.Any:
|
||||
const = node.as_const(frame.eval_ctx)
|
||||
|
||||
if not has_safe_repr(const):
|
||||
raise nodes.Impossible()
|
||||
|
||||
if isinstance(node, nodes.TemplateData):
|
||||
return const
|
||||
|
||||
return finalize.const(const) # type: ignore
|
||||
|
||||
def _output_child_pre(
|
||||
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
||||
) -> None:
|
||||
if finalize.src is not None:
|
||||
self.write(finalize.src)
|
||||
|
||||
def _output_child_post(
|
||||
self, node: nodes.Expr, frame: Frame, finalize: CodeGenerator._FinalizeInfo
|
||||
) -> None:
|
||||
if finalize.src is not None:
|
||||
self.write(")")
|
||||
|
||||
|
||||
class NativeEnvironment(Environment):
|
||||
"""An environment that renders templates to native Python types."""
|
||||
|
||||
code_generator_class = NativeCodeGenerator
|
||||
concat = staticmethod(native_concat) # type: ignore
|
||||
|
||||
|
||||
class NativeTemplate(Template):
|
||||
environment_class = NativeEnvironment
|
||||
|
||||
def render(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
"""Render the template to produce a native Python type. If the
|
||||
result is a single node, its value is returned. Otherwise, the
|
||||
nodes are concatenated as strings. If the result can be parsed
|
||||
with :func:`ast.literal_eval`, the parsed value is returned.
|
||||
Otherwise, the string is returned.
|
||||
"""
|
||||
ctx = self.new_context(dict(*args, **kwargs))
|
||||
|
||||
try:
|
||||
return self.environment_class.concat( # type: ignore
|
||||
self.root_render_func(ctx)
|
||||
)
|
||||
except Exception:
|
||||
return self.environment.handle_exception()
|
||||
|
||||
async def render_async(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
if not self.environment.is_async:
|
||||
raise RuntimeError(
|
||||
"The environment was not created with async mode enabled."
|
||||
)
|
||||
|
||||
ctx = self.new_context(dict(*args, **kwargs))
|
||||
|
||||
try:
|
||||
return self.environment_class.concat( # type: ignore
|
||||
[n async for n in self.root_render_func(ctx)] # type: ignore
|
||||
)
|
||||
except Exception:
|
||||
return self.environment.handle_exception()
|
||||
|
||||
|
||||
NativeEnvironment.template_class = NativeTemplate
|
1206
env/lib/python3.11/site-packages/jinja2/nodes.py
vendored
Normal file
1206
env/lib/python3.11/site-packages/jinja2/nodes.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
48
env/lib/python3.11/site-packages/jinja2/optimizer.py
vendored
Normal file
48
env/lib/python3.11/site-packages/jinja2/optimizer.py
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
"""The optimizer tries to constant fold expressions and modify the AST
|
||||
in place so that it should be faster to evaluate.
|
||||
|
||||
Because the AST does not contain all the scoping information and the
|
||||
compiler has to find that out, we cannot do all the optimizations we
|
||||
want. For example, loop unrolling doesn't work because unrolled loops
|
||||
would have a different scope. The solution would be a second syntax tree
|
||||
that stored the scoping rules.
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
|
||||
from . import nodes
|
||||
from .visitor import NodeTransformer
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .environment import Environment
|
||||
|
||||
|
||||
def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node:
|
||||
"""The context hint can be used to perform an static optimization
|
||||
based on the context given."""
|
||||
optimizer = Optimizer(environment)
|
||||
return t.cast(nodes.Node, optimizer.visit(node))
|
||||
|
||||
|
||||
class Optimizer(NodeTransformer):
|
||||
def __init__(self, environment: "t.Optional[Environment]") -> None:
|
||||
self.environment = environment
|
||||
|
||||
def generic_visit(
|
||||
self, node: nodes.Node, *args: t.Any, **kwargs: t.Any
|
||||
) -> nodes.Node:
|
||||
node = super().generic_visit(node, *args, **kwargs)
|
||||
|
||||
# Do constant folding. Some other nodes besides Expr have
|
||||
# as_const, but folding them causes errors later on.
|
||||
if isinstance(node, nodes.Expr):
|
||||
try:
|
||||
return nodes.Const.from_untrusted(
|
||||
node.as_const(args[0] if args else None),
|
||||
lineno=node.lineno,
|
||||
environment=self.environment,
|
||||
)
|
||||
except nodes.Impossible:
|
||||
pass
|
||||
|
||||
return node
|
1049
env/lib/python3.11/site-packages/jinja2/parser.py
vendored
Normal file
1049
env/lib/python3.11/site-packages/jinja2/parser.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
0
env/lib/python3.11/site-packages/jinja2/py.typed
vendored
Normal file
0
env/lib/python3.11/site-packages/jinja2/py.typed
vendored
Normal file
1062
env/lib/python3.11/site-packages/jinja2/runtime.py
vendored
Normal file
1062
env/lib/python3.11/site-packages/jinja2/runtime.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
436
env/lib/python3.11/site-packages/jinja2/sandbox.py
vendored
Normal file
436
env/lib/python3.11/site-packages/jinja2/sandbox.py
vendored
Normal file
@ -0,0 +1,436 @@
|
||||
"""A sandbox layer that ensures unsafe operations cannot be performed.
|
||||
Useful when the template itself comes from an untrusted source.
|
||||
"""
|
||||
|
||||
import operator
|
||||
import types
|
||||
import typing as t
|
||||
from _string import formatter_field_name_split # type: ignore
|
||||
from collections import abc
|
||||
from collections import deque
|
||||
from functools import update_wrapper
|
||||
from string import Formatter
|
||||
|
||||
from markupsafe import EscapeFormatter
|
||||
from markupsafe import Markup
|
||||
|
||||
from .environment import Environment
|
||||
from .exceptions import SecurityError
|
||||
from .runtime import Context
|
||||
from .runtime import Undefined
|
||||
|
||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
|
||||
#: maximum number of items a range may produce
|
||||
MAX_RANGE = 100000
|
||||
|
||||
#: Unsafe function attributes.
|
||||
UNSAFE_FUNCTION_ATTRIBUTES: t.Set[str] = set()
|
||||
|
||||
#: Unsafe method attributes. Function attributes are unsafe for methods too.
|
||||
UNSAFE_METHOD_ATTRIBUTES: t.Set[str] = set()
|
||||
|
||||
#: unsafe generator attributes.
|
||||
UNSAFE_GENERATOR_ATTRIBUTES = {"gi_frame", "gi_code"}
|
||||
|
||||
#: unsafe attributes on coroutines
|
||||
UNSAFE_COROUTINE_ATTRIBUTES = {"cr_frame", "cr_code"}
|
||||
|
||||
#: unsafe attributes on async generators
|
||||
UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = {"ag_code", "ag_frame"}
|
||||
|
||||
_mutable_spec: t.Tuple[t.Tuple[t.Type[t.Any], t.FrozenSet[str]], ...] = (
|
||||
(
|
||||
abc.MutableSet,
|
||||
frozenset(
|
||||
[
|
||||
"add",
|
||||
"clear",
|
||||
"difference_update",
|
||||
"discard",
|
||||
"pop",
|
||||
"remove",
|
||||
"symmetric_difference_update",
|
||||
"update",
|
||||
]
|
||||
),
|
||||
),
|
||||
(
|
||||
abc.MutableMapping,
|
||||
frozenset(["clear", "pop", "popitem", "setdefault", "update"]),
|
||||
),
|
||||
(
|
||||
abc.MutableSequence,
|
||||
frozenset(
|
||||
["append", "clear", "pop", "reverse", "insert", "sort", "extend", "remove"]
|
||||
),
|
||||
),
|
||||
(
|
||||
deque,
|
||||
frozenset(
|
||||
[
|
||||
"append",
|
||||
"appendleft",
|
||||
"clear",
|
||||
"extend",
|
||||
"extendleft",
|
||||
"pop",
|
||||
"popleft",
|
||||
"remove",
|
||||
"rotate",
|
||||
]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def safe_range(*args: int) -> range:
|
||||
"""A range that can't generate ranges with a length of more than
|
||||
MAX_RANGE items.
|
||||
"""
|
||||
rng = range(*args)
|
||||
|
||||
if len(rng) > MAX_RANGE:
|
||||
raise OverflowError(
|
||||
"Range too big. The sandbox blocks ranges larger than"
|
||||
f" MAX_RANGE ({MAX_RANGE})."
|
||||
)
|
||||
|
||||
return rng
|
||||
|
||||
|
||||
def unsafe(f: F) -> F:
|
||||
"""Marks a function or method as unsafe.
|
||||
|
||||
.. code-block: python
|
||||
|
||||
@unsafe
|
||||
def delete(self):
|
||||
pass
|
||||
"""
|
||||
f.unsafe_callable = True # type: ignore
|
||||
return f
|
||||
|
||||
|
||||
def is_internal_attribute(obj: t.Any, attr: str) -> bool:
|
||||
"""Test if the attribute given is an internal python attribute. For
|
||||
example this function returns `True` for the `func_code` attribute of
|
||||
python objects. This is useful if the environment method
|
||||
:meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
|
||||
|
||||
>>> from jinja2.sandbox import is_internal_attribute
|
||||
>>> is_internal_attribute(str, "mro")
|
||||
True
|
||||
>>> is_internal_attribute(str, "upper")
|
||||
False
|
||||
"""
|
||||
if isinstance(obj, types.FunctionType):
|
||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
||||
return True
|
||||
elif isinstance(obj, types.MethodType):
|
||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES or attr in UNSAFE_METHOD_ATTRIBUTES:
|
||||
return True
|
||||
elif isinstance(obj, type):
|
||||
if attr == "mro":
|
||||
return True
|
||||
elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
|
||||
return True
|
||||
elif isinstance(obj, types.GeneratorType):
|
||||
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
|
||||
return True
|
||||
elif hasattr(types, "CoroutineType") and isinstance(obj, types.CoroutineType):
|
||||
if attr in UNSAFE_COROUTINE_ATTRIBUTES:
|
||||
return True
|
||||
elif hasattr(types, "AsyncGeneratorType") and isinstance(
|
||||
obj, types.AsyncGeneratorType
|
||||
):
|
||||
if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
|
||||
return True
|
||||
return attr.startswith("__")
|
||||
|
||||
|
||||
def modifies_known_mutable(obj: t.Any, attr: str) -> bool:
|
||||
"""This function checks if an attribute on a builtin mutable object
|
||||
(list, dict, set or deque) or the corresponding ABCs would modify it
|
||||
if called.
|
||||
|
||||
>>> modifies_known_mutable({}, "clear")
|
||||
True
|
||||
>>> modifies_known_mutable({}, "keys")
|
||||
False
|
||||
>>> modifies_known_mutable([], "append")
|
||||
True
|
||||
>>> modifies_known_mutable([], "index")
|
||||
False
|
||||
|
||||
If called with an unsupported object, ``False`` is returned.
|
||||
|
||||
>>> modifies_known_mutable("foo", "upper")
|
||||
False
|
||||
"""
|
||||
for typespec, unsafe in _mutable_spec:
|
||||
if isinstance(obj, typespec):
|
||||
return attr in unsafe
|
||||
return False
|
||||
|
||||
|
||||
class SandboxedEnvironment(Environment):
|
||||
"""The sandboxed environment. It works like the regular environment but
|
||||
tells the compiler to generate sandboxed code. Additionally subclasses of
|
||||
this environment may override the methods that tell the runtime what
|
||||
attributes or functions are safe to access.
|
||||
|
||||
If the template tries to access insecure code a :exc:`SecurityError` is
|
||||
raised. However also other exceptions may occur during the rendering so
|
||||
the caller has to ensure that all exceptions are caught.
|
||||
"""
|
||||
|
||||
sandboxed = True
|
||||
|
||||
#: default callback table for the binary operators. A copy of this is
|
||||
#: available on each instance of a sandboxed environment as
|
||||
#: :attr:`binop_table`
|
||||
default_binop_table: t.Dict[str, t.Callable[[t.Any, t.Any], t.Any]] = {
|
||||
"+": operator.add,
|
||||
"-": operator.sub,
|
||||
"*": operator.mul,
|
||||
"/": operator.truediv,
|
||||
"//": operator.floordiv,
|
||||
"**": operator.pow,
|
||||
"%": operator.mod,
|
||||
}
|
||||
|
||||
#: default callback table for the unary operators. A copy of this is
|
||||
#: available on each instance of a sandboxed environment as
|
||||
#: :attr:`unop_table`
|
||||
default_unop_table: t.Dict[str, t.Callable[[t.Any], t.Any]] = {
|
||||
"+": operator.pos,
|
||||
"-": operator.neg,
|
||||
}
|
||||
|
||||
#: a set of binary operators that should be intercepted. Each operator
|
||||
#: that is added to this set (empty by default) is delegated to the
|
||||
#: :meth:`call_binop` method that will perform the operator. The default
|
||||
#: operator callback is specified by :attr:`binop_table`.
|
||||
#:
|
||||
#: The following binary operators are interceptable:
|
||||
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
|
||||
#:
|
||||
#: The default operation form the operator table corresponds to the
|
||||
#: builtin function. Intercepted calls are always slower than the native
|
||||
#: operator call, so make sure only to intercept the ones you are
|
||||
#: interested in.
|
||||
#:
|
||||
#: .. versionadded:: 2.6
|
||||
intercepted_binops: t.FrozenSet[str] = frozenset()
|
||||
|
||||
#: a set of unary operators that should be intercepted. Each operator
|
||||
#: that is added to this set (empty by default) is delegated to the
|
||||
#: :meth:`call_unop` method that will perform the operator. The default
|
||||
#: operator callback is specified by :attr:`unop_table`.
|
||||
#:
|
||||
#: The following unary operators are interceptable: ``+``, ``-``
|
||||
#:
|
||||
#: The default operation form the operator table corresponds to the
|
||||
#: builtin function. Intercepted calls are always slower than the native
|
||||
#: operator call, so make sure only to intercept the ones you are
|
||||
#: interested in.
|
||||
#:
|
||||
#: .. versionadded:: 2.6
|
||||
intercepted_unops: t.FrozenSet[str] = frozenset()
|
||||
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.globals["range"] = safe_range
|
||||
self.binop_table = self.default_binop_table.copy()
|
||||
self.unop_table = self.default_unop_table.copy()
|
||||
|
||||
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
|
||||
"""The sandboxed environment will call this method to check if the
|
||||
attribute of an object is safe to access. Per default all attributes
|
||||
starting with an underscore are considered private as well as the
|
||||
special attributes of internal python objects as returned by the
|
||||
:func:`is_internal_attribute` function.
|
||||
"""
|
||||
return not (attr.startswith("_") or is_internal_attribute(obj, attr))
|
||||
|
||||
def is_safe_callable(self, obj: t.Any) -> bool:
|
||||
"""Check if an object is safely callable. By default callables
|
||||
are considered safe unless decorated with :func:`unsafe`.
|
||||
|
||||
This also recognizes the Django convention of setting
|
||||
``func.alters_data = True``.
|
||||
"""
|
||||
return not (
|
||||
getattr(obj, "unsafe_callable", False) or getattr(obj, "alters_data", False)
|
||||
)
|
||||
|
||||
def call_binop(
|
||||
self, context: Context, operator: str, left: t.Any, right: t.Any
|
||||
) -> t.Any:
|
||||
"""For intercepted binary operator calls (:meth:`intercepted_binops`)
|
||||
this function is executed instead of the builtin operator. This can
|
||||
be used to fine tune the behavior of certain operators.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return self.binop_table[operator](left, right)
|
||||
|
||||
def call_unop(self, context: Context, operator: str, arg: t.Any) -> t.Any:
|
||||
"""For intercepted unary operator calls (:meth:`intercepted_unops`)
|
||||
this function is executed instead of the builtin operator. This can
|
||||
be used to fine tune the behavior of certain operators.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return self.unop_table[operator](arg)
|
||||
|
||||
def getitem(
|
||||
self, obj: t.Any, argument: t.Union[str, t.Any]
|
||||
) -> t.Union[t.Any, Undefined]:
|
||||
"""Subscribe an object from sandboxed code."""
|
||||
try:
|
||||
return obj[argument]
|
||||
except (TypeError, LookupError):
|
||||
if isinstance(argument, str):
|
||||
try:
|
||||
attr = str(argument)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
value = getattr(obj, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
fmt = self.wrap_str_format(value)
|
||||
if fmt is not None:
|
||||
return fmt
|
||||
if self.is_safe_attribute(obj, argument, value):
|
||||
return value
|
||||
return self.unsafe_undefined(obj, argument)
|
||||
return self.undefined(obj=obj, name=argument)
|
||||
|
||||
def getattr(self, obj: t.Any, attribute: str) -> t.Union[t.Any, Undefined]:
|
||||
"""Subscribe an object from sandboxed code and prefer the
|
||||
attribute. The attribute passed *must* be a bytestring.
|
||||
"""
|
||||
try:
|
||||
value = getattr(obj, attribute)
|
||||
except AttributeError:
|
||||
try:
|
||||
return obj[attribute]
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
else:
|
||||
fmt = self.wrap_str_format(value)
|
||||
if fmt is not None:
|
||||
return fmt
|
||||
if self.is_safe_attribute(obj, attribute, value):
|
||||
return value
|
||||
return self.unsafe_undefined(obj, attribute)
|
||||
return self.undefined(obj=obj, name=attribute)
|
||||
|
||||
def unsafe_undefined(self, obj: t.Any, attribute: str) -> Undefined:
|
||||
"""Return an undefined object for unsafe attributes."""
|
||||
return self.undefined(
|
||||
f"access to attribute {attribute!r} of"
|
||||
f" {type(obj).__name__!r} object is unsafe.",
|
||||
name=attribute,
|
||||
obj=obj,
|
||||
exc=SecurityError,
|
||||
)
|
||||
|
||||
def wrap_str_format(self, value: t.Any) -> t.Optional[t.Callable[..., str]]:
|
||||
"""If the given value is a ``str.format`` or ``str.format_map`` method,
|
||||
return a new function than handles sandboxing. This is done at access
|
||||
rather than in :meth:`call`, so that calls made without ``call`` are
|
||||
also sandboxed.
|
||||
"""
|
||||
if not isinstance(
|
||||
value, (types.MethodType, types.BuiltinMethodType)
|
||||
) or value.__name__ not in ("format", "format_map"):
|
||||
return None
|
||||
|
||||
f_self: t.Any = value.__self__
|
||||
|
||||
if not isinstance(f_self, str):
|
||||
return None
|
||||
|
||||
str_type: t.Type[str] = type(f_self)
|
||||
is_format_map = value.__name__ == "format_map"
|
||||
formatter: SandboxedFormatter
|
||||
|
||||
if isinstance(f_self, Markup):
|
||||
formatter = SandboxedEscapeFormatter(self, escape=f_self.escape)
|
||||
else:
|
||||
formatter = SandboxedFormatter(self)
|
||||
|
||||
vformat = formatter.vformat
|
||||
|
||||
def wrapper(*args: t.Any, **kwargs: t.Any) -> str:
|
||||
if is_format_map:
|
||||
if kwargs:
|
||||
raise TypeError("format_map() takes no keyword arguments")
|
||||
|
||||
if len(args) != 1:
|
||||
raise TypeError(
|
||||
f"format_map() takes exactly one argument ({len(args)} given)"
|
||||
)
|
||||
|
||||
kwargs = args[0]
|
||||
args = ()
|
||||
|
||||
return str_type(vformat(f_self, args, kwargs))
|
||||
|
||||
return update_wrapper(wrapper, value)
|
||||
|
||||
def call(
|
||||
__self, # noqa: B902
|
||||
__context: Context,
|
||||
__obj: t.Any,
|
||||
*args: t.Any,
|
||||
**kwargs: t.Any,
|
||||
) -> t.Any:
|
||||
"""Call an object from sandboxed code."""
|
||||
|
||||
# the double prefixes are to avoid double keyword argument
|
||||
# errors when proxying the call.
|
||||
if not __self.is_safe_callable(__obj):
|
||||
raise SecurityError(f"{__obj!r} is not safely callable")
|
||||
return __context.call(__obj, *args, **kwargs)
|
||||
|
||||
|
||||
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
||||
"""Works exactly like the regular `SandboxedEnvironment` but does not
|
||||
permit modifications on the builtin mutable objects `list`, `set`, and
|
||||
`dict` by using the :func:`modifies_known_mutable` function.
|
||||
"""
|
||||
|
||||
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
|
||||
if not super().is_safe_attribute(obj, attr, value):
|
||||
return False
|
||||
|
||||
return not modifies_known_mutable(obj, attr)
|
||||
|
||||
|
||||
class SandboxedFormatter(Formatter):
|
||||
def __init__(self, env: Environment, **kwargs: t.Any) -> None:
|
||||
self._env = env
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def get_field(
|
||||
self, field_name: str, args: t.Sequence[t.Any], kwargs: t.Mapping[str, t.Any]
|
||||
) -> t.Tuple[t.Any, str]:
|
||||
first, rest = formatter_field_name_split(field_name)
|
||||
obj = self.get_value(first, args, kwargs)
|
||||
for is_attr, i in rest:
|
||||
if is_attr:
|
||||
obj = self._env.getattr(obj, i)
|
||||
else:
|
||||
obj = self._env.getitem(obj, i)
|
||||
return obj, first
|
||||
|
||||
|
||||
class SandboxedEscapeFormatter(SandboxedFormatter, EscapeFormatter):
|
||||
pass
|
256
env/lib/python3.11/site-packages/jinja2/tests.py
vendored
Normal file
256
env/lib/python3.11/site-packages/jinja2/tests.py
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
"""Built-in template tests used with the ``is`` operator."""
|
||||
|
||||
import operator
|
||||
import typing as t
|
||||
from collections import abc
|
||||
from numbers import Number
|
||||
|
||||
from .runtime import Undefined
|
||||
from .utils import pass_environment
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .environment import Environment
|
||||
|
||||
|
||||
def test_odd(value: int) -> bool:
|
||||
"""Return true if the variable is odd."""
|
||||
return value % 2 == 1
|
||||
|
||||
|
||||
def test_even(value: int) -> bool:
|
||||
"""Return true if the variable is even."""
|
||||
return value % 2 == 0
|
||||
|
||||
|
||||
def test_divisibleby(value: int, num: int) -> bool:
|
||||
"""Check if a variable is divisible by a number."""
|
||||
return value % num == 0
|
||||
|
||||
|
||||
def test_defined(value: t.Any) -> bool:
|
||||
"""Return true if the variable is defined:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% if variable is defined %}
|
||||
value of variable: {{ variable }}
|
||||
{% else %}
|
||||
variable is not defined
|
||||
{% endif %}
|
||||
|
||||
See the :func:`default` filter for a simple way to set undefined
|
||||
variables.
|
||||
"""
|
||||
return not isinstance(value, Undefined)
|
||||
|
||||
|
||||
def test_undefined(value: t.Any) -> bool:
|
||||
"""Like :func:`defined` but the other way round."""
|
||||
return isinstance(value, Undefined)
|
||||
|
||||
|
||||
@pass_environment
|
||||
def test_filter(env: "Environment", value: str) -> bool:
|
||||
"""Check if a filter exists by name. Useful if a filter may be
|
||||
optionally available.
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% if 'markdown' is filter %}
|
||||
{{ value | markdown }}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
|
||||
.. versionadded:: 3.0
|
||||
"""
|
||||
return value in env.filters
|
||||
|
||||
|
||||
@pass_environment
|
||||
def test_test(env: "Environment", value: str) -> bool:
|
||||
"""Check if a test exists by name. Useful if a test may be
|
||||
optionally available.
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% if 'loud' is test %}
|
||||
{% if value is loud %}
|
||||
{{ value|upper }}
|
||||
{% else %}
|
||||
{{ value|lower }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
|
||||
.. versionadded:: 3.0
|
||||
"""
|
||||
return value in env.tests
|
||||
|
||||
|
||||
def test_none(value: t.Any) -> bool:
|
||||
"""Return true if the variable is none."""
|
||||
return value is None
|
||||
|
||||
|
||||
def test_boolean(value: t.Any) -> bool:
|
||||
"""Return true if the object is a boolean value.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
return value is True or value is False
|
||||
|
||||
|
||||
def test_false(value: t.Any) -> bool:
|
||||
"""Return true if the object is False.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
return value is False
|
||||
|
||||
|
||||
def test_true(value: t.Any) -> bool:
|
||||
"""Return true if the object is True.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
return value is True
|
||||
|
||||
|
||||
# NOTE: The existing 'number' test matches booleans and floats
|
||||
def test_integer(value: t.Any) -> bool:
|
||||
"""Return true if the object is an integer.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
return isinstance(value, int) and value is not True and value is not False
|
||||
|
||||
|
||||
# NOTE: The existing 'number' test matches booleans and integers
|
||||
def test_float(value: t.Any) -> bool:
|
||||
"""Return true if the object is a float.
|
||||
|
||||
.. versionadded:: 2.11
|
||||
"""
|
||||
return isinstance(value, float)
|
||||
|
||||
|
||||
def test_lower(value: str) -> bool:
|
||||
"""Return true if the variable is lowercased."""
|
||||
return str(value).islower()
|
||||
|
||||
|
||||
def test_upper(value: str) -> bool:
|
||||
"""Return true if the variable is uppercased."""
|
||||
return str(value).isupper()
|
||||
|
||||
|
||||
def test_string(value: t.Any) -> bool:
|
||||
"""Return true if the object is a string."""
|
||||
return isinstance(value, str)
|
||||
|
||||
|
||||
def test_mapping(value: t.Any) -> bool:
|
||||
"""Return true if the object is a mapping (dict etc.).
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return isinstance(value, abc.Mapping)
|
||||
|
||||
|
||||
def test_number(value: t.Any) -> bool:
|
||||
"""Return true if the variable is a number."""
|
||||
return isinstance(value, Number)
|
||||
|
||||
|
||||
def test_sequence(value: t.Any) -> bool:
|
||||
"""Return true if the variable is a sequence. Sequences are variables
|
||||
that are iterable.
|
||||
"""
|
||||
try:
|
||||
len(value)
|
||||
value.__getitem__ # noqa B018
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_sameas(value: t.Any, other: t.Any) -> bool:
|
||||
"""Check if an object points to the same memory address than another
|
||||
object:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% if foo.attribute is sameas false %}
|
||||
the foo attribute really is the `False` singleton
|
||||
{% endif %}
|
||||
"""
|
||||
return value is other
|
||||
|
||||
|
||||
def test_iterable(value: t.Any) -> bool:
|
||||
"""Check if it's possible to iterate over an object."""
|
||||
try:
|
||||
iter(value)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_escaped(value: t.Any) -> bool:
|
||||
"""Check if the value is escaped."""
|
||||
return hasattr(value, "__html__")
|
||||
|
||||
|
||||
def test_in(value: t.Any, seq: t.Container[t.Any]) -> bool:
|
||||
"""Check if value is in seq.
|
||||
|
||||
.. versionadded:: 2.10
|
||||
"""
|
||||
return value in seq
|
||||
|
||||
|
||||
TESTS = {
|
||||
"odd": test_odd,
|
||||
"even": test_even,
|
||||
"divisibleby": test_divisibleby,
|
||||
"defined": test_defined,
|
||||
"undefined": test_undefined,
|
||||
"filter": test_filter,
|
||||
"test": test_test,
|
||||
"none": test_none,
|
||||
"boolean": test_boolean,
|
||||
"false": test_false,
|
||||
"true": test_true,
|
||||
"integer": test_integer,
|
||||
"float": test_float,
|
||||
"lower": test_lower,
|
||||
"upper": test_upper,
|
||||
"string": test_string,
|
||||
"mapping": test_mapping,
|
||||
"number": test_number,
|
||||
"sequence": test_sequence,
|
||||
"iterable": test_iterable,
|
||||
"callable": callable,
|
||||
"sameas": test_sameas,
|
||||
"escaped": test_escaped,
|
||||
"in": test_in,
|
||||
"==": operator.eq,
|
||||
"eq": operator.eq,
|
||||
"equalto": operator.eq,
|
||||
"!=": operator.ne,
|
||||
"ne": operator.ne,
|
||||
">": operator.gt,
|
||||
"gt": operator.gt,
|
||||
"greaterthan": operator.gt,
|
||||
"ge": operator.ge,
|
||||
">=": operator.ge,
|
||||
"<": operator.lt,
|
||||
"lt": operator.lt,
|
||||
"lessthan": operator.lt,
|
||||
"<=": operator.le,
|
||||
"le": operator.le,
|
||||
}
|
766
env/lib/python3.11/site-packages/jinja2/utils.py
vendored
Normal file
766
env/lib/python3.11/site-packages/jinja2/utils.py
vendored
Normal file
@ -0,0 +1,766 @@
|
||||
import enum
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import typing as t
|
||||
from collections import abc
|
||||
from collections import deque
|
||||
from random import choice
|
||||
from random import randrange
|
||||
from threading import Lock
|
||||
from types import CodeType
|
||||
from urllib.parse import quote_from_bytes
|
||||
|
||||
import markupsafe
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
|
||||
|
||||
class _MissingType:
|
||||
def __repr__(self) -> str:
|
||||
return "missing"
|
||||
|
||||
def __reduce__(self) -> str:
|
||||
return "missing"
|
||||
|
||||
|
||||
missing: t.Any = _MissingType()
|
||||
"""Special singleton representing missing values for the runtime."""
|
||||
|
||||
internal_code: t.MutableSet[CodeType] = set()
|
||||
|
||||
concat = "".join
|
||||
|
||||
|
||||
def pass_context(f: F) -> F:
|
||||
"""Pass the :class:`~jinja2.runtime.Context` as the first argument
|
||||
to the decorated function when called while rendering a template.
|
||||
|
||||
Can be used on functions, filters, and tests.
|
||||
|
||||
If only ``Context.eval_context`` is needed, use
|
||||
:func:`pass_eval_context`. If only ``Context.environment`` is
|
||||
needed, use :func:`pass_environment`.
|
||||
|
||||
.. versionadded:: 3.0.0
|
||||
Replaces ``contextfunction`` and ``contextfilter``.
|
||||
"""
|
||||
f.jinja_pass_arg = _PassArg.context # type: ignore
|
||||
return f
|
||||
|
||||
|
||||
def pass_eval_context(f: F) -> F:
|
||||
"""Pass the :class:`~jinja2.nodes.EvalContext` as the first argument
|
||||
to the decorated function when called while rendering a template.
|
||||
See :ref:`eval-context`.
|
||||
|
||||
Can be used on functions, filters, and tests.
|
||||
|
||||
If only ``EvalContext.environment`` is needed, use
|
||||
:func:`pass_environment`.
|
||||
|
||||
.. versionadded:: 3.0.0
|
||||
Replaces ``evalcontextfunction`` and ``evalcontextfilter``.
|
||||
"""
|
||||
f.jinja_pass_arg = _PassArg.eval_context # type: ignore
|
||||
return f
|
||||
|
||||
|
||||
def pass_environment(f: F) -> F:
|
||||
"""Pass the :class:`~jinja2.Environment` as the first argument to
|
||||
the decorated function when called while rendering a template.
|
||||
|
||||
Can be used on functions, filters, and tests.
|
||||
|
||||
.. versionadded:: 3.0.0
|
||||
Replaces ``environmentfunction`` and ``environmentfilter``.
|
||||
"""
|
||||
f.jinja_pass_arg = _PassArg.environment # type: ignore
|
||||
return f
|
||||
|
||||
|
||||
class _PassArg(enum.Enum):
|
||||
context = enum.auto()
|
||||
eval_context = enum.auto()
|
||||
environment = enum.auto()
|
||||
|
||||
@classmethod
|
||||
def from_obj(cls, obj: F) -> t.Optional["_PassArg"]:
|
||||
if hasattr(obj, "jinja_pass_arg"):
|
||||
return obj.jinja_pass_arg # type: ignore
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def internalcode(f: F) -> F:
|
||||
"""Marks the function as internally used"""
|
||||
internal_code.add(f.__code__)
|
||||
return f
|
||||
|
||||
|
||||
def is_undefined(obj: t.Any) -> bool:
|
||||
"""Check if the object passed is undefined. This does nothing more than
|
||||
performing an instance check against :class:`Undefined` but looks nicer.
|
||||
This can be used for custom filters or tests that want to react to
|
||||
undefined variables. For example a custom default filter can look like
|
||||
this::
|
||||
|
||||
def default(var, default=''):
|
||||
if is_undefined(var):
|
||||
return default
|
||||
return var
|
||||
"""
|
||||
from .runtime import Undefined
|
||||
|
||||
return isinstance(obj, Undefined)
|
||||
|
||||
|
||||
def consume(iterable: t.Iterable[t.Any]) -> None:
|
||||
"""Consumes an iterable without doing anything with it."""
|
||||
for _ in iterable:
|
||||
pass
|
||||
|
||||
|
||||
def clear_caches() -> None:
|
||||
"""Jinja keeps internal caches for environments and lexers. These are
|
||||
used so that Jinja doesn't have to recreate environments and lexers all
|
||||
the time. Normally you don't have to care about that but if you are
|
||||
measuring memory consumption you may want to clean the caches.
|
||||
"""
|
||||
from .environment import get_spontaneous_environment
|
||||
from .lexer import _lexer_cache
|
||||
|
||||
get_spontaneous_environment.cache_clear()
|
||||
_lexer_cache.clear()
|
||||
|
||||
|
||||
def import_string(import_name: str, silent: bool = False) -> t.Any:
|
||||
"""Imports an object based on a string. This is useful if you want to
|
||||
use import paths as endpoints or something similar. An import path can
|
||||
be specified either in dotted notation (``xml.sax.saxutils.escape``)
|
||||
or with a colon as object delimiter (``xml.sax.saxutils:escape``).
|
||||
|
||||
If the `silent` is True the return value will be `None` if the import
|
||||
fails.
|
||||
|
||||
:return: imported object
|
||||
"""
|
||||
try:
|
||||
if ":" in import_name:
|
||||
module, obj = import_name.split(":", 1)
|
||||
elif "." in import_name:
|
||||
module, _, obj = import_name.rpartition(".")
|
||||
else:
|
||||
return __import__(import_name)
|
||||
return getattr(__import__(module, None, None, [obj]), obj)
|
||||
except (ImportError, AttributeError):
|
||||
if not silent:
|
||||
raise
|
||||
|
||||
|
||||
def open_if_exists(filename: str, mode: str = "rb") -> t.Optional[t.IO[t.Any]]:
|
||||
"""Returns a file descriptor for the filename if that file exists,
|
||||
otherwise ``None``.
|
||||
"""
|
||||
if not os.path.isfile(filename):
|
||||
return None
|
||||
|
||||
return open(filename, mode)
|
||||
|
||||
|
||||
def object_type_repr(obj: t.Any) -> str:
|
||||
"""Returns the name of the object's type. For some recognized
|
||||
singletons the name of the object is returned instead. (For
|
||||
example for `None` and `Ellipsis`).
|
||||
"""
|
||||
if obj is None:
|
||||
return "None"
|
||||
elif obj is Ellipsis:
|
||||
return "Ellipsis"
|
||||
|
||||
cls = type(obj)
|
||||
|
||||
if cls.__module__ == "builtins":
|
||||
return f"{cls.__name__} object"
|
||||
|
||||
return f"{cls.__module__}.{cls.__name__} object"
|
||||
|
||||
|
||||
def pformat(obj: t.Any) -> str:
|
||||
"""Format an object using :func:`pprint.pformat`."""
|
||||
from pprint import pformat
|
||||
|
||||
return pformat(obj)
|
||||
|
||||
|
||||
_http_re = re.compile(
|
||||
r"""
|
||||
^
|
||||
(
|
||||
(https?://|www\.) # scheme or www
|
||||
(([\w%-]+\.)+)? # subdomain
|
||||
(
|
||||
[a-z]{2,63} # basic tld
|
||||
|
|
||||
xn--[\w%]{2,59} # idna tld
|
||||
)
|
||||
|
|
||||
([\w%-]{2,63}\.)+ # basic domain
|
||||
(com|net|int|edu|gov|org|info|mil) # basic tld
|
||||
|
|
||||
(https?://) # scheme
|
||||
(
|
||||
(([\d]{1,3})(\.[\d]{1,3}){3}) # IPv4
|
||||
|
|
||||
(\[([\da-f]{0,4}:){2}([\da-f]{0,4}:?){1,6}]) # IPv6
|
||||
)
|
||||
)
|
||||
(?::[\d]{1,5})? # port
|
||||
(?:[/?#]\S*)? # path, query, and fragment
|
||||
$
|
||||
""",
|
||||
re.IGNORECASE | re.VERBOSE,
|
||||
)
|
||||
_email_re = re.compile(r"^\S+@\w[\w.-]*\.\w+$")
|
||||
|
||||
|
||||
def urlize(
|
||||
text: str,
|
||||
trim_url_limit: t.Optional[int] = None,
|
||||
rel: t.Optional[str] = None,
|
||||
target: t.Optional[str] = None,
|
||||
extra_schemes: t.Optional[t.Iterable[str]] = None,
|
||||
) -> str:
|
||||
"""Convert URLs in text into clickable links.
|
||||
|
||||
This may not recognize links in some situations. Usually, a more
|
||||
comprehensive formatter, such as a Markdown library, is a better
|
||||
choice.
|
||||
|
||||
Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email
|
||||
addresses. Links with trailing punctuation (periods, commas, closing
|
||||
parentheses) and leading punctuation (opening parentheses) are
|
||||
recognized excluding the punctuation. Email addresses that include
|
||||
header fields are not recognized (for example,
|
||||
``mailto:address@example.com?cc=copy@example.com``).
|
||||
|
||||
:param text: Original text containing URLs to link.
|
||||
:param trim_url_limit: Shorten displayed URL values to this length.
|
||||
:param target: Add the ``target`` attribute to links.
|
||||
:param rel: Add the ``rel`` attribute to links.
|
||||
:param extra_schemes: Recognize URLs that start with these schemes
|
||||
in addition to the default behavior.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``extra_schemes`` parameter was added.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Generate ``https://`` links for URLs without a scheme.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The parsing rules were updated. Recognize email addresses with
|
||||
or without the ``mailto:`` scheme. Validate IP addresses. Ignore
|
||||
parentheses and brackets in more cases.
|
||||
"""
|
||||
if trim_url_limit is not None:
|
||||
|
||||
def trim_url(x: str) -> str:
|
||||
if len(x) > trim_url_limit:
|
||||
return f"{x[:trim_url_limit]}..."
|
||||
|
||||
return x
|
||||
|
||||
else:
|
||||
|
||||
def trim_url(x: str) -> str:
|
||||
return x
|
||||
|
||||
words = re.split(r"(\s+)", str(markupsafe.escape(text)))
|
||||
rel_attr = f' rel="{markupsafe.escape(rel)}"' if rel else ""
|
||||
target_attr = f' target="{markupsafe.escape(target)}"' if target else ""
|
||||
|
||||
for i, word in enumerate(words):
|
||||
head, middle, tail = "", word, ""
|
||||
match = re.match(r"^([(<]|<)+", middle)
|
||||
|
||||
if match:
|
||||
head = match.group()
|
||||
middle = middle[match.end() :]
|
||||
|
||||
# Unlike lead, which is anchored to the start of the string,
|
||||
# need to check that the string ends with any of the characters
|
||||
# before trying to match all of them, to avoid backtracking.
|
||||
if middle.endswith((")", ">", ".", ",", "\n", ">")):
|
||||
match = re.search(r"([)>.,\n]|>)+$", middle)
|
||||
|
||||
if match:
|
||||
tail = match.group()
|
||||
middle = middle[: match.start()]
|
||||
|
||||
# Prefer balancing parentheses in URLs instead of ignoring a
|
||||
# trailing character.
|
||||
for start_char, end_char in ("(", ")"), ("<", ">"), ("<", ">"):
|
||||
start_count = middle.count(start_char)
|
||||
|
||||
if start_count <= middle.count(end_char):
|
||||
# Balanced, or lighter on the left
|
||||
continue
|
||||
|
||||
# Move as many as possible from the tail to balance
|
||||
for _ in range(min(start_count, tail.count(end_char))):
|
||||
end_index = tail.index(end_char) + len(end_char)
|
||||
# Move anything in the tail before the end char too
|
||||
middle += tail[:end_index]
|
||||
tail = tail[end_index:]
|
||||
|
||||
if _http_re.match(middle):
|
||||
if middle.startswith("https://") or middle.startswith("http://"):
|
||||
middle = (
|
||||
f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'
|
||||
)
|
||||
else:
|
||||
middle = (
|
||||
f'<a href="https://{middle}"{rel_attr}{target_attr}>'
|
||||
f"{trim_url(middle)}</a>"
|
||||
)
|
||||
|
||||
elif middle.startswith("mailto:") and _email_re.match(middle[7:]):
|
||||
middle = f'<a href="{middle}">{middle[7:]}</a>'
|
||||
|
||||
elif (
|
||||
"@" in middle
|
||||
and not middle.startswith("www.")
|
||||
# ignore values like `@a@b`
|
||||
and not middle.startswith("@")
|
||||
and ":" not in middle
|
||||
and _email_re.match(middle)
|
||||
):
|
||||
middle = f'<a href="mailto:{middle}">{middle}</a>'
|
||||
|
||||
elif extra_schemes is not None:
|
||||
for scheme in extra_schemes:
|
||||
if middle != scheme and middle.startswith(scheme):
|
||||
middle = f'<a href="{middle}"{rel_attr}{target_attr}>{middle}</a>'
|
||||
|
||||
words[i] = f"{head}{middle}{tail}"
|
||||
|
||||
return "".join(words)
|
||||
|
||||
|
||||
def generate_lorem_ipsum(
|
||||
n: int = 5, html: bool = True, min: int = 20, max: int = 100
|
||||
) -> str:
|
||||
"""Generate some lorem ipsum for the template."""
|
||||
from .constants import LOREM_IPSUM_WORDS
|
||||
|
||||
words = LOREM_IPSUM_WORDS.split()
|
||||
result = []
|
||||
|
||||
for _ in range(n):
|
||||
next_capitalized = True
|
||||
last_comma = last_fullstop = 0
|
||||
word = None
|
||||
last = None
|
||||
p = []
|
||||
|
||||
# each paragraph contains out of 20 to 100 words.
|
||||
for idx, _ in enumerate(range(randrange(min, max))):
|
||||
while True:
|
||||
word = choice(words)
|
||||
if word != last:
|
||||
last = word
|
||||
break
|
||||
if next_capitalized:
|
||||
word = word.capitalize()
|
||||
next_capitalized = False
|
||||
# add commas
|
||||
if idx - randrange(3, 8) > last_comma:
|
||||
last_comma = idx
|
||||
last_fullstop += 2
|
||||
word += ","
|
||||
# add end of sentences
|
||||
if idx - randrange(10, 20) > last_fullstop:
|
||||
last_comma = last_fullstop = idx
|
||||
word += "."
|
||||
next_capitalized = True
|
||||
p.append(word)
|
||||
|
||||
# ensure that the paragraph ends with a dot.
|
||||
p_str = " ".join(p)
|
||||
|
||||
if p_str.endswith(","):
|
||||
p_str = p_str[:-1] + "."
|
||||
elif not p_str.endswith("."):
|
||||
p_str += "."
|
||||
|
||||
result.append(p_str)
|
||||
|
||||
if not html:
|
||||
return "\n\n".join(result)
|
||||
return markupsafe.Markup(
|
||||
"\n".join(f"<p>{markupsafe.escape(x)}</p>" for x in result)
|
||||
)
|
||||
|
||||
|
||||
def url_quote(obj: t.Any, charset: str = "utf-8", for_qs: bool = False) -> str:
|
||||
"""Quote a string for use in a URL using the given charset.
|
||||
|
||||
:param obj: String or bytes to quote. Other types are converted to
|
||||
string then encoded to bytes using the given charset.
|
||||
:param charset: Encode text to bytes using this charset.
|
||||
:param for_qs: Quote "/" and use "+" for spaces.
|
||||
"""
|
||||
if not isinstance(obj, bytes):
|
||||
if not isinstance(obj, str):
|
||||
obj = str(obj)
|
||||
|
||||
obj = obj.encode(charset)
|
||||
|
||||
safe = b"" if for_qs else b"/"
|
||||
rv = quote_from_bytes(obj, safe)
|
||||
|
||||
if for_qs:
|
||||
rv = rv.replace("%20", "+")
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
@abc.MutableMapping.register
|
||||
class LRUCache:
|
||||
"""A simple LRU Cache implementation."""
|
||||
|
||||
# this is fast for small capacities (something below 1000) but doesn't
|
||||
# scale. But as long as it's only used as storage for templates this
|
||||
# won't do any harm.
|
||||
|
||||
def __init__(self, capacity: int) -> None:
|
||||
self.capacity = capacity
|
||||
self._mapping: t.Dict[t.Any, t.Any] = {}
|
||||
self._queue: te.Deque[t.Any] = deque()
|
||||
self._postinit()
|
||||
|
||||
def _postinit(self) -> None:
|
||||
# alias all queue methods for faster lookup
|
||||
self._popleft = self._queue.popleft
|
||||
self._pop = self._queue.pop
|
||||
self._remove = self._queue.remove
|
||||
self._wlock = Lock()
|
||||
self._append = self._queue.append
|
||||
|
||||
def __getstate__(self) -> t.Mapping[str, t.Any]:
|
||||
return {
|
||||
"capacity": self.capacity,
|
||||
"_mapping": self._mapping,
|
||||
"_queue": self._queue,
|
||||
}
|
||||
|
||||
def __setstate__(self, d: t.Mapping[str, t.Any]) -> None:
|
||||
self.__dict__.update(d)
|
||||
self._postinit()
|
||||
|
||||
def __getnewargs__(self) -> t.Tuple[t.Any, ...]:
|
||||
return (self.capacity,)
|
||||
|
||||
def copy(self) -> "te.Self":
|
||||
"""Return a shallow copy of the instance."""
|
||||
rv = self.__class__(self.capacity)
|
||||
rv._mapping.update(self._mapping)
|
||||
rv._queue.extend(self._queue)
|
||||
return rv
|
||||
|
||||
def get(self, key: t.Any, default: t.Any = None) -> t.Any:
|
||||
"""Return an item from the cache dict or `default`"""
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def setdefault(self, key: t.Any, default: t.Any = None) -> t.Any:
|
||||
"""Set `default` if the key is not in the cache otherwise
|
||||
leave unchanged. Return the value of this key.
|
||||
"""
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
self[key] = default
|
||||
return default
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Clear the cache."""
|
||||
with self._wlock:
|
||||
self._mapping.clear()
|
||||
self._queue.clear()
|
||||
|
||||
def __contains__(self, key: t.Any) -> bool:
|
||||
"""Check if a key exists in this cache."""
|
||||
return key in self._mapping
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""Return the current size of the cache."""
|
||||
return len(self._mapping)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{type(self).__name__} {self._mapping!r}>"
|
||||
|
||||
def __getitem__(self, key: t.Any) -> t.Any:
|
||||
"""Get an item from the cache. Moves the item up so that it has the
|
||||
highest priority then.
|
||||
|
||||
Raise a `KeyError` if it does not exist.
|
||||
"""
|
||||
with self._wlock:
|
||||
rv = self._mapping[key]
|
||||
|
||||
if self._queue[-1] != key:
|
||||
try:
|
||||
self._remove(key)
|
||||
except ValueError:
|
||||
# if something removed the key from the container
|
||||
# when we read, ignore the ValueError that we would
|
||||
# get otherwise.
|
||||
pass
|
||||
|
||||
self._append(key)
|
||||
|
||||
return rv
|
||||
|
||||
def __setitem__(self, key: t.Any, value: t.Any) -> None:
|
||||
"""Sets the value for an item. Moves the item up so that it
|
||||
has the highest priority then.
|
||||
"""
|
||||
with self._wlock:
|
||||
if key in self._mapping:
|
||||
self._remove(key)
|
||||
elif len(self._mapping) == self.capacity:
|
||||
del self._mapping[self._popleft()]
|
||||
|
||||
self._append(key)
|
||||
self._mapping[key] = value
|
||||
|
||||
def __delitem__(self, key: t.Any) -> None:
|
||||
"""Remove an item from the cache dict.
|
||||
Raise a `KeyError` if it does not exist.
|
||||
"""
|
||||
with self._wlock:
|
||||
del self._mapping[key]
|
||||
|
||||
try:
|
||||
self._remove(key)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def items(self) -> t.Iterable[t.Tuple[t.Any, t.Any]]:
|
||||
"""Return a list of items."""
|
||||
result = [(key, self._mapping[key]) for key in list(self._queue)]
|
||||
result.reverse()
|
||||
return result
|
||||
|
||||
def values(self) -> t.Iterable[t.Any]:
|
||||
"""Return a list of all values."""
|
||||
return [x[1] for x in self.items()]
|
||||
|
||||
def keys(self) -> t.Iterable[t.Any]:
|
||||
"""Return a list of all keys ordered by most recent usage."""
|
||||
return list(self)
|
||||
|
||||
def __iter__(self) -> t.Iterator[t.Any]:
|
||||
return reversed(tuple(self._queue))
|
||||
|
||||
def __reversed__(self) -> t.Iterator[t.Any]:
|
||||
"""Iterate over the keys in the cache dict, oldest items
|
||||
coming first.
|
||||
"""
|
||||
return iter(tuple(self._queue))
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
|
||||
def select_autoescape(
|
||||
enabled_extensions: t.Collection[str] = ("html", "htm", "xml"),
|
||||
disabled_extensions: t.Collection[str] = (),
|
||||
default_for_string: bool = True,
|
||||
default: bool = False,
|
||||
) -> t.Callable[[t.Optional[str]], bool]:
|
||||
"""Intelligently sets the initial value of autoescaping based on the
|
||||
filename of the template. This is the recommended way to configure
|
||||
autoescaping if you do not want to write a custom function yourself.
|
||||
|
||||
If you want to enable it for all templates created from strings or
|
||||
for all templates with `.html` and `.xml` extensions::
|
||||
|
||||
from jinja2 import Environment, select_autoescape
|
||||
env = Environment(autoescape=select_autoescape(
|
||||
enabled_extensions=('html', 'xml'),
|
||||
default_for_string=True,
|
||||
))
|
||||
|
||||
Example configuration to turn it on at all times except if the template
|
||||
ends with `.txt`::
|
||||
|
||||
from jinja2 import Environment, select_autoescape
|
||||
env = Environment(autoescape=select_autoescape(
|
||||
disabled_extensions=('txt',),
|
||||
default_for_string=True,
|
||||
default=True,
|
||||
))
|
||||
|
||||
The `enabled_extensions` is an iterable of all the extensions that
|
||||
autoescaping should be enabled for. Likewise `disabled_extensions` is
|
||||
a list of all templates it should be disabled for. If a template is
|
||||
loaded from a string then the default from `default_for_string` is used.
|
||||
If nothing matches then the initial value of autoescaping is set to the
|
||||
value of `default`.
|
||||
|
||||
For security reasons this function operates case insensitive.
|
||||
|
||||
.. versionadded:: 2.9
|
||||
"""
|
||||
enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
|
||||
disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)
|
||||
|
||||
def autoescape(template_name: t.Optional[str]) -> bool:
|
||||
if template_name is None:
|
||||
return default_for_string
|
||||
template_name = template_name.lower()
|
||||
if template_name.endswith(enabled_patterns):
|
||||
return True
|
||||
if template_name.endswith(disabled_patterns):
|
||||
return False
|
||||
return default
|
||||
|
||||
return autoescape
|
||||
|
||||
|
||||
def htmlsafe_json_dumps(
|
||||
obj: t.Any, dumps: t.Optional[t.Callable[..., str]] = None, **kwargs: t.Any
|
||||
) -> markupsafe.Markup:
|
||||
"""Serialize an object to a string of JSON with :func:`json.dumps`,
|
||||
then replace HTML-unsafe characters with Unicode escapes and mark
|
||||
the result safe with :class:`~markupsafe.Markup`.
|
||||
|
||||
This is available in templates as the ``|tojson`` filter.
|
||||
|
||||
The following characters are escaped: ``<``, ``>``, ``&``, ``'``.
|
||||
|
||||
The returned string is safe to render in HTML documents and
|
||||
``<script>`` tags. The exception is in HTML attributes that are
|
||||
double quoted; either use single quotes or the ``|forceescape``
|
||||
filter.
|
||||
|
||||
:param obj: The object to serialize to JSON.
|
||||
:param dumps: The ``dumps`` function to use. Defaults to
|
||||
``env.policies["json.dumps_function"]``, which defaults to
|
||||
:func:`json.dumps`.
|
||||
:param kwargs: Extra arguments to pass to ``dumps``. Merged onto
|
||||
``env.policies["json.dumps_kwargs"]``.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``dumper`` parameter is renamed to ``dumps``.
|
||||
|
||||
.. versionadded:: 2.9
|
||||
"""
|
||||
if dumps is None:
|
||||
dumps = json.dumps
|
||||
|
||||
return markupsafe.Markup(
|
||||
dumps(obj, **kwargs)
|
||||
.replace("<", "\\u003c")
|
||||
.replace(">", "\\u003e")
|
||||
.replace("&", "\\u0026")
|
||||
.replace("'", "\\u0027")
|
||||
)
|
||||
|
||||
|
||||
class Cycler:
|
||||
"""Cycle through values by yield them one at a time, then restarting
|
||||
once the end is reached. Available as ``cycler`` in templates.
|
||||
|
||||
Similar to ``loop.cycle``, but can be used outside loops or across
|
||||
multiple loops. For example, render a list of folders and files in a
|
||||
list, alternating giving them "odd" and "even" classes.
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
{% set row_class = cycler("odd", "even") %}
|
||||
<ul class="browser">
|
||||
{% for folder in folders %}
|
||||
<li class="folder {{ row_class.next() }}">{{ folder }}
|
||||
{% endfor %}
|
||||
{% for file in files %}
|
||||
<li class="file {{ row_class.next() }}">{{ file }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
:param items: Each positional argument will be yielded in the order
|
||||
given for each cycle.
|
||||
|
||||
.. versionadded:: 2.1
|
||||
"""
|
||||
|
||||
def __init__(self, *items: t.Any) -> None:
|
||||
if not items:
|
||||
raise RuntimeError("at least one item has to be provided")
|
||||
self.items = items
|
||||
self.pos = 0
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Resets the current item to the first item."""
|
||||
self.pos = 0
|
||||
|
||||
@property
|
||||
def current(self) -> t.Any:
|
||||
"""Return the current item. Equivalent to the item that will be
|
||||
returned next time :meth:`next` is called.
|
||||
"""
|
||||
return self.items[self.pos]
|
||||
|
||||
def next(self) -> t.Any:
|
||||
"""Return the current item, then advance :attr:`current` to the
|
||||
next item.
|
||||
"""
|
||||
rv = self.current
|
||||
self.pos = (self.pos + 1) % len(self.items)
|
||||
return rv
|
||||
|
||||
__next__ = next
|
||||
|
||||
|
||||
class Joiner:
|
||||
"""A joining helper for templates."""
|
||||
|
||||
def __init__(self, sep: str = ", ") -> None:
|
||||
self.sep = sep
|
||||
self.used = False
|
||||
|
||||
def __call__(self) -> str:
|
||||
if not self.used:
|
||||
self.used = True
|
||||
return ""
|
||||
return self.sep
|
||||
|
||||
|
||||
class Namespace:
|
||||
"""A namespace object that can hold arbitrary attributes. It may be
|
||||
initialized from a dictionary or with keyword arguments."""
|
||||
|
||||
def __init__(*args: t.Any, **kwargs: t.Any) -> None: # noqa: B902
|
||||
self, args = args[0], args[1:]
|
||||
self.__attrs = dict(*args, **kwargs)
|
||||
|
||||
def __getattribute__(self, name: str) -> t.Any:
|
||||
# __class__ is needed for the awaitable check in async mode
|
||||
if name in {"_Namespace__attrs", "__class__"}:
|
||||
return object.__getattribute__(self, name)
|
||||
try:
|
||||
return self.__attrs[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name) from None
|
||||
|
||||
def __setitem__(self, name: str, value: t.Any) -> None:
|
||||
self.__attrs[name] = value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Namespace {self.__attrs!r}>"
|
92
env/lib/python3.11/site-packages/jinja2/visitor.py
vendored
Normal file
92
env/lib/python3.11/site-packages/jinja2/visitor.py
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
"""API for traversing the AST nodes. Implemented by the compiler and
|
||||
meta introspection.
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
|
||||
from .nodes import Node
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
class VisitCallable(te.Protocol):
|
||||
def __call__(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any: ...
|
||||
|
||||
|
||||
class NodeVisitor:
|
||||
"""Walks the abstract syntax tree and call visitor functions for every
|
||||
node found. The visitor functions may return values which will be
|
||||
forwarded by the `visit` method.
|
||||
|
||||
Per default the visitor functions for the nodes are ``'visit_'`` +
|
||||
class name of the node. So a `TryFinally` node visit function would
|
||||
be `visit_TryFinally`. This behavior can be changed by overriding
|
||||
the `get_visitor` function. If no visitor function exists for a node
|
||||
(return value `None`) the `generic_visit` visitor is used instead.
|
||||
"""
|
||||
|
||||
def get_visitor(self, node: Node) -> "t.Optional[VisitCallable]":
|
||||
"""Return the visitor function for this node or `None` if no visitor
|
||||
exists for this node. In that case the generic visit function is
|
||||
used instead.
|
||||
"""
|
||||
return getattr(self, f"visit_{type(node).__name__}", None)
|
||||
|
||||
def visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
"""Visit a node."""
|
||||
f = self.get_visitor(node)
|
||||
|
||||
if f is not None:
|
||||
return f(node, *args, **kwargs)
|
||||
|
||||
return self.generic_visit(node, *args, **kwargs)
|
||||
|
||||
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
"""Called if no explicit visitor function exists for a node."""
|
||||
for child_node in node.iter_child_nodes():
|
||||
self.visit(child_node, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeTransformer(NodeVisitor):
|
||||
"""Walks the abstract syntax tree and allows modifications of nodes.
|
||||
|
||||
The `NodeTransformer` will walk the AST and use the return value of the
|
||||
visitor functions to replace or remove the old node. If the return
|
||||
value of the visitor function is `None` the node will be removed
|
||||
from the previous location otherwise it's replaced with the return
|
||||
value. The return value may be the original node in which case no
|
||||
replacement takes place.
|
||||
"""
|
||||
|
||||
def generic_visit(self, node: Node, *args: t.Any, **kwargs: t.Any) -> Node:
|
||||
for field, old_value in node.iter_fields():
|
||||
if isinstance(old_value, list):
|
||||
new_values = []
|
||||
for value in old_value:
|
||||
if isinstance(value, Node):
|
||||
value = self.visit(value, *args, **kwargs)
|
||||
if value is None:
|
||||
continue
|
||||
elif not isinstance(value, Node):
|
||||
new_values.extend(value)
|
||||
continue
|
||||
new_values.append(value)
|
||||
old_value[:] = new_values
|
||||
elif isinstance(old_value, Node):
|
||||
new_node = self.visit(old_value, *args, **kwargs)
|
||||
if new_node is None:
|
||||
delattr(node, field)
|
||||
else:
|
||||
setattr(node, field, new_node)
|
||||
return node
|
||||
|
||||
def visit_list(self, node: Node, *args: t.Any, **kwargs: t.Any) -> t.List[Node]:
|
||||
"""As transformers may return lists in some places this method
|
||||
can be used to enforce a list as return value.
|
||||
"""
|
||||
rv = self.visit(node, *args, **kwargs)
|
||||
|
||||
if not isinstance(rv, list):
|
||||
return [rv]
|
||||
|
||||
return rv
|
Reference in New Issue
Block a user