"""Templates utility functions for Sphinx. """ import os from functools import partial from os import path from typing import Callable, Dict, List, Tuple, Union from jinja2 import TemplateNotFound from jinja2.environment import Environment 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 class BaseRenderer: def __init__(self, loader: BaseLoader = 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: return self.env.get_template(template_name).render(context) def render_string(self, source: str, context: Dict) -> str: return self.env.from_string(source).render(context) class FileRenderer(BaseRenderer): def __init__(self, search_path: Union[str, List[str]]) -> None: if isinstance(search_path, str): 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: dirname = os.path.dirname(filename) basename = os.path.basename(filename) return cls(dirname).render(basename, context) class SphinxRenderer(FileRenderer): def __init__(self, template_path: Union[str, List[str]] = 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: return FileRenderer.render_from_file(filename, context) class LaTeXRenderer(SphinxRenderer): def __init__(self, template_path: str = None, latex_engine: str = 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: Union[str, List[str]] = None, language: str = None) -> None: # NOQA 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, templates_paths: List[str], system_templates_paths: List[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)