1""" 2 sphinx.util.template 3 ~~~~~~~~~~~~~~~~~~~~ 4 5 Templates utility functions for Sphinx. 6 7 :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 :license: BSD, see LICENSE for details. 9""" 10 11import os 12from functools import partial 13from os import path 14from typing import Callable, Dict, List, Tuple, Union 15 16from jinja2 import TemplateNotFound 17from jinja2.environment import Environment 18from jinja2.loaders import BaseLoader 19from jinja2.sandbox import SandboxedEnvironment 20 21from sphinx import package_dir 22from sphinx.jinja2glue import SphinxFileSystemLoader 23from sphinx.locale import get_translator 24from sphinx.util import rst, texescape 25 26 27class BaseRenderer: 28 def __init__(self, loader: BaseLoader = None) -> None: 29 self.env = SandboxedEnvironment(loader=loader, extensions=['jinja2.ext.i18n']) 30 self.env.filters['repr'] = repr 31 self.env.install_gettext_translations(get_translator()) 32 33 def render(self, template_name: str, context: Dict) -> str: 34 return self.env.get_template(template_name).render(context) 35 36 def render_string(self, source: str, context: Dict) -> str: 37 return self.env.from_string(source).render(context) 38 39 40class FileRenderer(BaseRenderer): 41 def __init__(self, search_path: Union[str, List[str]]) -> None: 42 if isinstance(search_path, str): 43 search_path = [search_path] 44 else: 45 # filter "None" paths 46 search_path = list(filter(None, search_path)) 47 48 loader = SphinxFileSystemLoader(search_path) 49 super().__init__(loader) 50 51 @classmethod 52 def render_from_file(cls, filename: str, context: Dict) -> str: 53 dirname = os.path.dirname(filename) 54 basename = os.path.basename(filename) 55 return cls(dirname).render(basename, context) 56 57 58class SphinxRenderer(FileRenderer): 59 def __init__(self, template_path: Union[str, List[str]] = None) -> None: 60 if template_path is None: 61 template_path = os.path.join(package_dir, 'templates') 62 super().__init__(template_path) 63 64 @classmethod 65 def render_from_file(cls, filename: str, context: Dict) -> str: 66 return FileRenderer.render_from_file(filename, context) 67 68 69class LaTeXRenderer(SphinxRenderer): 70 def __init__(self, template_path: str = None, latex_engine: str = None) -> None: 71 if template_path is None: 72 template_path = os.path.join(package_dir, 'templates', 'latex') 73 super().__init__(template_path) 74 75 # use texescape as escape filter 76 escape = partial(texescape.escape, latex_engine=latex_engine) 77 self.env.filters['e'] = escape 78 self.env.filters['escape'] = escape 79 self.env.filters['eabbr'] = texescape.escape_abbr 80 81 # use JSP/eRuby like tagging instead because curly bracket; the default 82 # tagging of jinja2 is not good for LaTeX sources. 83 self.env.variable_start_string = '<%=' 84 self.env.variable_end_string = '%>' 85 self.env.block_start_string = '<%' 86 self.env.block_end_string = '%>' 87 self.env.comment_start_string = '<#' 88 self.env.comment_end_string = '#>' 89 90 91class ReSTRenderer(SphinxRenderer): 92 def __init__(self, template_path: Union[str, List[str]] = None, language: str = None) -> None: # NOQA 93 super().__init__(template_path) 94 95 # add language to environment 96 self.env.extend(language=language) 97 98 # use texescape as escape filter 99 self.env.filters['e'] = rst.escape 100 self.env.filters['escape'] = rst.escape 101 self.env.filters['heading'] = rst.heading 102 103 104class SphinxTemplateLoader(BaseLoader): 105 """A loader supporting template inheritance""" 106 107 def __init__(self, confdir: str, templates_paths: List[str], 108 system_templates_paths: List[str]) -> None: 109 self.loaders = [] 110 self.sysloaders = [] 111 112 for templates_path in templates_paths: 113 loader = SphinxFileSystemLoader(path.join(confdir, templates_path)) 114 self.loaders.append(loader) 115 116 for templates_path in system_templates_paths: 117 loader = SphinxFileSystemLoader(templates_path) 118 self.loaders.append(loader) 119 self.sysloaders.append(loader) 120 121 def get_source(self, environment: Environment, template: str) -> Tuple[str, str, Callable]: 122 if template.startswith('!'): 123 # search a template from ``system_templates_paths`` 124 loaders = self.sysloaders 125 template = template[1:] 126 else: 127 loaders = self.loaders 128 129 for loader in loaders: 130 try: 131 return loader.get_source(environment, template) 132 except TemplateNotFound: 133 pass 134 raise TemplateNotFound(template) 135