Merge branch '3.x'

This commit is contained in:
Takeshi KOMIYA 2021-01-08 01:23:17 +09:00
commit 5460ea103b
22 changed files with 131 additions and 53 deletions

29
CHANGES
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -136,7 +136,10 @@ class MathDomain(Domain):
def get_objects(self) -> List:
return []
def has_equations(self) -> bool:
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())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ Test Math
math
page
nomath
.. math:: a^2+b^2=c^2

View File

View File

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

View File

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

View File

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

View File

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