Fix #7535: sphinx-autogen: crashes when custom template uses inheritance

This commit is contained in:
Takeshi KOMIYA 2020-04-23 01:19:47 +09:00
parent fd0a55223f
commit b15e3f6639
3 changed files with 64 additions and 15 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)