Close #8100: html: Show a better error message for html_static_files

The HTML Builder crashes if error raised on copying html_static_files.
This handles the exception and show a better error message to let users
the reason of errors (ex. failed on extracting Jinja templates).
This commit is contained in:
Takeshi KOMIYA 2020-08-14 00:07:01 +09:00
parent 4baa7ce99b
commit 1bca9f9587
3 changed files with 27 additions and 7 deletions

View File

@ -13,6 +13,9 @@ Deprecated
Features added Features added
-------------- --------------
* #8100: html: Show a better error message for failures on copying
html_static_files
Bugs fixed Bugs fixed
---------- ----------

View File

@ -751,18 +751,27 @@ class StandaloneHTMLBuilder(Builder):
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js')) copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
def copy_theme_static_files(self, context: Dict) -> None: def copy_theme_static_files(self, context: Dict) -> None:
def onerror(filename: str, error: Exception) -> None:
logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
filename, error)
if self.theme: if self.theme:
for entry in self.theme.get_theme_dirs()[::-1]: for entry in self.theme.get_theme_dirs()[::-1]:
copy_asset(path.join(entry, 'static'), copy_asset(path.join(entry, 'static'),
path.join(self.outdir, '_static'), path.join(self.outdir, '_static'),
excluded=DOTFILES, context=context, renderer=self.templates) excluded=DOTFILES, context=context,
renderer=self.templates, onerror=onerror)
def copy_html_static_files(self, context: Dict) -> None: def copy_html_static_files(self, context: Dict) -> None:
def onerror(filename: str, error: Exception) -> None:
logger.warning(__('Failed to copy a file in html_static_file: %s: %r'),
filename, error)
excluded = Matcher(self.config.exclude_patterns + ["**/.*"]) excluded = Matcher(self.config.exclude_patterns + ["**/.*"])
for entry in self.config.html_static_path: for entry in self.config.html_static_path:
copy_asset(path.join(self.confdir, entry), copy_asset(path.join(self.confdir, entry),
path.join(self.outdir, '_static'), path.join(self.outdir, '_static'),
excluded, context=context, renderer=self.templates) excluded, context=context, renderer=self.templates, onerror=onerror)
def copy_html_logo(self) -> None: def copy_html_logo(self) -> None:
if self.config.html_logo: if self.config.html_logo:

View File

@ -10,7 +10,7 @@
import os import os
import posixpath import posixpath
from typing import Dict from typing import Callable, Dict
from docutils.utils import relative_path from docutils.utils import relative_path
@ -56,7 +56,8 @@ def copy_asset_file(source: str, destination: str,
def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False, def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False,
context: Dict = None, renderer: "BaseRenderer" = None) -> None: context: Dict = None, renderer: "BaseRenderer" = None,
onerror: Callable[[str, Exception], None] = None) -> None:
"""Copy asset files to destination recursively. """Copy asset files to destination recursively.
On copying, it expands the template variables if context argument is given and On copying, it expands the template variables if context argument is given and
@ -67,6 +68,7 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat
:param excluded: The matcher to determine the given path should be copied or not :param excluded: The matcher to determine the given path should be copied or not
:param context: The template variables. If not given, template files are simply copied :param context: The template variables. If not given, template files are simply copied
:param renderer: The template engine. If not given, SphinxRenderer is used by default :param renderer: The template engine. If not given, SphinxRenderer is used by default
:param onerror: The error handler.
""" """
if not os.path.exists(source): if not os.path.exists(source):
return return
@ -90,6 +92,12 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat
for filename in files: for filename in files:
if not excluded(posixpath.join(reldir, filename)): if not excluded(posixpath.join(reldir, filename)):
copy_asset_file(posixpath.join(root, filename), try:
posixpath.join(destination, reldir), copy_asset_file(posixpath.join(root, filename),
context, renderer) posixpath.join(destination, reldir),
context, renderer)
except Exception as exc:
if onerror:
onerror(posixpath.join(root, filename), exc)
else:
raise