Merge branch '4.x'

This commit is contained in:
Takeshi KOMIYA 2021-11-11 02:25:42 +09:00
commit 0f6afa5992
15 changed files with 153 additions and 59 deletions

74
CHANGES
View File

@ -26,12 +26,54 @@ Bugs fixed
Testing Testing
-------- --------
Release 4.3.0 (in development) Release 4.4.0 (in development)
============================== ==============================
Dependencies Dependencies
------------ ------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 4.3.1 (in development)
==============================
Dependencies
------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 4.3.0 (released Nov 11, 2021)
=====================================
Dependencies
------------
* Support Python 3.10 * Support Python 3.10
Incompatible changes Incompatible changes
@ -51,7 +93,7 @@ Incompatible changes
* #9695: The rendering of Javascript domain declarations is implemented * #9695: The rendering of Javascript domain declarations is implemented
with more docutils nodes to allow better CSS styling. with more docutils nodes to allow better CSS styling.
It may break existing styling. It may break existing styling.
* #9450: mathjax: Load MathJax via "defer" strategy
Deprecated Deprecated
---------- ----------
@ -71,6 +113,8 @@ Features added
* #9691: C, added new info-field ``retval`` * #9691: C, added new info-field ``retval``
for :rst:dir:`c:function` and :rst:dir:`c:macro`. for :rst:dir:`c:function` and :rst:dir:`c:macro`.
* C++, added new info-field ``retval`` for :rst:dir:`cpp:function`. * C++, added new info-field ``retval`` for :rst:dir:`cpp:function`.
* #9618: i18n: Add :confval:`gettext_allow_fuzzy_translations` to allow "fuzzy"
messages for translation
* #9672: More CSS classes on Python domain descriptions * #9672: More CSS classes on Python domain descriptions
* #9695: More CSS classes on Javascript domain descriptions * #9695: More CSS classes on Javascript domain descriptions
* #9683: Revert the removal of ``add_stylesheet()`` API. It will be kept until * #9683: Revert the removal of ``add_stylesheet()`` API. It will be kept until
@ -80,6 +124,8 @@ Features added
inventory specification. Specific types of cross-references can be disabled, inventory specification. Specific types of cross-references can be disabled,
e.g., ``std:doc`` or all cross-references in a specific domain, e.g., ``std:doc`` or all cross-references in a specific domain,
e.g., ``std:*``. e.g., ``std:*``.
* #9623: Allow to suppress "toctree contains reference to excluded document"
warnings using :confval:`suppress_warnings`
Bugs fixed Bugs fixed
---------- ----------
@ -123,30 +169,6 @@ Bugs fixed
* Intersphinx, for unresolved references with an explicit inventory, * Intersphinx, for unresolved references with an explicit inventory,
e.g., ``proj:myFunc``, leave the inventory prefix in the unresolved text. e.g., ``proj:myFunc``, leave the inventory prefix in the unresolved text.
Testing
--------
Release 4.2.1 (in development)
==============================
Dependencies
------------
Incompatible changes
--------------------
Deprecated
----------
Features added
--------------
Bugs fixed
----------
Testing
--------
Release 4.2.0 (released Sep 12, 2021) Release 4.2.0 (released Sep 12, 2021)
===================================== =====================================

View File

@ -329,6 +329,8 @@ General configuration
* ``ref.python`` * ``ref.python``
* ``misc.highlighting_failure`` * ``misc.highlighting_failure``
* ``toc.circular`` * ``toc.circular``
* ``toc.excluded``
* ``toc.not_readable``
* ``toc.secnum`` * ``toc.secnum``
* ``epub.unknown_project_files`` * ``epub.unknown_project_files``
* ``epub.duplicated_toc_entry`` * ``epub.duplicated_toc_entry``
@ -360,6 +362,10 @@ General configuration
Added ``epub.duplicated_toc_entry`` Added ``epub.duplicated_toc_entry``
.. versionchanged:: 4.3
Added ``toc.excluded`` and ``toc.not_readable``
.. confval:: needs_sphinx .. confval:: needs_sphinx
If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will
@ -802,6 +808,13 @@ documentation on :ref:`intl` for details.
.. versionchanged:: 1.5 .. versionchanged:: 1.5
Use ``locales`` directory as a default value Use ``locales`` directory as a default value
.. confval:: gettext_allow_fuzzy_translations
If true, "fuzzy" messages in the message catalogs are used for translation.
The default is ``False``.
.. versionadded:: 4.3
.. confval:: gettext_compact .. confval:: gettext_compact
.. versionadded:: 1.1 .. versionadded:: 1.1

