Skip to content

Commit

Permalink
Merge branch '3.x' into 8200_typealias_break_type_annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
tk0miya committed Oct 4, 2020
2 parents a555e3d + 38bb377 commit d8cdad9
Show file tree
Hide file tree
Showing 17 changed files with 141 additions and 23 deletions.
12 changes: 12 additions & 0 deletions CHANGES
Expand Up @@ -12,6 +12,8 @@ Deprecated

* ``sphinx.builders.latex.LaTeXBuilder.usepackages``
* ``sphinx.builders.latex.LaTeXBuilder.usepackages_afger_hyperref``
* ``sphinx.ext.autodoc.SingledispatchFunctionDocumenter``
* ``sphinx.ext.autodoc.SingledispatchMethodDocumenter``

Features added
--------------
Expand All @@ -22,27 +24,37 @@ Features added
nested declarations.
* #8081: LaTeX: Allow to add LaTeX package via ``app.add_latex_package()`` until
just before writing .tex file
* #7996: manpage: Add :confval:`man_make_section_directory` to make a section
directory on build man page

Bugs fixed
----------

* #8085: i18n: Add support for having single text domain
* #6640: i18n: Failed to override system message translation
* #8143: autodoc: AttributeError is raised when False value is passed to
autodoc_default_options
* #8103: autodoc: functools.cached_property is not considered as a property
* #8190: autodoc: parsing error is raised if some extension replaces docstring
by string not ending with blank lines
* #8142: autodoc: Wrong constructor signature for the class derived from
typing.Generic
* #8157: autodoc: TypeError is raised when annotation has invalid __args__
* #7964: autodoc: Tuple in default value is wrongly rendered
* #8200: autodoc: type aliases break type formatting of autoattribute
* #8192: napoleon: description is disappeared when it contains inline literals
* #8142: napoleon: Potential of regex denial of service in google style docs
* #8169: LaTeX: pxjahyper loaded even when latex_engine is not platex
* #8175: intersphinx: Potential of regex denial of service by broken inventory
* #8277: sphinx-build: missing and redundant spacing (and etc) for console
output on building
* #7973: imgconverter: Check availability of imagemagick many times
* #8093: The highlight warning has wrong location in some builders (LaTeX,
singlehtml and so on)
* #8239: Failed to refer a token in productionlist if it is indented
* #8268: linkcheck: Report HTTP errors when ``linkcheck_anchors`` is ``True``
* #8245: linkcheck: take source directory into account for local files
* #6914: figure numbers are unexpectedly assigned to uncaptioned items

Testing
--------
Expand Down
10 changes: 10 additions & 0 deletions doc/extdev/deprecated.rst
Expand Up @@ -36,6 +36,16 @@ The following is a list of deprecated interfaces.
- 5.0
- N/A

* - ``sphinx.ext.autodoc.SingledispatchFunctionDocumenter``
- 3.3
- 5.0
- ``sphinx.ext.autodoc.FunctionDocumenter``

* - ``sphinx.ext.autodoc.SingledispatchMethodDocumenter``
- 3.3
- 5.0
- ``sphinx.ext.autodoc.MethodDocumenter``

* - ``sphinx.ext.autodoc.members_set_option()``
- 3.2
- 5.0
Expand Down
6 changes: 6 additions & 0 deletions doc/usage/configuration.rst
Expand Up @@ -2245,6 +2245,12 @@ These options influence manual page output.

.. versionadded:: 1.1

.. confval:: man_make_section_directory

If true, make a section directory on build man page. Default is False.

.. versionadded:: 3.3


.. _texinfo-options:

Expand Down
7 changes: 5 additions & 2 deletions sphinx/application.py
Expand Up @@ -18,7 +18,7 @@
from collections import deque
from io import StringIO
from os import path
from typing import Any, Callable, Dict, IO, List, Tuple, Union
from typing import Any, Callable, Dict, IO, List, Optional, Tuple, Union

from docutils import nodes
from docutils.nodes import Element, TextElement
Expand Down Expand Up @@ -293,7 +293,10 @@ def _init_i18n(self) -> None:
if catalog.domain == 'sphinx' and catalog.is_outdated():
catalog.write_mo(self.config.language)

