From 561199e5e8f66a610f1b867b4b40321de851524a Mon Sep 17 00:00:00 2001 From: Dmitry Shachnev Date: Mon, 27 Aug 2018 10:37:17 +0300 Subject: [PATCH 1/8] Make generated texinfo files reproducible by sorting the anchors --- sphinx/writers/texinfo.py | 2 +- tests/test_build_texinfo.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 051ce9de6..cbb1b80b8 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -612,7 +612,7 @@ class TexinfoTranslator(nodes.NodeVisitor): node_name = node['node_name'] pointers = tuple([node_name] + self.rellinks[node_name]) self.body.append('\n@node %s,%s,%s,%s\n' % pointers) # type: ignore - for id in self.next_section_ids: + for id in sorted(self.next_section_ids): self.add_anchor(id, node) self.next_section_ids.clear() diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py index 6b6892594..b1fd8c2a9 100644 --- a/tests/test_build_texinfo.py +++ b/tests/test_build_texinfo.py @@ -50,6 +50,10 @@ def test_texinfo_warnings(app, status, warning): def test_texinfo(app, status, warning): TexinfoTranslator.ignore_missing_images = True app.builder.build_all() + result = (app.outdir / 'SphinxTests.texi').text(encoding='utf8') + assert ('@anchor{markup doc}@anchor{12}' + '@anchor{markup id1}@anchor{13}' + '@anchor{markup testing-various-markup}@anchor{14}' in result) # now, try to run makeinfo over it cwd = os.getcwd() os.chdir(app.outdir) From 36a9c813e679cd497c45945d86eb92980c493066 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 30 Aug 2018 23:47:02 +0900 Subject: [PATCH 2/8] Update CHANGES for PR #5359 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index aca9836b3..cde111a01 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Deprecated Features added -------------- +* #5359: Make generated texinfo files reproducible by sorting the anchors + Bugs fixed ---------- From 5e22ff90453eddb36aa16f0b2f6543a618bfac14 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 29 Aug 2018 23:56:05 +0900 Subject: [PATCH 3/8] Fix #5361: crashed on incremental build if document uses include directive --- CHANGES | 2 ++ sphinx/environment/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index cde111a01..08708eb80 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,8 @@ Features added Bugs fixed ---------- +* #5361: crashed on incremental build if document uses include directive + Testing -------- diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 2a48e5b4e..d5479d46f 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -72,7 +72,7 @@ default_settings = { # or changed to properly invalidate pickle files. # # NOTE: increase base version by 2 to have distinct numbers for Py2 and 3 -ENV_VERSION = 52 + (sys.version_info[0] - 2) +ENV_VERSION = 54 + (sys.version_info[0] - 2) versioning_conditions = { From 9d7b1b756a4db76f9a49cbcd87e74ace61009323 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Sep 2018 16:33:26 +0900 Subject: [PATCH 4/8] refactor: Use set.union() to combine set objects --- sphinx/environment/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index d5479d46f..e0110bdda 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -22,7 +22,7 @@ from os import path from docutils.frontend import OptionParser from docutils.utils import Reporter, get_source_line from six import BytesIO, itervalues, class_types, next -from six.moves import cPickle as pickle, reduce +from six.moves import cPickle as pickle from sphinx import addnodes, versioning from sphinx.deprecation import RemovedInSphinx20Warning @@ -914,7 +914,7 @@ class BuildEnvironment(object): def check_consistency(self): # type: () -> None """Do consistency checks.""" - included = reduce(lambda x, y: x | y, self.included.values(), set()) # type: Set[unicode] # NOQA + included = set().union(*self.included.values()) # type: ignore for docname in sorted(self.all_docs): if docname not in self.files_to_rebuild: if docname == self.config.master_doc: From 86a43b011919c47e142667eb3e71c8bc983f676d Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Sep 2018 20:25:51 +0900 Subject: [PATCH 5/8] Fix #4379: toctree shows confusible warning when document is excluded --- CHANGES | 1 + sphinx/directives/other.py | 13 +++++++++---- sphinx/environment/adapters/toctree.py | 10 ++++++++-- sphinx/ext/autosummary/__init__.py | 10 ++++++++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 80d657998..dfcf2a6bd 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Bugs fixed * #5348: download reference to remote file is not displayed * #5282: html theme: ``pygments_style`` of theme was overrided by ``conf.py`` by default +* #4379: toctree shows confusible warning when document is excluded Testing -------- diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 2e44feb95..a1e39de77 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -22,7 +22,7 @@ from sphinx.domains.changeset import VersionChange # NOQA # for compatibility from sphinx.locale import _ from sphinx.util import url_re, docname_join from sphinx.util.docutils import SphinxDirective -from sphinx.util.matching import patfilter +from sphinx.util.matching import Matcher, patfilter from sphinx.util.nodes import explicit_title_re, set_source_info, \ process_index_entry @@ -96,6 +96,7 @@ class TocTree(SphinxDirective): all_docnames.remove(self.env.docname) # remove current document ret = [] + excluded = Matcher(self.config.exclude_patterns) for entry in self.content: if not entry: continue @@ -131,9 +132,13 @@ class TocTree(SphinxDirective): if url_re.match(ref) or ref == 'self': toctree['entries'].append((title, ref)) elif docname not in self.env.found_docs: - ret.append(self.state.document.reporter.warning( - 'toctree contains reference to nonexisting ' - 'document %r' % docname, line=self.lineno)) + if excluded(self.env.doc2path(docname, None)): + message = 'toctree contains reference to excluded document %r' + else: + message = 'toctree contains reference to nonexisting document %r' + + ret.append(self.state.document.reporter.warning(message % docname, + line=self.lineno)) self.env.note_reread() else: all_docnames.discard(docname) diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py index f1261bcdc..565396ec4 100644 --- a/sphinx/environment/adapters/toctree.py +++ b/sphinx/environment/adapters/toctree.py @@ -15,6 +15,7 @@ from six import iteritems from sphinx import addnodes from sphinx.locale import __ from sphinx.util import url_re, logging +from sphinx.util.matching import Matcher from sphinx.util.nodes import clean_astext, process_only_nodes if False: @@ -83,6 +84,7 @@ class TocTree(object): # interactions between marking and pruning the tree (see bug #1046). toctree_ancestors = self.get_toctree_ancestors(docname) + excluded = Matcher(self.env.config.exclude_patterns) def _toctree_add_classes(node, depth): # type: (nodes.Node, int) -> None @@ -172,8 +174,12 @@ class TocTree(object): ref, location=toctreenode) except KeyError: # this is raised if the included file does not exist - logger.warning(__('toctree contains reference to nonexisting document %r'), - ref, location=toctreenode) + if excluded(self.env.doc2path(ref, None)): + message = __('toctree contains reference to excluded document %r') + else: + message = __('toctree contains reference to nonexisting document %r') + + logger.warning(message, ref, location=toctreenode) else: # if titles_only is given, only keep the main title and # sub-toctrees diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 7cb70be9b..de3682606 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -81,6 +81,7 @@ from sphinx.util import import_object, rst, logging from sphinx.util.docutils import ( NullReporter, SphinxDirective, new_document, switch_source_input ) +from sphinx.util.matching import Matcher if False: # For type annotation @@ -261,12 +262,17 @@ class Autosummary(SphinxDirective): tree_prefix = self.options['toctree'].strip() docnames = [] + excluded = Matcher(self.config.exclude_patterns) for name, sig, summary, real_name in items: docname = posixpath.join(tree_prefix, real_name) docname = posixpath.normpath(posixpath.join(dirname, docname)) if docname not in self.env.found_docs: - self.warn('toctree references unknown document %r' - % docname) + if excluded(self.env.doc2path(docname, None)): + self.warn('toctree references excluded document %r' + % docname) + else: + self.warn('toctree references unknown document %r' + % docname) docnames.append(docname) tocnode = addnodes.toctree() From 06aa1a78647bd8794eb37d7c62934feaec33db3c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Sep 2018 22:30:51 +0900 Subject: [PATCH 6/8] Fix #2401: autodoc: ``:members:`` causes ``:special-members:`` not to be shown --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 24 +++++++++++++ sphinx/ext/autosummary/generate.py | 5 +-- tests/py35/test_autodoc_py35.py | 4 +-- tests/test_autodoc.py | 56 +++++++++++++----------------- 5 files changed, 55 insertions(+), 35 deletions(-) diff --git a/CHANGES b/CHANGES index 80d657998..c0f972ffe 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Bugs fixed * #5348: download reference to remote file is not displayed * #5282: html theme: ``pygments_style`` of theme was overrided by ``conf.py`` by default +* #2401: autodoc: ``:members:`` causes ``:special-members:`` not to be shown Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 41a6731d2..ead3a25fd 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -110,6 +110,20 @@ def bool_option(arg): return True +def merge_special_members_option(options): + # type: (Dict) -> None + """Merge :special-members: option to :members: option.""" + if 'special-members' in options and options['special-members'] is not ALL: + if options.get('members') is ALL: + pass + elif options.get('members'): + for member in options['special-members']: + if member not in options['members']: + options['members'].append(member) + else: + options['members'] = options['special-members'] + + class AutodocReporter(object): """ A reporter replacement that assigns the correct source name @@ -823,6 +837,11 @@ class ModuleDocumenter(Documenter): 'imported-members': bool_option, 'ignore-module-all': bool_option } # type: Dict[unicode, Callable] + def __init__(self, *args): + # type: (Any) -> None + super(ModuleDocumenter, self).__init__(*args) + merge_special_members_option(self.options) + @classmethod def can_document_member(cls, member, membername, isattr, parent): # type: (Any, unicode, bool, Any) -> bool @@ -1075,6 +1094,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: 'private-members': bool_option, 'special-members': members_option, } # type: Dict[unicode, Callable] + def __init__(self, *args): + # type: (Any) -> None + super(ClassDocumenter, self).__init__(*args) + merge_special_members_option(self.options) + @classmethod def can_document_member(cls, member, membername, isattr, parent): # type: (Any, unicode, bool, Any) -> bool diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index c45576c3a..dbadfe8d5 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -43,11 +43,12 @@ from sphinx.util.rst import escape as rst_escape if False: # For type annotation - from typing import Any, Callable, Dict, Tuple, List # NOQA + from typing import Any, Callable, Dict, List, Tuple, Type # NOQA from jinja2 import BaseLoader # NOQA from sphinx import addnodes # NOQA from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA + from sphinx.ext.autodoc import Documenter # NOQA class DummyApplication(object): @@ -69,7 +70,7 @@ def setup_documenters(app): ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, FunctionDocumenter, MethodDocumenter, AttributeDocumenter, InstanceAttributeDocumenter - ] + ] # type: List[Type[Documenter]] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) diff --git a/tests/py35/test_autodoc_py35.py b/tests/py35/test_autodoc_py35.py index 51a9ef1ff..23b3eb83e 100644 --- a/tests/py35/test_autodoc_py35.py +++ b/tests/py35/test_autodoc_py35.py @@ -18,7 +18,7 @@ import six from docutils.statemachine import ViewList from six import StringIO -from sphinx.ext.autodoc import add_documenter, FunctionDocumenter, ALL # NOQA +from sphinx.ext.autodoc import add_documenter, FunctionDocumenter, ALL, Options # NOQA from sphinx.testing.util import SphinxTestApp, Struct from sphinx.util import logging @@ -49,7 +49,7 @@ def setup_test(): global options, directive global processed_docstrings, processed_signatures - options = Struct( + options = Options( inherited_members = False, undoc_members = False, private_members = False, diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index c9a342f94..cefceb833 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -21,7 +21,7 @@ from six import PY3 from sphinx.ext.autodoc import ( AutoDirective, ModuleLevelDocumenter, cut_lines, between, ALL, - merge_autodoc_default_flags + merge_autodoc_default_flags, Options ) from sphinx.ext.autodoc.directive import DocumenterBridge, process_documenter_options from sphinx.testing.util import SphinxTestApp, Struct # NOQA @@ -79,7 +79,7 @@ def setup_test(): global options, directive global processed_docstrings, processed_signatures - options = Struct( + options = Options( inherited_members = False, undoc_members = False, private_members = False, @@ -757,6 +757,29 @@ def test_autodoc_imported_members(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_special_members(app): + # specific special methods + options = {"undoc-members": None, + "special-members": "__init__,__special1__"} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ] + + # combination with specific members + options = {"members": "attr,docattr", + "undoc-members": None, + "special-members": "__init__,__special1__"} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ] + # all special methods options = {"members": None, "undoc-members": None, @@ -786,33 +809,6 @@ def test_autodoc_special_members(app): ' .. py:method:: Class.undocmeth()' ] - # specific special methods - options = {"members": None, - "undoc-members": None, - "special-members": "__init__,__special1__"} - actual = do_autodoc(app, 'class', 'target.Class', options) - assert list(filter(lambda l: '::' in l, actual)) == [ - '.. py:class:: Class(arg)', - ' .. py:method:: Class.__init__(arg)', - ' .. py:method:: Class.__special1__()', - ' .. py:attribute:: Class.attr', - ' .. py:attribute:: Class.descr', - ' .. py:attribute:: Class.docattr', - ' .. py:method:: Class.excludemeth()', - ' .. py:attribute:: Class.inst_attr_comment', - ' .. py:attribute:: Class.inst_attr_inline', - ' .. py:attribute:: Class.inst_attr_string', - ' .. py:attribute:: Class.mdocattr', - ' .. py:method:: Class.meth()', - ' .. py:classmethod:: Class.moore(a, e, f) -> happiness', - ' .. py:attribute:: Class.prop', - ROGER_METHOD, - ' .. py:attribute:: Class.skipattr', - ' .. py:method:: Class.skipmeth()', - ' .. py:attribute:: Class.udocattr', - ' .. py:method:: Class.undocmeth()' - ] - @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_ignore_module_all(app): @@ -1551,9 +1547,7 @@ def test_autodoc_default_options_with_values(app): assert ' .. py:attribute:: EnumCls.val4' not in actual # with :special-members: - # Note that :members: must be *on* for :special-members: to work. app.config.autodoc_default_options = { - 'members': None, 'special-members': '__init__,__iter__', } actual = do_autodoc(app, 'class', 'target.CustomIter') From 1cd87a11ef8df6b783d2e48d9668370a4e1640c9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 5 Sep 2018 23:05:02 +0900 Subject: [PATCH 7/8] Bump to 1.7.9 final --- CHANGES | 16 ++-------------- sphinx/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 08708eb80..9992e9194 100644 --- a/CHANGES +++ b/CHANGES @@ -1,14 +1,5 @@ -Release 1.7.9 (in development) -============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- +Release 1.7.9 (released Sep 05, 2018) +===================================== Features added -------------- @@ -20,9 +11,6 @@ Bugs fixed * #5361: crashed on incremental build if document uses include directive -Testing --------- - Release 1.7.8 (released Aug 29, 2018) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 144ece042..ab0625549 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -31,13 +31,13 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '1.7.9+' +__version__ = '1.7.9' __released__ = '1.7.9' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 7, 9, 'beta', 0) +version_info = (1, 7, 9, 'final', 0) package_dir = path.abspath(path.dirname(__file__)) From 403f7edb171970041193c9800db53daa4c6bbb50 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 5 Sep 2018 23:07:09 +0900 Subject: [PATCH 8/8] Bump version --- CHANGES | 21 +++++++++++++++++++++ sphinx/__init__.py | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 9992e9194..044ea5906 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,24 @@ +Release 1.7.10 (in development) +=============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + Release 1.7.9 (released Sep 05, 2018) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index ab0625549..3af1733cd 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -31,13 +31,13 @@ if 'PYTHONWARNINGS' not in os.environ: warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '1.7.9' -__released__ = '1.7.9' # used when Sphinx builds its own docs +__version__ = '1.7.10+' +__released__ = '1.7.10' # used when Sphinx builds its own docs # version info for better programmatic use # possible values for 3rd element: 'alpha', 'beta', 'rc', 'final' # 'final' has 0 as the last element -version_info = (1, 7, 9, 'final', 0) +version_info = (1, 7, 10, 'beta', 0) package_dir = path.abspath(path.dirname(__file__))