View File

@ -38,7 +38,11 @@ __released__ = '5.0.0' # used when Sphinx builds its own docs
#: #:
#: .. versionadded:: 1.2 #: .. versionadded:: 1.2
#: Before version 1.2, check the string ``sphinx.__version__``. #: Before version 1.2, check the string ``sphinx.__version__``.
<<<<<<< HEAD
version_info = (5, 0, 0, 'final', 0) version_info = (5, 0, 0, 'final', 0)
=======
version_info = (4, 4, 0, 'beta', 0)
>>>>>>> 4.x
package_dir = path.abspath(path.dirname(__file__)) package_dir = path.abspath(path.dirname(__file__))

View File

@ -284,7 +284,8 @@ class Sphinx:
self.config.language, self.config.source_encoding) self.config.language, self.config.source_encoding)
for catalog in repo.catalogs: for catalog in repo.catalogs:
if catalog.domain == 'sphinx' and catalog.is_outdated(): if catalog.domain == 'sphinx' and catalog.is_outdated():
catalog.write_mo(self.config.language) catalog.write_mo(self.config.language,
self.config.gettext_allow_fuzzy_translations)
locale_dirs: List[Optional[str]] = list(repo.locale_dirs) locale_dirs: List[Optional[str]] = list(repo.locale_dirs)
locale_dirs += [None] locale_dirs += [None]

View File

@ -217,7 +217,8 @@ class Builder:
for catalog in status_iterator(catalogs, __('writing output... '), "darkgreen", for catalog in status_iterator(catalogs, __('writing output... '), "darkgreen",
len(catalogs), self.app.verbosity, len(catalogs), self.app.verbosity,
stringify_func=cat2relpath): stringify_func=cat2relpath):
catalog.write_mo(self.config.language) catalog.write_mo(self.config.language,
self.config.gettext_allow_fuzzy_translations)
def compile_all_catalogs(self) -> None: def compile_all_catalogs(self) -> None:
repo = CatalogRepository(self.srcdir, self.config.locale_dirs, repo = CatalogRepository(self.srcdir, self.config.locale_dirs,

View File

@ -103,6 +103,7 @@ class Config:
'language': (None, 'env', [str]), 'language': (None, 'env', [str]),
'locale_dirs': (['locales'], 'env', []), 'locale_dirs': (['locales'], 'env', []),
'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]), 'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]),
'gettext_allow_fuzzy_translations': (False, 'gettext', []),
'master_doc': ('index', 'env', []), 'master_doc': ('index', 'env', []),
'root_doc': (lambda config: config.master_doc, 'env', []), 'root_doc': (lambda config: config.master_doc, 'env', []),

View File

