136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
|
"""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)
|