summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES29
-rw-r--r--doc/extdev/appapi.rst3
-rw-r--r--doc/usage/configuration.rst14
-rw-r--r--sphinx/application.py24
-rw-r--r--sphinx/builders/html/__init__.py8
-rw-r--r--sphinx/directives/__init__.py12
-rw-r--r--sphinx/domains/c.py2
-rw-r--r--sphinx/domains/cpp.py2
-rw-r--r--sphinx/domains/javascript.py2
-rw-r--r--sphinx/domains/math.py7
-rw-r--r--sphinx/domains/python.py2
-rw-r--r--sphinx/domains/rst.py2
-rw-r--r--sphinx/domains/std.py4
-rw-r--r--sphinx/ext/autodoc/mock.py5
-rw-r--r--sphinx/ext/mathjax.py26
-rw-r--r--sphinx/transforms/post_transforms/images.py6
-rw-r--r--tests/roots/test-ext-math/index.rst1
-rw-r--r--tests/roots/test-ext-math/nomath.rst0
-rw-r--r--tests/roots/test-html_assets/conf.py6
-rw-r--r--tests/test_build_html.py8
-rw-r--r--tests/test_ext_math.py13
-rw-r--r--utils/release-checklist8
22 files changed, 131 insertions, 53 deletions
diff --git a/CHANGES b/CHANGES
index 1d95a888e..16920e78a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -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``