@ -18,8 +18,8 @@ from docutils.parsers.rst.directives.misc import Include as BaseInclude
from sphinx import addnodes from sphinx import addnodes
from sphinx.domains.changeset import VersionChange # NOQA # for compatibility from sphinx.domains.changeset import VersionChange # NOQA # for compatibility
from sphinx.locale import _ from sphinx.locale import _, __
from sphinx.util import docname_join, url_re from sphinx.util import docname_join, logging, url_re
from sphinx.util.docutils import SphinxDirective from sphinx.util.docutils import SphinxDirective
from sphinx.util.matching import Matcher, patfilter from sphinx.util.matching import Matcher, patfilter
from sphinx.util.nodes import explicit_title_re from sphinx.util.nodes import explicit_title_re
@ -30,6 +30,7 @@ if TYPE_CHECKING:
glob_re = re.compile(r'.*[*?\[].*') glob_re = re.compile(r'.*[*?\[].*')
logger = logging.getLogger(__name__)
def int_or_nothing(argument: str) -> int: def int_or_nothing(argument: str) -> int:
@ -106,9 +107,8 @@ class TocTree(SphinxDirective):
toctree['entries'].append((None, docname)) toctree['entries'].append((None, docname))
toctree['includefiles'].append(docname) toctree['includefiles'].append(docname)
if not docnames: if not docnames:
ret.append(self.state.document.reporter.warning( logger.warning(__('toctree glob pattern %r didn\'t match any documents'),
'toctree glob pattern %r didn\'t match any documents' entry, location=toctree)
% entry, line=self.lineno))
else: else:
if explicit: if explicit:
ref = explicit.group(2) ref = explicit.group(2)
@ -128,20 +128,21 @@ class TocTree(SphinxDirective):
toctree['entries'].append((title, ref)) toctree['entries'].append((title, ref))
elif docname not in self.env.found_docs: elif docname not in self.env.found_docs:
if excluded(self.env.doc2path(docname, None)): if excluded(self.env.doc2path(docname, None)):
message = 'toctree contains reference to excluded document %r' message = __('toctree contains reference to excluded document %r')
subtype = 'excluded'
else: else:
message = 'toctree contains reference to nonexisting document %r' message = __('toctree contains reference to nonexisting document %r')
subtype = 'not_readable'
ret.append(self.state.document.reporter.warning(message % docname, logger.warning(message, docname, type='toc', subtype=subtype,
line=self.lineno)) location=toctree)
self.env.note_reread() self.env.note_reread()
else: else:
if docname in all_docnames: if docname in all_docnames:
all_docnames.remove(docname) all_docnames.remove(docname)
else: else:
message = 'duplicated entry found in toctree: %s' logger.warning(__('duplicated entry found in toctree: %s'), docname,
ret.append(self.state.document.reporter.warning(message % docname, location=toctree)
line=self.lineno))
toctree['entries'].append((title, docname)) toctree['entries'].append((title, docname))
toctree['includefiles'].append(docname) toctree['includefiles'].append(docname)
@ -250,8 +251,9 @@ class Acks(SphinxDirective):
self.state.nested_parse(self.content, self.content_offset, node) self.state.nested_parse(self.content, self.content_offset, node)
if len(node.children) != 1 or not isinstance(node.children[0], if len(node.children) != 1 or not isinstance(node.children[0],
nodes.bullet_list): nodes.bullet_list):
reporter = self.state.document.reporter logger.warning(__('.. acks content is not a list'),
return [reporter.warning('.. acks content is not a list', line=self.lineno)] location=(self.env.docname, self.lineno))
return []
return [node] return [node]
@ -274,8 +276,9 @@ class HList(SphinxDirective):
self.state.nested_parse(self.content, self.content_offset, node) self.state.nested_parse(self.content, self.content_offset, node)
if len(node.children) != 1 or not isinstance(node.children[0], if len(node.children) != 1 or not isinstance(node.children[0],
nodes.bullet_list): nodes.bullet_list):
reporter = self.state.document.reporter logger.warning(__('.. hlist content is not a list'),
return [reporter.warning('.. hlist content is not a list', line=self.lineno)] location=(self.env.docname, self.lineno))
return []
fulllist = node.children[0] fulllist = node.children[0]
# create a hlist node where the items are distributed # create a hlist node where the items are distributed
npercol, nmore = divmod(len(fulllist), ncolumns) npercol, nmore = divmod(len(fulllist), ncolumns)

View File

@ -26,7 +26,7 @@ from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition
from sphinx.application import Sphinx from sphinx.application import Sphinx
from sphinx.builders import Builder from sphinx.builders import Builder
from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning
from sphinx.directives import ObjectDescription from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, Index, IndexEntry, ObjType from sphinx.domains import Domain, Index, IndexEntry, ObjType
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
@ -495,7 +495,17 @@ class PyObject(ObjectDescription[Tuple[str, str]]):
sig_prefix = self.get_signature_prefix(sig) sig_prefix = self.get_signature_prefix(sig)
if sig_prefix: if sig_prefix:
signode += addnodes.desc_annotation(str(sig_prefix), '', *sig_prefix) if type(sig_prefix) is str:
warnings.warn(
"Python directive method get_signature_prefix()"
" returning a string is deprecated."
" It must now return a list of nodes."
" Return value was '{}'.".format(sig_prefix),
RemovedInSphinx60Warning)
signode += addnodes.desc_annotation(sig_prefix, '', # type: ignore
nodes.Text(sig_prefix)) # type: ignore
else:
signode += addnodes.desc_annotation(str(sig_prefix), '', *sig_prefix)
if prefix: if prefix:
signode += addnodes.desc_addname(prefix, prefix) signode += addnodes.desc_addname(prefix, prefix)

View File