locale_dirs = [None, path.join(package_dir, 'locale')] + list(repo.locale_dirs)
locale_dirs = [None] # type: List[Optional[str]]
locale_dirs += list(repo.locale_dirs)
locale_dirs += [path.join(package_dir, 'locale')]

self.translator, has_translation = locale.init(locale_dirs, self.config.language)
if has_translation or self.config.language == 'en':
# "en" never needs to be translated
Expand Down
12 changes: 6 additions & 6 deletions sphinx/builders/html/__init__.py
Expand Up @@ -641,17 +641,17 @@ def gen_pages_from_extensions(self) -> None:
def gen_additional_pages(self) -> None:
# additional pages from conf.py
for pagename, template in self.config.html_additional_pages.items():
logger.info(' ' + pagename, nonl=True)
logger.info(pagename + ' ', nonl=True)
self.handle_page(pagename, {}, template)

# the search page
if self.search:
logger.info(' search', nonl=True)
logger.info('search ', nonl=True)
self.handle_page('search', {}, 'search.html')

# the opensearch xml file
if self.config.html_use_opensearch and self.search:
logger.info(' opensearch', nonl=True)
logger.info('opensearch ', nonl=True)
fn = path.join(self.outdir, '_static', 'opensearch.xml')
self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)

Expand All @@ -669,7 +669,7 @@ def write_genindex(self) -> None:
'genindexcounts': indexcounts,
'split_index': self.config.html_split_index,
}
logger.info(' genindex', nonl=True)
logger.info('genindex ', nonl=True)

if self.config.html_split_index:
self.handle_page('genindex', genindexcontext,
Expand All @@ -691,7 +691,7 @@ def write_domain_indices(self) -> None:
'content': content,
'collapse_index': collapse,
}
logger.info(' ' + indexname, nonl=True)
logger.info(indexname + ' ', nonl=True)
self.handle_page(indexname, indexcontext, 'domainindex.html')

def copy_image_files(self) -> None:
Expand Down Expand Up @@ -785,7 +785,7 @@ def copy_html_favicon(self) -> None:

def copy_static_files(self) -> None:
try:
with progress_message(__('copying static files... ')):
with progress_message(__('copying static files')):
ensuredir(path.join(self.outdir, '_static'))

# prepare context for templates
Expand Down
7 changes: 4 additions & 3 deletions sphinx/builders/linkcheck.py
Expand Up @@ -211,7 +211,7 @@ def check_uri() -> Tuple[str, str, int]:
else:
return 'redirected', new_url, 0

def check() -> Tuple[str, str, int]:
def check(docname: str) -> Tuple[str, str, int]:
# check for various conditions without bothering the network
if len(uri) == 0 or uri.startswith(('#', 'mailto:')):
return 'unchecked', '', 0
Expand All @@ -220,7 +220,8 @@ def check() -> Tuple[str, str, int]:
# non supported URI schemes (ex. ftp)
return 'unchecked', '', 0
else:
if path.exists(path.join(self.srcdir, uri)):
srcdir = path.dirname(self.env.doc2path(docname))
if path.exists(path.join(srcdir, uri)):
return 'working', '', 0
else:
for rex in self.to_ignore:
Expand Down Expand Up @@ -257,7 +258,7 @@ def check() -> Tuple[str, str, int]:
uri, docname, lineno = self.wqueue.get()
if uri is None:
break
status, info, code = check()
status, info, code = check(docname)
self.rqueue.put((uri, docname, lineno, status, info, code))

def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None:
Expand Down
10 changes: 8 additions & 2 deletions sphinx/builders/manpage.py
Expand Up @@ -24,7 +24,7 @@
from sphinx.util import progress_message
from sphinx.util.console import darkgreen # type: ignore
from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.osutil import make_filename_from_project
from sphinx.util.osutil import ensuredir, make_filename_from_project
from sphinx.writers.manpage import ManualPageWriter, ManualPageTranslator


Expand Down Expand Up @@ -80,7 +80,12 @@ def write(self, *ignored: Any) -> None:
docsettings.authors = authors
docsettings.section = section

targetname = '%s.%s' % (name, section)
if self.config.man_make_section_directory:
ensuredir(path.join(self.outdir, str(section)))
targetname = '%s/%s.%s' % (section, name, section)
else:
targetname = '%s.%s' % (name, section)

logger.info(darkgreen(targetname) + ' { ', nonl=True)
destination = FileOutput(
destination_path=path.join(self.outdir, targetname),
Expand Down Expand Up @@ -115,6 +120,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:

app.add_config_value('man_pages', default_man_pages, None)
app.add_config_value('man_show_urls', False, None)
app.add_config_value('man_make_section_directory', False, None)

return {
'version': 'builtin',
Expand Down
4 changes: 4 additions & 0 deletions sphinx/environment/collectors/toctree.py
Expand Up @@ -224,6 +224,10 @@ def assign_figure_numbers(self, env: BuildEnvironment) -> List[str]:
def get_figtype(node: Node) -> str:
for domain in env.domains.values():
figtype = domain.get_enumerable_node_type(node)
if domain.name == 'std' and not domain.get_numfig_title(node): # type: ignore
# Skip if uncaptioned node
continue

if figtype:
return figtype

Expand Down
10 changes: 10 additions & 0 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -1302,6 +1302,11 @@ class SingledispatchFunctionDocumenter(FunctionDocumenter):
Retained for backwards compatibility, now does the same as the FunctionDocumenter
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
warnings.warn("%s is deprecated." % self.__class__.__name__,
RemovedInSphinx50Warning, stacklevel=2)
super().__init__(*args, **kwargs)


class DecoratorDocumenter(FunctionDocumenter):
"""
Expand Down Expand Up @@ -1938,6 +1943,11 @@ class SingledispatchMethodDocumenter(MethodDocumenter):
Retained for backwards compatibility, now does the same as the MethodDocumenter
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
warnings.warn("%s is deprecated." % self.__class__.__name__,
RemovedInSphinx50Warning, stacklevel=2)
super().__init__(*args, **kwargs)


class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore
"""
Expand Down
2 changes: 1 addition & 1 deletion sphinx/locale/__init__.py
Expand Up @@ -106,7 +106,7 @@ def __repr__(self) -> str:
translators = defaultdict(NullTranslations) # type: Dict[Tuple[str, str], NullTranslations]


def init(locale_dirs: List[str], language: str,
def init(locale_dirs: List[Optional[str]], language: str,
catalog: str = 'sphinx', namespace: str = 'general') -> Tuple[NullTranslations, bool]:
"""Look for message catalogs in `locale_dirs` and *ensure* that there is at
least a NullTranslations catalog set in `translators`. If called multiple
Expand Down
18 changes: 16 additions & 2 deletions sphinx/pycode/ast.py
Expand Up @@ -166,14 +166,28 @@ def visit_Set(self, node: ast.Set) -> str:
return "{" + ", ".join(self.visit(e) for e in node.elts) + "}"

def visit_Subscript(self, node: ast.Subscript) -> str:
return "%s[%s]" % (self.visit(node.value), self.visit(node.slice))
def is_simple_tuple(value: ast.AST) -> bool:
return (
isinstance(value, ast.Tuple) and
bool(value.elts) and
not any(isinstance(elt, ast.Starred) for elt in value.elts)
)

if is_simple_tuple(node.slice):
elts = ", ".join(self.visit(e) for e in node.slice.elts) # type: ignore
return "%s[%s]" % (self.visit(node.value), elts)
elif isinstance(node.slice, ast.Index) and is_simple_tuple(node.slice.value):
elts = ", ".join(self.visit(e) for e in node.slice.value.elts) # type: ignore
return "%s[%s]" % (self.visit(node.value), elts)
else:
return "%s[%s]" % (self.visit(node.value), self.visit(node.slice))

def visit_UnaryOp(self, node: ast.UnaryOp) -> str:
return "%s %s" % (self.visit(node.op), self.visit(node.operand))

def visit_Tuple(self, node: ast.Tuple) -> str:
if node.elts:
return ", ".join(self.visit(e) for e in node.elts)
return "(" + ", ".join(self.visit(e) for e in node.elts) + ")"
else:
return "()"

Expand Down
15 changes: 10 additions & 5 deletions sphinx/transforms/post_transforms/images.py
Expand Up @@ -11,7 +11,7 @@
import os
import re
from math import ceil
from typing import Any, Dict, List, Tuple
from typing import Any, Dict, List, Optional, Tuple

from docutils import nodes

Expand Down Expand Up @@ -175,6 +175,13 @@ class ImageConverter(BaseImageConverter):
"""
default_priority = 200

#: The converter is available or not. Will be filled at the first call of
#: the build. The result is shared in the same process.
#:
#: .. todo:: This should be refactored not to store the state without class
#: variable.
available = None # type: Optional[bool]

#: A conversion rules the image converter supports.
#: It is represented as a list of pair of source image format (mimetype) and
#: destination one::
Expand All @@ -187,16 +194,14 @@ class ImageConverter(BaseImageConverter):
conversion_rules = [] # type: List[Tuple[str, str]]

def __init__(self, *args: Any, **kwargs: Any) -> None:
self.available = None # type: bool
# the converter is available or not.
# Will be checked at first conversion
super().__init__(*args, **kwargs)

def match(self, node: nodes.image) -> bool:
if not self.app.builder.supported_image_types:
return False
elif self.available is None:
self.available = self.is_available()
# 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
Expand Down
5 changes: 4 additions & 1 deletion sphinx/util/typing.py
Expand Up @@ -109,7 +109,10 @@ def _stringify_py37(annotation: Any) -> str:
return repr(annotation)

if getattr(annotation, '__args__', None):
if qualname == 'Union':
if not isinstance(annotation.__args__, (list, tuple)):
# broken __args__ found
pass
elif qualname == 'Union':
if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType:
if len(annotation.__args__) > 2:
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
Expand Down
7 changes: 7 additions & 0 deletions tests/test_build_manpage.py
Expand Up @@ -30,6 +30,13 @@ def test_all(app, status, warning):
assert 'Footnotes' not in content


@pytest.mark.sphinx('man', testroot='basic',
confoverrides={'man_make_section_directory': True})
def test_man_make_section_directory(app, status, warning):
app.build()
assert (app.outdir / '1' / 'python.1').exists()


@pytest.mark.sphinx('man', testroot='directive-code')
def test_captioned_code_block(app, status, warning):
app.builder.build_all()
Expand Down
29 changes: 29 additions & 0 deletions tests/test_intl.py
Expand Up @@ -14,8 +14,10 @@

import pytest
from babel.messages import pofile, mofile
from babel.messages.catalog import Catalog
from docutils import nodes

from sphinx import locale
from sphinx.testing.util import (
path, etree_parse, strip_escseq,
assert_re_search, assert_not_re_search, assert_startswith, assert_node
Expand Down Expand Up @@ -1289,3 +1291,30 @@ def test_image_glob_intl_using_figure_language_filename(app):

def getwarning(warnings):
return strip_escseq(warnings.getvalue().replace(os.sep, '/'))


@pytest.mark.sphinx('html', testroot='basic', confoverrides={'language': 'de'})
def test_customize_system_message(make_app, app_params, sphinx_test_tempdir):
try:
# clear translators cache
locale.translators.clear()

# prepare message catalog (.po)
locale_dir = sphinx_test_tempdir / 'basic' / 'locales' / 'de' / 'LC_MESSAGES'
locale_dir.makedirs()
with (locale_dir / 'sphinx.po').open('wb') as f:
catalog = Catalog()
catalog.add('Quick search', 'QUICK SEARCH')
pofile.write_po(f, catalog)

# construct application and convert po file to .mo
args, kwargs = app_params
app = make_app(*args, **kwargs)
assert (locale_dir / 'sphinx.mo').exists()
assert app.translator.gettext('Quick search') == 'QUICK SEARCH'

app.build()
content = (app.outdir / 'index.html').read_text()
assert 'QUICK SEARCH' in content
finally:
locale.translators.clear()
2 changes: 1 addition & 1 deletion tests/test_pycode_ast.py
Expand Up @@ -53,7 +53,7 @@
("+ a", "+ a"), # UAdd
("- 1", "- 1"), # UnaryOp
("- a", "- a"), # USub
("(1, 2, 3)", "1, 2, 3"), # Tuple
("(1, 2, 3)", "(1, 2, 3)"), # Tuple
("()", "()"), # Tuple (empty)
])
def test_unparse(source, expected):
Expand Down

0 comments on commit d8cdad9

Please sign in to comment.