summaryrefslogtreecommitdiffstats
path: root/sphinx/util/template.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/util/template.py')
-rw-r--r--sphinx/util/template.py135
1 files changed, 135 insertions, 0 deletions
diff --git a/sphinx/util/template.py b/sphinx/util/template.py
new file mode 100644
index 0000000..a16a7a1
--- /dev/null
+++ b/sphinx/util/template.py
@@ -0,0 +1,135 @@
+"""Templates utility functions for Sphinx."""
+
+from __future__ import annotations
+
+import os
+from functools import partial
+from os import path
+from typing import TYPE_CHECKING, Any, Callable
+
+from jinja2 import TemplateNotFound
+from jinja2.loaders import BaseLoader
+from jinja2.sandbox import SandboxedEnvironment
+
+from sphinx import package_dir
+from sphinx.jinja2glue import SphinxFileSystemLoader
+from sphinx.locale import get_translator
+from sphinx.util import rst, texescape
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+ from jinja2.environment import Environment
+
+
+class BaseRenderer:
+ def __init__(self, loader: BaseLoader | None = None) -> None:
+ self.env = SandboxedEnvironment(loader=loader, extensions=['jinja2.ext.i18n'])
+ self.env.filters['repr'] = repr
+ self.env.install_gettext_translations(get_translator())
+
+ def render(self, template_name: str, context: dict[str, Any]) -> str:
+ return self.env.get_template(template_name).render(context)
+
+ def render_string(self, source: str, context: dict[str, Any]) -> str:
+ return self.env.from_string(source).render(context)
+
+
+class FileRenderer(BaseRenderer):
+ def __init__(self, search_path: Sequence[str | os.PathLike[str]]) -> None:
+ if isinstance(search_path, (str, os.PathLike)):
+ search_path = [search_path]
+ else:
+ # filter "None" paths
+ search_path = list(filter(None, search_path))
+
+ loader = SphinxFileSystemLoader(search_path)
+ super().__init__(loader)
+
+ @classmethod
+ def render_from_file(cls, filename: str, context: dict[str, Any]) -> str:
+ dirname = os.path.dirname(filename)
+ basename = os.path.basename(filename)
+ return cls(dirname).render(basename, context)
+
+
+class SphinxRenderer(FileRenderer):
+ def __init__(self, template_path: Sequence[str | os.PathLike[str]] | None = None) -> None:
+ if template_path is None:
+ template_path = os.path.join(package_dir, 'templates')
+ super().__init__(template_path)
+
+ @classmethod
+ def render_from_file(cls, filename: str, context: dict[str, Any]) -> str:
+ return FileRenderer.render_from_file(filename, context)
+
+
+class LaTeXRenderer(SphinxRenderer):
+ def __init__(self, template_path: Sequence[str | os.PathLike[str]] | None = None,
+ latex_engine: str | None = None) -> None:
+ if template_path is None:
+ template_path = [os.path.join(package_dir, 'templates', 'latex')]
+ super().__init__(template_path)
+
+ # use texescape as escape filter
+ escape = partial(texescape.escape, latex_engine=latex_engine)
+ self.env.filters['e'] = escape
+ self.env.filters['escape'] = escape
+ self.env.filters['eabbr'] = texescape.escape_abbr
+
+ # use JSP/eRuby like tagging instead because curly bracket; the default
+ # tagging of jinja2 is not good for LaTeX sources.
+ self.env.variable_start_string = '<%='
+ self.env.variable_end_string = '%>'
+ self.env.block_start_string = '<%'
+ self.env.block_end_string = '%>'
+ self.env.comment_start_string = '<#'
+ self.env.comment_end_string = '#>'
+
+
+class ReSTRenderer(SphinxRenderer):
+ def __init__(self, template_path: Sequence[str | os.PathLike[str]] | None = None,
+ language: str | None = None) -> None:
+ super().__init__(template_path)
+
+ # add language to environment
+ self.env.extend(language=language)
+
+ # use texescape as escape filter
+ self.env.filters['e'] = rst.escape
+ self.env.filters['escape'] = rst.escape
+ self.env.filters['heading'] = rst.heading
+
+
+class SphinxTemplateLoader(BaseLoader):
+ """A loader supporting template inheritance"""
+
+ def __init__(self, confdir: str | os.PathLike[str],
+ templates_paths: Sequence[str | os.PathLike[str]],
+ system_templates_paths: Sequence[str | os.PathLike[str]]) -> None:
+ self.loaders = []
+ self.sysloaders = []
+
+ for templates_path in templates_paths:
+ loader = SphinxFileSystemLoader(path.join(confdir, templates_path))
+ self.loaders.append(loader)
+
+ for templates_path in system_templates_paths:
+ loader = SphinxFileSystemLoader(templates_path)
+ self.loaders.append(loader)
+ self.sysloaders.append(loader)
+
+ def get_source(self, environment: Environment, template: str) -> tuple[str, str, Callable]:
+ if template.startswith('!'):
+ # search a template from ``system_templates_paths``
+ loaders = self.sysloaders
+ template = template[1:]
+ else:
+ loaders = self.loaders
+
+ for loader in loaders:
+ try:
+ return loader.get_source(environment, template)
+ except TemplateNotFound:
+ pass
+ raise TemplateNotFound(template)