767 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			767 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 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}>"
 | 
