From b15e3f6639f47d5885a31a65e9d56bf8ce9f4568 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 23 Apr 2020 01:19:47 +0900 Subject: [PATCH] Fix #7535: sphinx-autogen: crashes when custom template uses inheritance --- CHANGES | 1 + sphinx/ext/autosummary/generate.py | 40 +++++++++++++++++++----------- sphinx/util/template.py | 38 +++++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index ad544bf12..b7eff7554 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Features added caption to the toctree * #248, #6040: autosummary: Add ``:recursive:`` option to autosummary directive to generate stub files recursively +* #7535: sphinx-autogen: crashes when custom template uses inheritance * #7481: html theme: Add right margin to footnote/citation labels * #7482: html theme: CSS spacing for code blocks with captions and line numbers * #7443: html theme: Add new options :confval:`globaltoc_collapse` and diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 0449837ff..4d680acd6 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -25,25 +25,27 @@ import pydoc import re import sys import warnings +from os import path from typing import Any, Callable, Dict, List, NamedTuple, Set, Tuple -from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound +from jinja2 import TemplateNotFound from jinja2.sandbox import SandboxedEnvironment import sphinx.locale from sphinx import __display_version__ from sphinx import package_dir from sphinx.builders import Builder +from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.ext.autodoc import Documenter from sphinx.ext.autosummary import import_by_name, get_documenter -from sphinx.jinja2glue import BuiltinTemplateLoader from sphinx.locale import __ from sphinx.registry import SphinxComponentRegistry from sphinx.util import logging from sphinx.util import rst from sphinx.util.inspect import safe_getattr from sphinx.util.osutil import ensuredir +from sphinx.util.template import SphinxTemplateLoader if False: # For type annotation @@ -59,6 +61,7 @@ class DummyApplication: def __init__(self) -> None: self.registry = SphinxComponentRegistry() self.messagelog = [] # type: List[str] + self.translator = None self.verbosity = 0 self._warncount = 0 self.warningiserror = False @@ -67,6 +70,21 @@ class DummyApplication: pass +class DummyBuilder: + """Dummy builder class for sphinx-autogen command.""" + + def __init__(self, app: DummyApplication, template_path: str) -> None: + if template_path: + templates_path = [path.abspath(template_path)] + else: + templates_path = [] + + self.app = app + self.srcdir = "/" + self.config = Config(overrides={'templates_path': templates_path}) + self.config.init_values() + + AutosummaryEntry = NamedTuple('AutosummaryEntry', [('name', str), ('path', str), ('template', str), @@ -110,16 +128,10 @@ class AutosummaryRenderer: """A helper class for rendering.""" def __init__(self, builder: Builder, template_dir: str) -> None: - loader = None # type: BaseLoader - template_dirs = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] - if builder is None: - if template_dir: - template_dirs.insert(0, template_dir) - loader = FileSystemLoader(template_dirs) - else: - # allow the user to override the templates - loader = BuiltinTemplateLoader() - loader.init(builder, dirs=template_dirs) + system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] + loader = SphinxTemplateLoader(builder.srcdir, + builder.config.templates_path, + system_templates_path) self.env = SandboxedEnvironment(loader=loader) self.env.filters['escape'] = rst.escape @@ -514,9 +526,9 @@ def main(argv: List[str] = sys.argv[1:]) -> None: logging.setup(app, sys.stdout, sys.stderr) # type: ignore setup_documenters(app) args = get_parser().parse_args(argv) + builder = DummyBuilder(app, args.templates) generate_autosummary_docs(args.source_file, args.output_dir, - '.' + args.suffix, - template_dir=args.templates, + '.' + args.suffix, builder=builder, # type: ignore imported_members=args.imported_members, app=app) diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 1337f407c..2449a60a1 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -10,8 +10,11 @@ import os from functools import partial -from typing import Dict, List, Union +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 @@ -94,3 +97,36 @@ class ReSTRenderer(SphinxRenderer): 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)