1e31aef6aSopenharmony_ci"""API and implementations for loading templates from different data
2e31aef6aSopenharmony_cisources.
3e31aef6aSopenharmony_ci"""
4e31aef6aSopenharmony_ciimport importlib.util
5e31aef6aSopenharmony_ciimport os
6e31aef6aSopenharmony_ciimport posixpath
7e31aef6aSopenharmony_ciimport sys
8e31aef6aSopenharmony_ciimport typing as t
9e31aef6aSopenharmony_ciimport weakref
10e31aef6aSopenharmony_ciimport zipimport
11e31aef6aSopenharmony_cifrom collections import abc
12e31aef6aSopenharmony_cifrom hashlib import sha1
13e31aef6aSopenharmony_cifrom importlib import import_module
14e31aef6aSopenharmony_cifrom types import ModuleType
15e31aef6aSopenharmony_ci
16e31aef6aSopenharmony_cifrom .exceptions import TemplateNotFound
17e31aef6aSopenharmony_cifrom .utils import internalcode
18e31aef6aSopenharmony_ci
19e31aef6aSopenharmony_ciif t.TYPE_CHECKING:
20e31aef6aSopenharmony_ci    from .environment import Environment
21e31aef6aSopenharmony_ci    from .environment import Template
22e31aef6aSopenharmony_ci
23e31aef6aSopenharmony_ci
24e31aef6aSopenharmony_cidef split_template_path(template: str) -> t.List[str]:
25e31aef6aSopenharmony_ci    """Split a path into segments and perform a sanity check.  If it detects
26e31aef6aSopenharmony_ci    '..' in the path it will raise a `TemplateNotFound` error.
27e31aef6aSopenharmony_ci    """
28e31aef6aSopenharmony_ci    pieces = []
29e31aef6aSopenharmony_ci    for piece in template.split("/"):
30e31aef6aSopenharmony_ci        if (
31e31aef6aSopenharmony_ci            os.path.sep in piece
32e31aef6aSopenharmony_ci            or (os.path.altsep and os.path.altsep in piece)
33e31aef6aSopenharmony_ci            or piece == os.path.pardir
34e31aef6aSopenharmony_ci        ):
35e31aef6aSopenharmony_ci            raise TemplateNotFound(template)
36e31aef6aSopenharmony_ci        elif piece and piece != ".":
37e31aef6aSopenharmony_ci            pieces.append(piece)
38e31aef6aSopenharmony_ci    return pieces
39e31aef6aSopenharmony_ci
40e31aef6aSopenharmony_ci
41e31aef6aSopenharmony_ciclass BaseLoader:
42e31aef6aSopenharmony_ci    """Baseclass for all loaders.  Subclass this and override `get_source` to
43e31aef6aSopenharmony_ci    implement a custom loading mechanism.  The environment provides a
44e31aef6aSopenharmony_ci    `get_template` method that calls the loader's `load` method to get the
45e31aef6aSopenharmony_ci    :class:`Template` object.
46e31aef6aSopenharmony_ci
47e31aef6aSopenharmony_ci    A very basic example for a loader that looks up templates on the file
48e31aef6aSopenharmony_ci    system could look like this::
49e31aef6aSopenharmony_ci
50e31aef6aSopenharmony_ci        from jinja2 import BaseLoader, TemplateNotFound
51e31aef6aSopenharmony_ci        from os.path import join, exists, getmtime
52e31aef6aSopenharmony_ci
53e31aef6aSopenharmony_ci        class MyLoader(BaseLoader):
54e31aef6aSopenharmony_ci
55e31aef6aSopenharmony_ci            def __init__(self, path):
56e31aef6aSopenharmony_ci                self.path = path
57e31aef6aSopenharmony_ci
58e31aef6aSopenharmony_ci            def get_source(self, environment, template):
59e31aef6aSopenharmony_ci                path = join(self.path, template)
60e31aef6aSopenharmony_ci                if not exists(path):
61e31aef6aSopenharmony_ci                    raise TemplateNotFound(template)
62e31aef6aSopenharmony_ci                mtime = getmtime(path)
63e31aef6aSopenharmony_ci                with open(path) as f:
64e31aef6aSopenharmony_ci                    source = f.read()
65e31aef6aSopenharmony_ci                return source, path, lambda: mtime == getmtime(path)
66e31aef6aSopenharmony_ci    """
67e31aef6aSopenharmony_ci
68e31aef6aSopenharmony_ci    #: if set to `False` it indicates that the loader cannot provide access
69e31aef6aSopenharmony_ci    #: to the source of templates.
70e31aef6aSopenharmony_ci    #:
71e31aef6aSopenharmony_ci    #: .. versionadded:: 2.4
72e31aef6aSopenharmony_ci    has_source_access = True
73e31aef6aSopenharmony_ci
74e31aef6aSopenharmony_ci    def get_source(
75e31aef6aSopenharmony_ci        self, environment: "Environment", template: str
76e31aef6aSopenharmony_ci    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
77e31aef6aSopenharmony_ci        """Get the template source, filename and reload helper for a template.
78e31aef6aSopenharmony_ci        It's passed the environment and template name and has to return a
79e31aef6aSopenharmony_ci        tuple in the form ``(source, filename, uptodate)`` or raise a
80e31aef6aSopenharmony_ci        `TemplateNotFound` error if it can't locate the template.
81e31aef6aSopenharmony_ci
82e31aef6aSopenharmony_ci        The source part of the returned tuple must be the source of the
83e31aef6aSopenharmony_ci        template as a string. The filename should be the name of the
84e31aef6aSopenharmony_ci        file on the filesystem if it was loaded from there, otherwise
85e31aef6aSopenharmony_ci        ``None``. The filename is used by Python for the tracebacks
86e31aef6aSopenharmony_ci        if no loader extension is used.
87e31aef6aSopenharmony_ci
88e31aef6aSopenharmony_ci        The last item in the tuple is the `uptodate` function.  If auto
89e31aef6aSopenharmony_ci        reloading is enabled it's always called to check if the template
90e31aef6aSopenharmony_ci        changed.  No arguments are passed so the function must store the
91e31aef6aSopenharmony_ci        old state somewhere (for example in a closure).  If it returns `False`
92e31aef6aSopenharmony_ci        the template will be reloaded.
93e31aef6aSopenharmony_ci        """
94e31aef6aSopenharmony_ci        if not self.has_source_access:
95e31aef6aSopenharmony_ci            raise RuntimeError(
96e31aef6aSopenharmony_ci                f"{type(self).__name__} cannot provide access to the source"
97e31aef6aSopenharmony_ci            )
98e31aef6aSopenharmony_ci        raise TemplateNotFound(template)
99e31aef6aSopenharmony_ci
100e31aef6aSopenharmony_ci    def list_templates(self) -> t.List[str]:
101e31aef6aSopenharmony_ci        """Iterates over all templates.  If the loader does not support that
102e31aef6aSopenharmony_ci        it should raise a :exc:`TypeError` which is the default behavior.
103e31aef6aSopenharmony_ci        """
104e31aef6aSopenharmony_ci        raise TypeError("this loader cannot iterate over all templates")
105e31aef6aSopenharmony_ci
106e31aef6aSopenharmony_ci    @internalcode
107e31aef6aSopenharmony_ci    def load(
108e31aef6aSopenharmony_ci        self,
109e31aef6aSopenharmony_ci        environment: "Environment",
110e31aef6aSopenharmony_ci        name: str,
111e31aef6aSopenharmony_ci        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
112e31aef6aSopenharmony_ci    ) -> "Template":
113e31aef6aSopenharmony_ci        """Loads a template.  This method looks up the template in the cache
114e31aef6aSopenharmony_ci        or loads one by calling :meth:`get_source`.  Subclasses should not
115e31aef6aSopenharmony_ci        override this method as loaders working on collections of other
116e31aef6aSopenharmony_ci        loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
117e31aef6aSopenharmony_ci        will not call this method but `get_source` directly.
118e31aef6aSopenharmony_ci        """
119e31aef6aSopenharmony_ci        code = None
120e31aef6aSopenharmony_ci        if globals is None:
121e31aef6aSopenharmony_ci            globals = {}
122e31aef6aSopenharmony_ci
123e31aef6aSopenharmony_ci        # first we try to get the source for this template together
124e31aef6aSopenharmony_ci        # with the filename and the uptodate function.
125e31aef6aSopenharmony_ci        source, filename, uptodate = self.get_source(environment, name)
126e31aef6aSopenharmony_ci
127e31aef6aSopenharmony_ci        # try to load the code from the bytecode cache if there is a
128e31aef6aSopenharmony_ci        # bytecode cache configured.
129e31aef6aSopenharmony_ci        bcc = environment.bytecode_cache
130e31aef6aSopenharmony_ci        if bcc is not None:
131e31aef6aSopenharmony_ci            bucket = bcc.get_bucket(environment, name, filename, source)
132e31aef6aSopenharmony_ci            code = bucket.code
133e31aef6aSopenharmony_ci
134e31aef6aSopenharmony_ci        # if we don't have code so far (not cached, no longer up to
135e31aef6aSopenharmony_ci        # date) etc. we compile the template
136e31aef6aSopenharmony_ci        if code is None:
137e31aef6aSopenharmony_ci            code = environment.compile(source, name, filename)
138e31aef6aSopenharmony_ci
139e31aef6aSopenharmony_ci        # if the bytecode cache is available and the bucket doesn't
140e31aef6aSopenharmony_ci        # have a code so far, we give the bucket the new code and put
141e31aef6aSopenharmony_ci        # it back to the bytecode cache.
142e31aef6aSopenharmony_ci        if bcc is not None and bucket.code is None:
143e31aef6aSopenharmony_ci            bucket.code = code
144e31aef6aSopenharmony_ci            bcc.set_bucket(bucket)
145e31aef6aSopenharmony_ci
146e31aef6aSopenharmony_ci        return environment.template_class.from_code(
147e31aef6aSopenharmony_ci            environment, code, globals, uptodate
148e31aef6aSopenharmony_ci        )
149e31aef6aSopenharmony_ci
150e31aef6aSopenharmony_ci
151e31aef6aSopenharmony_ciclass FileSystemLoader(BaseLoader):
152e31aef6aSopenharmony_ci    """Load templates from a directory in the file system.
153e31aef6aSopenharmony_ci
154e31aef6aSopenharmony_ci    The path can be relative or absolute. Relative paths are relative to
155e31aef6aSopenharmony_ci    the current working directory.
156e31aef6aSopenharmony_ci
157e31aef6aSopenharmony_ci    .. code-block:: python
158e31aef6aSopenharmony_ci
159e31aef6aSopenharmony_ci        loader = FileSystemLoader("templates")
160e31aef6aSopenharmony_ci
161e31aef6aSopenharmony_ci    A list of paths can be given. The directories will be searched in
162e31aef6aSopenharmony_ci    order, stopping at the first matching template.
163e31aef6aSopenharmony_ci
164e31aef6aSopenharmony_ci    .. code-block:: python
165e31aef6aSopenharmony_ci
166e31aef6aSopenharmony_ci        loader = FileSystemLoader(["/override/templates", "/default/templates"])
167e31aef6aSopenharmony_ci
168e31aef6aSopenharmony_ci    :param searchpath: A path, or list of paths, to the directory that
169e31aef6aSopenharmony_ci        contains the templates.
170e31aef6aSopenharmony_ci    :param encoding: Use this encoding to read the text from template
171e31aef6aSopenharmony_ci        files.
172e31aef6aSopenharmony_ci    :param followlinks: Follow symbolic links in the path.
173e31aef6aSopenharmony_ci
174e31aef6aSopenharmony_ci    .. versionchanged:: 2.8
175e31aef6aSopenharmony_ci        Added the ``followlinks`` parameter.
176e31aef6aSopenharmony_ci    """
177e31aef6aSopenharmony_ci
178e31aef6aSopenharmony_ci    def __init__(
179e31aef6aSopenharmony_ci        self,
180e31aef6aSopenharmony_ci        searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]],
181e31aef6aSopenharmony_ci        encoding: str = "utf-8",
182e31aef6aSopenharmony_ci        followlinks: bool = False,
183e31aef6aSopenharmony_ci    ) -> None:
184e31aef6aSopenharmony_ci        if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
185e31aef6aSopenharmony_ci            searchpath = [searchpath]
186e31aef6aSopenharmony_ci
187e31aef6aSopenharmony_ci        self.searchpath = [os.fspath(p) for p in searchpath]
188e31aef6aSopenharmony_ci        self.encoding = encoding
189e31aef6aSopenharmony_ci        self.followlinks = followlinks
190e31aef6aSopenharmony_ci
191e31aef6aSopenharmony_ci    def get_source(
192e31aef6aSopenharmony_ci        self, environment: "Environment", template: str
193e31aef6aSopenharmony_ci    ) -> t.Tuple[str, str, t.Callable[[], bool]]:
194e31aef6aSopenharmony_ci        pieces = split_template_path(template)
195e31aef6aSopenharmony_ci
196e31aef6aSopenharmony_ci        for searchpath in self.searchpath:
197e31aef6aSopenharmony_ci            # Use posixpath even on Windows to avoid "drive:" or UNC
198e31aef6aSopenharmony_ci            # segments breaking out of the search directory.
199e31aef6aSopenharmony_ci            filename = posixpath.join(searchpath, *pieces)
200e31aef6aSopenharmony_ci
201e31aef6aSopenharmony_ci            if os.path.isfile(filename):
202e31aef6aSopenharmony_ci                break
203e31aef6aSopenharmony_ci        else:
204e31aef6aSopenharmony_ci            raise TemplateNotFound(template)
205e31aef6aSopenharmony_ci
206e31aef6aSopenharmony_ci        with open(filename, encoding=self.encoding) as f:
207e31aef6aSopenharmony_ci            contents = f.read()
208e31aef6aSopenharmony_ci
209e31aef6aSopenharmony_ci        mtime = os.path.getmtime(filename)
210e31aef6aSopenharmony_ci
211e31aef6aSopenharmony_ci        def uptodate() -> bool:
212e31aef6aSopenharmony_ci            try:
213e31aef6aSopenharmony_ci                return os.path.getmtime(filename) == mtime
214e31aef6aSopenharmony_ci            except OSError:
215e31aef6aSopenharmony_ci                return False
216e31aef6aSopenharmony_ci
217e31aef6aSopenharmony_ci        # Use normpath to convert Windows altsep to sep.
218e31aef6aSopenharmony_ci        return contents, os.path.normpath(filename), uptodate
219e31aef6aSopenharmony_ci
220e31aef6aSopenharmony_ci    def list_templates(self) -> t.List[str]:
221e31aef6aSopenharmony_ci        found = set()
222e31aef6aSopenharmony_ci        for searchpath in self.searchpath:
223e31aef6aSopenharmony_ci            walk_dir = os.walk(searchpath, followlinks=self.followlinks)
224e31aef6aSopenharmony_ci            for dirpath, _, filenames in walk_dir:
225e31aef6aSopenharmony_ci                for filename in filenames:
226e31aef6aSopenharmony_ci                    template = (
227e31aef6aSopenharmony_ci                        os.path.join(dirpath, filename)[len(searchpath) :]
228e31aef6aSopenharmony_ci                        .strip(os.path.sep)
229e31aef6aSopenharmony_ci                        .replace(os.path.sep, "/")
230e31aef6aSopenharmony_ci                    )
231e31aef6aSopenharmony_ci                    if template[:2] == "./":
232e31aef6aSopenharmony_ci                        template = template[2:]
233e31aef6aSopenharmony_ci                    if template not in found:
234e31aef6aSopenharmony_ci                        found.add(template)
235e31aef6aSopenharmony_ci        return sorted(found)
236e31aef6aSopenharmony_ci
237e31aef6aSopenharmony_ci
238e31aef6aSopenharmony_ciclass PackageLoader(BaseLoader):
239e31aef6aSopenharmony_ci    """Load templates from a directory in a Python package.
240e31aef6aSopenharmony_ci
241e31aef6aSopenharmony_ci    :param package_name: Import name of the package that contains the
242e31aef6aSopenharmony_ci        template directory.
243e31aef6aSopenharmony_ci    :param package_path: Directory within the imported package that
244e31aef6aSopenharmony_ci        contains the templates.
245e31aef6aSopenharmony_ci    :param encoding: Encoding of template files.
246e31aef6aSopenharmony_ci
247e31aef6aSopenharmony_ci    The following example looks up templates in the ``pages`` directory
248e31aef6aSopenharmony_ci    within the ``project.ui`` package.
249e31aef6aSopenharmony_ci
250e31aef6aSopenharmony_ci    .. code-block:: python
251e31aef6aSopenharmony_ci
252e31aef6aSopenharmony_ci        loader = PackageLoader("project.ui", "pages")
253e31aef6aSopenharmony_ci
254e31aef6aSopenharmony_ci    Only packages installed as directories (standard pip behavior) or
255e31aef6aSopenharmony_ci    zip/egg files (less common) are supported. The Python API for
256e31aef6aSopenharmony_ci    introspecting data in packages is too limited to support other
257e31aef6aSopenharmony_ci    installation methods the way this loader requires.
258e31aef6aSopenharmony_ci
259e31aef6aSopenharmony_ci    There is limited support for :pep:`420` namespace packages. The
260e31aef6aSopenharmony_ci    template directory is assumed to only be in one namespace
261e31aef6aSopenharmony_ci    contributor. Zip files contributing to a namespace are not
262e31aef6aSopenharmony_ci    supported.
263e31aef6aSopenharmony_ci
264e31aef6aSopenharmony_ci    .. versionchanged:: 3.0
265e31aef6aSopenharmony_ci        No longer uses ``setuptools`` as a dependency.
266e31aef6aSopenharmony_ci
267e31aef6aSopenharmony_ci    .. versionchanged:: 3.0
268e31aef6aSopenharmony_ci        Limited PEP 420 namespace package support.
269e31aef6aSopenharmony_ci    """
270e31aef6aSopenharmony_ci
271e31aef6aSopenharmony_ci    def __init__(
272e31aef6aSopenharmony_ci        self,
273e31aef6aSopenharmony_ci        package_name: str,
274e31aef6aSopenharmony_ci        package_path: "str" = "templates",
275e31aef6aSopenharmony_ci        encoding: str = "utf-8",
276e31aef6aSopenharmony_ci    ) -> None:
277e31aef6aSopenharmony_ci        package_path = os.path.normpath(package_path).rstrip(os.path.sep)
278e31aef6aSopenharmony_ci
279e31aef6aSopenharmony_ci        # normpath preserves ".", which isn't valid in zip paths.
280e31aef6aSopenharmony_ci        if package_path == os.path.curdir:
281e31aef6aSopenharmony_ci            package_path = ""
282e31aef6aSopenharmony_ci        elif package_path[:2] == os.path.curdir + os.path.sep:
283e31aef6aSopenharmony_ci            package_path = package_path[2:]
284e31aef6aSopenharmony_ci
285e31aef6aSopenharmony_ci        self.package_path = package_path
286e31aef6aSopenharmony_ci        self.package_name = package_name
287e31aef6aSopenharmony_ci        self.encoding = encoding
288e31aef6aSopenharmony_ci
289e31aef6aSopenharmony_ci        # Make sure the package exists. This also makes namespace
290e31aef6aSopenharmony_ci        # packages work, otherwise get_loader returns None.
291e31aef6aSopenharmony_ci        import_module(package_name)
292e31aef6aSopenharmony_ci        spec = importlib.util.find_spec(package_name)
293e31aef6aSopenharmony_ci        assert spec is not None, "An import spec was not found for the package."
294e31aef6aSopenharmony_ci        loader = spec.loader
295e31aef6aSopenharmony_ci        assert loader is not None, "A loader was not found for the package."
296e31aef6aSopenharmony_ci        self._loader = loader
297e31aef6aSopenharmony_ci        self._archive = None
298e31aef6aSopenharmony_ci        template_root = None
299e31aef6aSopenharmony_ci
300e31aef6aSopenharmony_ci        if isinstance(loader, zipimport.zipimporter):
301e31aef6aSopenharmony_ci            self._archive = loader.archive
302e31aef6aSopenharmony_ci            pkgdir = next(iter(spec.submodule_search_locations))  # type: ignore
303e31aef6aSopenharmony_ci            template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
304e31aef6aSopenharmony_ci        else:
305e31aef6aSopenharmony_ci            roots: t.List[str] = []
306e31aef6aSopenharmony_ci
307e31aef6aSopenharmony_ci            # One element for regular packages, multiple for namespace
308e31aef6aSopenharmony_ci            # packages, or None for single module file.
309e31aef6aSopenharmony_ci            if spec.submodule_search_locations:
310e31aef6aSopenharmony_ci                roots.extend(spec.submodule_search_locations)
311e31aef6aSopenharmony_ci            # A single module file, use the parent directory instead.
312e31aef6aSopenharmony_ci            elif spec.origin is not None:
313e31aef6aSopenharmony_ci                roots.append(os.path.dirname(spec.origin))
314e31aef6aSopenharmony_ci
315e31aef6aSopenharmony_ci            for root in roots:
316e31aef6aSopenharmony_ci                root = os.path.join(root, package_path)
317e31aef6aSopenharmony_ci
318e31aef6aSopenharmony_ci                if os.path.isdir(root):
319e31aef6aSopenharmony_ci                    template_root = root
320e31aef6aSopenharmony_ci                    break
321e31aef6aSopenharmony_ci
322e31aef6aSopenharmony_ci        if template_root is None:
323e31aef6aSopenharmony_ci            raise ValueError(
324e31aef6aSopenharmony_ci                f"The {package_name!r} package was not installed in a"
325e31aef6aSopenharmony_ci                " way that PackageLoader understands."
326e31aef6aSopenharmony_ci            )
327e31aef6aSopenharmony_ci
328e31aef6aSopenharmony_ci        self._template_root = template_root
329e31aef6aSopenharmony_ci
330e31aef6aSopenharmony_ci    def get_source(
331e31aef6aSopenharmony_ci        self, environment: "Environment", template: str
332e31aef6aSopenharmony_ci    ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
333e31aef6aSopenharmony_ci        # Use posixpath even on Windows to avoid "drive:" or UNC
334e31aef6aSopenharmony_ci        # segments breaking out of the search directory. Use normpath to
335e31aef6aSopenharmony_ci        # convert Windows altsep to sep.
336e31aef6aSopenharmony_ci        p = os.path.normpath(
337e31aef6aSopenharmony_ci            posixpath.join(self._template_root, *split_template_path(template))
338e31aef6aSopenharmony_ci        )
339e31aef6aSopenharmony_ci        up_to_date: t.Optional[t.Callable[[], bool]]
340e31aef6aSopenharmony_ci
341e31aef6aSopenharmony_ci        if self._archive is None:
342e31aef6aSopenharmony_ci            # Package is a directory.
343e31aef6aSopenharmony_ci            if not os.path.isfile(p):
344e31aef6aSopenharmony_ci                raise TemplateNotFound(template)
345e31aef6aSopenharmony_ci
346e31aef6aSopenharmony_ci            with open(p, "rb") as f:
347e31aef6aSopenharmony_ci                source = f.read()
348e31aef6aSopenharmony_ci
349e31aef6aSopenharmony_ci            mtime = os.path.getmtime(p)
350e31aef6aSopenharmony_ci
351e31aef6aSopenharmony_ci            def up_to_date() -> bool:
352e31aef6aSopenharmony_ci                return os.path.isfile(p) and os.path.getmtime(p) == mtime
353e31aef6aSopenharmony_ci
354e31aef6aSopenharmony_ci        else:
355e31aef6aSopenharmony_ci            # Package is a zip file.
356e31aef6aSopenharmony_ci            try:
357e31aef6aSopenharmony_ci                source = self._loader.get_data(p)  # type: ignore
358e31aef6aSopenharmony_ci            except OSError as e:
359e31aef6aSopenharmony_ci                raise TemplateNotFound(template) from e
360e31aef6aSopenharmony_ci
361e31aef6aSopenharmony_ci            # Could use the zip's mtime for all template mtimes, but
362e31aef6aSopenharmony_ci            # would need to safely reload the module if it's out of
363e31aef6aSopenharmony_ci            # date, so just report it as always current.
364e31aef6aSopenharmony_ci            up_to_date = None
365e31aef6aSopenharmony_ci
366e31aef6aSopenharmony_ci        return source.decode(self.encoding), p, up_to_date
367e31aef6aSopenharmony_ci
368e31aef6aSopenharmony_ci    def list_templates(self) -> t.List[str]:
369e31aef6aSopenharmony_ci        results: t.List[str] = []
370e31aef6aSopenharmony_ci
371e31aef6aSopenharmony_ci        if self._archive is None:
372e31aef6aSopenharmony_ci            # Package is a directory.
373e31aef6aSopenharmony_ci            offset = len(self._template_root)
374e31aef6aSopenharmony_ci
375e31aef6aSopenharmony_ci            for dirpath, _, filenames in os.walk(self._template_root):
376e31aef6aSopenharmony_ci                dirpath = dirpath[offset:].lstrip(os.path.sep)
377e31aef6aSopenharmony_ci                results.extend(
378e31aef6aSopenharmony_ci                    os.path.join(dirpath, name).replace(os.path.sep, "/")
379e31aef6aSopenharmony_ci                    for name in filenames
380e31aef6aSopenharmony_ci                )
381e31aef6aSopenharmony_ci        else:
382e31aef6aSopenharmony_ci            if not hasattr(self._loader, "_files"):
383e31aef6aSopenharmony_ci                raise TypeError(
384e31aef6aSopenharmony_ci                    "This zip import does not have the required"
385e31aef6aSopenharmony_ci                    " metadata to list templates."
386e31aef6aSopenharmony_ci                )
387e31aef6aSopenharmony_ci
388e31aef6aSopenharmony_ci            # Package is a zip file.
389e31aef6aSopenharmony_ci            prefix = (
390e31aef6aSopenharmony_ci                self._template_root[len(self._archive) :].lstrip(os.path.sep)
391e31aef6aSopenharmony_ci                + os.path.sep
392e31aef6aSopenharmony_ci            )
393e31aef6aSopenharmony_ci            offset = len(prefix)
394e31aef6aSopenharmony_ci
395e31aef6aSopenharmony_ci            for name in self._loader._files.keys():
396e31aef6aSopenharmony_ci                # Find names under the templates directory that aren't directories.
397e31aef6aSopenharmony_ci                if name.startswith(prefix) and name[-1] != os.path.sep:
398e31aef6aSopenharmony_ci                    results.append(name[offset:].replace(os.path.sep, "/"))
399e31aef6aSopenharmony_ci
400e31aef6aSopenharmony_ci        results.sort()
401e31aef6aSopenharmony_ci        return results
402e31aef6aSopenharmony_ci
403e31aef6aSopenharmony_ci
404e31aef6aSopenharmony_ciclass DictLoader(BaseLoader):
405e31aef6aSopenharmony_ci    """Loads a template from a Python dict mapping template names to
406e31aef6aSopenharmony_ci    template source.  This loader is useful for unittesting:
407e31aef6aSopenharmony_ci
408e31aef6aSopenharmony_ci    >>> loader = DictLoader({'index.html': 'source here'})
409e31aef6aSopenharmony_ci
410e31aef6aSopenharmony_ci    Because auto reloading is rarely useful this is disabled per default.
411e31aef6aSopenharmony_ci    """
412e31aef6aSopenharmony_ci
413e31aef6aSopenharmony_ci    def __init__(self, mapping: t.Mapping[str, str]) -> None:
414e31aef6aSopenharmony_ci        self.mapping = mapping
415e31aef6aSopenharmony_ci
416e31aef6aSopenharmony_ci    def get_source(
417e31aef6aSopenharmony_ci        self, environment: "Environment", template: str
418e31aef6aSopenharmony_ci    ) -> t.Tuple[str, None, t.Callable[[], bool]]:
419e31aef6aSopenharmony_ci        if template in self.mapping:
420e31aef6aSopenharmony_ci            source = self.mapping[template]
421e31aef6aSopenharmony_ci            return source, None, lambda: source == self.mapping.get(template)
422e31aef6aSopenharmony_ci        raise TemplateNotFound(template)
423e31aef6aSopenharmony_ci
424e31aef6aSopenharmony_ci    def list_templates(self) -> t.List[str]:
425e31aef6aSopenharmony_ci        return sorted(self.mapping)
426e31aef6aSopenharmony_ci
427e31aef6aSopenharmony_ci
428e31aef6aSopenharmony_ciclass FunctionLoader(BaseLoader):
429e31aef6aSopenharmony_ci    """A loader that is passed a function which does the loading.  The
430e31aef6aSopenharmony_ci    function receives the name of the template and has to return either
431e31aef6aSopenharmony_ci    a string with the template source, a tuple in the form ``(source,
432e31aef6aSopenharmony_ci    filename, uptodatefunc)`` or `None` if the template does not exist.
433e31aef6aSopenharmony_ci
434e31aef6aSopenharmony_ci    >>> def load_template(name):
435e31aef6aSopenharmony_ci    ...     if name == 'index.html':
436e31aef6aSopenharmony_ci    ...         return '...'
437e31aef6aSopenharmony_ci    ...
438e31aef6aSopenharmony_ci    >>> loader = FunctionLoader(load_template)
439e31aef6aSopenharmony_ci
440e31aef6aSopenharmony_ci    The `uptodatefunc` is a function that is called if autoreload is enabled
441e31aef6aSopenharmony_ci    and has to return `True` if the template is still up to date.  For more
442e31aef6aSopenharmony_ci    details have a look at :meth:`BaseLoader.get_source` which has the same
443e31aef6aSopenharmony_ci    return value.
444e31aef6aSopenharmony_ci    """
445e31aef6aSopenharmony_ci
446e31aef6aSopenharmony_ci    def __init__(
447e31aef6aSopenharmony_ci        self,
448e31aef6aSopenharmony_ci        load_func: t.Callable[
449e31aef6aSopenharmony_ci            [str],
450e31aef6aSopenharmony_ci            t.Optional[
451e31aef6aSopenharmony_ci                t.Union[
452e31aef6aSopenharmony_ci                    str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
453e31aef6aSopenharmony_ci                ]
454e31aef6aSopenharmony_ci            ],
455e31aef6aSopenharmony_ci        ],
456e31aef6aSopenharmony_ci    ) -> None:
457e31aef6aSopenharmony_ci        self.load_func = load_func
458e31aef6aSopenharmony_ci
459e31aef6aSopenharmony_ci    def get_source(
460e31aef6aSopenharmony_ci        self, environment: "Environment", template: str
461e31aef6aSopenharmony_ci    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
462e31aef6aSopenharmony_ci        rv = self.load_func(template)
463e31aef6aSopenharmony_ci
464e31aef6aSopenharmony_ci        if rv is None:
465e31aef6aSopenharmony_ci            raise TemplateNotFound(template)
466e31aef6aSopenharmony_ci
467e31aef6aSopenharmony_ci        if isinstance(rv, str):
468e31aef6aSopenharmony_ci            return rv, None, None
469e31aef6aSopenharmony_ci
470e31aef6aSopenharmony_ci        return rv
471e31aef6aSopenharmony_ci
472e31aef6aSopenharmony_ci
473e31aef6aSopenharmony_ciclass PrefixLoader(BaseLoader):
474e31aef6aSopenharmony_ci    """A loader that is passed a dict of loaders where each loader is bound
475e31aef6aSopenharmony_ci    to a prefix.  The prefix is delimited from the template by a slash per
476e31aef6aSopenharmony_ci    default, which can be changed by setting the `delimiter` argument to
477e31aef6aSopenharmony_ci    something else::
478e31aef6aSopenharmony_ci
479e31aef6aSopenharmony_ci        loader = PrefixLoader({
480e31aef6aSopenharmony_ci            'app1':     PackageLoader('mypackage.app1'),
481e31aef6aSopenharmony_ci            'app2':     PackageLoader('mypackage.app2')
482e31aef6aSopenharmony_ci        })
483e31aef6aSopenharmony_ci
484e31aef6aSopenharmony_ci    By loading ``'app1/index.html'`` the file from the app1 package is loaded,
485e31aef6aSopenharmony_ci    by loading ``'app2/index.html'`` the file from the second.
486e31aef6aSopenharmony_ci    """
487e31aef6aSopenharmony_ci
488e31aef6aSopenharmony_ci    def __init__(
489e31aef6aSopenharmony_ci        self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
490e31aef6aSopenharmony_ci    ) -> None:
491e31aef6aSopenharmony_ci        self.mapping = mapping
492e31aef6aSopenharmony_ci        self.delimiter = delimiter
493e31aef6aSopenharmony_ci
494e31aef6aSopenharmony_ci    def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
495e31aef6aSopenharmony_ci        try:
496e31aef6aSopenharmony_ci            prefix, name = template.split(self.delimiter, 1)
497e31aef6aSopenharmony_ci            loader = self.mapping[prefix]
498e31aef6aSopenharmony_ci        except (ValueError, KeyError) as e:
499e31aef6aSopenharmony_ci            raise TemplateNotFound(template) from e
500e31aef6aSopenharmony_ci        return loader, name
501e31aef6aSopenharmony_ci
502e31aef6aSopenharmony_ci    def get_source(
503e31aef6aSopenharmony_ci        self, environment: "Environment", template: str
504e31aef6aSopenharmony_ci    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
505e31aef6aSopenharmony_ci        loader, name = self.get_loader(template)
506e31aef6aSopenharmony_ci        try:
507e31aef6aSopenharmony_ci            return loader.get_source(environment, name)
508e31aef6aSopenharmony_ci        except TemplateNotFound as e:
509e31aef6aSopenharmony_ci            # re-raise the exception with the correct filename here.
510e31aef6aSopenharmony_ci            # (the one that includes the prefix)
511e31aef6aSopenharmony_ci            raise TemplateNotFound(template) from e
512e31aef6aSopenharmony_ci
513e31aef6aSopenharmony_ci    @internalcode
514e31aef6aSopenharmony_ci    def load(
515e31aef6aSopenharmony_ci        self,
516e31aef6aSopenharmony_ci        environment: "Environment",
517e31aef6aSopenharmony_ci        name: str,
518e31aef6aSopenharmony_ci        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
519e31aef6aSopenharmony_ci    ) -> "Template":
520e31aef6aSopenharmony_ci        loader, local_name = self.get_loader(name)
521e31aef6aSopenharmony_ci        try:
522e31aef6aSopenharmony_ci            return loader.load(environment, local_name, globals)
523e31aef6aSopenharmony_ci        except TemplateNotFound as e:
524e31aef6aSopenharmony_ci            # re-raise the exception with the correct filename here.
525e31aef6aSopenharmony_ci            # (the one that includes the prefix)
526e31aef6aSopenharmony_ci            raise TemplateNotFound(name) from e
527e31aef6aSopenharmony_ci
528e31aef6aSopenharmony_ci    def list_templates(self) -> t.List[str]:
529e31aef6aSopenharmony_ci        result = []
530e31aef6aSopenharmony_ci        for prefix, loader in self.mapping.items():
531e31aef6aSopenharmony_ci            for template in loader.list_templates():
532e31aef6aSopenharmony_ci                result.append(prefix + self.delimiter + template)
533e31aef6aSopenharmony_ci        return result
534e31aef6aSopenharmony_ci
535e31aef6aSopenharmony_ci
536e31aef6aSopenharmony_ciclass ChoiceLoader(BaseLoader):
537e31aef6aSopenharmony_ci    """This loader works like the `PrefixLoader` just that no prefix is
538e31aef6aSopenharmony_ci    specified.  If a template could not be found by one loader the next one
539e31aef6aSopenharmony_ci    is tried.
540e31aef6aSopenharmony_ci
541e31aef6aSopenharmony_ci    >>> loader = ChoiceLoader([
542e31aef6aSopenharmony_ci    ...     FileSystemLoader('/path/to/user/templates'),
543e31aef6aSopenharmony_ci    ...     FileSystemLoader('/path/to/system/templates')
544e31aef6aSopenharmony_ci    ... ])
545e31aef6aSopenharmony_ci
546e31aef6aSopenharmony_ci    This is useful if you want to allow users to override builtin templates
547e31aef6aSopenharmony_ci    from a different location.
548e31aef6aSopenharmony_ci    """
549e31aef6aSopenharmony_ci
550e31aef6aSopenharmony_ci    def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
551e31aef6aSopenharmony_ci        self.loaders = loaders
552e31aef6aSopenharmony_ci
553e31aef6aSopenharmony_ci    def get_source(
554e31aef6aSopenharmony_ci        self, environment: "Environment", template: str
555e31aef6aSopenharmony_ci    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
556e31aef6aSopenharmony_ci        for loader in self.loaders:
557e31aef6aSopenharmony_ci            try:
558e31aef6aSopenharmony_ci                return loader.get_source(environment, template)
559e31aef6aSopenharmony_ci            except TemplateNotFound:
560e31aef6aSopenharmony_ci                pass
561e31aef6aSopenharmony_ci        raise TemplateNotFound(template)
562e31aef6aSopenharmony_ci
563e31aef6aSopenharmony_ci    @internalcode
564e31aef6aSopenharmony_ci    def load(
565e31aef6aSopenharmony_ci        self,
566e31aef6aSopenharmony_ci        environment: "Environment",
567e31aef6aSopenharmony_ci        name: str,
568e31aef6aSopenharmony_ci        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
569e31aef6aSopenharmony_ci    ) -> "Template":
570e31aef6aSopenharmony_ci        for loader in self.loaders:
571e31aef6aSopenharmony_ci            try:
572e31aef6aSopenharmony_ci                return loader.load(environment, name, globals)
573e31aef6aSopenharmony_ci            except TemplateNotFound:
574e31aef6aSopenharmony_ci                pass
575e31aef6aSopenharmony_ci        raise TemplateNotFound(name)
576e31aef6aSopenharmony_ci
577e31aef6aSopenharmony_ci    def list_templates(self) -> t.List[str]:
578e31aef6aSopenharmony_ci        found = set()
579e31aef6aSopenharmony_ci        for loader in self.loaders:
580e31aef6aSopenharmony_ci            found.update(loader.list_templates())
581e31aef6aSopenharmony_ci        return sorted(found)
582e31aef6aSopenharmony_ci
583e31aef6aSopenharmony_ci
584e31aef6aSopenharmony_ciclass _TemplateModule(ModuleType):
585e31aef6aSopenharmony_ci    """Like a normal module but with support for weak references"""
586e31aef6aSopenharmony_ci
587e31aef6aSopenharmony_ci
588e31aef6aSopenharmony_ciclass ModuleLoader(BaseLoader):
589e31aef6aSopenharmony_ci    """This loader loads templates from precompiled templates.
590e31aef6aSopenharmony_ci
591e31aef6aSopenharmony_ci    Example usage:
592e31aef6aSopenharmony_ci
593e31aef6aSopenharmony_ci    >>> loader = ChoiceLoader([
594e31aef6aSopenharmony_ci    ...     ModuleLoader('/path/to/compiled/templates'),
595e31aef6aSopenharmony_ci    ...     FileSystemLoader('/path/to/templates')
596e31aef6aSopenharmony_ci    ... ])
597e31aef6aSopenharmony_ci
598e31aef6aSopenharmony_ci    Templates can be precompiled with :meth:`Environment.compile_templates`.
599e31aef6aSopenharmony_ci    """
600e31aef6aSopenharmony_ci
601e31aef6aSopenharmony_ci    has_source_access = False
602e31aef6aSopenharmony_ci
603e31aef6aSopenharmony_ci    def __init__(
604e31aef6aSopenharmony_ci        self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]]
605e31aef6aSopenharmony_ci    ) -> None:
606e31aef6aSopenharmony_ci        package_name = f"_jinja2_module_templates_{id(self):x}"
607e31aef6aSopenharmony_ci
608e31aef6aSopenharmony_ci        # create a fake module that looks for the templates in the
609e31aef6aSopenharmony_ci        # path given.
610e31aef6aSopenharmony_ci        mod = _TemplateModule(package_name)
611e31aef6aSopenharmony_ci
612e31aef6aSopenharmony_ci        if not isinstance(path, abc.Iterable) or isinstance(path, str):
613e31aef6aSopenharmony_ci            path = [path]
614e31aef6aSopenharmony_ci
615e31aef6aSopenharmony_ci        mod.__path__ = [os.fspath(p) for p in path]
616e31aef6aSopenharmony_ci
617e31aef6aSopenharmony_ci        sys.modules[package_name] = weakref.proxy(
618e31aef6aSopenharmony_ci            mod, lambda x: sys.modules.pop(package_name, None)
619e31aef6aSopenharmony_ci        )
620e31aef6aSopenharmony_ci
621e31aef6aSopenharmony_ci        # the only strong reference, the sys.modules entry is weak
622e31aef6aSopenharmony_ci        # so that the garbage collector can remove it once the
623e31aef6aSopenharmony_ci        # loader that created it goes out of business.
624e31aef6aSopenharmony_ci        self.module = mod
625e31aef6aSopenharmony_ci        self.package_name = package_name
626e31aef6aSopenharmony_ci
627e31aef6aSopenharmony_ci    @staticmethod
628e31aef6aSopenharmony_ci    def get_template_key(name: str) -> str:
629e31aef6aSopenharmony_ci        return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
630e31aef6aSopenharmony_ci
631e31aef6aSopenharmony_ci    @staticmethod
632e31aef6aSopenharmony_ci    def get_module_filename(name: str) -> str:
633e31aef6aSopenharmony_ci        return ModuleLoader.get_template_key(name) + ".py"
634e31aef6aSopenharmony_ci
635e31aef6aSopenharmony_ci    @internalcode
636e31aef6aSopenharmony_ci    def load(
637e31aef6aSopenharmony_ci        self,
638e31aef6aSopenharmony_ci        environment: "Environment",
639e31aef6aSopenharmony_ci        name: str,
640e31aef6aSopenharmony_ci        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
641e31aef6aSopenharmony_ci    ) -> "Template":
642e31aef6aSopenharmony_ci        key = self.get_template_key(name)
643e31aef6aSopenharmony_ci        module = f"{self.package_name}.{key}"
644e31aef6aSopenharmony_ci        mod = getattr(self.module, module, None)
645e31aef6aSopenharmony_ci
646e31aef6aSopenharmony_ci        if mod is None:
647e31aef6aSopenharmony_ci            try:
648e31aef6aSopenharmony_ci                mod = __import__(module, None, None, ["root"])
649e31aef6aSopenharmony_ci            except ImportError as e:
650e31aef6aSopenharmony_ci                raise TemplateNotFound(name) from e
651e31aef6aSopenharmony_ci
652e31aef6aSopenharmony_ci            # remove the entry from sys.modules, we only want the attribute
653e31aef6aSopenharmony_ci            # on the module object we have stored on the loader.
654e31aef6aSopenharmony_ci            sys.modules.pop(module, None)
655e31aef6aSopenharmony_ci
656e31aef6aSopenharmony_ci        if globals is None:
657e31aef6aSopenharmony_ci            globals = {}
658e31aef6aSopenharmony_ci
659e31aef6aSopenharmony_ci        return environment.template_class.from_module_dict(
660e31aef6aSopenharmony_ci            environment, mod.__dict__, globals
661e31aef6aSopenharmony_ci        )
662