diff options
-rw-r--r-- | CHANGES | 29 | ||||
-rw-r--r-- | doc/extdev/appapi.rst | 3 | ||||
-rw-r--r-- | doc/usage/configuration.rst | 14 | ||||
-rw-r--r-- | sphinx/application.py | 24 | ||||
-rw-r--r-- | sphinx/builders/html/__init__.py | 8 | ||||
-rw-r--r-- | sphinx/directives/__init__.py | 12 | ||||
-rw-r--r-- | sphinx/domains/c.py | 2 | ||||
-rw-r--r-- | sphinx/domains/cpp.py | 2 | ||||
-rw-r--r-- | sphinx/domains/javascript.py | 2 | ||||
-rw-r--r-- | sphinx/domains/math.py | 7 | ||||
-rw-r--r-- | sphinx/domains/python.py | 2 | ||||
-rw-r--r-- | sphinx/domains/rst.py | 2 | ||||
-rw-r--r-- | sphinx/domains/std.py | 4 | ||||
-rw-r--r-- | sphinx/ext/autodoc/mock.py | 5 | ||||
-rw-r--r-- | sphinx/ext/mathjax.py | 26 | ||||
-rw-r--r-- | sphinx/transforms/post_transforms/images.py | 6 | ||||
-rw-r--r-- | tests/roots/test-ext-math/index.rst | 1 | ||||
-rw-r--r-- | tests/roots/test-ext-math/nomath.rst | 0 | ||||
-rw-r--r-- | tests/roots/test-html_assets/conf.py | 6 | ||||
-rw-r--r-- | tests/test_build_html.py | 8 | ||||
-rw-r--r-- | tests/test_ext_math.py | 13 | ||||
-rw-r--r-- | utils/release-checklist | 8 |
22 files changed, 131 insertions, 53 deletions
@@ -81,6 +81,12 @@ Features added * #8619: html: kbd role generates customizable HTML tags for compound keys * #8634: html: Allow to change the order of JS/CSS via ``priority`` parameter for :meth:`Sphinx.add_js_file()` and :meth:`Sphinx.add_css_file()` +* #6241: html: Allow to add JS/CSS files to the specific page when an extension + calls ``app.add_js_file()`` or ``app.add_css_file()`` on + :event:`html-page-context` event +* #8649: imgconverter: Skip availability check if builder supports the image + type +* #6241: mathjax: Include mathjax.js only on the document using equations * #8132: Add :confval:`project_copyright` as an alias of :confval:`copyright` Bugs fixed @@ -102,7 +108,7 @@ Bugs fixed Testing -------- -Release 3.4.2 (in development) +Release 3.4.4 (in development) ============================== Dependencies @@ -120,15 +126,30 @@ Features added Bugs fixed ---------- +Testing +-------- + +Release 3.4.3 (released Jan 08, 2021) +===================================== + +Bugs fixed +---------- + +* #8655: autodoc: Failed to generate document if target module contains an + object that raises an exception on ``hasattr()`` + +Release 3.4.2 (released Jan 04, 2021) +===================================== + +Bugs fixed +---------- + * #8164: autodoc: Classes that inherit mocked class are not documented * #8602: autodoc: The ``autodoc-process-docstring`` event is emitted to the non-datadescriptors unexpectedly * #8616: autodoc: AttributeError is raised on non-class object is passed to autoclass directive -Testing --------- - Release 3.4.1 (released Dec 25, 2020) ===================================== diff --git a/doc/extdev/appapi.rst b/doc/extdev/appapi.rst index 5c9106bbd..1a4c30d6d 100644 --- a/doc/extdev/appapi.rst +++ b/doc/extdev/appapi.rst @@ -376,6 +376,9 @@ Here is a more detailed list of these events. You can return a string from the handler, it will then replace ``'page.html'`` as the HTML template for this page. + .. note:: You can install JS/CSS files for the specific page via + :meth:`Sphinx.add_js_file` and :meth:`Sphinx.add_css_file` since v3.5.0. + .. versionadded:: 0.4 .. versionchanged:: 1.3 diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 2cfc7d9ff..7a92cc12a 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -1003,7 +1003,14 @@ that use Sphinx's HTMLWriter class. 'https://example.com/css/custom.css', ('print.css', {'media': 'print'})] + As a special attribute, *priority* can be set as an integer to load the CSS + file earlier or lazier step. For more information, refer + :meth:`Sphinx.add_css_files()`. + .. versionadded:: 1.8 + .. versionchanged:: 3.5 + + Support priority attribute .. confval:: html_js_files @@ -1019,7 +1026,14 @@ that use Sphinx's HTMLWriter class. 'https://example.com/scripts/custom.js', ('custom.js', {'async': 'async'})] + As a special attribute, *priority* can be set as an integer to load the CSS + file earlier or lazier step. For more information, refer + :meth:`Sphinx.add_css_files()`. + .. versionadded:: 1.8 + .. versionchanged:: 3.5 + + Support priority attribute .. confval:: html_static_path diff --git a/sphinx/application.py b/sphinx/application.py index 40098e92d..710d7701a 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -917,9 +917,11 @@ class Sphinx: Add *filename* to the list of JavaScript files that the default HTML template will include in order of *priority* (ascending). The filename must be relative to the HTML static path , or a full URI with scheme. - If the keyword argument ``body`` is given, its value will be added - between the ``<script>`` tags. Extra keyword arguments are included as - attributes of the ``<script>`` tag. + If the priority of JavaScript file is the same as others, the JavaScript + files will be included in order of the registration. If the keyword + argument ``body`` is given, its value will be added between the + ``<script>`` tags. Extra keyword arguments are included as attributes of + the ``<script>`` tag. Example:: @@ -944,6 +946,9 @@ class Sphinx: * - 800 - default priority for :confval:`html_js_files` + A JavaScript file can be added to the specific HTML page when on extension + calls this method on :event:`html-page-context` event. + .. versionadded:: 0.5 .. versionchanged:: 1.8 @@ -951,7 +956,7 @@ class Sphinx: And it allows keyword arguments as attributes of script tag. .. versionchanged:: 3.5 - Take priority argument. + Take priority argument. Allow to add a JavaScript file to the specific page. """ self.registry.add_js_file(filename, priority=priority, **kwargs) if hasattr(self.builder, 'add_js_file'): @@ -962,8 +967,10 @@ class Sphinx: Add *filename* to the list of CSS files that the default HTML template will include in order of *priority* (ascending). The filename must be - relative to the HTML static path, or a full URI with scheme. The - eyword arguments are also accepted for attributes of ``<link>`` tag. + relative to the HTML static path, or a full URI with scheme. If the + priority of CSS file is the same as others, the CSS files will be + included in order of the registration. The keyword arguments are also + accepted for attributes of ``<link>`` tag. Example:: @@ -990,6 +997,9 @@ class Sphinx: * - 800 - default priority for :confval:`html_css_files` + A CSS file can be added to the specific HTML page when on extension calls + this method on :event:`html-page-context` event. + .. versionadded:: 1.0 .. versionchanged:: 1.6 @@ -1004,7 +1014,7 @@ class Sphinx: And it allows keyword arguments as attributes of link tag. .. versionchanged:: 3.5 - Take priority argument. + Take priority argument. Allow to add a CSS file to the specific page. """ logger.debug('[app] adding stylesheet: %r', filename) self.registry.add_css_files(filename, priority=priority, **kwargs) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index fe8a5773f..faee0a976 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -481,6 +481,10 @@ class StandaloneHTMLBuilder(Builder): rellinks.append((indexname, indexcls.localname, '', indexcls.shortname)) + # back up script_files and css_files to allow adding JS/CSS files to a specific page. + self._script_files = list(self.script_files) + self._css_files = list(self.css_files) + self.globalcontext = { 'embedded': self.embedded, 'project': self.config.project, @@ -1014,6 +1018,10 @@ class StandaloneHTMLBuilder(Builder): self.add_sidebars(pagename, ctx) ctx.update(addctx) + # revert script_files and css_files + self.script_files[:] = self._script_files + self.css_files[:] = self.css_files + self.update_page_context(pagename, templatename, ctx, event_arg) newtmpl = self.app.emit_firstresult('html-page-context', pagename, templatename, ctx, event_arg) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index aa09040eb..d4c82c9f3 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -9,7 +9,7 @@ """ import re -from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast +from typing import TYPE_CHECKING, Any, Dict, Generic, List, Tuple, TypeVar, cast from docutils import nodes from docutils.nodes import Node @@ -31,6 +31,8 @@ if TYPE_CHECKING: nl_escape_re = re.compile(r'\\\n') strip_backslash_re = re.compile(r'\\(.)') +T = TypeVar('T') + def optional_int(argument: str) -> int: """ @@ -45,7 +47,7 @@ def optional_int(argument: str) -> int: return value -class ObjectDescription(SphinxDirective): +class ObjectDescription(SphinxDirective, Generic[T]): """ Directive to describe a class, function or similar object. Not used directly, but subclassed (in domain-specific directives) to add custom @@ -95,7 +97,7 @@ class ObjectDescription(SphinxDirective): else: return [line.strip() for line in lines] - def handle_signature(self, sig: str, signode: desc_signature) -> Any: + def handle_signature(self, sig: str, signode: desc_signature) -> T: """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -107,7 +109,7 @@ class ObjectDescription(SphinxDirective): """ raise ValueError - def add_target_and_index(self, name: Any, sig: str, signode: desc_signature) -> None: + def add_target_and_index(self, name: T, sig: str, signode: desc_signature) -> None: """ Add cross-reference IDs and entries to self.indexnode, if applicable. @@ -171,7 +173,7 @@ class ObjectDescription(SphinxDirective): if self.domain: node['classes'].append(self.domain) - self.names = [] # type: List[Any] + self.names = [] # type: List[T] signatures = self.get_signatures() for i, sig in enumerate(signatures): # add a signature node for each signature in the current unit diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 87f115c4a..fb4da502d 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -3099,7 +3099,7 @@ def _make_phony_error_name() -> ASTNestedName: return ASTNestedName([ASTIdentifier("PhonyNameDueToError")], rooted=False) -class CObject(ObjectDescription): +class CObject(ObjectDescription[ASTDeclaration]): """ Description of a C language object. """ diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 389630a32..f6e746809 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -6670,7 +6670,7 @@ def _make_phony_error_name() -> ASTNestedName: return ASTNestedName([nne], [False], rooted=False) -class CPPObject(ObjectDescription): +class CPPObject(ObjectDescription[ASTDeclaration]): """Description of a C++ language object.""" doc_field_types = [ diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index a4b2eca2e..f612fb914 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -32,7 +32,7 @@ from sphinx.util.nodes import make_id, make_refnode logger = logging.getLogger(__name__) -class JSObject(ObjectDescription): +class JSObject(ObjectDescription[Tuple[str, str]]): """ Description of a JavaScript object. """ diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py index 4f9638ab0..70a27e642 100644 --- a/sphinx/domains/math.py +++ b/sphinx/domains/math.py @@ -136,8 +136,11 @@ class MathDomain(Domain): def get_objects(self) -> List: return [] - def has_equations(self) -> bool: - return any(self.data['has_equations'].values()) + def has_equations(self, docname: str = None) -> bool: + if docname: + return self.data['has_equations'].get(docname, False) + else: + return any(self.data['has_equations'].values()) def setup(app: "Sphinx") -> Dict[str, Any]: diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 5afe2993a..c07c31e87 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -333,7 +333,7 @@ class PyTypedField(PyXrefMixin, TypedField): return super().make_xref(rolename, domain, target, innernode, contnode, env) -class PyObject(ObjectDescription): +class PyObject(ObjectDescription[Tuple[str, str]]): """ Description of a general Python object. diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 0539197bc..07bf46b75 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -31,7 +31,7 @@ logger = logging.getLogger(__name__) dir_sig_re = re.compile(r'\.\. (.+?)::(.*)$') -class ReSTMarkup(ObjectDescription): +class ReSTMarkup(ObjectDescription[str]): """ Description of generic reST markup. """ diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 57b5796ac..18e62c3cb 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -46,7 +46,7 @@ option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)') token_re = re.compile(r'`(\w+)`', re.U) -class GenericObject(ObjectDescription): +class GenericObject(ObjectDescription[str]): """ A generic x-ref directive registered with Sphinx.add_object_type(). """ @@ -176,7 +176,7 @@ class Target(SphinxDirective): return self.name + '-' + name -class Cmdoption(ObjectDescription): +class Cmdoption(ObjectDescription[str]): """ Description of a command-line option (.. option). """ diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index e11f63559..513ce523f 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -153,7 +153,10 @@ def mock(modnames: List[str]) -> Generator[None, None, None]: def ismock(subject: Any) -> bool: """Check if the object is mocked.""" # check the object has '__sphinx_mock__' attribute - if not hasattr(subject, '__sphinx_mock__'): + try: + if safe_getattr(subject, '__sphinx_mock__', None) is None: + return False + except AttributeError: return False # check the object is mocked module diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py index b2e0b4f0d..1cd2bd1cc 100644 --- a/sphinx/ext/mathjax.py +++ b/sphinx/ext/mathjax.py @@ -17,14 +17,16 @@ from docutils import nodes import sphinx from sphinx.application import Sphinx -from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.domains.math import MathDomain -from sphinx.environment import BuildEnvironment from sphinx.errors import ExtensionError from sphinx.locale import _ from sphinx.util.math import get_node_equation_number from sphinx.writers.html import HTMLTranslator +# more information for mathjax secure url is here: +# https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn +MATHJAX_URL = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js' + def html_visit_math(self: HTMLTranslator, node: nodes.math) -> None: self.body.append(self.starttag(node, 'span', '', CLASS='math notranslate nohighlight')) @@ -66,25 +68,25 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None raise nodes.SkipNode -def install_mathjax(app: Sphinx, env: BuildEnvironment) -> None: +def install_mathjax(app: Sphinx, pagename: str, templatename: str, context: Dict, + event_arg: Any) -> None: if app.builder.format != 'html' or app.builder.math_renderer_name != 'mathjax': # type: ignore # NOQA return if not app.config.mathjax_path: raise ExtensionError('mathjax_path config value must be set for the ' 'mathjax extension to work') - builder = cast(StandaloneHTMLBuilder, app.builder) - domain = cast(MathDomain, env.get_domain('math')) - if domain.has_equations(): + domain = cast(MathDomain, app.env.get_domain('math')) + if domain.has_equations(pagename): # Enable mathjax only if equations exists options = {'async': 'async'} if app.config.mathjax_options: options.update(app.config.mathjax_options) - builder.add_js_file(app.config.mathjax_path, **options) + app.add_js_file(app.config.mathjax_path, **options) # type: ignore if app.config.mathjax_config: body = "MathJax.Hub.Config(%s)" % json.dumps(app.config.mathjax_config) - builder.add_js_file(None, type="text/x-mathjax-config", body=body) + app.add_js_file(None, type="text/x-mathjax-config", body=body) def setup(app: Sphinx) -> Dict[str, Any]: @@ -92,15 +94,11 @@ def setup(app: Sphinx) -> Dict[str, Any]: (html_visit_math, None), (html_visit_displaymath, None)) - # more information for mathjax secure url is here: - # https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn - app.add_config_value('mathjax_path', - 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js', - 'html') + app.add_config_value('mathjax_path', MATHJAX_URL, 'html') app.add_config_value('mathjax_options', {}, 'html') app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html') app.add_config_value('mathjax_display', [r'\[', r'\]'], 'html') app.add_config_value('mathjax_config', None, 'html') - app.connect('env-updated', install_mathjax) + app.connect('html-page-context', install_mathjax) return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/sphinx/transforms/post_transforms/images.py b/sphinx/transforms/post_transforms/images.py index fb4c3ca20..2603e0458 100644 --- a/sphinx/transforms/post_transforms/images.py +++ b/sphinx/transforms/post_transforms/images.py @@ -197,15 +197,15 @@ class ImageConverter(BaseImageConverter): def match(self, node: nodes.image) -> bool: if not self.app.builder.supported_image_types: return False + elif set(node['candidates']) & set(self.app.builder.supported_image_types): + # builder supports the image; no need to convert + return False elif self.available is None: # store the value to the class variable to share it during the build self.__class__.available = self.is_available() if not self.available: return False - elif set(node['candidates']) & set(self.app.builder.supported_image_types): - # builder supports the image; no need to convert - return False else: rule = self.get_conversion_rule(node) if rule: diff --git a/tests/roots/test-ext-math/index.rst b/tests/roots/test-ext-math/index.rst index 4237b73ff..221284aeb 100644 --- a/tests/roots/test-ext-math/index.rst +++ b/tests/roots/test-ext-math/index.rst @@ -6,6 +6,7 @@ Test Math math page + nomath .. math:: a^2+b^2=c^2 diff --git a/tests/roots/test-ext-math/nomath.rst b/tests/roots/test-ext-math/nomath.rst new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/roots/test-ext-math/nomath.rst diff --git a/tests/roots/test-html_assets/conf.py b/tests/roots/test-html_assets/conf.py index ec53011c2..7f94bbbce 100644 --- a/tests/roots/test-html_assets/conf.py +++ b/tests/roots/test-html_assets/conf.py @@ -4,7 +4,9 @@ version = '1.4.4' html_static_path = ['static', 'subdir'] html_extra_path = ['extra', 'subdir'] html_css_files = ['css/style.css', - ('https://example.com/custom.css', {'title': 'title', 'media': 'print'})] + ('https://example.com/custom.css', + {'title': 'title', 'media': 'print', 'priority': 400})] html_js_files = ['js/custom.js', - ('https://example.com/script.js', {'async': 'async'})] + ('https://example.com/script.js', + {'async': 'async', 'priority': 400})] exclude_patterns = ['**/_build', '**/.htpasswd'] diff --git a/tests/test_build_html.py b/tests/test_build_html.py index b2d212549..eecd25d07 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -1220,15 +1220,15 @@ def test_assets_order(app): # css_files expected = ['_static/early.css', '_static/pygments.css', '_static/alabaster.css', - '_static/normal.css', '_static/late.css', '_static/css/style.css', - 'https://example.com/custom.css', '_static/lazy.css'] + 'https://example.com/custom.css', '_static/normal.css', '_static/late.css', + '_static/css/style.css', '_static/lazy.css'] pattern = '.*'.join('href="%s"' % f for f in expected) assert re.search(pattern, content, re.S) # js_files expected = ['_static/early.js', '_static/jquery.js', '_static/underscore.js', - '_static/doctools.js', '_static/normal.js', '_static/late.js', - '_static/js/custom.js', 'https://example.com/script.js', '_static/lazy.js'] + '_static/doctools.js', 'https://example.com/script.js', '_static/normal.js', + '_static/late.js', '_static/js/custom.js', '_static/lazy.js'] pattern = '.*'.join('src="%s"' % f for f in expected) assert re.search(pattern, content, re.S) diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py index 790b5a087..bd124c8c6 100644 --- a/tests/test_ext_math.py +++ b/tests/test_ext_math.py @@ -15,6 +15,7 @@ import warnings import pytest from docutils import nodes +from sphinx.ext.mathjax import MATHJAX_URL from sphinx.testing.util import assert_node @@ -224,6 +225,18 @@ def test_mathjax_config(app, status, warning): '</script>' in content) +@pytest.mark.sphinx('html', testroot='ext-math', + confoverrides={'extensions': ['sphinx.ext.mathjax']}) +def test_mathjax_is_installed_only_if_document_having_math(app, status, warning): + app.builder.build_all() + + content = (app.outdir / 'index.html').read_text() + assert MATHJAX_URL in content + + content = (app.outdir / 'nomath.html').read_text() + assert MATHJAX_URL not in content + + @pytest.mark.sphinx('html', testroot='basic', confoverrides={'extensions': ['sphinx.ext.mathjax']}) def test_mathjax_is_not_installed_if_no_equations(app, status, warning): diff --git a/utils/release-checklist b/utils/release-checklist index dad83c850..671f932d8 100644 --- a/utils/release-checklist +++ b/utils/release-checklist @@ -4,7 +4,7 @@ Release checklist for stable releases ------------------- -* open https://github.com/sphinx-doc/sphinx/actions?query=branch:X.Y.x and all tests has passed +* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:X.Y.x" and all tests has passed * Run ``git fetch; git status`` and check nothing changed * ``python utils/bump_version.py X.Y.Z`` * Check diff by ``git diff`` @@ -28,7 +28,7 @@ for stable releases for first beta releases ----------------------- -* open https://github.com/sphinx-doc/sphinx/actions?query=branch:master and all tests has passed +* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:master" and all tests has passed * Run ``git fetch; git status`` and check nothing changed * Run ``python setup.py extract_messages`` * Run ``(cd sphinx/locale; tx push -s)`` @@ -58,7 +58,7 @@ for first beta releases for other beta releases ----------------------- -* open https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x and all tests has passed +* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x" and all tests has passed * Run ``git fetch; git status`` and check nothing changed * ``python utils/bump_version.py X.Y.0bN`` * Check diff by ``git diff`` @@ -81,7 +81,7 @@ for other beta releases for major releases ------------------ -* open https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x and all tests has passed +* open "https://github.com/sphinx-doc/sphinx/actions?query=branch:X.x" and all tests has passed * Run ``git fetch; git status`` and check nothing changed * Run ``(cd sphinx/locale; tx pull -a -f)`` * Run ``python setup.py compile_catalog`` |