@ -81,7 +81,7 @@ def install_mathjax(app: Sphinx, pagename: str, templatename: str, context: Dict
domain = cast(MathDomain, app.env.get_domain('math')) domain = cast(MathDomain, app.env.get_domain('math'))
if app.registry.html_assets_policy == 'always' or domain.has_equations(pagename): if app.registry.html_assets_policy == 'always' or domain.has_equations(pagename):
# Enable mathjax only if equations exists # Enable mathjax only if equations exists
options = {'async': 'async'} options = {'defer': 'defer'}
if app.config.mathjax_options: if app.config.mathjax_options:
options.update(app.config.mathjax_options) options.update(app.config.mathjax_options)
app.add_js_file(app.config.mathjax_path, **options) # type: ignore app.add_js_file(app.config.mathjax_path, **options) # type: ignore

View File

@ -8,11 +8,11 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
from distutils.version import LooseVersion
from functools import partial from functools import partial
from importlib import import_module from importlib import import_module
from typing import Any, Dict from typing import Any, Dict
from packaging import version
from pygments import __version__ as pygmentsversion from pygments import __version__ as pygmentsversion
from pygments import highlight from pygments import highlight
from pygments.filters import ErrorToken from pygments.filters import ErrorToken
@ -64,7 +64,7 @@ _LATEX_ADD_STYLES_FIXPYG = r'''
{\let\fcolorbox\spx@fixpyg@fcolorbox\PYG@do{#2}}} {\let\fcolorbox\spx@fixpyg@fcolorbox\PYG@do{#2}}}
\makeatother \makeatother
''' '''
if tuple(LooseVersion(pygmentsversion).version) <= (2, 7, 4): if version.parse(pygmentsversion).release <= (2, 7, 4):
_LATEX_ADD_STYLES += _LATEX_ADD_STYLES_FIXPYG _LATEX_ADD_STYLES += _LATEX_ADD_STYLES_FIXPYG

View File

@ -12,7 +12,6 @@ import os
import re import re
from contextlib import contextmanager from contextlib import contextmanager
from copy import copy from copy import copy
from distutils.version import LooseVersion
from os import path from os import path
from types import ModuleType from types import ModuleType
from typing import (IO, TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Set, from typing import (IO, TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Set,
@ -26,6 +25,7 @@ from docutils.parsers.rst import Directive, directives, roles
from docutils.parsers.rst.states import Inliner from docutils.parsers.rst.states import Inliner
from docutils.statemachine import State, StateMachine, StringList from docutils.statemachine import State, StateMachine, StringList
from docutils.utils import Reporter, unescape from docutils.utils import Reporter, unescape
from packaging import version
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.locale import _ from sphinx.locale import _
@ -41,7 +41,7 @@ if TYPE_CHECKING:
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
__version_info__ = tuple(LooseVersion(docutils.__version__).version) __version_info__ = version.parse(docutils.__version__).release
additional_nodes: Set[Type[Element]] = set() additional_nodes: Set[Type[Element]] = set()

View File

@ -59,7 +59,7 @@ class CatalogInfo(LocaleFileInfoBase):
not path.exists(self.mo_path) or not path.exists(self.mo_path) or
path.getmtime(self.mo_path) < path.getmtime(self.po_path)) path.getmtime(self.mo_path) < path.getmtime(self.po_path))
def write_mo(self, locale: str) -> None: def write_mo(self, locale: str, use_fuzzy: bool = False) -> None:
with open(self.po_path, encoding=self.charset) as file_po: with open(self.po_path, encoding=self.charset) as file_po:
try: try:
po = read_po(file_po, locale) po = read_po(file_po, locale)
@ -69,7 +69,7 @@ class CatalogInfo(LocaleFileInfoBase):
with open(self.mo_path, 'wb') as file_mo: with open(self.mo_path, 'wb') as file_mo:
try: try:
write_mo(file_mo, po) write_mo(file_mo, po, use_fuzzy)
except Exception as exc: except Exception as exc:
logger.warning(__('writing error: %s, %s'), self.mo_path, exc) logger.warning(__('writing error: %s, %s'), self.mo_path, exc)

View File

@ -10,13 +10,13 @@
import os import os
import re import re
from distutils.version import LooseVersion
from itertools import chain, cycle from itertools import chain, cycle
from unittest.mock import ANY, call, patch from unittest.mock import ANY, call, patch
import pygments import pygments
import pytest import pytest
from html5lib import HTMLParser from html5lib import HTMLParser
from packaging import version
from sphinx.builders.html import validate_html_extra_path, validate_html_static_path from sphinx.builders.html import validate_html_extra_path, validate_html_static_path
from sphinx.errors import ConfigError from sphinx.errors import ConfigError
@ -30,6 +30,9 @@ else:
FIGURE_CAPTION = ".//figure/figcaption/p" FIGURE_CAPTION = ".//figure/figcaption/p"
PYGMENTS_VERSION = version.parse(pygments.__version__).release
ENV_WARNINGS = """\ ENV_WARNINGS = """\
%(root)s/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \ %(root)s/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \
WARNING: Explicit markup ends without a blank line; unexpected unindent. WARNING: Explicit markup ends without a blank line; unexpected unindent.
@ -1576,8 +1579,7 @@ def test_html_codeblock_linenos_style_table(app):
app.build() app.build()
content = (app.outdir / 'index.html').read_text() content = (app.outdir / 'index.html').read_text()
pygments_version = tuple(LooseVersion(pygments.__version__).version) if PYGMENTS_VERSION >= (2, 8):
if pygments_version >= (2, 8):
assert ('<div class="linenodiv"><pre><span class="normal">1</span>\n' assert ('<div class="linenodiv"><pre><span class="normal">1</span>\n'
'<span class="normal">2</span>\n' '<span class="normal">2</span>\n'
'<span class="normal">3</span>\n' '<span class="normal">3</span>\n'
@ -1592,8 +1594,7 @@ def test_html_codeblock_linenos_style_inline(app):
app.build() app.build()
content = (app.outdir / 'index.html').read_text() content = (app.outdir / 'index.html').read_text()
pygments_version = tuple(LooseVersion(pygments.__version__).version) if PYGMENTS_VERSION > (2, 7):
if pygments_version > (2, 7):
assert '<span class="linenos">1</span>' in content assert '<span class="linenos">1</span>' in content
else: else:
assert '<span class="lineno">1 </span>' in content assert '<span class="lineno">1 </span>' in content

View File

@ -71,7 +71,7 @@ def test_mathjax_options(app, status, warning):
app.builder.build_all() app.builder.build_all()
content = (app.outdir / 'index.html').read_text() content = (app.outdir / 'index.html').read_text()
assert ('<script async="async" integrity="sha384-0123456789" ' assert ('<script defer="defer" integrity="sha384-0123456789" '
'src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">' 'src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js">'
'</script>' in content) '</script>' in content)

View File

@ -1301,6 +1301,44 @@ def getwarning(warnings):
return strip_escseq(warnings.getvalue().replace(os.sep, '/')) return strip_escseq(warnings.getvalue().replace(os.sep, '/'))
@pytest.mark.sphinx('html', testroot='basic',
srcdir='gettext_allow_fuzzy_translations',
confoverrides={
'language': 'de',
'gettext_allow_fuzzy_translations': True
})
def test_gettext_allow_fuzzy_translations(app):
locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES'
locale_dir.makedirs()
with (locale_dir / 'index.po').open('wb') as f:
catalog = Catalog()
catalog.add('features', 'FEATURES', flags=('fuzzy',))
pofile.write_po(f, catalog)
app.build()
content = (app.outdir / 'index.html').read_text()
assert 'FEATURES' in content
@pytest.mark.sphinx('html', testroot='basic',
srcdir='gettext_disallow_fuzzy_translations',
confoverrides={
'language': 'de',
'gettext_allow_fuzzy_translations': False
})
def test_gettext_disallow_fuzzy_translations(app):
locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES'
locale_dir.makedirs()
with (locale_dir / 'index.po').open('wb') as f:
catalog = Catalog()
catalog.add('features', 'FEATURES', flags=('fuzzy',))
pofile.write_po(f, catalog)
app.build()
content = (app.outdir / 'index.html').read_text()
assert 'FEATURES' not in content
@pytest.mark.sphinx('html', testroot='basic', confoverrides={'language': 'de'}) @pytest.mark.sphinx('html', testroot='basic', confoverrides={'language': 'de'})
def test_customize_system_message(make_app, app_params, sphinx_test_tempdir): def test_customize_system_message(make_app, app_params, sphinx_test_tempdir):
try: try: