From 6dcdce685dbcefa8021a9e06819318fc640ef4a8 Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Tue, 1 May 2018 22:57:32 +0200 Subject: [PATCH 1/7] i18n: Ignore dot-directories like .git/ in LC_MESSAGES/. This avoids warnings when find_catalog_source_files returns files found in .git/ like: WARNING: Start of line didn't match any expected keyword./refs/heads/freezed-library/colorsys.mo WARNING: Problem on line 1: 0000000000000000000000000000000000000000 032a00a7391ec8a155bbc54d66ba72fb1b7bdbf1 Julien Palard 1521582790 +0100 branch: Created from freezed/library/colorsys.po `.po` files can typically reside in .git/refs/heads/ if some branches name end with .po, in which case they contain a SHA1, not translations, leading to warnings. --- sphinx/builders/__init__.py | 10 +++++++--- sphinx/util/i18n.py | 13 +++++++++++-- tests/test_util_i18n.py | 11 +++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index a587f11e0..7464e055b 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -19,6 +19,7 @@ from sphinx.environment.adapters.asset import ImageAdapter from sphinx.util import i18n, logging, status_iterator from sphinx.util.console import bold # type: ignore from sphinx.util.i18n import find_catalog +from sphinx.util.matching import Matcher from sphinx.util.osutil import SEP, ensuredir, relative_uri, relpath from sphinx.util.parallel import ParallelTasks, SerialTasks, make_chunks, \ parallel_available @@ -252,7 +253,8 @@ class Builder(object): self.config.language, charset=self.config.source_encoding, gettext_compact=self.config.gettext_compact, - force_all=True) + force_all=True, + excluded=Matcher(['**/.?**'])) message = 'all of %d po files' % len(catalogs) self.compile_catalogs(catalogs, message) @@ -273,7 +275,8 @@ class Builder(object): self.config.language, domains=list(specified_domains), charset=self.config.source_encoding, - gettext_compact=self.config.gettext_compact) + gettext_compact=self.config.gettext_compact, + excluded=Matcher(['**/.?**'])) message = 'targets for %d po files that are specified' % len(catalogs) self.compile_catalogs(catalogs, message) @@ -283,7 +286,8 @@ class Builder(object): [path.join(self.srcdir, x) for x in self.config.locale_dirs], self.config.language, charset=self.config.source_encoding, - gettext_compact=self.config.gettext_compact) + gettext_compact=self.config.gettext_compact, + excluded=Matcher(['**/.?**'])) message = 'targets for %d po files that are out of date' % len(catalogs) self.compile_catalogs(catalogs, message) diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index ff7f8bd75..14b4f9d5b 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -24,6 +24,12 @@ from sphinx.errors import SphinxError from sphinx.util import logging from sphinx.util.osutil import SEP, relpath, walk +if False: + # For type annotation + from typing import Union # NOQA + from sphinx.util.matching import Matcher # NOQA + + logger = logging.getLogger(__name__) if False: @@ -101,8 +107,9 @@ def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction): def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact=False, - charset='utf-8', force_all=False): - # type: (List[unicode], unicode, List[unicode], bool, unicode, bool) -> Set[CatalogInfo] + charset='utf-8', force_all=False, + excluded=lambda path: False): + # type: (List[unicode], unicode, List[unicode], bool, unicode, bool, Union[Callable[[unicode], bool], Matcher]) -> Set[CatalogInfo] # NOQA """ :param list locale_dirs: list of path as `['locale_dir1', 'locale_dir2', ...]` to find @@ -136,6 +143,8 @@ def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact for dirpath, dirnames, filenames in walk(base_dir, followlinks=True): filenames = [f for f in filenames if f.endswith('.po')] for filename in filenames: + if excluded(path.join(relpath(dirpath, base_dir), filename)): + continue base = path.splitext(filename)[0] domain = relpath(path.join(dirpath, base), base_dir) if gettext_compact and path.sep in domain: diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 1ae6dcb67..da8abe265 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -157,6 +157,17 @@ def test_get_catalogs_with_compact(tempdir): assert domains == set(['test1', 'test2', 'sub']) +def test_get_catalogs_excluded(tempdir): + (tempdir / 'loc1' / 'en' / 'LC_MESSAGES' / '.git').makedirs() + (tempdir / 'loc1' / 'en' / 'LC_MESSAGES' / 'en_dom.po').write_text('#') + (tempdir / 'loc1' / 'en' / 'LC_MESSAGES' / '.git' / 'no_no.po').write_text('#') + + catalogs = i18n.find_catalog_source_files( + [tempdir / 'loc1'], 'en', force_all=False, excluded=lambda path: '.git' in path) + domains = set(c.domain for c in catalogs) + assert domains == set(['en_dom']) + + def test_format_date(): date = datetime.date(2016, 2, 7) From 269becfbf6eadbf00c03c35c18580e31af9b46d4 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 19 May 2018 02:29:47 +0900 Subject: [PATCH 2/7] Fix #4978: latex: shorthandoff is not set up for Brazil locale --- CHANGES | 1 + sphinx/writers/latex.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c3dc3996d..abafe5bb0 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Bugs fixed * #4825: C++, properly parse expr roles and give better error messages when (escaped) line breaks are present. * #4915, #4916: links on search page are broken when using dirhtml builder +* #4978: latex: shorthandoff is not set up for Brazil locale Testing -------- diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 17eabc462..9356bcee4 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -196,7 +196,7 @@ class ExtBabel(Babel): shortlang = self.language.split('_')[0] if shortlang in ('de', 'ngerman', 'sl', 'slovene', 'pt', 'portuges', 'es', 'spanish', 'nl', 'dutch', 'pl', 'polish', 'it', - 'italian'): + 'italian', 'pt-BR', 'brazil'): return '\\ifnum\\catcode`\\"=\\active\\shorthandoff{"}\\fi' elif shortlang in ('tr', 'turkish'): # memo: if ever Sphinx starts supporting 'Latin', do as for Turkish From de6f3b252e4bfd0fb8291f79a417eb1d46e5aeba Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 19 May 2018 14:15:02 +0900 Subject: [PATCH 3/7] Fix #4979: latex: Incorrect escaping of curly braces in index entries --- CHANGES | 1 + sphinx/texinputs/sphinx.sty | 4 ++++ sphinx/writers/latex.py | 17 +++++++++++------ tests/roots/test-latex-index/index.rst | 4 ++++ tests/test_build_latex.py | 1 + 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index f0da26db9..be395cf22 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Bugs fixed * #4969: autodoc: constructor method should not have return annotation * latex: deeply nested enumerated list which is beginning with non-1 causes LaTeX engine crashed +* #4979: latex: Incorrect escaping of curly braces in index entries Testing -------- diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index e323b2a5d..6a885c3f5 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -1608,6 +1608,10 @@ % figure legend comes after caption and may contain arbitrary body elements \newenvironment{sphinxlegend}{\par\small}{\par} +% For curly braces inside \index macro +\def\sphinxleftcurlybrace{\{} +\def\sphinxrightcurlybrace{\}} + % Declare Unicode characters used by linux tree command to pdflatex utf8/utf8x \def\spx@bd#1#2{% \leavevmode diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index d4deb49ac..419a75a79 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1949,6 +1949,12 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_index(self, node, scre=re.compile(r';\s*')): # type: (nodes.Node, Pattern) -> None + def escape(value): + value = self.encode(value) + value = value.replace(r'\{', r'\sphinxleftcurlybrace') + value = value.replace(r'\}', r'\sphinxrightcurlybrace') + return value + if not node.get('inline', True): self.body.append('\n') entries = node['entries'] @@ -1958,24 +1964,23 @@ class LaTeXTranslator(nodes.NodeVisitor): m = '|textbf' try: if type == 'single': - p = scre.sub('!', self.encode(string)) + p = scre.sub('!', escape(string)) self.body.append(r'\index{%s%s}' % (p, m)) elif type == 'pair': - p1, p2 = [self.encode(x) for x in split_into(2, 'pair', string)] + p1, p2 = [escape(x) for x in split_into(2, 'pair', string)] self.body.append(r'\index{%s!%s%s}\index{%s!%s%s}' % (p1, p2, m, p2, p1, m)) elif type == 'triple': - p1, p2, p3 = [self.encode(x) - for x in split_into(3, 'triple', string)] + p1, p2, p3 = [escape(x) for x in split_into(3, 'triple', string)] self.body.append( r'\index{%s!%s %s%s}\index{%s!%s, %s%s}' r'\index{%s!%s %s%s}' % (p1, p2, p3, m, p2, p3, p1, m, p3, p1, p2, m)) elif type == 'see': - p1, p2 = [self.encode(x) for x in split_into(2, 'see', string)] + p1, p2 = [escape(x) for x in split_into(2, 'see', string)] self.body.append(r'\index{%s|see{%s}}' % (p1, p2)) elif type == 'seealso': - p1, p2 = [self.encode(x) for x in split_into(2, 'seealso', string)] + p1, p2 = [escape(x) for x in split_into(2, 'seealso', string)] self.body.append(r'\index{%s|see{%s}}' % (p1, p2)) else: logger.warning('unknown index entry type %s found', type) diff --git a/tests/roots/test-latex-index/index.rst b/tests/roots/test-latex-index/index.rst index 3c1bb46f9..5b612798f 100644 --- a/tests/roots/test-latex-index/index.rst +++ b/tests/roots/test-latex-index/index.rst @@ -10,3 +10,7 @@ A :index:`famous` :index:`equation`: .. index:: Einstein, relativity and some text. + +.. index:: main { + +An index entry containing non paired curly brace diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 75515cf64..1a038c309 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -1209,6 +1209,7 @@ def test_latex_index(app, status, warning): result = (app.outdir / 'Python.tex').text(encoding='utf8') assert 'A \\index{famous}famous \\index{equation}equation:\n' in result assert '\n\\index{Einstein}\\index{relativity}\\ignorespaces \nand' in result + assert '\n\\index{main \\sphinxleftcurlybrace}\\ignorespaces ' in result @pytest.mark.sphinx('latex', testroot='latex-equations') From 6740d2d3918e22d820f0b70df5c2c71a788d3a36 Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Wed, 16 May 2018 10:50:23 +0200 Subject: [PATCH 4/7] Use a Matcher as default argument to simplify type anotation. --- sphinx/util/i18n.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 14b4f9d5b..1826d25c5 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -22,13 +22,9 @@ from babel.messages.pofile import read_po from sphinx.errors import SphinxError from sphinx.util import logging +from sphinx.util.matching import Matcher from sphinx.util.osutil import SEP, relpath, walk -if False: - # For type annotation - from typing import Union # NOQA - from sphinx.util.matching import Matcher # NOQA - logger = logging.getLogger(__name__) @@ -108,8 +104,8 @@ def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction): def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact=False, charset='utf-8', force_all=False, - excluded=lambda path: False): - # type: (List[unicode], unicode, List[unicode], bool, unicode, bool, Union[Callable[[unicode], bool], Matcher]) -> Set[CatalogInfo] # NOQA + excluded=Matcher([])): + # type: (List[unicode], unicode, List[unicode], bool, unicode, bool, Matcher) -> Set[CatalogInfo] # NOQA """ :param list locale_dirs: list of path as `['locale_dir1', 'locale_dir2', ...]` to find From bd6442bf96fd736559a65088fe648947f6142868 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 19 May 2018 16:44:55 +0900 Subject: [PATCH 5/7] Update CHANGES for PR #4928 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index ad85ccd93..9f2c96dea 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Bugs fixed * latex: deeply nested enumerated list which is beginning with non-1 causes LaTeX engine crashed * #4978: latex: shorthandoff is not set up for Brazil locale +* #4928: i18n: Ignore dot-directories like .git/ in LC_MESSAGES/ Testing -------- From 9ccff74f66a55ea84276291c3d7ec474e675d621 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 19 May 2018 22:50:10 +0900 Subject: [PATCH 6/7] Fix #4946: py domain: type field could not handle "None" as a type --- CHANGES | 1 + sphinx/domains/python.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 9f2c96dea..f9b6e6af5 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,7 @@ Bugs fixed LaTeX engine crashed * #4978: latex: shorthandoff is not set up for Brazil locale * #4928: i18n: Ignore dot-directories like .git/ in LC_MESSAGES/ +* #4946: py domain: type field could not handle "None" as a type Testing -------- diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 2b46596af..1d1f28b03 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -156,7 +156,15 @@ class PyGroupedField(PyXrefMixin, GroupedField): class PyTypedField(PyXrefMixin, TypedField): - pass + def make_xref(self, rolename, domain, target, + innernode=nodes.emphasis, contnode=None, env=None): + # type: (unicode, unicode, unicode, nodes.Node, nodes.Node, BuildEnvironment) -> nodes.Node # NOQA + if rolename == 'class' and target == 'None': + # None is not a type, so use obj role instead. + rolename = 'obj' + + return super(PyTypedField, self).make_xref(rolename, domain, target, + innernode, contnode, env) class PyObject(ObjectDescription): From 3080d24330849ae5ec4b538e2308b82bfea38658 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 20 May 2018 23:55:36 +0900 Subject: [PATCH 7/7] Fix #4956: autodoc: Failed to extract document from a subclass of the class on mocked module --- CHANGES | 2 ++ sphinx/ext/autodoc/importer.py | 14 +++++++++++++- tests/test_ext_autodoc_importer.py | 31 ++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/test_ext_autodoc_importer.py diff --git a/CHANGES b/CHANGES index 585dd42b7..3fa74fea5 100644 --- a/CHANGES +++ b/CHANGES @@ -34,6 +34,8 @@ Bugs fixed * #4928: i18n: Ignore dot-directories like .git/ in LC_MESSAGES/ * #4946: py domain: type field could not handle "None" as a type * #4979: latex: Incorrect escaping of curly braces in index entries +* #4956: autodoc: Failed to extract document from a subclass of the class on + mocked module Testing -------- diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index 00a55c4c5..e3bf1560c 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -23,7 +23,7 @@ from sphinx.util.inspect import isenumclass, safe_getattr if False: # For type annotation - from typing import Any, Callable, Dict, Generator, List, Optional # NOQA + from typing import Any, Callable, Dict, Generator, List, Optional, Tuple # NOQA logger = logging.getLogger(__name__) @@ -31,6 +31,14 @@ logger = logging.getLogger(__name__) class _MockObject(object): """Used by autodoc_mock_imports.""" + def __new__(cls, *args, **kwargs): + # type: (Any, Any) -> Any + if len(args) == 3 and isinstance(args[1], tuple) and args[1][-1].__class__ is cls: + # subclassing MockObject + return type(args[0], (_MockObject,), args[2], **kwargs) # type: ignore + else: + return super(_MockObject, cls).__new__(cls) + def __init__(self, *args, **kwargs): # type: (Any, Any) -> None pass @@ -47,6 +55,10 @@ class _MockObject(object): # type: () -> None pass + def __mro_entries__(self, bases): + # type: (Tuple) -> Tuple + return bases + def __getitem__(self, key): # type: (str) -> _MockObject return self diff --git a/tests/test_ext_autodoc_importer.py b/tests/test_ext_autodoc_importer.py new file mode 100644 index 000000000..fe0c9f2bc --- /dev/null +++ b/tests/test_ext_autodoc_importer.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +""" + test_ext_autodoc_importer + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Test the autodoc extension. + + :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from sphinx.ext.autodoc.importer import _MockObject + + +def test_MockObject(): + mock = _MockObject() + assert isinstance(mock.some_attr, _MockObject) + assert isinstance(mock.some_method, _MockObject) + assert isinstance(mock.attr1.attr2, _MockObject) + assert isinstance(mock.attr1.attr2.meth(), _MockObject) + + class SubClass(mock.SomeClass): + """docstring of SubClass""" + def method(self): + return "string" + + obj = SubClass() + assert SubClass.__doc__ == "docstring of SubClass" + assert isinstance(obj, SubClass) + assert obj.method() == "string" + assert isinstance(obj.other_method(), SubClass)