diff --git a/CHANGES b/CHANGES index 9e4ba80a3..5c6d9753b 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,8 @@ 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 +* #2401: autodoc: ``:members:`` causes ``:special-members:`` not to be shown * autodoc: ImportError is replaced by AttributeError for deeper module Testing @@ -284,8 +286,8 @@ Documentation * #5083: Fix wrong make.bat option for internationalization. * #5115: napoleon: add admonitions added by #4613 to the docs. -Release 1.7.9 (in development) -============================== +Release 1.7.10 (in development) +=============================== Dependencies ------------ @@ -305,6 +307,19 @@ Bugs fixed Testing -------- +Release 1.7.9 (released Sep 05, 2018) +===================================== + +Features added +-------------- + +* #5359: Make generated texinfo files reproducible by sorting the anchors + +Bugs fixed +---------- + +* #5361: crashed on incremental build if document uses include directive + Release 1.7.8 (released Aug 29, 2018) ===================================== 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/__init__.py b/sphinx/environment/__init__.py index 7b116970f..100531be8 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -19,7 +19,7 @@ from os import path from docutils.utils import get_source_line from six import BytesIO, next -from six.moves import cPickle as pickle, reduce +from six.moves import cPickle as pickle from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx20Warning, RemovedInSphinx30Warning @@ -67,7 +67,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 = 53 + (sys.version_info[0] - 2) +ENV_VERSION = 54 + (sys.version_info[0] - 2) # config status CONFIG_OK = 1 @@ -724,7 +724,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: 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/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/__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() 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/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 6b7ec5932..701880a61 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/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') 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)